From c87b52a2167b62b6b0fb1316302bec6a025d24b6 Mon Sep 17 00:00:00 2001 From: federico Tartarini Date: Tue, 5 Dec 2023 14:56:40 +1100 Subject: [PATCH 01/91] build: trying to build from source --- .dockerignore | 296 ------- .github/workflows/cypress.yml | 5 +- .github/workflows/python.yml | 7 +- .gitignore | 3 - .python-version | 1 + Dockerfile | 20 - Pipfile | 26 - Pipfile.lock | 1021 ---------------------- docs/contributing/run-project-locally.md | 9 + 9 files changed, 15 insertions(+), 1373 deletions(-) delete mode 100644 .dockerignore create mode 100644 .python-version delete mode 100644 Dockerfile delete mode 100644 Pipfile delete mode 100644 Pipfile.lock diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 90a83e9d..00000000 --- a/.dockerignore +++ /dev/null @@ -1,296 +0,0 @@ -README.md -LICENSE -Procfile -Procfile.windows -*.pyo -*.pyd -__pycache__ -.pytest_cache -file_system_store -.git -docs -test - -assets/data/Region*.kml - -venv -.idea -cache-directory -cloud-run-key-file.json - -# Created by .ignore support plugin (hsz.mobi) -### VisualStudioCode template -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -### JetBrains template -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/modules.xml -# .idea/*.iml -# .idea/modules - -# CMake -cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client -.idea/httpRequests -### Node template -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env - -# parcel-bundler cache (https://parceljs.org/) -.cache - -# next.js build output -.next - -# nuxt.js build output -.nuxt - -# vuepress build output -.vuepress/dist - -# Serverless directories -.serverless -### Python template -# Byte-compiled / optimized / DLL files -__pycache__/ -*/__pycache__/* -*.pyc -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -nosetests.xml -coverage.xml -*.cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: -local_settings.py -db.sqlite3 - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# pyenv -.python-version - -# celery beat schedule file -celerybeat-schedule - -# SageMath parsed files -*.sage.py - -# Environments -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -### SublimeText template -# Cache files for Sublime Text -*.tmlanguage.cache -*.tmPreferences.cache -*.stTheme.cache - -# Workspace files are user-specific -*.sublime-workspace - -# Project files should be checked into the repository, unless a significant -# proportion of contributors will probably not be using Sublime Text -# *.sublime-project - -# SFTP configuration file -sftp-config.json - -# Package control specific files -Package Control.last-run -Package Control.ca-list -Package Control.ca-bundle -Package Control.system-ca-bundle -Package Control.cache/ -Package Control.ca-certs/ -Package Control.merged-ca-bundle -Package Control.user-ca-bundle -oscrypto-ca-bundle.crt -bh_unicode_properties.cache - -# Sublime-github package stores a github token in this file -# https://packagecontrol.io/packages/sublime-github -GitHub.sublime-settings \ No newline at end of file diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index f1810e28..475691b0 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -14,12 +14,11 @@ jobs: - name: Build Clima run: |- - pip install pipenv - pipenv install + pip install -r requirements.txt - name: Start Clima run: |- - pipenv run python main.py & + python main.py & - name: Setup Node uses: actions/setup-node@v4 diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index fd21bca2..bcd136c8 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -12,14 +12,13 @@ jobs: with: python-version: '3.10' - - name: Build Clima + - name: Install run: |- - pip install pipenv - pipenv install --dev + pip install -r requirements.txt - name: Test Clima run: |- - pipenv run python -m pytest + python -m pytest - name: Run Black # TODO: Add to dev dependencies: Adding it right now diff --git a/.gitignore b/.gitignore index 33964f3b..c35b2a01 100644 --- a/.gitignore +++ b/.gitignore @@ -220,9 +220,6 @@ target/ # Jupyter Notebook .ipynb_checkpoints -# pyenv -.python-version - # celery beat schedule file celerybeat-schedule diff --git a/.python-version b/.python-version new file mode 100644 index 00000000..7c7a975f --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.10 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index a2f4c68c..00000000 --- a/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -# Use the official lightweight Python image. -# https://hub.docker.com/_/python -FROM python:3.10-slim - -RUN apt-get update \ -&& apt-get install gcc -y \ -&& apt-get clean - -ENV APP_HOME /app -WORKDIR $APP_HOME - -COPY . ./ - -# Install production dependencies. -RUN pip install --upgrade pip -RUN pip install -r requirements.txt - -EXPOSE 8080 - -CMD python main.py \ No newline at end of file diff --git a/Pipfile b/Pipfile deleted file mode 100644 index ca008090..00000000 --- a/Pipfile +++ /dev/null @@ -1,26 +0,0 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] -dash = "==2.4.1" -gunicorn = "==20.1.0" -pvlib = "==0.9.1" -pythermalcomfort = "*" -dash-bootstrap-components = "==1.2.0" -dash-extensions = "*" -flask-caching = "==1.10.1" -dash-mantine-components = "*" -flask = "==2.2.2" -werkzeug = "==2.2.2" - -[dev-packages] -cleanpy = "*" -pytest = "*" -bump2version = "*" -pandas = "*" -requests = "*" - -[requires] -python_version = "3.10" diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index 373597fc..00000000 --- a/Pipfile.lock +++ /dev/null @@ -1,1021 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "018b80d6588d5229685e1b6366b6d52fdb8c79f893c10cede59a8daee2af9436" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.10" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "brotli": { - "hashes": [ - "sha256:03d20af184290887bdea3f0f78c4f737d126c74dc2f3ccadf07e54ceca3bf208", - "sha256:0541e747cce78e24ea12d69176f6a7ddb690e62c425e01d31cc065e69ce55b48", - "sha256:069a121ac97412d1fe506da790b3e69f52254b9df4eb665cd42460c837193354", - "sha256:0b63b949ff929fbc2d6d3ce0e924c9b93c9785d877a21a1b678877ffbbc4423a", - "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128", - "sha256:11d00ed0a83fa22d29bc6b64ef636c4552ebafcef57154b4ddd132f5638fbd1c", - "sha256:141bd4d93984070e097521ed07e2575b46f817d08f9fa42b16b9b5f27b5ac088", - "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9", - "sha256:1ab4fbee0b2d9098c74f3057b2bc055a8bd92ccf02f65944a241b4349229185a", - "sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3", - "sha256:224e57f6eac61cc449f498cc5f0e1725ba2071a3d4f48d5d9dffba42db196438", - "sha256:22fc2a8549ffe699bfba2256ab2ed0421a7b8fadff114a3d201794e45a9ff578", - "sha256:23032ae55523cc7bccb4f6a0bf368cd25ad9bcdcc1990b64a647e7bbcce9cb5b", - "sha256:2333e30a5e00fe0fe55903c8832e08ee9c3b1382aacf4db26664a16528d51b4b", - "sha256:2954c1c23f81c2eaf0b0717d9380bd348578a94161a65b3a2afc62c86467dd68", - "sha256:2de9d02f5bda03d27ede52e8cfe7b865b066fa49258cbab568720aa5be80a47d", - "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd", - "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409", - "sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da", - "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50", - "sha256:3d7954194c36e304e1523f55d7042c59dc53ec20dd4e9ea9d151f1b62b4415c0", - "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180", - "sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d", - "sha256:4d4a848d1837973bf0f4b5e54e3bec977d99be36a7895c61abb659301b02c112", - "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc", - "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265", - "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327", - "sha256:587ca6d3cef6e4e868102672d3bd9dc9698c309ba56d41c2b9c85bbb903cdb95", - "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd", - "sha256:5eeb539606f18a0b232d4ba45adccde4125592f3f636a6182b4a8a436548b914", - "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0", - "sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a", - "sha256:6172447e1b368dcbc458925e5ddaf9113477b0ed542df258d84fa28fc45ceea7", - "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0", - "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451", - "sha256:7905193081db9bfa73b1219140b3d315831cbff0d8941f22da695832f0dd188f", - "sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e", - "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248", - "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91", - "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724", - "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966", - "sha256:890b5a14ce214389b2cc36ce82f3093f96f4cc730c1cffdbefff77a7c71f2a97", - "sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d", - "sha256:8dadd1314583ec0bf2d1379f7008ad627cd6336625d6679cf2f8e67081b83acf", - "sha256:901032ff242d479a0efa956d853d16875d42157f98951c0230f69e69f9c09bac", - "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951", - "sha256:919e32f147ae93a09fe064d77d5ebf4e35502a8df75c29fb05788528e330fe74", - "sha256:929811df5462e182b13920da56c6e0284af407d1de637d8e536c5cd00a7daf60", - "sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c", - "sha256:a090ca607cbb6a34b0391776f0cb48062081f5f60ddcce5d11838e67a01928d1", - "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8", - "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d", - "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc", - "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61", - "sha256:a599669fd7c47233438a56936988a2478685e74854088ef5293802123b5b2460", - "sha256:a743e5a28af5f70f9c080380a5f908d4d21d40e8f0e0c8901604d15cfa9ba751", - "sha256:a77def80806c421b4b0af06f45d65a136e7ac0bdca3c09d9e2ea4e515367c7e9", - "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1", - "sha256:ae15b066e5ad21366600ebec29a7ccbc86812ed267e4b28e860b8ca16a2bc474", - "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2", - "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6", - "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9", - "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2", - "sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467", - "sha256:cdbc1fc1bc0bff1cef838eafe581b55bfbffaed4ed0318b724d0b71d4d377619", - "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf", - "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408", - "sha256:d143fd47fad1db3d7c27a1b1d66162e855b5d50a89666af46e1679c496e8e579", - "sha256:d192f0f30804e55db0d0e0a35d83a9fead0e9a359a9ed0285dbacea60cc10a84", - "sha256:db85ecf4e609a48f4b29055f1e144231b90edc90af7481aa731ba2d059226b1b", - "sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59", - "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752", - "sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80", - "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0", - "sha256:e93dfc1a1165e385cc8239fab7c036fb2cd8093728cbd85097b284d7b99249a2", - "sha256:efa8b278894b14d6da122a72fefcebc28445f2d3f880ac59d46c90f4c13be9a3", - "sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64", - "sha256:f296c40e23065d0d6650c4aefe7470d2a25fffda489bcc3eb66083f3ac9f6643", - "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e", - "sha256:f733d788519c7e3e71f0855c96618720f5d3d60c3cb829d8bbb722dddce37985", - "sha256:fce1473f3ccc4187f75b4690cfc922628aed4d3dd013d047f95a9b3919a86596", - "sha256:fd5f17ff8f14003595ab414e45fce13d073e0762394f957182e69035c9f3d7c2", - "sha256:fdc3ff3bfccdc6b9cc7c342c03aa2400683f0cb891d46e94b64a197910dc4064" - ], - "markers": "platform_python_implementation != 'PyPy'", - "version": "==1.1.0" - }, - "certifi": { - "hashes": [ - "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1", - "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474" - ], - "markers": "python_version >= '3.6'", - "version": "==2023.11.17" - }, - "charset-normalizer": { - "hashes": [ - "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", - "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", - "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", - "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", - "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", - "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", - "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", - "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", - "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", - "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", - "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", - "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", - "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", - "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", - "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", - "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", - "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", - "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", - "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", - "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", - "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", - "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", - "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", - "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", - "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", - "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", - "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", - "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", - "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", - "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", - "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", - "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", - "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", - "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", - "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", - "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", - "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", - "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", - "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", - "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", - "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", - "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", - "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", - "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", - "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", - "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", - "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", - "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", - "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", - "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", - "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", - "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", - "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", - "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", - "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", - "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", - "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", - "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", - "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", - "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", - "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", - "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", - "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", - "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", - "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", - "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", - "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", - "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", - "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", - "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", - "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", - "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", - "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", - "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", - "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", - "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", - "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", - "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", - "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", - "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", - "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", - "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", - "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", - "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", - "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", - "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", - "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", - "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", - "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", - "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" - ], - "markers": "python_full_version >= '3.7.0'", - "version": "==3.3.2" - }, - "click": { - "hashes": [ - "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", - "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" - ], - "markers": "python_version >= '3.7'", - "version": "==8.1.7" - }, - "dash": { - "hashes": [ - "sha256:44ad5593d69f8aba37e4dd8fa07b1e5dfc012aebd8cccf6e4f68591bca02d177", - "sha256:aef5fdbb2e948a378c1c8175d52a76c59cf998c62b25eaff616cd947310b04d3" - ], - "index": "pypi", - "markers": "python_version >= '3.6'", - "version": "==2.4.1" - }, - "dash-bootstrap-components": { - "hashes": [ - "sha256:4f95470952ecb9a0f2dbef34be2969353c5c16925817d48da50db51ade80f50f", - "sha256:d7fd69cb2b1e86f9cc4bcee4036302e5d534d0bf102d331b29392a3c355d776a" - ], - "index": "pypi", - "markers": "python_version >= '3.6' and python_version < '4'", - "version": "==1.2.0" - }, - "dash-core-components": { - "hashes": [ - "sha256:52b8e8cce13b18d0802ee3acbc5e888cb1248a04968f962d63d070400af2e346", - "sha256:c6733874af975e552f95a1398a16c2ee7df14ce43fa60bb3718a3c6e0b63ffee" - ], - "version": "==2.0.0" - }, - "dash-extensions": { - "hashes": [ - "sha256:249bdc751cef8e17947d045664d342b21618247c464f0a6c88b982c45dea9ead", - "sha256:5bef3cdbf420777c89fec4570ddca6dfbb7842b44f3e8e888002db4078ba3a2f" - ], - "index": "pypi", - "markers": "python_version >= '3.7' and python_version < '4'", - "version": "==0.1.3" - }, - "dash-html-components": { - "hashes": [ - "sha256:8703a601080f02619a6390998e0b3da4a5daabe97a1fd7a9cebc09d015f26e50", - "sha256:b42cc903713c9706af03b3f2548bda4be7307a7cf89b7d6eae3da872717d1b63" - ], - "version": "==2.0.0" - }, - "dash-mantine-components": { - "hashes": [ - "sha256:2630bca31cb96d96fb2c4f986e639b9f92d6319aba8cba02f76da6c0d8f5ca48", - "sha256:c3dcbfd89813a1539654b8d016eb953dc5f67aafe1a77d45b5ec9faa6f25d3e7" - ], - "index": "pypi", - "version": "==0.12.1" - }, - "dash-table": { - "hashes": [ - "sha256:18624d693d4c8ef2ddec99a6f167593437a7ea0bf153aa20f318c170c5bc7308", - "sha256:19036fa352bb1c11baf38068ec62d172f0515f73ca3276c79dee49b95ddc16c9" - ], - "version": "==5.0.0" - }, - "editorconfig": { - "hashes": [ - "sha256:57f8ce78afcba15c8b18d46b5170848c88d56fd38f05c2ec60dbbfcb8996e89e", - "sha256:6b0851425aa875b08b16789ee0eeadbd4ab59666e9ebe728e526314c4a2e52c1" - ], - "version": "==0.12.3" - }, - "flask": { - "hashes": [ - "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b", - "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526" - ], - "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==2.2.2" - }, - "flask-caching": { - "hashes": [ - "sha256:bcda8acbc7508e31e50f63e9b1ab83185b446f6b6318bd9dd1d45626fba2e903", - "sha256:cf19b722fcebc2ba03e4ae7c55b532ed53f0cbf683ce36fafe5e881789a01c00" - ], - "index": "pypi", - "markers": "python_version >= '3.5'", - "version": "==1.10.1" - }, - "flask-compress": { - "hashes": [ - "sha256:b86c9808f0f38ea2246c9730972cf978f2cdf6a9a1a69102ba81e07891e6b26c", - "sha256:e46528f37b91857012be38e24e65db1a248662c3dc32ee7808b5986bf1d123ee" - ], - "version": "==1.14" - }, - "gunicorn": { - "hashes": [ - "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e", - "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8" - ], - "index": "pypi", - "markers": "python_version >= '3.5'", - "version": "==20.1.0" - }, - "h5py": { - "hashes": [ - "sha256:012ab448590e3c4f5a8dd0f3533255bc57f80629bf7c5054cf4c87b30085063c", - "sha256:212bb997a91e6a895ce5e2f365ba764debeaef5d2dca5c6fb7098d66607adf99", - "sha256:2381e98af081b6df7f6db300cd88f88e740649d77736e4b53db522d8874bf2dc", - "sha256:2c8e4fda19eb769e9a678592e67eaec3a2f069f7570c82d2da909c077aa94339", - "sha256:3074ec45d3dc6e178c6f96834cf8108bf4a60ccb5ab044e16909580352010a97", - "sha256:3c97d03f87f215e7759a354460fb4b0d0f27001450b18b23e556e7856a0b21c3", - "sha256:43a61b2c2ad65b1fabc28802d133eed34debcc2c8b420cb213d3d4ef4d3e2229", - "sha256:492305a074327e8d2513011fa9fffeb54ecb28a04ca4c4227d7e1e9616d35641", - "sha256:5dfc65ac21fa2f630323c92453cadbe8d4f504726ec42f6a56cf80c2f90d6c52", - "sha256:667fe23ab33d5a8a6b77970b229e14ae3bb84e4ea3382cc08567a02e1499eedd", - "sha256:6c013d2e79c00f28ffd0cc24e68665ea03ae9069e167087b2adb5727d2736a52", - "sha256:781a24263c1270a62cd67be59f293e62b76acfcc207afa6384961762bb88ea03", - "sha256:86df4c2de68257b8539a18646ceccdcf2c1ce6b1768ada16c8dcfb489eafae20", - "sha256:90286b79abd085e4e65e07c1bd7ee65a0f15818ea107f44b175d2dfe1a4674b7", - "sha256:92273ce69ae4983dadb898fd4d3bea5eb90820df953b401282ee69ad648df684", - "sha256:93dd840bd675787fc0b016f7a05fc6efe37312a08849d9dd4053fd0377b1357f", - "sha256:9450464b458cca2c86252b624279115dcaa7260a40d3cb1594bf2b410a2bd1a3", - "sha256:ae2f0201c950059676455daf92700eeb57dcf5caaf71b9e1328e6e6593601770", - "sha256:aece0e2e1ed2aab076c41802e50a0c3e5ef8816d60ece39107d68717d4559824", - "sha256:b963fb772964fc1d1563c57e4e2e874022ce11f75ddc6df1a626f42bd49ab99f", - "sha256:ba9ab36be991119a3ff32d0c7cbe5faf9b8d2375b5278b2aea64effbeba66039", - "sha256:d4682b94fd36ab217352be438abd44c8f357c5449b8995e63886b431d260f3d3", - "sha256:d93adc48ceeb33347eb24a634fb787efc7ae4644e6ea4ba733d099605045c049", - "sha256:f42e6c30698b520f0295d70157c4e202a9e402406f50dc08f5a7bc416b24e52d", - "sha256:fd6f6d1384a9f491732cee233b99cd4bfd6e838a8815cc86722f9d2ee64032af" - ], - "markers": "python_version >= '3.8'", - "version": "==3.10.0" - }, - "idna": { - "hashes": [ - "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", - "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" - ], - "markers": "python_version >= '3.5'", - "version": "==3.6" - }, - "itsdangerous": { - "hashes": [ - "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", - "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a" - ], - "markers": "python_version >= '3.7'", - "version": "==2.1.2" - }, - "jinja2": { - "hashes": [ - "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", - "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" - ], - "markers": "python_version >= '3.7'", - "version": "==3.1.2" - }, - "jsbeautifier": { - "hashes": [ - "sha256:6b632581ea60dd1c133cd25a48ad187b4b91f526623c4b0fb5443ef805250505" - ], - "version": "==1.14.11" - }, - "llvmlite": { - "hashes": [ - "sha256:04725975e5b2af416d685ea0769f4ecc33f97be541e301054c9f741003085802", - "sha256:0dd0338da625346538f1173a17cabf21d1e315cf387ca21b294ff209d176e244", - "sha256:150d0bc275a8ac664a705135e639178883293cf08c1a38de3bbaa2f693a0a867", - "sha256:1eee5cf17ec2b4198b509272cf300ee6577229d237c98cc6e63861b08463ddc6", - "sha256:210e458723436b2469d61b54b453474e09e12a94453c97ea3fbb0742ba5a83d8", - "sha256:2181bb63ef3c607e6403813421b46982c3ac6bfc1f11fa16a13eaafb46f578e6", - "sha256:24091a6b31242bcdd56ae2dbea40007f462260bc9bdf947953acc39dffd54f8f", - "sha256:2b76acee82ea0e9304be6be9d4b3840208d050ea0dcad75b1635fa06e949a0ae", - "sha256:2d92c51e6e9394d503033ffe3292f5bef1566ab73029ec853861f60ad5c925d0", - "sha256:5940bc901fb0325970415dbede82c0b7f3e35c2d5fd1d5e0047134c2c46b3281", - "sha256:8454c1133ef701e8c050a59edd85d238ee18bb9a0eb95faf2fca8b909ee3c89a", - "sha256:855f280e781d49e0640aef4c4af586831ade8f1a6c4df483fb901cbe1a48d127", - "sha256:880cb57ca49e862e1cd077104375b9d1dfdc0622596dfa22105f470d7bacb309", - "sha256:8b0a9a47c28f67a269bb62f6256e63cef28d3c5f13cbae4fab587c3ad506778b", - "sha256:92c32356f669e036eb01016e883b22add883c60739bc1ebee3a1cc0249a50828", - "sha256:92f093986ab92e71c9ffe334c002f96defc7986efda18397d0f08534f3ebdc4d", - "sha256:9564c19b31a0434f01d2025b06b44c7ed422f51e719ab5d24ff03b7560066c9a", - "sha256:b67340c62c93a11fae482910dc29163a50dff3dfa88bc874872d28ee604a83be", - "sha256:bf14aa0eb22b58c231243dccf7e7f42f7beec48970f2549b3a6acc737d1a4ba4", - "sha256:c1e1029d47ee66d3a0c4d6088641882f75b93db82bd0e6178f7bd744ebce42b9", - "sha256:df75594e5a4702b032684d5481db3af990b69c249ccb1d32687b8501f0689432", - "sha256:f19f767a018e6ec89608e1f6b13348fa2fcde657151137cb64e56d48598a92db", - "sha256:f8afdfa6da33f0b4226af8e64cfc2b28986e005528fbf944d0a24a72acfc9432", - "sha256:fa1469901a2e100c17eb8fe2678e34bd4255a3576d1a543421356e9c14d6e2ae" - ], - "markers": "python_version >= '3.8'", - "version": "==0.41.1" - }, - "markupsafe": { - "hashes": [ - "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e", - "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e", - "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431", - "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686", - "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c", - "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559", - "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc", - "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb", - "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939", - "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c", - "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0", - "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4", - "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9", - "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575", - "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba", - "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d", - "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd", - "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3", - "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00", - "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155", - "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac", - "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52", - "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f", - "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8", - "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b", - "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007", - "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24", - "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea", - "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198", - "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0", - "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee", - "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be", - "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2", - "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1", - "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707", - "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6", - "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c", - "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58", - "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823", - "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779", - "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636", - "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c", - "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad", - "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee", - "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc", - "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2", - "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48", - "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7", - "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e", - "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b", - "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa", - "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5", - "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e", - "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb", - "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9", - "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57", - "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc", - "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc", - "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2", - "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11" - ], - "markers": "python_version >= '3.7'", - "version": "==2.1.3" - }, - "more-itertools": { - "hashes": [ - "sha256:1bc4f91ee5b1b31ac7ceacc17c09befe6a40a503907baf9c839c229b5095cfd2", - "sha256:c09443cd3d5438b8dafccd867a6bc1cb0894389e90cb53d227456b0b0bccb750" - ], - "markers": "python_version >= '3.5'", - "version": "==8.14.0" - }, - "numba": { - "hashes": [ - "sha256:07f2fa7e7144aa6f275f27260e73ce0d808d3c62b30cff8906ad1dec12d87bbe", - "sha256:240e7a1ae80eb6b14061dc91263b99dc8d6af9ea45d310751b780888097c1aaa", - "sha256:45698b995914003f890ad839cfc909eeb9c74921849c712a05405d1a79c50f68", - "sha256:487ded0633efccd9ca3a46364b40006dbdaca0f95e99b8b83e778d1195ebcbaa", - "sha256:4e79b6cc0d2bf064a955934a2e02bf676bc7995ab2db929dbbc62e4c16551be6", - "sha256:55a01e1881120e86d54efdff1be08381886fe9f04fc3006af309c602a72bc44d", - "sha256:5c765aef472a9406a97ea9782116335ad4f9ef5c9f93fc05fd44aab0db486954", - "sha256:6fe7a9d8e3bd996fbe5eac0683227ccef26cba98dae6e5cee2c1894d4b9f16c1", - "sha256:7bf1ddd4f7b9c2306de0384bf3854cac3edd7b4d8dffae2ec1b925e4c436233f", - "sha256:811305d5dc40ae43c3ace5b192c670c358a89a4d2ae4f86d1665003798ea7a1a", - "sha256:81fe5b51532478149b5081311b0fd4206959174e660c372b94ed5364cfb37c82", - "sha256:898af055b03f09d33a587e9425500e5be84fc90cd2f80b3fb71c6a4a17a7e354", - "sha256:9e9356e943617f5e35a74bf56ff6e7cc83e6b1865d5e13cee535d79bf2cae954", - "sha256:a1eaa744f518bbd60e1f7ccddfb8002b3d06bd865b94a5d7eac25028efe0e0ff", - "sha256:bc2d904d0319d7a5857bd65062340bed627f5bfe9ae4a495aef342f072880d50", - "sha256:bcecd3fb9df36554b342140a4d77d938a549be635d64caf8bd9ef6c47a47f8aa", - "sha256:bd3dda77955be03ff366eebbfdb39919ce7c2620d86c906203bed92124989032", - "sha256:bf68df9c307fb0aa81cacd33faccd6e419496fdc621e83f1efce35cdc5e79cac", - "sha256:d3e2fe81fe9a59fcd99cc572002101119059d64d31eb6324995ee8b0f144a306", - "sha256:e63d6aacaae1ba4ef3695f1c2122b30fa3d8ba039c8f517784668075856d79e2", - "sha256:ea5bfcf7d641d351c6a80e8e1826eb4a145d619870016eeaf20bbd71ef5caa22" - ], - "markers": "python_version >= '3.8'", - "version": "==0.58.1" - }, - "numpy": { - "hashes": [ - "sha256:06fa1ed84aa60ea6ef9f91ba57b5ed963c3729534e6e54055fc151fad0423f0a", - "sha256:174a8880739c16c925799c018f3f55b8130c1f7c8e75ab0a6fa9d41cab092fd6", - "sha256:1a13860fdcd95de7cf58bd6f8bc5a5ef81c0b0625eb2c9a783948847abbef2c2", - "sha256:1cc3d5029a30fb5f06704ad6b23b35e11309491c999838c31f124fee32107c79", - "sha256:22f8fc02fdbc829e7a8c578dd8d2e15a9074b630d4da29cda483337e300e3ee9", - "sha256:26c9d33f8e8b846d5a65dd068c14e04018d05533b348d9eaeef6c1bd787f9919", - "sha256:2b3fca8a5b00184828d12b073af4d0fc5fdd94b1632c2477526f6bd7842d700d", - "sha256:2beef57fb031dcc0dc8fa4fe297a742027b954949cabb52a2a376c144e5e6060", - "sha256:36340109af8da8805d8851ef1d74761b3b88e81a9bd80b290bbfed61bd2b4f75", - "sha256:3703fc9258a4a122d17043e57b35e5ef1c5a5837c3db8be396c82e04c1cf9b0f", - "sha256:3ced40d4e9e18242f70dd02d739e44698df3dcb010d31f495ff00a31ef6014fe", - "sha256:4a06263321dfd3598cacb252f51e521a8cb4b6df471bb12a7ee5cbab20ea9167", - "sha256:4eb8df4bf8d3d90d091e0146f6c28492b0be84da3e409ebef54349f71ed271ef", - "sha256:5d5244aabd6ed7f312268b9247be47343a654ebea52a60f002dc70c769048e75", - "sha256:64308ebc366a8ed63fd0bf426b6a9468060962f1a4339ab1074c228fa6ade8e3", - "sha256:6a3cdb4d9c70e6b8c0814239ead47da00934666f668426fc6e94cce869e13fd7", - "sha256:854ab91a2906ef29dc3925a064fcd365c7b4da743f84b123002f6139bcb3f8a7", - "sha256:94cc3c222bb9fb5a12e334d0479b97bb2df446fbe622b470928f5284ffca3f8d", - "sha256:96ca5482c3dbdd051bcd1fce8034603d6ebfc125a7bd59f55b40d8f5d246832b", - "sha256:a2bbc29fcb1771cd7b7425f98b05307776a6baf43035d3b80c4b0f29e9545186", - "sha256:a4cd6ed4a339c21f1d1b0fdf13426cb3b284555c27ac2f156dfdaaa7e16bfab0", - "sha256:aa18428111fb9a591d7a9cc1b48150097ba6a7e8299fb56bdf574df650e7d1f1", - "sha256:aa317b2325f7aa0a9471663e6093c210cb2ae9c0ad824732b307d2c51983d5b6", - "sha256:b04f5dc6b3efdaab541f7857351aac359e6ae3c126e2edb376929bd3b7f92d7e", - "sha256:b272d4cecc32c9e19911891446b72e986157e6a1809b7b56518b4f3755267523", - "sha256:b361d369fc7e5e1714cf827b731ca32bff8d411212fccd29ad98ad622449cc36", - "sha256:b96e7b9c624ef3ae2ae0e04fa9b460f6b9f17ad8b4bec6d7756510f1f6c0c841", - "sha256:baf8aab04a2c0e859da118f0b38617e5ee65d75b83795055fb66c0d5e9e9b818", - "sha256:bcc008217145b3d77abd3e4d5ef586e3bdfba8fe17940769f8aa09b99e856c00", - "sha256:bd3f0091e845164a20bd5a326860c840fe2af79fa12e0469a12768a3ec578d80", - "sha256:cc392fdcbd21d4be6ae1bb4475a03ce3b025cd49a9be5345d76d7585aea69440", - "sha256:d73a3abcac238250091b11caef9ad12413dab01669511779bc9b29261dd50210", - "sha256:f43740ab089277d403aa07567be138fc2a89d4d9892d113b76153e0e412409f8", - "sha256:f65738447676ab5777f11e6bbbdb8ce11b785e105f690bc45966574816b6d3ea", - "sha256:f79b231bf5c16b1f39c7f4875e1ded36abee1591e98742b05d8a0fb55d8a3eec", - "sha256:fe6b44fb8fcdf7eda4ef4461b97b3f63c466b27ab151bec2366db8b197387841" - ], - "markers": "python_version >= '3.9'", - "version": "==1.26.2" - }, - "packaging": { - "hashes": [ - "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", - "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" - ], - "markers": "python_version >= '3.7'", - "version": "==23.2" - }, - "pandas": { - "hashes": [ - "sha256:0296a66200dee556850d99b24c54c7dfa53a3264b1ca6f440e42bad424caea03", - "sha256:04d4c58e1f112a74689da707be31cf689db086949c71828ef5da86727cfe3f82", - "sha256:08637041279b8981a062899da0ef47828df52a1838204d2b3761fbd3e9fcb549", - "sha256:11a771450f36cebf2a4c9dbd3a19dfa8c46c4b905a3ea09dc8e556626060fe71", - "sha256:1329dbe93a880a3d7893149979caa82d6ba64a25e471682637f846d9dbc10dd2", - "sha256:1f539e113739a3e0cc15176bf1231a553db0239bfa47a2c870283fd93ba4f683", - "sha256:22929f84bca106921917eb73c1521317ddd0a4c71b395bcf767a106e3494209f", - "sha256:321ecdb117bf0f16c339cc6d5c9a06063854f12d4d9bc422a84bb2ed3207380a", - "sha256:35172bff95f598cc5866c047f43c7f4df2c893acd8e10e6653a4b792ed7f19bb", - "sha256:3cc4469ff0cf9aa3a005870cb49ab8969942b7156e0a46cc3f5abd6b11051dfb", - "sha256:4441ac94a2a2613e3982e502ccec3bdedefe871e8cea54b8775992485c5660ef", - "sha256:465571472267a2d6e00657900afadbe6097c8e1dc43746917db4dfc862e8863e", - "sha256:59dfe0e65a2f3988e940224e2a70932edc964df79f3356e5f2997c7d63e758b4", - "sha256:72c84ec1b1d8e5efcbff5312abe92bfb9d5b558f11e0cf077f5496c4f4a3c99e", - "sha256:7cf4cf26042476e39394f1f86868d25b265ff787c9b2f0d367280f11afbdee6d", - "sha256:7fa2ad4ff196768ae63a33f8062e6838efed3a319cf938fdf8b95e956c813042", - "sha256:a5d53c725832e5f1645e7674989f4c106e4b7249c1d57549023ed5462d73b140", - "sha256:acf08a73b5022b479c1be155d4988b72f3020f308f7a87c527702c5f8966d34f", - "sha256:b99c4e51ef2ed98f69099c72c75ec904dd610eb41a32847c4fcbc1a975f2d2b8", - "sha256:d5ded6ff28abbf0ea7689f251754d3789e1edb0c4d0d91028f0b980598418a58", - "sha256:de21e12bf1511190fc1e9ebc067f14ca09fccfb189a813b38d63211d54832f5f", - "sha256:f7ea8ae8004de0381a2376662c0505bb0a4f679f4c61fbfd122aa3d1b0e5f09d", - "sha256:fc77309da3b55732059e484a1efc0897f6149183c522390772d3561f9bf96c00", - "sha256:fca5680368a5139d4920ae3dc993eb5106d49f814ff24018b64d8850a52c6ed2", - "sha256:fcd76d67ca2d48f56e2db45833cf9d58f548f97f61eecd3fdc74268417632b8a" - ], - "markers": "python_version >= '3.9'", - "version": "==2.1.3" - }, - "plotly": { - "hashes": [ - "sha256:23aa8ea2f4fb364a20d34ad38235524bd9d691bf5299e800bca608c31e8db8de", - "sha256:360a31e6fbb49d12b007036eb6929521343d6bee2236f8459915821baefa2cbb" - ], - "markers": "python_version >= '3.6'", - "version": "==5.18.0" - }, - "pvlib": { - "hashes": [ - "sha256:ead96f47898befd7728ab0b61b9747231008e151ef78a26d5e41d0b6a95a3a9d", - "sha256:f03b63e2d2fca13d5ca4c69010929dca5046e935da943d2dd1806e265aa44e93" - ], - "index": "pypi", - "markers": "python_version >= '3.6'", - "version": "==0.9.1" - }, - "pythermalcomfort": { - "hashes": [ - "sha256:49658026921b27d9f70211ac06ad2a0362e2bc569848604986e420fc7e0cbc17", - "sha256:ff6ff88476bff23619411bc90656f1ae54354a30d5b3a554434adc68a18d76c6" - ], - "index": "pypi", - "markers": "python_full_version >= '3.8.0'", - "version": "==2.8.10" - }, - "python-dateutil": { - "hashes": [ - "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", - "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.8.2" - }, - "pytz": { - "hashes": [ - "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b", - "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7" - ], - "version": "==2023.3.post1" - }, - "requests": { - "hashes": [ - "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", - "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" - ], - "markers": "python_version >= '3.7'", - "version": "==2.31.0" - }, - "scipy": { - "hashes": [ - "sha256:00150c5eae7b610c32589dda259eacc7c4f1665aedf25d921907f4d08a951b1c", - "sha256:028eccd22e654b3ea01ee63705681ee79933652b2d8f873e7949898dda6d11b6", - "sha256:1b7c3dca977f30a739e0409fb001056484661cb2541a01aba0bb0029f7b68db8", - "sha256:2c6ff6ef9cc27f9b3db93a6f8b38f97387e6e0591600369a297a50a8e96e835d", - "sha256:36750b7733d960d7994888f0d148d31ea3017ac15eef664194b4ef68d36a4a97", - "sha256:530f9ad26440e85766509dbf78edcfe13ffd0ab7fec2560ee5c36ff74d6269ff", - "sha256:5e347b14fe01003d3b78e196e84bd3f48ffe4c8a7b8a1afbcb8f5505cb710993", - "sha256:6550466fbeec7453d7465e74d4f4b19f905642c89a7525571ee91dd7adabb5a3", - "sha256:6df1468153a31cf55ed5ed39647279beb9cfb5d3f84369453b49e4b8502394fd", - "sha256:6e619aba2df228a9b34718efb023966da781e89dd3d21637b27f2e54db0410d7", - "sha256:8fce70f39076a5aa62e92e69a7f62349f9574d8405c0a5de6ed3ef72de07f446", - "sha256:90a2b78e7f5733b9de748f589f09225013685f9b218275257f8a8168ededaeaa", - "sha256:91af76a68eeae0064887a48e25c4e616fa519fa0d38602eda7e0f97d65d57937", - "sha256:933baf588daa8dc9a92c20a0be32f56d43faf3d1a60ab11b3f08c356430f6e56", - "sha256:acf8ed278cc03f5aff035e69cb511741e0418681d25fbbb86ca65429c4f4d9cd", - "sha256:ad669df80528aeca5f557712102538f4f37e503f0c5b9541655016dd0932ca79", - "sha256:b030c6674b9230d37c5c60ab456e2cf12f6784596d15ce8da9365e70896effc4", - "sha256:b9999c008ccf00e8fbcce1236f85ade5c569d13144f77a1946bef8863e8f6eb4", - "sha256:bc9a714581f561af0848e6b69947fda0614915f072dfd14142ed1bfe1b806710", - "sha256:ce7fff2e23ab2cc81ff452a9444c215c28e6305f396b2ba88343a567feec9660", - "sha256:cf00bd2b1b0211888d4dc75656c0412213a8b25e80d73898083f402b50f47e41", - "sha256:d10e45a6c50211fe256da61a11c34927c68f277e03138777bdebedd933712fea", - "sha256:ee410e6de8f88fd5cf6eadd73c135020bfbbbdfcd0f6162c36a7638a1ea8cc65", - "sha256:f313b39a7e94f296025e3cffc2c567618174c0b1dde173960cf23808f9fae4be", - "sha256:f3cd9e7b3c2c1ec26364856f9fbe78695fe631150f94cd1c22228456404cf1ec" - ], - "markers": "python_version >= '3.9'", - "version": "==1.11.4" - }, - "setuptools": { - "hashes": [ - "sha256:1e8fdff6797d3865f37397be788a4e3cba233608e9b509382a2777d25ebde7f2", - "sha256:735896e78a4742605974de002ac60562d286fa8051a7e2299445e8e8fbb01aa6" - ], - "markers": "python_version >= '3.8'", - "version": "==69.0.2" - }, - "six": { - "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.16.0" - }, - "tenacity": { - "hashes": [ - "sha256:5398ef0d78e63f40007c1fb4c0bff96e1911394d2fa8d194f77619c05ff6cc8a", - "sha256:ce510e327a630c9e1beaf17d42e6ffacc88185044ad85cf74c0a8887c6a0f88c" - ], - "markers": "python_version >= '3.7'", - "version": "==8.2.3" - }, - "tzdata": { - "hashes": [ - "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a", - "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda" - ], - "markers": "python_version >= '2'", - "version": "==2023.3" - }, - "urllib3": { - "hashes": [ - "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3", - "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54" - ], - "markers": "python_version >= '3.8'", - "version": "==2.1.0" - }, - "werkzeug": { - "hashes": [ - "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f", - "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5" - ], - "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==2.2.2" - } - }, - "develop": { - "bump2version": { - "hashes": [ - "sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410", - "sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6" - ], - "index": "pypi", - "markers": "python_version >= '3.5'", - "version": "==1.0.1" - }, - "certifi": { - "hashes": [ - "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1", - "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474" - ], - "markers": "python_version >= '3.6'", - "version": "==2023.11.17" - }, - "charset-normalizer": { - "hashes": [ - "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", - "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", - "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", - "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", - "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", - "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", - "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", - "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", - "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", - "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", - "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", - "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", - "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", - "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", - "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", - "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", - "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", - "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", - "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", - "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", - "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", - "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", - "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", - "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", - "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", - "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", - "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", - "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", - "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", - "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", - "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", - "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", - "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", - "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", - "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", - "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", - "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", - "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", - "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", - "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", - "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", - "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", - "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", - "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", - "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", - "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", - "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", - "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", - "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", - "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", - "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", - "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", - "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", - "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", - "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", - "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", - "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", - "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", - "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", - "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", - "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", - "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", - "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", - "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", - "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", - "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", - "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", - "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", - "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", - "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", - "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", - "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", - "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", - "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", - "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", - "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", - "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", - "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", - "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", - "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", - "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", - "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", - "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", - "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", - "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", - "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", - "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", - "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", - "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", - "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" - ], - "markers": "python_full_version >= '3.7.0'", - "version": "==3.3.2" - }, - "cleanpy": { - "hashes": [ - "sha256:291dd6b2643b2f434a95c0a4bbefac8b152e7b63cad1d369b0b605b156dbfa90", - "sha256:9d047ed8c6d3e1439f99b02633a94b1cbae65bbd4d928b5484367a674d66b5d6" - ], - "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==0.4.0" - }, - "exceptiongroup": { - "hashes": [ - "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14", - "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68" - ], - "markers": "python_version < '3.11'", - "version": "==1.2.0" - }, - "idna": { - "hashes": [ - "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", - "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" - ], - "markers": "python_version >= '3.5'", - "version": "==3.6" - }, - "iniconfig": { - "hashes": [ - "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", - "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" - ], - "markers": "python_version >= '3.7'", - "version": "==2.0.0" - }, - "numpy": { - "hashes": [ - "sha256:06fa1ed84aa60ea6ef9f91ba57b5ed963c3729534e6e54055fc151fad0423f0a", - "sha256:174a8880739c16c925799c018f3f55b8130c1f7c8e75ab0a6fa9d41cab092fd6", - "sha256:1a13860fdcd95de7cf58bd6f8bc5a5ef81c0b0625eb2c9a783948847abbef2c2", - "sha256:1cc3d5029a30fb5f06704ad6b23b35e11309491c999838c31f124fee32107c79", - "sha256:22f8fc02fdbc829e7a8c578dd8d2e15a9074b630d4da29cda483337e300e3ee9", - "sha256:26c9d33f8e8b846d5a65dd068c14e04018d05533b348d9eaeef6c1bd787f9919", - "sha256:2b3fca8a5b00184828d12b073af4d0fc5fdd94b1632c2477526f6bd7842d700d", - "sha256:2beef57fb031dcc0dc8fa4fe297a742027b954949cabb52a2a376c144e5e6060", - "sha256:36340109af8da8805d8851ef1d74761b3b88e81a9bd80b290bbfed61bd2b4f75", - "sha256:3703fc9258a4a122d17043e57b35e5ef1c5a5837c3db8be396c82e04c1cf9b0f", - "sha256:3ced40d4e9e18242f70dd02d739e44698df3dcb010d31f495ff00a31ef6014fe", - "sha256:4a06263321dfd3598cacb252f51e521a8cb4b6df471bb12a7ee5cbab20ea9167", - "sha256:4eb8df4bf8d3d90d091e0146f6c28492b0be84da3e409ebef54349f71ed271ef", - "sha256:5d5244aabd6ed7f312268b9247be47343a654ebea52a60f002dc70c769048e75", - "sha256:64308ebc366a8ed63fd0bf426b6a9468060962f1a4339ab1074c228fa6ade8e3", - "sha256:6a3cdb4d9c70e6b8c0814239ead47da00934666f668426fc6e94cce869e13fd7", - "sha256:854ab91a2906ef29dc3925a064fcd365c7b4da743f84b123002f6139bcb3f8a7", - "sha256:94cc3c222bb9fb5a12e334d0479b97bb2df446fbe622b470928f5284ffca3f8d", - "sha256:96ca5482c3dbdd051bcd1fce8034603d6ebfc125a7bd59f55b40d8f5d246832b", - "sha256:a2bbc29fcb1771cd7b7425f98b05307776a6baf43035d3b80c4b0f29e9545186", - "sha256:a4cd6ed4a339c21f1d1b0fdf13426cb3b284555c27ac2f156dfdaaa7e16bfab0", - "sha256:aa18428111fb9a591d7a9cc1b48150097ba6a7e8299fb56bdf574df650e7d1f1", - "sha256:aa317b2325f7aa0a9471663e6093c210cb2ae9c0ad824732b307d2c51983d5b6", - "sha256:b04f5dc6b3efdaab541f7857351aac359e6ae3c126e2edb376929bd3b7f92d7e", - "sha256:b272d4cecc32c9e19911891446b72e986157e6a1809b7b56518b4f3755267523", - "sha256:b361d369fc7e5e1714cf827b731ca32bff8d411212fccd29ad98ad622449cc36", - "sha256:b96e7b9c624ef3ae2ae0e04fa9b460f6b9f17ad8b4bec6d7756510f1f6c0c841", - "sha256:baf8aab04a2c0e859da118f0b38617e5ee65d75b83795055fb66c0d5e9e9b818", - "sha256:bcc008217145b3d77abd3e4d5ef586e3bdfba8fe17940769f8aa09b99e856c00", - "sha256:bd3f0091e845164a20bd5a326860c840fe2af79fa12e0469a12768a3ec578d80", - "sha256:cc392fdcbd21d4be6ae1bb4475a03ce3b025cd49a9be5345d76d7585aea69440", - "sha256:d73a3abcac238250091b11caef9ad12413dab01669511779bc9b29261dd50210", - "sha256:f43740ab089277d403aa07567be138fc2a89d4d9892d113b76153e0e412409f8", - "sha256:f65738447676ab5777f11e6bbbdb8ce11b785e105f690bc45966574816b6d3ea", - "sha256:f79b231bf5c16b1f39c7f4875e1ded36abee1591e98742b05d8a0fb55d8a3eec", - "sha256:fe6b44fb8fcdf7eda4ef4461b97b3f63c466b27ab151bec2366db8b197387841" - ], - "markers": "python_version >= '3.9'", - "version": "==1.26.2" - }, - "packaging": { - "hashes": [ - "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", - "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" - ], - "markers": "python_version >= '3.7'", - "version": "==23.2" - }, - "pandas": { - "hashes": [ - "sha256:0296a66200dee556850d99b24c54c7dfa53a3264b1ca6f440e42bad424caea03", - "sha256:04d4c58e1f112a74689da707be31cf689db086949c71828ef5da86727cfe3f82", - "sha256:08637041279b8981a062899da0ef47828df52a1838204d2b3761fbd3e9fcb549", - "sha256:11a771450f36cebf2a4c9dbd3a19dfa8c46c4b905a3ea09dc8e556626060fe71", - "sha256:1329dbe93a880a3d7893149979caa82d6ba64a25e471682637f846d9dbc10dd2", - "sha256:1f539e113739a3e0cc15176bf1231a553db0239bfa47a2c870283fd93ba4f683", - "sha256:22929f84bca106921917eb73c1521317ddd0a4c71b395bcf767a106e3494209f", - "sha256:321ecdb117bf0f16c339cc6d5c9a06063854f12d4d9bc422a84bb2ed3207380a", - "sha256:35172bff95f598cc5866c047f43c7f4df2c893acd8e10e6653a4b792ed7f19bb", - "sha256:3cc4469ff0cf9aa3a005870cb49ab8969942b7156e0a46cc3f5abd6b11051dfb", - "sha256:4441ac94a2a2613e3982e502ccec3bdedefe871e8cea54b8775992485c5660ef", - "sha256:465571472267a2d6e00657900afadbe6097c8e1dc43746917db4dfc862e8863e", - "sha256:59dfe0e65a2f3988e940224e2a70932edc964df79f3356e5f2997c7d63e758b4", - "sha256:72c84ec1b1d8e5efcbff5312abe92bfb9d5b558f11e0cf077f5496c4f4a3c99e", - "sha256:7cf4cf26042476e39394f1f86868d25b265ff787c9b2f0d367280f11afbdee6d", - "sha256:7fa2ad4ff196768ae63a33f8062e6838efed3a319cf938fdf8b95e956c813042", - "sha256:a5d53c725832e5f1645e7674989f4c106e4b7249c1d57549023ed5462d73b140", - "sha256:acf08a73b5022b479c1be155d4988b72f3020f308f7a87c527702c5f8966d34f", - "sha256:b99c4e51ef2ed98f69099c72c75ec904dd610eb41a32847c4fcbc1a975f2d2b8", - "sha256:d5ded6ff28abbf0ea7689f251754d3789e1edb0c4d0d91028f0b980598418a58", - "sha256:de21e12bf1511190fc1e9ebc067f14ca09fccfb189a813b38d63211d54832f5f", - "sha256:f7ea8ae8004de0381a2376662c0505bb0a4f679f4c61fbfd122aa3d1b0e5f09d", - "sha256:fc77309da3b55732059e484a1efc0897f6149183c522390772d3561f9bf96c00", - "sha256:fca5680368a5139d4920ae3dc993eb5106d49f814ff24018b64d8850a52c6ed2", - "sha256:fcd76d67ca2d48f56e2db45833cf9d58f548f97f61eecd3fdc74268417632b8a" - ], - "markers": "python_version >= '3.9'", - "version": "==2.1.3" - }, - "pluggy": { - "hashes": [ - "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12", - "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7" - ], - "markers": "python_version >= '3.8'", - "version": "==1.3.0" - }, - "pytest": { - "hashes": [ - "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac", - "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5" - ], - "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==7.4.3" - }, - "python-dateutil": { - "hashes": [ - "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", - "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.8.2" - }, - "pytz": { - "hashes": [ - "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b", - "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7" - ], - "version": "==2023.3.post1" - }, - "requests": { - "hashes": [ - "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", - "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" - ], - "markers": "python_version >= '3.7'", - "version": "==2.31.0" - }, - "six": { - "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.16.0" - }, - "tomli": { - "hashes": [ - "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", - "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" - ], - "markers": "python_version < '3.11'", - "version": "==2.0.1" - }, - "tzdata": { - "hashes": [ - "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a", - "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda" - ], - "markers": "python_version >= '2'", - "version": "==2023.3" - }, - "urllib3": { - "hashes": [ - "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3", - "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54" - ], - "markers": "python_version >= '3.8'", - "version": "==2.1.0" - } - } -} diff --git a/docs/contributing/run-project-locally.md b/docs/contributing/run-project-locally.md index b2fe7236..1a54f717 100644 --- a/docs/contributing/run-project-locally.md +++ b/docs/contributing/run-project-locally.md @@ -110,3 +110,12 @@ gcloud builds submit --tag us-docker.pkg.dev/clima-316917/gcr.io/clima-test --p gcloud run deploy clima-test --image us-docker.pkg.dev/clima-316917/gcr.io/clima-test --platform managed --project=clima-316917 --allow-unauthenticated --region=us-central1 --memory=4Gi --concurrency=80 --cpu=2 ``` + +### Test project + +```text +gcloud components update +gcloud config set run/region us-central1 +gcloud config set project clima-316917 +gcloud run deploy clima-test --source . +``` From d96245f717627d906cc1669e3ec3abb101a86e37 Mon Sep 17 00:00:00 2001 From: t2the0bi <49641232+t2the0bi@users.noreply.github.com> Date: Sat, 4 May 2024 14:14:47 -0700 Subject: [PATCH 02/91] Restructure app responding to #151 --- .gitignore | 4 +- app.py | 1 + main.py | 115 +++++++-------- my_project/page_changelog/__init__.py | 0 my_project/tab_data_explorer/__init__.py | 0 .../tab_natural_ventilation/__init__.py | 0 my_project/tab_outdoor_comfort/__init__.py | 0 my_project/tab_psy_chart/__init__.py | 0 my_project/tab_select/__init__.py | 0 my_project/tab_summary/__init__.py | 0 my_project/tab_sun/__init__.py | 0 my_project/tab_t_rh/__init__.py | 0 my_project/tab_under_construction/__init__.py | 0 .../tab_under_construction/construction.py | 12 -- my_project/tab_wind/__init__.py | 0 .../app_changelog.py => pages/changelog.py | 4 +- .../app_data_explorer.py => pages/explorer.py | 38 ++--- {my_project => pages/lib}/__init__.py | 0 .../lib}/charts_data_explorer.py | 2 +- .../lib}/charts_summary.py | 0 .../tab_sun => pages/lib}/charts_sun.py | 4 +- {my_project => pages/lib}/extract_df.py | 6 +- {my_project => pages/lib}/global_scheme.py | 0 .../lib}/import_one_building_files.py | 2 +- {my_project => pages/lib}/layout.py | 0 {my_project => pages/lib}/template_graphs.py | 8 +- {my_project => pages/lib}/utils.py | 2 +- .../natural_ventilation.py | 131 ++++++++++-------- {my_project => pages}/not_found_404.py | 2 +- .../outdoor.py | 34 ++--- .../app_psy_chart.py => pages/psy-chart.py | 36 +++-- .../app_select.py => pages/select.py | 98 +++++-------- .../app_summary.py => pages/summary.py | 95 ++++++++----- my_project/tab_sun/app_sun.py => pages/sun.py | 113 ++++++++------- .../tab_t_rh/app_t_rh.py => pages/t_rh.py | 33 ++--- .../tab_wind/app_wind.py => pages/wind.py | 37 +++-- 36 files changed, 396 insertions(+), 381 deletions(-) delete mode 100644 my_project/page_changelog/__init__.py delete mode 100644 my_project/tab_data_explorer/__init__.py delete mode 100644 my_project/tab_natural_ventilation/__init__.py delete mode 100644 my_project/tab_outdoor_comfort/__init__.py delete mode 100644 my_project/tab_psy_chart/__init__.py delete mode 100644 my_project/tab_select/__init__.py delete mode 100644 my_project/tab_summary/__init__.py delete mode 100644 my_project/tab_sun/__init__.py delete mode 100644 my_project/tab_t_rh/__init__.py delete mode 100644 my_project/tab_under_construction/__init__.py delete mode 100644 my_project/tab_under_construction/construction.py delete mode 100644 my_project/tab_wind/__init__.py rename my_project/page_changelog/app_changelog.py => pages/changelog.py (71%) rename my_project/tab_data_explorer/app_data_explorer.py => pages/explorer.py (98%) rename {my_project => pages/lib}/__init__.py (100%) rename {my_project/tab_data_explorer => pages/lib}/charts_data_explorer.py (98%) rename {my_project/tab_summary => pages/lib}/charts_summary.py (100%) rename {my_project/tab_sun => pages/lib}/charts_sun.py (99%) rename {my_project => pages/lib}/extract_df.py (99%) rename {my_project => pages/lib}/global_scheme.py (100%) rename {my_project => pages/lib}/import_one_building_files.py (98%) rename {my_project => pages/lib}/layout.py (100%) rename {my_project => pages/lib}/template_graphs.py (99%) rename {my_project => pages/lib}/utils.py (99%) rename my_project/tab_natural_ventilation/app_natural_ventilation.py => pages/natural_ventilation.py (90%) rename {my_project => pages}/not_found_404.py (94%) rename my_project/tab_outdoor_comfort/app_outdoor_comfort.py => pages/outdoor.py (97%) rename my_project/tab_psy_chart/app_psy_chart.py => pages/psy-chart.py (97%) rename my_project/tab_select/app_select.py => pages/select.py (85%) rename my_project/tab_summary/app_summary.py => pages/summary.py (94%) rename my_project/tab_sun/app_sun.py => pages/sun.py (83%) rename my_project/tab_t_rh/app_t_rh.py => pages/t_rh.py (91%) rename my_project/tab_wind/app_wind.py => pages/wind.py (98%) diff --git a/.gitignore b/.gitignore index c35b2a01..5da7aa3f 100644 --- a/.gitignore +++ b/.gitignore @@ -276,4 +276,6 @@ bh_unicode_properties.cache # Sublime-github package stores a github token in this file # https://packagecontrol.io/packages/sublime-github -GitHub.sublime-settings \ No newline at end of file +GitHub.sublime-settings + +file_system_backend \ No newline at end of file diff --git a/app.py b/app.py index e3bacdb7..f4e09200 100644 --- a/app.py +++ b/app.py @@ -4,6 +4,7 @@ app = DashProxy( __name__, + use_pages=True, external_stylesheets=[dbc.themes.BOOTSTRAP], transforms=[ServersideOutputTransform()], suppress_callback_exceptions=True, diff --git a/main.py b/main.py index 502eb7d5..bb8379be 100644 --- a/main.py +++ b/main.py @@ -4,19 +4,7 @@ from dash.dependencies import Input, Output from app import app -from my_project.layout import banner, build_tabs, footer -from my_project.page_changelog.app_changelog import changelog -from my_project.tab_data_explorer.app_data_explorer import layout_data_explorer -from my_project.tab_natural_ventilation.app_natural_ventilation import ( - layout_natural_ventilation, -) -from my_project.tab_outdoor_comfort.app_outdoor_comfort import layout_outdoor_comfort -from my_project.tab_psy_chart.app_psy_chart import layout_psy_chart -from my_project.tab_select.app_select import layout_select -from my_project.tab_summary.app_summary import layout_summary -from my_project.tab_sun.app_sun import layout_sun -from my_project.tab_t_rh.app_t_rh import layout_t_rh -from my_project.tab_wind.app_wind import layout_wind +from pages.lib.layout import banner, build_tabs, footer, store server = app.server @@ -25,55 +13,74 @@ fluid=True, style={"padding": "0"}, children=[ - dcc.Location(id="url", refresh=False), + dcc.Location(id="url", refresh=False), # connected to callback below banner(), - html.Div(id="page-content"), + dbc.Nav( + [ + dbc.NavLink( + html.Div(page["name"], className="ms-2"), + href=page["path"], + active="exact", + style={'color':'white','text-align': 'center', 'height': '100%'} + ) + for page in dash.page_registry.values() if page["name"] not in ["404", "changelog"] + ], + pills=True, + justified=True, + style={'background-color': '#003262', + 'padding': '0.25rem 0.5rem', + 'justify-content': 'center', + 'align-items': 'center', + } + ), + html.Div(id="store-container", children=[store()]), + dash.page_container, footer(), ], ) -@app.callback( - dash.dependencies.Output("page-content", "children"), - [dash.dependencies.Input("url", "pathname")], -) -def display_page(pathname): - if pathname == "/": - return build_tabs() - elif pathname == "/changelog": - return html.Div(children=[changelog()]) +# @app.callback( +# dash.dependencies.Output("page-content", "children"), +# [dash.dependencies.Input("url", "pathname")], +# ) +# def display_page(pathname): +# if pathname == "/": +# return build_tabs() +# elif pathname == "/changelog": +# return html.Div(children=[changelog()]) -# Handle tab selection -@app.callback( - Output("tabs-content", "children"), - [ - Input("tabs", "value"), - Input("si-ip-radio-input", "value"), - ], -) -def render_content(tab, si_ip): - """Update the contents of the page depending on what tab the user selects.""" - if tab == "tab-select": - return layout_select() - elif tab == "tab-summary": - return layout_summary(si_ip) - elif tab == "tab-t-rh": - return layout_t_rh() - elif tab == "tab-sun": - return layout_sun(si_ip) - elif tab == "tab-wind": - return layout_wind() - elif tab == "tab-data-explorer": - return layout_data_explorer() - elif tab == "tab-outdoor-comfort": - return layout_outdoor_comfort() - elif tab == "tab-natural-ventilation": - return layout_natural_ventilation(si_ip) - elif tab == "tab-psy-chart": - return layout_psy_chart() - else: - return "404" +# # Handle tab selection +# @app.callback( +# Output("tabs-content", "children"), +# [ +# Input("tabs", "value"), +# Input("si-ip-radio-input", "value"), +# ], +# ) +# def render_content(tab, si_ip): +# """Update the contents of the page depending on what tab the user selects.""" +# if tab == "tab-select": +# return layout_select() +# elif tab == "tab-summary": +# return layout_summary(si_ip) +# elif tab == "tab-t-rh": +# return layout_t_rh() +# elif tab == "tab-sun": +# return layout_sun(si_ip) +# elif tab == "tab-wind": +# return layout_wind() +# elif tab == "tab-data-explorer": +# return layout_data_explorer() +# elif tab == "tab-outdoor-comfort": +# return layout_outdoor_comfort() +# elif tab == "tab-natural-ventilation": +# return layout_natural_ventilation(si_ip) +# elif tab == "tab-psy-chart": +# return layout_psy_chart() +# else: +# return "404" if __name__ == "__main__": diff --git a/my_project/page_changelog/__init__.py b/my_project/page_changelog/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/my_project/tab_data_explorer/__init__.py b/my_project/tab_data_explorer/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/my_project/tab_natural_ventilation/__init__.py b/my_project/tab_natural_ventilation/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/my_project/tab_outdoor_comfort/__init__.py b/my_project/tab_outdoor_comfort/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/my_project/tab_psy_chart/__init__.py b/my_project/tab_psy_chart/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/my_project/tab_select/__init__.py b/my_project/tab_select/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/my_project/tab_summary/__init__.py b/my_project/tab_summary/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/my_project/tab_sun/__init__.py b/my_project/tab_sun/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/my_project/tab_t_rh/__init__.py b/my_project/tab_t_rh/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/my_project/tab_under_construction/__init__.py b/my_project/tab_under_construction/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/my_project/tab_under_construction/construction.py b/my_project/tab_under_construction/construction.py deleted file mode 100644 index a9f724ff..00000000 --- a/my_project/tab_under_construction/construction.py +++ /dev/null @@ -1,12 +0,0 @@ -from dash import html - - -def construction(): - return html.Div( - className="container-col", - id="construction-container", - children=[ - html.H4("This tab is under construction."), - html.P("Come back soon to check it out!"), - ], - ) diff --git a/my_project/tab_wind/__init__.py b/my_project/tab_wind/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/my_project/page_changelog/app_changelog.py b/pages/changelog.py similarity index 71% rename from my_project/page_changelog/app_changelog.py rename to pages/changelog.py index e6b81ee7..fb90c27a 100644 --- a/my_project/page_changelog/app_changelog.py +++ b/pages/changelog.py @@ -1,8 +1,10 @@ +import dash import dash_bootstrap_components as dbc from dash import dcc +dash.register_page(__name__, path='/changelog', name='changelog') -def changelog(): +def layout(): """changelog page""" f = open("CHANGELOG.md", "r") diff --git a/my_project/tab_data_explorer/app_data_explorer.py b/pages/explorer.py similarity index 98% rename from my_project/tab_data_explorer/app_data_explorer.py rename to pages/explorer.py index e0a5c2fa..edead0fc 100644 --- a/my_project/tab_data_explorer/app_data_explorer.py +++ b/pages/explorer.py @@ -1,9 +1,12 @@ +import dash +from dash import dcc, html import dash_bootstrap_components as dbc -from copy import deepcopy -from dash import dcc -from dash import html +from dash_extensions.enrich import Output, Input, State, callback from dash.exceptions import PreventUpdate -from my_project.utils import ( + +from copy import deepcopy + +from pages.lib.utils import ( generate_chart_name, generate_custom_inputs, generate_custom_inputs_explorer, @@ -16,7 +19,7 @@ dropdown, ) -from my_project.global_scheme import ( +from pages.lib.global_scheme import ( fig_config, dropdown_names, sun_cloud_tab_dropdown_names, @@ -26,14 +29,13 @@ container_col_center_one_of_three, mapping_dictionary, ) -from dash.dependencies import Input, Output, State -from my_project.tab_data_explorer.charts_data_explorer import ( +from pages.lib.charts_data_explorer import ( custom_heatmap, two_var_graph, three_var_graph, ) -from my_project.template_graphs import ( +from pages.lib.template_graphs import ( heatmap, yearly_profile, daily_profile, @@ -41,7 +43,8 @@ filter_df_by_month_and_hour, ) -from app import app +dash.register_page(__name__, name= 'Data Explorer', order=8) + explore_dropdown_names = {} explore_dropdown_names.update(deepcopy(dropdown_names)) @@ -604,7 +607,7 @@ def section_three(): ) -def layout_data_explorer(): +def layout(): """Return the contents of tab six." """ return html.Div( className="continer-col justify-center", @@ -612,7 +615,7 @@ def layout_data_explorer(): ) -@app.callback( +@callback( Output("yearly-explore", "children"), # Section One [ @@ -645,7 +648,7 @@ def update_tab_yearly(ts, var, global_local, df, meta, si_ip): ) -@app.callback( +@callback( Output("query-daily", "children"), [ Input("df-store", "modified_timestamp"), @@ -670,7 +673,7 @@ def update_tab_daily(ts, var, global_local, df, meta, si_ip): ) -@app.callback( +@callback( Output("query-heatmap", "children"), [ Input("df-store", "modified_timestamp"), @@ -695,7 +698,7 @@ def update_tab_heatmap(ts, var, global_local, df, meta, si_ip): ) -@app.callback( +@callback( [ Output("custom-heatmap", "children"), Output("custom-summary", "style"), @@ -808,7 +811,7 @@ def update_heatmap( ) -@app.callback( +@callback( [Output("three-var", "children"), Output("two-var", "children")], [ Input("df-store", "modified_timestamp"), @@ -832,7 +835,6 @@ def update_heatmap( State("si-ip-unit-store", "data"), ], ) -@code_timer def update_more_charts( ts, var_x, @@ -901,7 +903,7 @@ def update_more_charts( ) -@app.callback( +@callback( Output("table-data-explorer", "children"), [ Input("df-store", "modified_timestamp"), @@ -940,4 +942,4 @@ def update_table( ] return summary_table_tmp_rh_tab( filtered_df[["month", "hour", dd_value, "month_names"]], dd_value, si_ip - ) + ) \ No newline at end of file diff --git a/my_project/__init__.py b/pages/lib/__init__.py similarity index 100% rename from my_project/__init__.py rename to pages/lib/__init__.py diff --git a/my_project/tab_data_explorer/charts_data_explorer.py b/pages/lib/charts_data_explorer.py similarity index 98% rename from my_project/tab_data_explorer/charts_data_explorer.py rename to pages/lib/charts_data_explorer.py index 9211e8c2..0b162943 100644 --- a/my_project/tab_data_explorer/charts_data_explorer.py +++ b/pages/lib/charts_data_explorer.py @@ -5,7 +5,7 @@ import math import plotly.express as px import plotly.graph_objects as go -from my_project.global_scheme import template, mapping_dictionary, month_lst +from pages.lib.global_scheme import template, mapping_dictionary, month_lst def custom_heatmap(df, global_local, var, time_filter_info, data_filter_info, si_ip): diff --git a/my_project/tab_summary/charts_summary.py b/pages/lib/charts_summary.py similarity index 100% rename from my_project/tab_summary/charts_summary.py rename to pages/lib/charts_summary.py diff --git a/my_project/tab_sun/charts_sun.py b/pages/lib/charts_sun.py similarity index 99% rename from my_project/tab_sun/charts_sun.py rename to pages/lib/charts_sun.py index df6c6c21..2556dc7c 100644 --- a/my_project/tab_sun/charts_sun.py +++ b/pages/lib/charts_sun.py @@ -5,7 +5,7 @@ import numpy as np import pandas as pd import plotly.graph_objects as go -from my_project.global_scheme import ( +from pages.lib.global_scheme import ( template, mapping_dictionary, degrees_unit, @@ -122,7 +122,7 @@ def polar_graph(df, meta, global_local, var, si_ip): tz = "UTC" times = pd.date_range( - "2019-01-01 00:00:00", "2020-01-01", inclusive="left", freq="H", tz=tz + "2019-01-01 00:00:00", "2020-01-01", inclusive="left", freq="h", tz=tz ) delta = timedelta(days=0, hours=time_zone - 1, minutes=0) times = times - delta diff --git a/my_project/extract_df.py b/pages/lib/extract_df.py similarity index 99% rename from my_project/extract_df.py rename to pages/lib/extract_df.py index 1621a7c5..d3f96ec4 100644 --- a/my_project/extract_df.py +++ b/pages/lib/extract_df.py @@ -16,8 +16,8 @@ from pythermalcomfort.models import utci from pythermalcomfort.utilities import running_mean_outdoor_temperature -from my_project.global_scheme import month_lst -from my_project.utils import code_timer +from pages.lib.global_scheme import month_lst +from pages.lib.utils import code_timer @code_timer @@ -207,7 +207,7 @@ def create_df(lst, file_name): # Add in times df times = pd.date_range( - "2019-01-01 00:00:00", "2020-01-01", inclusive="left", freq="H", tz="UTC" + "2019-01-01 00:00:00", "2020-01-01", inclusive="left", freq="h", tz="UTC" ) epw_df["UTC_time"] = pd.to_datetime(times) delta = timedelta(days=0, hours=location_info["time_zone"] - 1, minutes=0) diff --git a/my_project/global_scheme.py b/pages/lib/global_scheme.py similarity index 100% rename from my_project/global_scheme.py rename to pages/lib/global_scheme.py diff --git a/my_project/import_one_building_files.py b/pages/lib/import_one_building_files.py similarity index 98% rename from my_project/import_one_building_files.py rename to pages/lib/import_one_building_files.py index 9d8fc8cb..32a57c5e 100644 --- a/my_project/import_one_building_files.py +++ b/pages/lib/import_one_building_files.py @@ -1,5 +1,5 @@ import pandas as pd -from my_project.utils import code_timer +from pages.lib.utils import code_timer import re diff --git a/my_project/layout.py b/pages/lib/layout.py similarity index 100% rename from my_project/layout.py rename to pages/lib/layout.py diff --git a/my_project/template_graphs.py b/pages/lib/template_graphs.py similarity index 99% rename from my_project/template_graphs.py rename to pages/lib/template_graphs.py index 696affb5..754f3dd8 100644 --- a/my_project/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -4,7 +4,7 @@ import pandas as pd import plotly.graph_objects as go from plotly.subplots import make_subplots -from my_project.global_scheme import mapping_dictionary +from pages.lib.global_scheme import mapping_dictionary import dash_bootstrap_components as dbc from .global_scheme import month_lst, template, tight_margins @@ -507,14 +507,14 @@ def wind_rose(df, title, month, hour, labels, si_ip): df["wind_dir"], bins=dir_bins, labels=dir_labels, right=False ) ) - .replace({"WindDir_bins": {360: 0}}) - .groupby(by=["WindSpd_bins", "WindDir_bins"]) + .ser.cat.rename_categories({"WindDir_bins": {360: 0}}) + .groupby(by=["WindSpd_bins", "WindDir_bins"], observed=False) .size() .unstack(level="WindSpd_bins") .fillna(0) .assign(calm=lambda df: calm_count / df.shape[0]) .sort_index(axis=1) - .applymap(lambda x: x / total_count * 100) + .map(lambda x: x / total_count * 100) ) fig = go.Figure() for i, col in enumerate(rose.columns): diff --git a/my_project/utils.py b/pages/lib/utils.py similarity index 99% rename from my_project/utils.py rename to pages/lib/utils.py index 03f89098..3547cc7e 100644 --- a/my_project/utils.py +++ b/pages/lib/utils.py @@ -1,6 +1,6 @@ import functools import time -from my_project.global_scheme import fig_config, mapping_dictionary +from pages.lib.global_scheme import fig_config, mapping_dictionary import pandas as pd import json from pandas import json_normalize diff --git a/my_project/tab_natural_ventilation/app_natural_ventilation.py b/pages/natural_ventilation.py similarity index 90% rename from my_project/tab_natural_ventilation/app_natural_ventilation.py rename to pages/natural_ventilation.py index 70f3d280..b3c6305c 100644 --- a/my_project/tab_natural_ventilation/app_natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -1,10 +1,15 @@ +import dash +from dash import dcc, html +import dash_bootstrap_components as dbc +from dash_extensions.enrich import Output, Input, State, callback + +import numpy as np import math import json -from dash import dcc -import dash_bootstrap_components as dbc -from dash import html import plotly.graph_objects as go -from my_project.global_scheme import ( + + +from pages.lib.global_scheme import ( template, mapping_dictionary, tight_margins, @@ -12,11 +17,10 @@ container_row_center_full, container_col_center_one_of_three, ) -from dash.dependencies import Input, Output, State -import numpy as np -from my_project.template_graphs import filter_df_by_month_and_hour -from my_project.utils import ( +from pages.lib.template_graphs import filter_df_by_month_and_hour + +from pages.lib.utils import ( title_with_tooltip, generate_chart_name, generate_units_degree, @@ -25,10 +29,24 @@ determine_month_and_hour_filter, ) -from app import app +dash.register_page(__name__, name= 'Natural Ventilation', order=6) + + +def layout(): + return html.Div( + className="container-col", + id='main-nv-section', + children=[ + # + ] + ) -def layout_natural_ventilation(si_ip): +@callback( + Output('main-nv-section', 'children'), + [Input('si-ip-radio-input', 'value')] +) +def update_layout(si_ip): if si_ip == "ip": tdb_set_min = 50 tdb_set_max = 75 @@ -37,55 +55,54 @@ def layout_natural_ventilation(si_ip): tdb_set_min = 10 tdb_set_max = 24 dpt_set = 16 - - return html.Div( - className="container-col", - children=[ - inputs_tab(tdb_set_min, tdb_set_max, dpt_set), - dcc.Loading( - html.Div( - id="nv-heatmap-chart", - style={"marginTop": "1rem"}, - ), - type="circle", - ), + + return [ + inputs_tab(tdb_set_min, tdb_set_max, dpt_set), + dcc.Loading( html.Div( - className="container-row align-center justify-center", - children=[ - dbc.Checklist( - options=[ - {"label": "", "value": 1}, - ], - value=[1], - id="switches-input", - switch=True, - style={ - "padding": "1rem", - "marginTop": "1rem", - "marginRight": "-2rem", - }, - ), - html.Div( - children=title_with_tooltip( - text="Normalize data", - tooltip_text=( - "If normalized is enabled it calculates the % " - "time otherwise it calculates the total number of hours" - ), - id_button="nv_normalize", - ), - ), - ], + id="nv-heatmap-chart", + style={"marginTop": "1rem"}, ), - dcc.Loading( + type="circle", + ), + html.Div( + className="container-row align-center justify-center", + children=[ + dbc.Checklist( + options=[ + {"label": "", "value": 1}, + ], + value=[1], + id="switches-input", + switch=True, + style={ + "padding": "1rem", + "marginTop": "1rem", + "marginRight": "-2rem", + }, + ), html.Div( - id="nv-bar-chart", - style={"marginTop": "1rem"}, + children=title_with_tooltip( + text="Normalize data", + tooltip_text=( + "If normalized is enabled it calculates the % " + "time otherwise it calculates the total number of hours" + ), + id_button="nv_normalize", + ), ), - type="circle", + ], + ), + dcc.Loading( + html.Div( + id="nv-bar-chart", + style={"marginTop": "1rem"}, ), - ], - ) + type="circle", + ), + ] + + def inputs_tab(t_min, t_max, d_set): @@ -254,7 +271,7 @@ def inputs_tab(t_min, t_max, d_set): ) -@app.callback( +@callback( Output("nv-heatmap-chart", "children"), [ Input("df-store", "modified_timestamp"), @@ -421,7 +438,7 @@ def nv_heatmap( ) -@app.callback( +@callback( Output("nv-bar-chart", "children"), [ Input("df-store", "modified_timestamp"), @@ -585,7 +602,7 @@ def nv_bar_chart( ) -@app.callback( +@callback( Output("nv-dpt-filter", "disabled"), Input("enable-condensation", "value") ) def enable_disable_button_data_filter(state_checklist): diff --git a/my_project/not_found_404.py b/pages/not_found_404.py similarity index 94% rename from my_project/not_found_404.py rename to pages/not_found_404.py index 9e077fec..84d92fa4 100644 --- a/my_project/not_found_404.py +++ b/pages/not_found_404.py @@ -3,7 +3,7 @@ from dash_iconify import DashIconify from dash_extensions import Lottie -dash.register_page(__name__) +dash.register_page(__name__, name= '404') layout = [ dmc.Title("I could not find the page you are currently looking for", order=4), diff --git a/my_project/tab_outdoor_comfort/app_outdoor_comfort.py b/pages/outdoor.py similarity index 97% rename from my_project/tab_outdoor_comfort/app_outdoor_comfort.py rename to pages/outdoor.py index aab6edfa..93f7486a 100644 --- a/my_project/tab_outdoor_comfort/app_outdoor_comfort.py +++ b/pages/outdoor.py @@ -1,21 +1,24 @@ -from dash import dcc -from dash import html +import dash +from dash import dcc, html import dash_bootstrap_components as dbc -from my_project.global_scheme import ( +from dash_extensions.enrich import Output, Input, State, callback + +import numpy as np + +from pages.lib.global_scheme import ( outdoor_dropdown_names, tight_margins, month_lst, container_row_center_full, container_col_center_one_of_three, ) -from my_project.utils import dropdown -from dash.dependencies import Input, Output, State -from my_project.template_graphs import ( +from pages.lib.template_graphs import ( heatmap_with_filter, thermal_stress_stacked_barchart, ) -from my_project.utils import ( +from pages.lib.utils import ( + dropdown, generate_chart_name, generate_units_degree, generate_units, @@ -24,8 +27,7 @@ ) -import numpy as np -from app import app +dash.register_page(__name__, name= 'Outdoor Comfort', order=7) def inputs_outdoor_comfort(): @@ -196,7 +198,7 @@ def outdoor_comfort_chart(): ) -def layout_outdoor_comfort(): +def layout(): return ( dcc.Loading( type="circle", @@ -208,7 +210,7 @@ def layout_outdoor_comfort(): ) -@app.callback( +@callback( Output("outdoor-comfort-output", "children"), [ Input("df-store", "modified_timestamp"), @@ -240,7 +242,7 @@ def update_outdoor_comfort_output(ts, df): return f"The Best Weather Condition is: {', '.join(colsWithTheHighestNumberOfZero)}" -@app.callback( +@callback( Output("utci-heatmap", "children"), [ Input("df-store", "modified_timestamp"), @@ -290,7 +292,7 @@ def update_tab_utci_value( ) -@app.callback( +@callback( Output("image-selection", "children"), Input("tab7-dropdown", "value"), ) @@ -307,7 +309,7 @@ def change_image_based_on_selection(value): return html.Img(src=source, height=50) -@app.callback( +@callback( Output("utci-category-heatmap", "children"), [ Input("df-store", "modified_timestamp"), @@ -377,7 +379,7 @@ def update_tab_utci_category( ) -@app.callback( +@callback( Output("utci-summary-chart", "children"), [ Input("tab7-dropdown", "value"), @@ -413,4 +415,4 @@ def update_tab_utci_summary_chart( return dcc.Graph( config=generate_chart_name("summary", meta, custom_inputs, units), figure=utci_summary_chart, - ) + ) \ No newline at end of file diff --git a/my_project/tab_psy_chart/app_psy_chart.py b/pages/psy-chart.py similarity index 97% rename from my_project/tab_psy_chart/app_psy_chart.py rename to pages/psy-chart.py index f3a5b56c..2795f8fa 100644 --- a/my_project/tab_psy_chart/app_psy_chart.py +++ b/pages/psy-chart.py @@ -1,18 +1,18 @@ +import dash +from dash import dcc, html +import dash_bootstrap_components as dbc +from dash_extensions.enrich import Output, Input, State, callback + import numpy as np +import pandas as pd import plotly.graph_objects as go import json from pythermalcomfort import psychrometrics as psy from math import ceil, floor -import dash_bootstrap_components as dbc from copy import deepcopy -from dash import dcc -from dash import html -from my_project.global_scheme import ( - container_row_center_full, - container_col_center_one_of_three, -) -from my_project.template_graphs import filter_df_by_month_and_hour -from my_project.utils import ( + +from pages.lib.template_graphs import filter_df_by_month_and_hour +from pages.lib.utils import ( generate_chart_name, generate_units, generate_custom_inputs_psy, @@ -20,23 +20,21 @@ title_with_link, dropdown, ) -from my_project.global_scheme import ( +from pages.lib.global_scheme import ( + container_row_center_full, + container_col_center_one_of_three, dropdown_names, sun_cloud_tab_dropdown_names, more_variables_dropdown, sun_cloud_tab_explore_dropdown_names, -) -from dash.dependencies import Input, Output, State -import pandas as pd - -from app import app - -from my_project.global_scheme import ( template, mapping_dictionary, tight_margins, ) + +dash.register_page(__name__, name= 'Psychrometric Chart', order=5) + psy_dropdown_names = { "None": "None", "Frequency": "Frequency", @@ -207,7 +205,7 @@ def inputs(): ) -def layout_psy_chart(): +def layout(): return ( html.Div( children=title_with_link( @@ -227,7 +225,7 @@ def layout_psy_chart(): # psychrometric chart -@app.callback( +@callback( Output("psych-chart", "children"), [ Input("df-store", "modified_timestamp"), diff --git a/my_project/tab_select/app_select.py b/pages/select.py similarity index 85% rename from my_project/tab_select/app_select.py rename to pages/select.py index d530758d..9c667d37 100644 --- a/my_project/tab_select/app_select.py +++ b/pages/select.py @@ -1,19 +1,19 @@ import base64 -import io import json import re + import dash import dash_bootstrap_components as dbc from dash.exceptions import PreventUpdate +from dash_extensions.enrich import Serverside, Output, Input, State, html, dcc, callback -from app import app -from my_project.extract_df import create_df, get_data, get_location_info -from my_project.utils import plot_location_epw_files, generate_chart_name -from my_project.global_scheme import mapping_dictionary -from my_project.extract_df import convert_data -from dash_extensions.enrich import ServersideOutput, Output, Input, State, html, dcc +from pages.lib.extract_df import convert_data +from pages.lib.extract_df import create_df, get_data, get_location_info +from pages.lib.global_scheme import mapping_dictionary +from pages.lib.utils import plot_location_epw_files, generate_chart_name +dash.register_page(__name__, path='/', name='Select Weather File', order=0) messages_alert = { "start": "To start, upload an EPW file or click on a point on the map!", @@ -23,8 +23,7 @@ "wrong_extension": "The file you have uploaded is not an EPW file", } - -def layout_select(): +def layout(): """Contents in the first tab 'Select Weather File'""" return html.Div( className="container-col tab-container", @@ -84,7 +83,6 @@ def layout_select(): ], ) - def alert(): """Alert layout for the submit button.""" return html.Div( @@ -102,7 +100,7 @@ def alert(): # add si-ip and map dictionary in the output -@app.callback( +@callback( [ Output("meta-store", "data"), Output("lines-store", "data"), @@ -197,9 +195,9 @@ def submitted_data( # add switch_si_ip function and convert the data-store -@app.callback( +@callback( [ - ServersideOutput("df-store", "data"), + Output("df-store", "data"), Output("si-ip-unit-store", "data"), ], [ @@ -214,7 +212,7 @@ def switch_si_ip(ts, si_ip_input, url_store, lines): map_json = json.dumps(mapping_dictionary) if si_ip_input == "ip": map_json = convert_data(df, map_json) - return df, si_ip_input + return Serverside(df), si_ip_input else: return ( None, @@ -222,53 +220,7 @@ def switch_si_ip(ts, si_ip_input, url_store, lines): ) -@app.callback( - [ - Output("tab-summary", "disabled"), - Output("tab-t-rh", "disabled"), - Output("tab-sun", "disabled"), - Output("tab-wind", "disabled"), - Output("tab-psy-chart", "disabled"), - Output("tab-data-explorer", "disabled"), - Output("tab-outdoor-comfort", "disabled"), - Output("tab-natural-ventilation", "disabled"), - Output("banner-subtitle", "children"), - ], - [ - Input("meta-store", "data"), - Input("df-store", "data"), - ], -) -def enable_tabs_when_data_is_loaded(meta, data): - """Hide tabs when data are not loaded""" - default = "Current Location: N/A" - if data is None: - return ( - True, - True, - True, - True, - True, - True, - True, - True, - default, - ) - else: - return ( - False, - False, - False, - False, - False, - False, - False, - False, - "Current Location: " + meta["city"] + ", " + meta["country"], - ) - - -@app.callback( +@callback( [ Output("modal", "is_open"), Output("url-store", "data"), @@ -291,7 +243,7 @@ def display_modal_when_data_clicked(clicks_use_epw, click_map, close_clicks, is_ return is_open, "" -@app.callback( +@callback( [ Output("modal-header", "children"), ], @@ -305,3 +257,25 @@ def display_modal_when_data_clicked(click_map): if click_map: return [f"Analyse data from {click_map['points'][0]['hovertext']}?"] return ["Analyse data from this location?"] + + +@callback( + [ + Output("banner-subtitle", "children"), + ], + [ + Input("meta-store", "data"), + Input("df-store", "data"), + ], +) +def enable_location_display(meta, data): + """Display current location in banner""" + default = "Current Location: N/A" + if data is None: + return ( + default, + ) + else: + return ( + "Current Location: " + meta["city"] + ", " + meta["country"], + ) \ No newline at end of file diff --git a/my_project/tab_summary/app_summary.py b/pages/summary.py similarity index 94% rename from my_project/tab_summary/app_summary.py rename to pages/summary.py index 3ff7fbb6..f3ef3d02 100644 --- a/my_project/tab_summary/app_summary.py +++ b/pages/summary.py @@ -1,27 +1,44 @@ -import dash_bootstrap_components as dbc import dash -import json +import dash_bootstrap_components as dbc +import plotly.graph_objects as go +import requests from dash.exceptions import PreventUpdate -from app import app -from my_project.tab_summary.charts_summary import world_map -from my_project.template_graphs import violin -from my_project.utils import ( +from dash_extensions.enrich import dcc, html, Output, Input, State, callback + +from pages.lib.extract_df import get_data +from pages.lib.global_scheme import template, tight_margins, mapping_dictionary +from pages.lib.charts_summary import world_map +from pages.lib.template_graphs import violin +from pages.lib.utils import ( generate_chart_name, generate_units, generate_units_degree, title_with_tooltip, title_with_link, ) -import plotly.graph_objects as go -from my_project.global_scheme import template, tight_margins, mapping_dictionary -import requests -from my_project.extract_df import convert_data, get_data -from my_project.utils import code_timer -from dash_extensions.enrich import dcc, html, Output, Input, State + +dash.register_page(__name__, name='Climate Summary', order=1) -def layout_summary(si_ip): +def layout(): """Contents in the second tab 'Climate Summary'.""" + + return html.Div( + className="container-col", + id="tab-two-container", + children=[ + # + ] + ) + + + +@callback( + Output('tab-two-container', 'children'), + [Input('si-ip-radio-input', 'value')] +) +def update_layout(si_ip): + if si_ip == "si": heating_setpoint = 10 cooling_setpoint = 18 @@ -30,10 +47,6 @@ def layout_summary(si_ip): cooling_setpoint = 64 return html.Div( - className="container-col", - id="tab-two-container", - children=[ - html.Div( className="container-col", id="tab2-sec1-container", children=[ @@ -164,16 +177,27 @@ def layout_summary(si_ip): ], ), ], - ), - ], - ) + ) + + + +# @callback( +# [Output('input-hdd-set-point', 'value'), Output('input-cdd-set-point', 'value')], +# [Input('si-ip-radio-input', 'value')] +# ) +# def update_setpoints(si_ip_unit_store_data): +# if si_ip_unit_store_data == 'si': +# return 10, 18 +# else: +# return 50, 64 + -@app.callback( + +@callback( Output("world-map", "children"), Input("meta-store", "data"), ) -@code_timer def update_map(meta): """Update the contents of tab two. Passing in the general info (df, meta).""" map_world = dcc.Graph( @@ -185,7 +209,7 @@ def update_map(meta): return map_world -@app.callback( +@callback( Output("location-info", "children"), Input("df-store", "modified_timestamp"), [ @@ -194,7 +218,6 @@ def update_map(meta): State("si-ip-unit-store", "data"), ], ) -@code_timer def update_location_info(ts, df, meta, si_ip): """Update the contents of tab two. Passing in the general info (df, meta).""" location = f"Location: {meta['city']}, {meta['country']}" @@ -275,7 +298,10 @@ def update_location_info(ts, df, meta, si_ip): return location_info -@app.callback( + + + +@callback( [ Output("degree-days-chart-wrapper", "children"), Output("warning-cdd-higher-hdd", "is_open"), @@ -293,7 +319,6 @@ def update_location_info(ts, df, meta, si_ip): State("si-ip-unit-store", "data"), ], ) -@code_timer def degree_day_chart(ts, ts_click, df, meta, hdd_value, cdd_value, n_clicks, si_ip): """Update the contents of tab two. Passing in the general info (df, meta).""" @@ -387,7 +412,7 @@ def degree_day_chart(ts, ts_click, df, meta, hdd_value, cdd_value, n_clicks, si_ return chart, warning_setpoint -@app.callback( +@callback( Output("temp-profile-graph", "children"), [ Input("df-store", "modified_timestamp"), @@ -399,7 +424,6 @@ def degree_day_chart(ts, ts_click, df, meta, hdd_value, cdd_value, n_clicks, si_ State("si-ip-unit-store", "data"), ], ) -@code_timer def update_violin_tdb(ts, global_local, df, meta, si_ip): units = generate_units_degree(si_ip) return dcc.Graph( @@ -410,7 +434,7 @@ def update_violin_tdb(ts, global_local, df, meta, si_ip): ) -@app.callback( +@callback( Output("wind-speed-graph", "children"), [ Input("df-store", "modified_timestamp"), @@ -422,7 +446,6 @@ def update_violin_tdb(ts, global_local, df, meta, si_ip): State("si-ip-unit-store", "data"), ], ) -@code_timer def update_tab_wind(ts, global_local, df, meta, si_ip): """Update the contents of tab two. Passing in the general info (df, meta).""" units = generate_units(si_ip) @@ -434,7 +457,7 @@ def update_tab_wind(ts, global_local, df, meta, si_ip): ) -@app.callback( +@callback( Output("humidity-profile-graph", "children"), [ Input("df-store", "modified_timestamp"), @@ -446,7 +469,6 @@ def update_tab_wind(ts, global_local, df, meta, si_ip): State("si-ip-unit-store", "data"), ], ) -@code_timer def update_tab_rh(ts, global_local, df, meta, si_ip): """Update the contents of tab two. Passing in the general info (df, meta).""" units = generate_units(si_ip) @@ -458,7 +480,7 @@ def update_tab_rh(ts, global_local, df, meta, si_ip): ) -@app.callback( +@callback( Output("solar-radiation-graph", "children"), [ Input("df-store", "modified_timestamp"), @@ -470,7 +492,6 @@ def update_tab_rh(ts, global_local, df, meta, si_ip): State("si-ip-unit-store", "data"), ], ) -@code_timer def update_tab_gh_rad(ts, global_local, df, meta, si_ip): """Update the contents of tab two. Passing in the general info (df, meta).""" units = generate_units(si_ip) @@ -482,7 +503,7 @@ def update_tab_gh_rad(ts, global_local, df, meta, si_ip): ) -@app.callback( +@callback( Output("download-dataframe-csv", "data"), [Input("download-button", "n_clicks")], [ @@ -492,7 +513,6 @@ def update_tab_gh_rad(ts, global_local, df, meta, si_ip): ], prevent_initial_call=True, ) -@code_timer def download_clima_dataframe(n_clicks, df, meta, si_ip): if n_clicks is None: raise PreventUpdate @@ -509,13 +529,12 @@ def download_clima_dataframe(n_clicks, df, meta, si_ip): print("df not loaded yet") -@app.callback( +@callback( Output("download-epw", "data"), [Input("download-epw-button", "n_clicks")], [State("meta-store", "data")], prevent_initial_call=True, ) -@code_timer def download_clima_dataframe(n_clicks, meta): if n_clicks is None: raise PreventUpdate diff --git a/my_project/tab_sun/app_sun.py b/pages/sun.py similarity index 83% rename from my_project/tab_sun/app_sun.py rename to pages/sun.py index cc639550..1b3795ba 100644 --- a/my_project/tab_sun/app_sun.py +++ b/pages/sun.py @@ -1,9 +1,13 @@ +import dash +from dash import html, dcc +from dash_extensions.enrich import Output, Input, State, callback +import dash_bootstrap_components as dbc + + import numpy as np from copy import deepcopy -from dash import dcc -from dash import html -import dash_bootstrap_components as dbc -from my_project.global_scheme import ( + +from pages.lib.global_scheme import ( sun_cloud_tab_dropdown_names, sun_cloud_tab_explore_dropdown_names, dropdown_names, @@ -12,17 +16,17 @@ month_lst, mapping_dictionary, ) -from my_project.utils import dropdown +from pages.lib.utils import dropdown from dash.dependencies import Input, Output, State -from my_project.tab_sun.charts_sun import ( +from pages.lib.charts_sun import ( monthly_solar, polar_graph, custom_cartesian_solar, ) -from my_project.template_graphs import heatmap, barchart, daily_profile -from my_project.utils import code_timer -from my_project.utils import ( +from pages.lib.template_graphs import heatmap, barchart, daily_profile +from pages.lib.utils import code_timer +from pages.lib.utils import ( title_with_tooltip, generate_chart_name, generate_units, @@ -30,7 +34,9 @@ title_with_link, ) -from app import app + +dash.register_page(__name__, name= 'Sun and Clouds', order=3) + sc_dropdown_names = { "None": "None", @@ -48,6 +54,7 @@ sc_dropdown_names.pop("UTCI: no Sun & no Wind : categories", None) + def sun_path(): """Return the layout for the custom sun path and its dropdowns.""" return html.Div( @@ -144,52 +151,64 @@ def explore_daily_heatmap(): ) -def static_section(si_ip): - if si_ip == "si": - hor_unit = "Wh/m²" - if si_ip == "ip": - hor_unit = "Btu/ft²" + +def static_section(): return html.Div( + id='static-section', className="container-col full-width", children=[ - html.Div( - children=title_with_link( - text="Global and Diffuse Horizontal Solar Radiation (" - + hor_unit - + ")", - id_button="monthly-chart-label", - doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/sun-and-cloud/global-and-diffuse-horizontal-solar-radiation", - ), - ), - dcc.Loading( - type="circle", - children=html.Div(id="monthly-solar"), - ), - html.Div( - children=title_with_link( - text="Cloud coverage", - id_button="cloud-chart-label", - doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/sun-and-cloud/cloud-coverage", - ), - ), - dcc.Loading( - type="circle", - children=html.Div(id="cloud-cover"), - ), + # ... ], ) -def layout_sun(si_ip): +def layout(): """Contents of tab four.""" return html.Div( className="container-col", id="tab-four-container", - children=[sun_path(), static_section(si_ip), explore_daily_heatmap()], + children=[sun_path(), static_section(), explore_daily_heatmap()], ) -@app.callback( +@callback( + Output('static-section', 'children'), + [Input('si-ip-radio-input', 'value')] +) +def update_static_section(si_ip): + if si_ip == "si": + hor_unit = "Wh/m²" + if si_ip == "ip": + hor_unit = "Btu/ft²" + return [ + html.Div( + children=title_with_link( + text="Global and Diffuse Horizontal Solar Radiation (" + + hor_unit + + ")", + id_button="monthly-chart-label", + doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/sun-and-cloud/global-and-diffuse-horizontal-solar-radiation", + ), + ), + dcc.Loading( + type="circle", + children=html.Div(id="monthly-solar"), + ), + html.Div( + children=title_with_link( + text="Cloud coverage", + id_button="cloud-chart-label", + doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/sun-and-cloud/cloud-coverage", + ), + ), + dcc.Loading( + type="circle", + children=html.Div(id="cloud-cover"), + ), + ] + + +@callback( [ Output("monthly-solar", "children"), Output("cloud-cover", "children"), @@ -203,7 +222,6 @@ def layout_sun(si_ip): State("si-ip-unit-store", "data"), ], ) -@code_timer def monthly_and_cloud_chart(ts, df, meta, si_ip): """Update the contents of tab four. Passing in the polar selection and the general info (df, meta).""" @@ -233,7 +251,7 @@ def monthly_and_cloud_chart(ts, df, meta, si_ip): ) -@app.callback( +@callback( Output("custom-sunpath", "children"), [ Input("df-store", "modified_timestamp"), @@ -247,7 +265,6 @@ def monthly_and_cloud_chart(ts, df, meta, si_ip): State("si-ip-unit-store", "data"), ], ) -@code_timer def sun_path_chart(ts, view, var, global_local, df, meta, si_ip): """Update the contents of tab four. Passing in the polar selection and the general info (df, meta).""" custom_inputs = "" if var == "None" else f"{var}" @@ -264,7 +281,7 @@ def sun_path_chart(ts, view, var, global_local, df, meta, si_ip): ) -@app.callback( +@callback( Output("tab4-daily", "children"), [ Input("df-store", "modified_timestamp"), @@ -277,7 +294,6 @@ def sun_path_chart(ts, view, var, global_local, df, meta, si_ip): State("si-ip-unit-store", "data"), ], ) -@code_timer def daily(ts, var, global_local, df, meta, si_ip): """Update the contents of tab four section two. Passing in the general info (df, meta).""" custom_inputs = generate_custom_inputs(var) @@ -288,7 +304,7 @@ def daily(ts, var, global_local, df, meta, si_ip): ) -@app.callback( +@callback( Output("tab4-heatmap", "children"), [ Input("df-store", "modified_timestamp"), @@ -301,7 +317,6 @@ def daily(ts, var, global_local, df, meta, si_ip): State("si-ip-unit-store", "data"), ], ) -@code_timer def update_heatmap(ts, var, global_local, df, meta, si_ip): custom_inputs = generate_custom_inputs(var) units = generate_units(si_ip) diff --git a/my_project/tab_t_rh/app_t_rh.py b/pages/t_rh.py similarity index 91% rename from my_project/tab_t_rh/app_t_rh.py rename to pages/t_rh.py index cf68c5de..e315b0bd 100644 --- a/my_project/tab_t_rh/app_t_rh.py +++ b/pages/t_rh.py @@ -1,6 +1,9 @@ -from dash import dcc, html -from dash_extensions.enrich import Output, Input, State -from my_project.utils import ( +import dash +from dash_extensions.enrich import Output, Input, State, dcc, html, callback + +from pages.lib.global_scheme import dropdown_names +from pages.lib.template_graphs import heatmap, yearly_profile, daily_profile +from pages.lib.utils import ( generate_chart_name, generate_units, generate_units_degree, @@ -9,17 +12,13 @@ title_with_link, dropdown, ) -from my_project.template_graphs import heatmap, yearly_profile, daily_profile -from my_project.global_scheme import dropdown_names -from my_project.utils import code_timer -from my_project.extract_df import convert_data -from app import app, cache, TIMEOUT +dash.register_page(__name__, name= 'Temperature and Humidity', order=2) var_to_plot = ["Dry bulb temperature", "Relative humidity"] -def layout_t_rh(): +def layout(): return html.Div( className="container-col full-width", children=[ @@ -89,7 +88,7 @@ def layout_t_rh(): ) -@app.callback( +@callback( Output("yearly-chart", "children"), [ Input("df-store", "modified_timestamp"), @@ -102,8 +101,6 @@ def layout_t_rh(): State("si-ip-unit-store", "data"), ], ) -@cache.memoize(timeout=TIMEOUT) -@code_timer def update_yearly_chart(ts, global_local, dd_value, df, meta, si_ip): if dd_value == dropdown_names[var_to_plot[0]]: dbt_yearly = yearly_profile(df, "DBT", global_local, si_ip) @@ -123,7 +120,7 @@ def update_yearly_chart(ts, global_local, dd_value, df, meta, si_ip): ) -@app.callback( +@callback( Output("daily", "children"), [ Input("df-store", "modified_timestamp"), @@ -136,8 +133,6 @@ def update_yearly_chart(ts, global_local, dd_value, df, meta, si_ip): State("si-ip-unit-store", "data"), ], ) -@cache.memoize(timeout=TIMEOUT) -@code_timer def update_daily(ts, global_local, dd_value, df, meta, si_ip): if dd_value == dropdown_names[var_to_plot[0]]: units = generate_units_degree(si_ip) @@ -163,7 +158,7 @@ def update_daily(ts, global_local, dd_value, df, meta, si_ip): ) -@app.callback( +@callback( [Output("heatmap", "children")], [ Input("df-store", "modified_timestamp"), @@ -176,8 +171,6 @@ def update_daily(ts, global_local, dd_value, df, meta, si_ip): State("si-ip-unit-store", "data"), ], ) -@cache.memoize(timeout=TIMEOUT) -@code_timer def update_heatmap(ts, global_local, dd_value, df, meta, si_ip): """Update the contents of tab three. Passing in general info (df, meta).""" if dd_value == dropdown_names[var_to_plot[0]]: @@ -204,7 +197,7 @@ def update_heatmap(ts, global_local, dd_value, df, meta, si_ip): ) -@app.callback( +@callback( Output("table-tmp-hum", "children"), [ Input("df-store", "modified_timestamp"), @@ -212,8 +205,6 @@ def update_heatmap(ts, global_local, dd_value, df, meta, si_ip): ], [State("df-store", "data"), State("si-ip-unit-store", "data")], ) -@cache.memoize(timeout=TIMEOUT) -@code_timer def update_table(ts, dd_value, df, si_ip): """Update the contents of tab three. Passing in general info (df, meta).""" return summary_table_tmp_rh_tab( diff --git a/my_project/tab_wind/app_wind.py b/pages/wind.py similarity index 98% rename from my_project/tab_wind/app_wind.py rename to pages/wind.py index be937104..60b17ec0 100644 --- a/my_project/tab_wind/app_wind.py +++ b/pages/wind.py @@ -1,8 +1,10 @@ +import dash from dash import dcc, html -from my_project.global_scheme import month_lst, container_row_center_full -from dash.dependencies import Input, Output, State -from my_project.template_graphs import heatmap, wind_rose -from my_project.utils import ( +from dash_extensions.enrich import Output, Input, State, callback + +from pages.lib.global_scheme import month_lst, container_row_center_full +from pages.lib.template_graphs import heatmap, wind_rose +from pages.lib.utils import ( title_with_tooltip, generate_chart_name, generate_units, @@ -10,9 +12,10 @@ title_with_link, dropdown, ) -from my_project.utils import code_timer +from pages.lib.utils import code_timer + -from app import app +dash.register_page(__name__, name= 'Wind', order=4) def sliders(): @@ -312,7 +315,7 @@ def custom_wind_rose(): ) -def layout_wind(): +def layout(): """Contents in the fifth tab 'Wind'.""" return html.Div( className="container-col justify-center", @@ -346,7 +349,7 @@ def layout_wind(): # wind rose -@app.callback( +@callback( Output("wind-rose", "children"), Input("df-store", "modified_timestamp"), [ @@ -355,7 +358,6 @@ def layout_wind(): State("si-ip-unit-store", "data"), ], ) -@code_timer def update_annual_wind_rose(ts, df, meta, si_ip): """Update the contents of tab five. Passing in the info from the sliders and the general info (df, meta).""" @@ -368,7 +370,7 @@ def update_annual_wind_rose(ts, df, meta, si_ip): # wind speed -@app.callback( +@callback( Output("wind-speed", "children"), # General [ @@ -381,7 +383,6 @@ def update_annual_wind_rose(ts, df, meta, si_ip): State("si-ip-unit-store", "data"), ], ) -@code_timer def update_tab_wind_speed(ts, global_local, df, meta, si_ip): """Update the contents of tab five. Passing in the info from the sliders and the general info (df, meta).""" @@ -394,7 +395,7 @@ def update_tab_wind_speed(ts, global_local, df, meta, si_ip): # wind direction -@app.callback( +@callback( Output("wind-direction", "children"), # General [ @@ -406,7 +407,6 @@ def update_tab_wind_speed(ts, global_local, df, meta, si_ip): State("si-ip-unit-store", "data"), ], ) -@code_timer def update_tab_wind_direction(global_local, df, meta, si_ip): """Update the contents of tab five. Passing in the info from the sliders and the general info (df, meta).""" @@ -419,7 +419,7 @@ def update_tab_wind_direction(global_local, df, meta, si_ip): # Custom Wind rose -@app.callback( +@callback( Output("custom-wind-rose", "children"), # Custom Graph Input [ @@ -435,7 +435,6 @@ def update_tab_wind_direction(global_local, df, meta, si_ip): State("si-ip-unit-store", "data"), ], ) -@code_timer def update_custom_wind_rose( ts, start_month, start_hour, end_month, end_hour, df, meta, si_ip ): @@ -469,7 +468,7 @@ def update_custom_wind_rose( ### Seasonal Graphs ### -@app.callback( +@callback( [ Output("winter-wind-rose", "children"), Output("spring-wind-rose", "children"), @@ -489,7 +488,6 @@ def update_custom_wind_rose( State("si-ip-unit-store", "data"), ], ) -@code_timer def update_seasonal_graphs(ts, df, meta, si_ip): hours = [1, 24] winter_months = [12, 2] @@ -586,7 +584,7 @@ def seasonal_chart_caption(month_start, month_end, count, n_calm): ### Daily Graphs ### -@app.callback( +@callback( # Daily Graphs [ Output("morning-wind-rose", "children"), @@ -604,7 +602,6 @@ def seasonal_chart_caption(month_start, month_end, count, n_calm): State("si-ip-unit-store", "data"), ], ) -@code_timer def update_daily_graphs(ts, df, meta, si_ip): """Update the contents of tab five. Passing in the info from the sliders and the general info (df, meta).""" @@ -673,4 +670,4 @@ def daily_chart_caption(hour_start, hour_end, count, calm_count): morning_text, noon_text, night_text, - ) + ) \ No newline at end of file From 120e26af4b51af005def6a4b2bde869cb030b98d Mon Sep 17 00:00:00 2001 From: t2the0bi <49641232+t2the0bi@users.noreply.github.com> Date: Sat, 4 May 2024 14:42:13 -0700 Subject: [PATCH 03/91] Modify tab style --- main.py | 4 +--- pages/explorer.py | 36 ++++++++++++++++++------------------ pages/natural_ventilation.py | 8 +++----- pages/outdoor.py | 1 - pages/psy-chart.py | 25 +++++++++++++------------ pages/select.py | 13 ++++++++++--- pages/summary.py | 13 +++++++++---- pages/sun.py | 20 +++++++++----------- pages/t_rh.py | 6 +++++- pages/wind.py | 2 +- 10 files changed, 69 insertions(+), 59 deletions(-) diff --git a/main.py b/main.py index bb8379be..ba418da4 100644 --- a/main.py +++ b/main.py @@ -21,7 +21,7 @@ html.Div(page["name"], className="ms-2"), href=page["path"], active="exact", - style={'color':'white','text-align': 'center', 'height': '100%'} + style={'color':'white'} ) for page in dash.page_registry.values() if page["name"] not in ["404", "changelog"] ], @@ -29,8 +29,6 @@ justified=True, style={'background-color': '#003262', 'padding': '0.25rem 0.5rem', - 'justify-content': 'center', - 'align-items': 'center', } ), html.Div(id="store-container", children=[store()]), diff --git a/pages/explorer.py b/pages/explorer.py index edead0fc..0fe68241 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -6,19 +6,11 @@ from copy import deepcopy -from pages.lib.utils import ( - generate_chart_name, - generate_custom_inputs, - generate_custom_inputs_explorer, - generate_units, - title_with_tooltip, - summary_table_tmp_rh_tab, - code_timer, - title_with_link, - determine_month_and_hour_filter, - dropdown, +from pages.lib.charts_data_explorer import ( + custom_heatmap, + two_var_graph, + three_var_graph, ) - from pages.lib.global_scheme import ( fig_config, dropdown_names, @@ -29,12 +21,6 @@ container_col_center_one_of_three, mapping_dictionary, ) - -from pages.lib.charts_data_explorer import ( - custom_heatmap, - two_var_graph, - three_var_graph, -) from pages.lib.template_graphs import ( heatmap, yearly_profile, @@ -43,6 +29,20 @@ filter_df_by_month_and_hour, ) +from pages.lib.utils import ( + generate_chart_name, + generate_custom_inputs, + generate_custom_inputs_explorer, + generate_units, + title_with_tooltip, + summary_table_tmp_rh_tab, + code_timer, + title_with_link, + determine_month_and_hour_filter, + dropdown, +) + + dash.register_page(__name__, name= 'Data Explorer', order=8) diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index b3c6305c..0792c434 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -1,14 +1,14 @@ +import math +import json + import dash from dash import dcc, html import dash_bootstrap_components as dbc from dash_extensions.enrich import Output, Input, State, callback import numpy as np -import math -import json import plotly.graph_objects as go - from pages.lib.global_scheme import ( template, mapping_dictionary, @@ -17,9 +17,7 @@ container_row_center_full, container_col_center_one_of_three, ) - from pages.lib.template_graphs import filter_df_by_month_and_hour - from pages.lib.utils import ( title_with_tooltip, generate_chart_name, diff --git a/pages/outdoor.py b/pages/outdoor.py index 93f7486a..14bb42d1 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -12,7 +12,6 @@ container_row_center_full, container_col_center_one_of_three, ) - from pages.lib.template_graphs import ( heatmap_with_filter, thermal_stress_stacked_barchart, diff --git a/pages/psy-chart.py b/pages/psy-chart.py index 2795f8fa..a1745078 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -1,25 +1,17 @@ +import json +from math import ceil, floor + import dash from dash import dcc, html import dash_bootstrap_components as dbc from dash_extensions.enrich import Output, Input, State, callback +from copy import deepcopy import numpy as np import pandas as pd import plotly.graph_objects as go -import json from pythermalcomfort import psychrometrics as psy -from math import ceil, floor -from copy import deepcopy -from pages.lib.template_graphs import filter_df_by_month_and_hour -from pages.lib.utils import ( - generate_chart_name, - generate_units, - generate_custom_inputs_psy, - determine_month_and_hour_filter, - title_with_link, - dropdown, -) from pages.lib.global_scheme import ( container_row_center_full, container_col_center_one_of_three, @@ -31,6 +23,15 @@ mapping_dictionary, tight_margins, ) +from pages.lib.template_graphs import filter_df_by_month_and_hour +from pages.lib.utils import ( + generate_chart_name, + generate_units, + generate_custom_inputs_psy, + determine_month_and_hour_filter, + title_with_link, + dropdown, +) dash.register_page(__name__, name= 'Psychrometric Chart', order=5) diff --git a/pages/select.py b/pages/select.py index 9c667d37..e8e80dc7 100644 --- a/pages/select.py +++ b/pages/select.py @@ -7,11 +7,18 @@ from dash.exceptions import PreventUpdate from dash_extensions.enrich import Serverside, Output, Input, State, html, dcc, callback - from pages.lib.extract_df import convert_data -from pages.lib.extract_df import create_df, get_data, get_location_info +from pages.lib.extract_df import ( + create_df, + get_data, + get_location_info +) from pages.lib.global_scheme import mapping_dictionary -from pages.lib.utils import plot_location_epw_files, generate_chart_name +from pages.lib.utils import ( + plot_location_epw_files, + generate_chart_name +) + dash.register_page(__name__, path='/', name='Select Weather File', order=0) diff --git a/pages/summary.py b/pages/summary.py index f3ef3d02..c882af7b 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -1,13 +1,18 @@ import dash import dash_bootstrap_components as dbc -import plotly.graph_objects as go -import requests from dash.exceptions import PreventUpdate from dash_extensions.enrich import dcc, html, Output, Input, State, callback -from pages.lib.extract_df import get_data -from pages.lib.global_scheme import template, tight_margins, mapping_dictionary +import plotly.graph_objects as go +import requests + from pages.lib.charts_summary import world_map +from pages.lib.extract_df import get_data +from pages.lib.global_scheme import ( + template, + tight_margins, + mapping_dictionary +) from pages.lib.template_graphs import violin from pages.lib.utils import ( generate_chart_name, diff --git a/pages/sun.py b/pages/sun.py index 1b3795ba..b6b7c8d0 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -2,11 +2,16 @@ from dash import html, dcc from dash_extensions.enrich import Output, Input, State, callback import dash_bootstrap_components as dbc - +# from dash.dependencies import Input, Output, State import numpy as np from copy import deepcopy +from pages.lib.charts_sun import ( + monthly_solar, + polar_graph, + custom_cartesian_solar, +) from pages.lib.global_scheme import ( sun_cloud_tab_dropdown_names, sun_cloud_tab_explore_dropdown_names, @@ -16,17 +21,10 @@ month_lst, mapping_dictionary, ) -from pages.lib.utils import dropdown -from dash.dependencies import Input, Output, State - -from pages.lib.charts_sun import ( - monthly_solar, - polar_graph, - custom_cartesian_solar, -) from pages.lib.template_graphs import heatmap, barchart, daily_profile -from pages.lib.utils import code_timer from pages.lib.utils import ( + code_timer, + dropdown, title_with_tooltip, generate_chart_name, generate_units, @@ -35,7 +33,7 @@ ) -dash.register_page(__name__, name= 'Sun and Clouds', order=3) +dash.register_page(__name__, name= 'Sun and Cloud Coverage', order=3) sc_dropdown_names = { diff --git a/pages/t_rh.py b/pages/t_rh.py index e315b0bd..eaf85f68 100644 --- a/pages/t_rh.py +++ b/pages/t_rh.py @@ -2,7 +2,11 @@ from dash_extensions.enrich import Output, Input, State, dcc, html, callback from pages.lib.global_scheme import dropdown_names -from pages.lib.template_graphs import heatmap, yearly_profile, daily_profile +from pages.lib.template_graphs import ( + heatmap, + yearly_profile, + daily_profile +) from pages.lib.utils import ( generate_chart_name, generate_units, diff --git a/pages/wind.py b/pages/wind.py index 60b17ec0..5231e005 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -5,6 +5,7 @@ from pages.lib.global_scheme import month_lst, container_row_center_full from pages.lib.template_graphs import heatmap, wind_rose from pages.lib.utils import ( + code_timer, title_with_tooltip, generate_chart_name, generate_units, @@ -12,7 +13,6 @@ title_with_link, dropdown, ) -from pages.lib.utils import code_timer dash.register_page(__name__, name= 'Wind', order=4) From df9fb520042daf58754d0b4c85f88b15e3ca62b1 Mon Sep 17 00:00:00 2001 From: t2the0bi <49641232+t2the0bi@users.noreply.github.com> Date: Mon, 6 May 2024 11:34:51 -0700 Subject: [PATCH 04/91] Remove redundant sections, adjust cypress config for new page structure --- main.py | 2 +- pages/lib/layout.py | 89 ------------------------------- pages/lib/template_graphs.py | 2 +- pages/sun.py | 3 +- tests/node/cypress/e2e/spec.cy.js | 3 +- tests/node/package-lock.json | 17 +++--- tests/node/package.json | 2 +- 7 files changed, 14 insertions(+), 104 deletions(-) diff --git a/main.py b/main.py index ba418da4..f90af3d5 100644 --- a/main.py +++ b/main.py @@ -4,7 +4,7 @@ from dash.dependencies import Input, Output from app import app -from pages.lib.layout import banner, build_tabs, footer, store +from pages.lib.layout import banner, footer, store #, build_tabs server = app.server diff --git a/pages/lib/layout.py b/pages/lib/layout.py index 898d891e..acab006a 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -135,95 +135,6 @@ def banner(): ) -def build_tabs(): - """Build the seven different tabs.""" - return html.Div( - id="tabs-container", - children=[ - dcc.Tabs( - id="tabs", - parent_className="custom-tabs", - value="tab-select", - children=[ - dcc.Tab( - label="Select Weather File", - value="tab-select", - className="custom-tab", - selected_className="custom-tab--selected", - ), - dcc.Tab( - id="tab-summary", - label="Climate Summary", - value="tab-summary", - className="custom-tab", - selected_className="custom-tab--selected", - disabled=True, - ), - dcc.Tab( - id="tab-t-rh", - label="Temperature and Humidity", - value="tab-t-rh", - className="custom-tab", - selected_className="custom-tab--selected", - disabled=True, - ), - dcc.Tab( - id="tab-sun", - label="Sun and Clouds", - value="tab-sun", - className="custom-tab", - selected_className="custom-tab--selected", - disabled=True, - ), - dcc.Tab( - id="tab-wind", - label="Wind", - value="tab-wind", - className="custom-tab", - selected_className="custom-tab--selected", - disabled=True, - ), - dcc.Tab( - id="tab-psy-chart", - label="Psychrometric Chart", - value="tab-psy-chart", - className="custom-tab", - selected_className="custom-tab--selected", - disabled=True, - ), - dcc.Tab( - id="tab-natural-ventilation", - label="Natural Ventilation", - value="tab-natural-ventilation", - className="custom-tab", - selected_className="custom-tab--selected", - disabled=True, - ), - dcc.Tab( - id="tab-outdoor-comfort", - label="Outdoor Comfort", - value="tab-outdoor-comfort", - className="custom-tab", - selected_className="custom-tab--selected", - disabled=True, - ), - dcc.Tab( - id="tab-data-explorer", - label="Data Explorer", - value="tab-data-explorer", - className="custom-tab", - selected_className="custom-tab--selected", - disabled=True, - ), - ], - ), - html.Div( - id="store-container", - children=[store(), html.Div(id="tabs-content")], - ), - ], - ) - def store(): return html.Div( diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index 754f3dd8..9a02b024 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -507,7 +507,7 @@ def wind_rose(df, title, month, hour, labels, si_ip): df["wind_dir"], bins=dir_bins, labels=dir_labels, right=False ) ) - .ser.cat.rename_categories({"WindDir_bins": {360: 0}}) + .replace({"WindDir_bins": {360: 0}}) .groupby(by=["WindSpd_bins", "WindDir_bins"], observed=False) .size() .unstack(level="WindSpd_bins") diff --git a/pages/sun.py b/pages/sun.py index b6b7c8d0..d880ac1f 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -2,7 +2,6 @@ from dash import html, dcc from dash_extensions.enrich import Output, Input, State, callback import dash_bootstrap_components as dbc -# from dash.dependencies import Input, Output, State import numpy as np from copy import deepcopy @@ -33,7 +32,7 @@ ) -dash.register_page(__name__, name= 'Sun and Cloud Coverage', order=3) +dash.register_page(__name__, name= 'Sun and Clouds', order=3) sc_dropdown_names = { diff --git a/tests/node/cypress/e2e/spec.cy.js b/tests/node/cypress/e2e/spec.cy.js index 753c43d7..e423f957 100644 --- a/tests/node/cypress/e2e/spec.cy.js +++ b/tests/node/cypress/e2e/spec.cy.js @@ -1,6 +1,5 @@ function click_tab(name) { - cy.get('.custom-tab') - .not('.tab--disabled') + cy.get('a.nav-link div') // adjusted to new page structure .contains(name) .click(); } diff --git a/tests/node/package-lock.json b/tests/node/package-lock.json index 08476797..c137b1f4 100644 --- a/tests/node/package-lock.json +++ b/tests/node/package-lock.json @@ -8,7 +8,7 @@ "name": "clima", "version": "0.0.0", "devDependencies": { - "cypress": "^13.5.1" + "cypress": "^13.8.1" } }, "node_modules/@colors/colors": { @@ -74,6 +74,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.10.tgz", "integrity": "sha512-luANqZxPmjTll8bduz4ACs/lNTCLuWssCyjqTY9yLdsv1xnViQp3ISKwsEWOIecO13JWUqjVdig/Vjjc09o8uA==", "dev": true, + "optional": true, "dependencies": { "undici-types": "~5.26.4" } @@ -548,21 +549,20 @@ } }, "node_modules/cypress": { - "version": "13.5.1", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.5.1.tgz", - "integrity": "sha512-yqLViT0D/lPI8Kkm7ciF/x/DCK/H/DnogdGyiTnQgX4OVR2aM30PtK+kvklTOD1u3TuItiD9wUQAF8EYWtyZug==", + "version": "13.8.1", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.8.1.tgz", + "integrity": "sha512-Uk6ovhRbTg6FmXjeZW/TkbRM07KPtvM5gah1BIMp4Y2s+i/NMxgaLw0+PbYTOdw1+egE0FP3mWRiGcRkjjmhzA==", "dev": true, "hasInstallScript": true, "dependencies": { "@cypress/request": "^3.0.0", "@cypress/xvfb": "^1.2.4", - "@types/node": "^18.17.5", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", "arch": "^2.2.0", "blob-util": "^2.0.2", "bluebird": "^3.7.2", - "buffer": "^5.6.0", + "buffer": "^5.7.1", "cachedir": "^2.3.0", "chalk": "^4.1.0", "check-more-types": "^2.24.0", @@ -580,7 +580,7 @@ "figures": "^3.2.0", "fs-extra": "^9.1.0", "getos": "^3.2.1", - "is-ci": "^3.0.0", + "is-ci": "^3.0.1", "is-installed-globally": "~0.4.0", "lazy-ass": "^1.6.0", "listr2": "^3.8.3", @@ -1947,7 +1947,8 @@ "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "dev": true, + "optional": true }, "node_modules/universalify": { "version": "0.2.0", diff --git a/tests/node/package.json b/tests/node/package.json index ceecd8f0..cb40d108 100644 --- a/tests/node/package.json +++ b/tests/node/package.json @@ -7,6 +7,6 @@ "cy:open": "cypress open" }, "devDependencies": { - "cypress": "^13.5.1" + "cypress": "^13.8.1" } } From e3dcf0a4c19ef5303e830baa0d2327a90d8d08d3 Mon Sep 17 00:00:00 2001 From: t2the0bi <49641232+t2the0bi@users.noreply.github.com> Date: Wed, 22 May 2024 14:34:55 -0700 Subject: [PATCH 05/91] fix: remove redundant code, adjust Python test - Removed redundant, commented code in `main.py` - Adjusted import statement for custom modules in `tests/python/test_utils.py` to reflect new page structure --- pages/__init__.py | 0 tests/python/test_utils.py | 5 +++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 pages/__init__.py diff --git a/pages/__init__.py b/pages/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/python/test_utils.py b/tests/python/test_utils.py index 1fbebf66..2e7dd86b 100644 --- a/tests/python/test_utils.py +++ b/tests/python/test_utils.py @@ -1,7 +1,8 @@ import requests -from my_project.utils import summary_table_tmp_rh_tab -from my_project.extract_df import get_data, create_df +from pages.lib.utils import summary_table_tmp_rh_tab +from pages.lib.extract_df import get_data, create_df + import pandas as pd import os From b219307e3670596d24e55be05a01bce86c03c24c Mon Sep 17 00:00:00 2001 From: t2the0bi <49641232+t2the0bi@users.noreply.github.com> Date: Wed, 22 May 2024 14:37:58 -0700 Subject: [PATCH 06/91] fix: remove redundant code, adjust Python test - Removed redundant, commented code in `main.py` - Adjusted import statement for custom modules in `tests/python/test_utils.py` to reflect new page structure --- main.py | 43 ------------------------------------------- 1 file changed, 43 deletions(-) diff --git a/main.py b/main.py index f90af3d5..593cf26f 100644 --- a/main.py +++ b/main.py @@ -38,49 +38,6 @@ ) -# @app.callback( -# dash.dependencies.Output("page-content", "children"), -# [dash.dependencies.Input("url", "pathname")], -# ) -# def display_page(pathname): -# if pathname == "/": -# return build_tabs() -# elif pathname == "/changelog": -# return html.Div(children=[changelog()]) - - -# # Handle tab selection -# @app.callback( -# Output("tabs-content", "children"), -# [ -# Input("tabs", "value"), -# Input("si-ip-radio-input", "value"), -# ], -# ) -# def render_content(tab, si_ip): -# """Update the contents of the page depending on what tab the user selects.""" -# if tab == "tab-select": -# return layout_select() -# elif tab == "tab-summary": -# return layout_summary(si_ip) -# elif tab == "tab-t-rh": -# return layout_t_rh() -# elif tab == "tab-sun": -# return layout_sun(si_ip) -# elif tab == "tab-wind": -# return layout_wind() -# elif tab == "tab-data-explorer": -# return layout_data_explorer() -# elif tab == "tab-outdoor-comfort": -# return layout_outdoor_comfort() -# elif tab == "tab-natural-ventilation": -# return layout_natural_ventilation(si_ip) -# elif tab == "tab-psy-chart": -# return layout_psy_chart() -# else: -# return "404" - - if __name__ == "__main__": app.run_server( debug=False, From 940523cbad663f99b537cb98532de7cffa2251f7 Mon Sep 17 00:00:00 2001 From: t2the0bi <49641232+t2the0bi@users.noreply.github.com> Date: Wed, 22 May 2024 16:52:48 -0700 Subject: [PATCH 07/91] docs: add link to 'Discussions' to README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9de3c272..e31ffe1c 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ It furthermore calculates a number of climate-related values \(i.e. solar azimuth and altitude, Universal Thermal Climate Index \(UTCI\), comfort indices, etc.\) that are not contained in the EPW files but can be derived from the information therein contained. It can be freely accessed at [clima.cbe.berkeley.edu](http://clima.cbe.berkeley.edu) +For any questions and feedback related to this tool, please use the [Discussions](https://github.com/CenterForTheBuiltEnvironment/clima/discussions) section. + If you use this tool please consider citing us. ## Official documentation From 00efe520598af1c99a79af93d267ad38d048c64c Mon Sep 17 00:00:00 2001 From: t2the0bi <49641232+t2the0bi@users.noreply.github.com> Date: Thu, 13 Jun 2024 09:57:07 -0700 Subject: [PATCH 08/91] style: adjust navbar to original tab style format Moving to multipage format required app restructuring. This commit adjusts the newly created navbar to the original tab style format. It also introduces Enums to store page urls. - add build_tabs() to build navbar tabs and pages - adjust navbar to original tab style format - add page_urls.py to store page urls using Enums - add reference to Enums in register_page definitions --- assets/tabs.css | 17 ++++++++++++-- main.py | 25 ++++---------------- pages/changelog.py | 9 +++++++- pages/explorer.py | 7 +++++- pages/lib/layout.py | 44 ++++++++++++++++++++++++++++++++++++ pages/lib/page_urls.py | 14 ++++++++++++ pages/natural_ventilation.py | 8 ++++++- pages/not_found_404.py | 6 ++++- pages/outdoor.py | 7 +++++- pages/psy-chart.py | 8 ++++++- pages/select.py | 8 ++++++- pages/summary.py | 8 ++++++- pages/sun.py | 7 +++++- pages/t_rh.py | 9 +++++++- pages/wind.py | 7 +++++- 15 files changed, 151 insertions(+), 33 deletions(-) create mode 100644 pages/lib/page_urls.py diff --git a/assets/tabs.css b/assets/tabs.css index 0be9dda2..1c0ca54b 100644 --- a/assets/tabs.css +++ b/assets/tabs.css @@ -56,14 +56,27 @@ align-items: center; justify-content: center; } -.custom-tab--selected { - color: black; + +.custom-tab:has(.active) { + background-color: white; box-shadow: 1px 1px 0 white; border-left: 1px solid lightgrey !important; border-right: 1px solid lightgrey !important; border-top: 6px solid #abd2ff !important; } +.nav-pills .nav-link { + padding: 0; + color: #586069; + font-family: "system-ui"; + background-color: transparent; +} + +.nav-pills .nav-link.active { + color: black; + background-color: white; +} + /* Tab One */ #tab-one-container { height: 100%; diff --git a/main.py b/main.py index 593cf26f..16ff61eb 100644 --- a/main.py +++ b/main.py @@ -4,7 +4,7 @@ from dash.dependencies import Input, Output from app import app -from pages.lib.layout import banner, footer, store #, build_tabs +from pages.lib.layout import banner, footer, build_tabs #, build_tabs server = app.server @@ -15,29 +15,14 @@ children=[ dcc.Location(id="url", refresh=False), # connected to callback below banner(), - dbc.Nav( - [ - dbc.NavLink( - html.Div(page["name"], className="ms-2"), - href=page["path"], - active="exact", - style={'color':'white'} - ) - for page in dash.page_registry.values() if page["name"] not in ["404", "changelog"] - ], - pills=True, - justified=True, - style={'background-color': '#003262', - 'padding': '0.25rem 0.5rem', - } - ), - html.Div(id="store-container", children=[store()]), - dash.page_container, + html.Div( + id="page-content", + children=build_tabs() + ), footer(), ], ) - if __name__ == "__main__": app.run_server( debug=False, diff --git a/pages/changelog.py b/pages/changelog.py index fb90c27a..39590099 100644 --- a/pages/changelog.py +++ b/pages/changelog.py @@ -2,7 +2,14 @@ import dash_bootstrap_components as dbc from dash import dcc -dash.register_page(__name__, path='/changelog', name='changelog') +from pages.lib.page_urls import PageUrls + + +dash.register_page(__name__, + name='changelog', + path=PageUrls.CHANGELOG.value + ) + def layout(): """changelog page""" diff --git a/pages/explorer.py b/pages/explorer.py index 0fe68241..41d15855 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -6,6 +6,7 @@ from copy import deepcopy +from pages.lib.page_urls import PageUrls from pages.lib.charts_data_explorer import ( custom_heatmap, two_var_graph, @@ -43,7 +44,11 @@ ) -dash.register_page(__name__, name= 'Data Explorer', order=8) +dash.register_page(__name__, + name= 'Data Explorer', + path=PageUrls.EXPLORER.value, + order=8 + ) explore_dropdown_names = {} diff --git a/pages/lib/layout.py b/pages/lib/layout.py index acab006a..6a89427f 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -1,4 +1,5 @@ import dash_bootstrap_components as dbc +import dash from dash import dcc, html @@ -153,3 +154,46 @@ def store(): ) ], ) + + +def build_tabs(): + return html.Div( + id="tabs-container", + children=[ + html.Div( + id="tabs-parent", + className="custom-tabs", + children=[ + dbc.Nav( + [ + dbc.NavItem( + dbc.NavLink( + page["name"], + href=page["path"], + active="exact", + className="nav-link" + ), + className="custom-tab" + ) + for page in dash.page_registry.values() if page["name"] not in ["404", "changelog"] + ], + id="tabs", + class_name="tab-container", + pills=True, + justified=True + ) + ] + ), + + html.Div( + id="store-container", + children=[ + store(), + html.Div( + id="tabs-content", + children=dash.page_container + ) + ] + ), + ] + ) \ No newline at end of file diff --git a/pages/lib/page_urls.py b/pages/lib/page_urls.py new file mode 100644 index 00000000..42d593b7 --- /dev/null +++ b/pages/lib/page_urls.py @@ -0,0 +1,14 @@ +from enum import Enum + +class PageUrls(Enum): + SELECT: str = '/' + SUMMARY: str = '/summary' + T_RH: str = '/t-rh' + SUN: str = '/sun' + WIND: str = '/wind' + PSY_CHART: str = '/psy-chart' + NATURAL_VENTILATION: str = '/natural-ventilation' + OUTDOOR: str = '/outdoor' + EXPLORER: str = '/explorer' + CHANGELOG: str = '/changelog' + NOT_FOUND: str = '"../assets/animations/page_not_found.json"' diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 0792c434..66c701b0 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -9,6 +9,7 @@ import numpy as np import plotly.graph_objects as go +from pages.lib.page_urls import PageUrls from pages.lib.global_scheme import ( template, mapping_dictionary, @@ -27,7 +28,12 @@ determine_month_and_hour_filter, ) -dash.register_page(__name__, name= 'Natural Ventilation', order=6) + +dash.register_page(__name__, + name= 'Natural Ventilation', + path=PageUrls.NATURAL_VENTILATION.value, + order=6 + ) def layout(): diff --git a/pages/not_found_404.py b/pages/not_found_404.py index 84d92fa4..5da94903 100644 --- a/pages/not_found_404.py +++ b/pages/not_found_404.py @@ -3,8 +3,12 @@ from dash_iconify import DashIconify from dash_extensions import Lottie +from pages.lib.page_urls import PageUrls + + dash.register_page(__name__, name= '404') + layout = [ dmc.Title("I could not find the page you are currently looking for", order=4), dmc.Text( @@ -25,6 +29,6 @@ rendererSettings=dict(preserveAspectRatio="xMidYMid slice"), ), width="100%", - url="../assets/animations/page_not_found.json", + url=PageUrls.NOT_FOUND.value, ), ] diff --git a/pages/outdoor.py b/pages/outdoor.py index 14bb42d1..7684432f 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -5,6 +5,7 @@ import numpy as np +from pages.lib.page_urls import PageUrls from pages.lib.global_scheme import ( outdoor_dropdown_names, tight_margins, @@ -26,7 +27,11 @@ ) -dash.register_page(__name__, name= 'Outdoor Comfort', order=7) +dash.register_page(__name__, + name= 'Outdoor Comfort', + path=PageUrls.OUTDOOR.value, + order=7 + ) def inputs_outdoor_comfort(): diff --git a/pages/psy-chart.py b/pages/psy-chart.py index a1745078..cf3bc5f9 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -12,6 +12,7 @@ import plotly.graph_objects as go from pythermalcomfort import psychrometrics as psy +from pages.lib.page_urls import PageUrls from pages.lib.global_scheme import ( container_row_center_full, container_col_center_one_of_three, @@ -34,7 +35,12 @@ ) -dash.register_page(__name__, name= 'Psychrometric Chart', order=5) +dash.register_page(__name__, + name= 'Psychrometric Chart', + path=PageUrls.PSY_CHART.value, + order=5 + ) + psy_dropdown_names = { "None": "None", diff --git a/pages/select.py b/pages/select.py index e8e80dc7..2e45bc78 100644 --- a/pages/select.py +++ b/pages/select.py @@ -7,6 +7,7 @@ from dash.exceptions import PreventUpdate from dash_extensions.enrich import Serverside, Output, Input, State, html, dcc, callback +from pages.lib.page_urls import PageUrls from pages.lib.extract_df import convert_data from pages.lib.extract_df import ( create_df, @@ -20,7 +21,12 @@ ) -dash.register_page(__name__, path='/', name='Select Weather File', order=0) +dash.register_page(__name__, + name='Select Weather File', + path=PageUrls.SELECT.value, + order=0 + ) + messages_alert = { "start": "To start, upload an EPW file or click on a point on the map!", diff --git a/pages/summary.py b/pages/summary.py index c882af7b..64f42fd5 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -6,6 +6,7 @@ import plotly.graph_objects as go import requests +from pages.lib.page_urls import PageUrls from pages.lib.charts_summary import world_map from pages.lib.extract_df import get_data from pages.lib.global_scheme import ( @@ -22,7 +23,12 @@ title_with_link, ) -dash.register_page(__name__, name='Climate Summary', order=1) + +dash.register_page(__name__, + name='Climate Summary', + path=PageUrls.SUMMARY.value, + order=1 + ) def layout(): diff --git a/pages/sun.py b/pages/sun.py index d880ac1f..d5a80976 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -6,6 +6,7 @@ import numpy as np from copy import deepcopy +from pages.lib.page_urls import PageUrls from pages.lib.charts_sun import ( monthly_solar, polar_graph, @@ -32,7 +33,11 @@ ) -dash.register_page(__name__, name= 'Sun and Clouds', order=3) +dash.register_page(__name__, + name= 'Sun and Clouds', + path=PageUrls.SUN.value, + order=3 + ) sc_dropdown_names = { diff --git a/pages/t_rh.py b/pages/t_rh.py index eaf85f68..b8db5f78 100644 --- a/pages/t_rh.py +++ b/pages/t_rh.py @@ -1,6 +1,7 @@ import dash from dash_extensions.enrich import Output, Input, State, dcc, html, callback +from pages.lib.page_urls import PageUrls from pages.lib.global_scheme import dropdown_names from pages.lib.template_graphs import ( heatmap, @@ -17,7 +18,13 @@ dropdown, ) -dash.register_page(__name__, name= 'Temperature and Humidity', order=2) + +dash.register_page(__name__, + name= 'Temperature and Humidity', + path=PageUrls.T_RH.value, + order=2 + ) + var_to_plot = ["Dry bulb temperature", "Relative humidity"] diff --git a/pages/wind.py b/pages/wind.py index 5231e005..0820576c 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -2,6 +2,7 @@ from dash import dcc, html from dash_extensions.enrich import Output, Input, State, callback +from pages.lib.page_urls import PageUrls from pages.lib.global_scheme import month_lst, container_row_center_full from pages.lib.template_graphs import heatmap, wind_rose from pages.lib.utils import ( @@ -15,7 +16,11 @@ ) -dash.register_page(__name__, name= 'Wind', order=4) +dash.register_page(__name__, + name= 'Wind', + path=PageUrls.WIND.value, + order=4 + ) def sliders(): From 04e820568c431d7191338ab2e2aae6cc9b26ff7d Mon Sep 17 00:00:00 2001 From: t2the0bi <49641232+t2the0bi@users.noreply.github.com> Date: Tue, 18 Jun 2024 20:09:58 -0700 Subject: [PATCH 09/91] fix: adjust navbar tabs behavior and callbacks - Adjusted the tabs behavior to mimic behaviour of original tabs, e.g., initial disabled tabs and style differences between active and inactive tabs; required changes in callbacks and CSS style definitions - Added multiple version for user survey alert (Dash Modal/Alert/Toast); final solution will be picked and finalized for next commit --- assets/tabs.css | 38 ++++++++--- main.py | 32 ++++++++-- pages/lib/layout.py | 101 ++++++++++++++++++++++++++---- pages/select.py | 74 +++++++++++++++------- tests/node/cypress/e2e/spec.cy.js | 2 +- 5 files changed, 200 insertions(+), 47 deletions(-) diff --git a/assets/tabs.css b/assets/tabs.css index 1c0ca54b..6fcab5f6 100644 --- a/assets/tabs.css +++ b/assets/tabs.css @@ -34,22 +34,22 @@ .custom-tabs-container { width: 85%; } + .custom-tabs { - border-top-left-radius: 3px; background-color: #f9f9f9; padding: 0 24px; border-bottom: 1px solid #d6d6d6; } .custom-tab { - color:#586069; - border-color: lightgrey; + border-color: rgb(238, 236, 236); border-top-left-radius: 3px; border-top-right-radius: 3px; border-top: 3px solid transparent !important; border-left: 1px solid lightgrey !important; border-right: 1px solid lightgrey !important; - background-color: #fafbfc; + border-bottom: 1px solid #d6d6d6; + background-color: #f6f8f8; padding: 12px !important; font-family: "system-ui"; display: flex !important; @@ -58,11 +58,24 @@ } .custom-tab:has(.active) { + color:#586069; background-color: white; box-shadow: 1px 1px 0 white; border-left: 1px solid lightgrey !important; border-right: 1px solid lightgrey !important; border-top: 6px solid #abd2ff !important; + border-bottom: 1px solid transparent; +} + +.nav-pills { + display: flex; + flex-wrap: wrap; +} + +@media (max-width: 900px) { + .nav-pills { + flex-direction: column; + } } .nav-pills .nav-link { @@ -72,6 +85,10 @@ background-color: transparent; } +.nav-pills .nav-link.disabled { + color: #c8c6c6; +} + .nav-pills .nav-link.active { color: black; background-color: white; @@ -259,7 +276,12 @@ p { align-items: stretch; } - - - - +.survey-alert { + color: white; + background-color: #0c2772; + opacity: 0.98; + font-family: "system-ui"; + font-size: 15px; + border-radius: 0.25rem; + border: 0.5px solid lightgrey; +} \ No newline at end of file diff --git a/main.py b/main.py index 16ff61eb..9e6ddfbb 100644 --- a/main.py +++ b/main.py @@ -1,10 +1,10 @@ import dash import dash_bootstrap_components as dbc -from dash import html, dcc -from dash.dependencies import Input, Output +from dash import html, dcc, no_update +from dash_extensions.enrich import Output, Input, State, callback from app import app -from pages.lib.layout import banner, footer, build_tabs #, build_tabs +from pages.lib.layout import banner, footer, build_tabs server = app.server @@ -23,6 +23,30 @@ ], ) + +# callbrack for Alert solution +@callback( + Output('alert-auto', 'is_open'), + Input('interval-component', 'n_intervals') +) +def display_alert(n): + return n == 1 + + +# callback for Modal solution +# @callback( +# Output('alert-auto', 'is_open'), +# [Input('interval-component', 'n_intervals'), Input("close", "n_clicks")], +# [State('alert-auto', 'is_open')] +# ) +# def toggle_alert(n_intervals, n_clicks, is_open): +# if n_intervals == 1 and not is_open: +# return True +# elif n_clicks: +# return False +# return is_open + + if __name__ == "__main__": app.run_server( debug=False, @@ -30,4 +54,4 @@ port=8080, processes=1, threaded=True, - ) + ) \ No newline at end of file diff --git a/pages/lib/layout.py b/pages/lib/layout.py index 6a89427f..560dda08 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -3,6 +3,80 @@ from dash import dcc, html +def alert(): + """Alert for survey.""" + return html.Div( + id="alert-container", + children=[ + + dbc.Toast( + [ + "If you have a moment, help us improving Clima and take a ", + html.A( + "quick user survey", + href="https://forms.gle/k289zP3R92jdu14M7", + className="alert-link", + target="_blank" + ), + "! ☀️" + ], + id="alert-auto", + header="CBE Clima User Survey", + icon="info", + is_open=False, + dismissable=True, + className="survey-alert", + style={"position": "fixed", "top": 25, "right": 10, "width": 400}, + ), + + + # Alert style solution + # dbc.Alert( + # [ + # "If you have a moment, help us improving Clima and take a ", + # html.A("quick user survey", href="https://lnkd.in/gFheGNrt", className="alert-link"), + # "!" + # ], + # id="alert-auto", + # class_name= "survey-alert", + # is_open=False, + # ), + + # Pop-up window solution + # dbc.Modal( + # [ + # dbc.ModalHeader(dbc.ModalTitle("CBE Clima User Survey")), + # dbc.ModalBody([ + # "If you have a moment, help us improving Clima and take the quick ", + # html.A( + # "user survey", + # href="https://forms.gle/k289zP3R92jdu14M7", + # style={'color':'black', 'font-weight': 'bold'} + # ), + # "!" + # ]), + # dbc.ModalFooter( + # dbc.Button( + # "Close", id="close", className="ms-auto", n_clicks=0 + # ) + # ), + # ], + # id="alert-auto", + # centered=True, + # autofocus=True, + # is_open=False, + # size="lg", + # ), + + dcc.Interval( + id='interval-component', + interval=12*1000, + n_intervals=0 + ) + ] + ) + + def footer(): return dbc.Row( align="center", @@ -166,16 +240,18 @@ def build_tabs(): children=[ dbc.Nav( [ - dbc.NavItem( - dbc.NavLink( - page["name"], - href=page["path"], - active="exact", - className="nav-link" - ), - className="custom-tab" - ) - for page in dash.page_registry.values() if page["name"] not in ["404", "changelog"] + dbc.NavItem( + dbc.NavLink( + page["name"], + id=page["path"], + href=page["path"], + active="exact", + className="nav-link", + disabled=True, + ), + className="custom-tab" + ) + for page in dash.page_registry.values() if page["name"] not in ["404", "changelog"] ], id="tabs", class_name="tab-container", @@ -191,7 +267,10 @@ def build_tabs(): store(), html.Div( id="tabs-content", - children=dash.page_container + children=[ + alert(), # alert can be removed after survey is done + dash.page_container + ], ) ] ), diff --git a/pages/select.py b/pages/select.py index 2e45bc78..c76b31b6 100644 --- a/pages/select.py +++ b/pages/select.py @@ -231,6 +231,56 @@ def switch_si_ip(ts, si_ip_input, url_store, lines): None, None, ) + + +@callback( + [ + Output("/", "disabled"), + Output("/summary", "disabled"), + Output("/t-rh", "disabled"), + Output("/sun", "disabled"), + Output("/wind", "disabled"), + Output("/psy-chart", "disabled"), + Output("/explorer", "disabled"), + Output("/outdoor", "disabled"), + Output("/natural-ventilation", "disabled"), + Output("banner-subtitle", "children"), + ], + [ + Input("meta-store", "data"), + Input("df-store", "data"), + ], +) +def enable_tabs_when_data_is_loaded(meta, data): + """Hide tabs when data are not loaded""" + default = "Current Location: N/A" + if data is None: + return ( + True, + True, + True, + True, + True, + True, + True, + True, + True, + default, + ) + else: + return ( + False, + False, + False, + False, + False, + False, + False, + False, + False, + "Current Location: " + meta["city"] + ", " + meta["country"], + ) + @callback( @@ -269,26 +319,4 @@ def display_modal_when_data_clicked(click_map): """change the text of the modal header""" if click_map: return [f"Analyse data from {click_map['points'][0]['hovertext']}?"] - return ["Analyse data from this location?"] - - -@callback( - [ - Output("banner-subtitle", "children"), - ], - [ - Input("meta-store", "data"), - Input("df-store", "data"), - ], -) -def enable_location_display(meta, data): - """Display current location in banner""" - default = "Current Location: N/A" - if data is None: - return ( - default, - ) - else: - return ( - "Current Location: " + meta["city"] + ", " + meta["country"], - ) \ No newline at end of file + return ["Analyse data from this location?"] \ No newline at end of file diff --git a/tests/node/cypress/e2e/spec.cy.js b/tests/node/cypress/e2e/spec.cy.js index e423f957..3c48fb23 100644 --- a/tests/node/cypress/e2e/spec.cy.js +++ b/tests/node/cypress/e2e/spec.cy.js @@ -1,5 +1,5 @@ function click_tab(name) { - cy.get('a.nav-link div') // adjusted to new page structure + cy.get('.custom-tab') .contains(name) .click(); } From 29f1036d9ffb24a1c27515db74e89b4b590294f2 Mon Sep 17 00:00:00 2001 From: t2the0bi <49641232+t2the0bi@users.noreply.github.com> Date: Mon, 24 Jun 2024 11:45:19 -0700 Subject: [PATCH 10/91] fix: adjust dynamic layout behavior for tabs - Fixed tabs layout behavior for smaller windows - Selected dbc.Toast for temporary survey alert - Adjust test files based on previous errors --- assets/tabs.css | 1 + main.py | 16 +---------- pages/lib/layout.py | 48 +++++-------------------------- tests/node/cypress/e2e/spec.cy.js | 2 +- tests/python/test_utils.py | 8 +++++- 5 files changed, 17 insertions(+), 58 deletions(-) diff --git a/assets/tabs.css b/assets/tabs.css index 6fcab5f6..15590dff 100644 --- a/assets/tabs.css +++ b/assets/tabs.css @@ -284,4 +284,5 @@ p { font-size: 15px; border-radius: 0.25rem; border: 0.5px solid lightgrey; + z-index: 1000; } \ No newline at end of file diff --git a/main.py b/main.py index 9e6ddfbb..764c023a 100644 --- a/main.py +++ b/main.py @@ -24,7 +24,7 @@ ) -# callbrack for Alert solution +# callback for survey alert (dbc.Toast) @callback( Output('alert-auto', 'is_open'), Input('interval-component', 'n_intervals') @@ -33,20 +33,6 @@ def display_alert(n): return n == 1 -# callback for Modal solution -# @callback( -# Output('alert-auto', 'is_open'), -# [Input('interval-component', 'n_intervals'), Input("close", "n_clicks")], -# [State('alert-auto', 'is_open')] -# ) -# def toggle_alert(n_intervals, n_clicks, is_open): -# if n_intervals == 1 and not is_open: -# return True -# elif n_clicks: -# return False -# return is_open - - if __name__ == "__main__": app.run_server( debug=False, diff --git a/pages/lib/layout.py b/pages/lib/layout.py index 560dda08..59dd7432 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -26,54 +26,20 @@ def alert(): is_open=False, dismissable=True, className="survey-alert", - style={"position": "fixed", "top": 25, "right": 10, "width": 400}, + style={ + "position": "fixed", + "top": 25, + "right": 10, + "width": 400 + }, ), - - # Alert style solution - # dbc.Alert( - # [ - # "If you have a moment, help us improving Clima and take a ", - # html.A("quick user survey", href="https://lnkd.in/gFheGNrt", className="alert-link"), - # "!" - # ], - # id="alert-auto", - # class_name= "survey-alert", - # is_open=False, - # ), - - # Pop-up window solution - # dbc.Modal( - # [ - # dbc.ModalHeader(dbc.ModalTitle("CBE Clima User Survey")), - # dbc.ModalBody([ - # "If you have a moment, help us improving Clima and take the quick ", - # html.A( - # "user survey", - # href="https://forms.gle/k289zP3R92jdu14M7", - # style={'color':'black', 'font-weight': 'bold'} - # ), - # "!" - # ]), - # dbc.ModalFooter( - # dbc.Button( - # "Close", id="close", className="ms-auto", n_clicks=0 - # ) - # ), - # ], - # id="alert-auto", - # centered=True, - # autofocus=True, - # is_open=False, - # size="lg", - # ), - dcc.Interval( id='interval-component', interval=12*1000, n_intervals=0 ) - ] + ], ) diff --git a/tests/node/cypress/e2e/spec.cy.js b/tests/node/cypress/e2e/spec.cy.js index 3c48fb23..dcc84470 100644 --- a/tests/node/cypress/e2e/spec.cy.js +++ b/tests/node/cypress/e2e/spec.cy.js @@ -1,5 +1,5 @@ function click_tab(name) { - cy.get('.custom-tab') + cy.get('.nav-item') .contains(name) .click(); } diff --git a/tests/python/test_utils.py b/tests/python/test_utils.py index 2e7dd86b..92672cc4 100644 --- a/tests/python/test_utils.py +++ b/tests/python/test_utils.py @@ -1,10 +1,16 @@ +import sys +import os + +root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) +if root_dir not in sys.path: + sys.path.append(root_dir) + import requests from pages.lib.utils import summary_table_tmp_rh_tab from pages.lib.extract_df import get_data, create_df import pandas as pd -import os def save_epw_test(path_file): From d8d036af9467c301131df21108d09422985f4068 Mon Sep 17 00:00:00 2001 From: Federico Tartarini Date: Sat, 12 Oct 2024 01:32:30 +0000 Subject: [PATCH 11/91] GITBOOK-54: No subject --- docs/.gitbook/assets/utci_image (1).png | Bin 0 -> 150010 bytes docs/README.md | 12 +++++++++--- .../tabs-explained/data-explorer.md | 9 +++++++-- .../tabs-explained/natural-ventilation.md | 10 +++++++++- .../tabs-explained/outdoor-comfort/README.md | 15 +++++++++++---- .../psychrometric-chart/README.md | 9 ++++++++- .../tabs-explained/sun-and-cloud/README.md | 15 +++++++++++---- docs/documentation/tabs-explained/tab-home.md | 14 ++++++++++++-- .../tabs-explained/tab-summary/README.md | 7 +++++++ .../temperature-and-humidity/README.md | 9 ++++++++- .../tabs-explained/wind/README.md | 7 +++++++ .../documentation/weather-file-repositories.md | 7 ++----- 12 files changed, 91 insertions(+), 23 deletions(-) create mode 100644 docs/.gitbook/assets/utci_image (1).png diff --git a/docs/.gitbook/assets/utci_image (1).png b/docs/.gitbook/assets/utci_image (1).png new file mode 100644 index 0000000000000000000000000000000000000000..cda064ad84f26f01abce0faab137fbf7e88f971a GIT binary patch literal 150010 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≈{TBS}3MiRrOkj^RR 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 If you use this tool please consider citing us. Betti, G., Tartarini, F., Nguyen, C, Schiavon, S. CBE Clima Tool: -> A free and open-source web application for climate analysis tailored to sustainable building design. -> Build. Simul. (2023). [https://doi.org/10.1007/s12273-023-1090-5](https://doi.org/10.1007/s12273-023-1090-5). +> If you use this tool please consider citing us. Betti, G., Tartarini, F., Nguyen, C, Schiavon, S. CBE Clima Tool: A free and open-source web application for climate analysis tailored to sustainable building design. Build. Simul. (2023). [https://doi.org/10.1007/s12273-023-1090-5](https://doi.org/10.1007/s12273-023-1090-5). The CBE Clima Tool is open source. We have released the source code on a [public repository](https://github.com/CenterForTheBuiltEnvironment/clima). We welcome contributions from the community ([more info here](contributing/contributing.md)). +### Video Tutorial + +Learn more about the CBE Clima Tool by watching the following video. + +{% embed url="https://www.youtube.com/watch?v=VJ_wOHadVdw" %} +CBE Clima tool tutorial and overview +{% endembed %} + ## Contributions This ongoing project results from the collaboration and contributions of the people listed below. diff --git a/docs/documentation/tabs-explained/data-explorer.md b/docs/documentation/tabs-explained/data-explorer.md index 56dfd797..f2ca916e 100644 --- a/docs/documentation/tabs-explained/data-explorer.md +++ b/docs/documentation/tabs-explained/data-explorer.md @@ -8,10 +8,15 @@ The tab is divided into three sections: * Single-variable + filter analysis * Triple-variable + filter analysis -The single-variable analysis allows data to be displayed in 4 outputs: a yearly chart, monthly daily charts, an annual heatmap chart, and a descriptive statistics table. +The single-variable analysis allows data to be displayed in 4 outputs: a yearly chart, monthly daily charts, an annual heatmap chart, and a descriptive statistics table. The single-variable + filter analysis allows data to be displayed in a customizable heatmap. The chart can be created starting from one variable inside the Clima dataframe and, eventually, filtered by another one. The triple-variable + filter analysis allows data to be displayed in two composite charts, a scatterplot, and a heat map. The baselines of the two graphs are driven by the choice of one variable to be placed on the x-axis and one on the y-axis. Then, data can be colored according to a third variable and filtered according to a fourth. -__ +### Video Tutorial + +Learn more about the Data Explorer tab by watching the following video. + +{% embed url="https://youtu.be/VJ_wOHadVdw?si=1iXC2GvWPdPBGzMc&t=825" %} + diff --git a/docs/documentation/tabs-explained/natural-ventilation.md b/docs/documentation/tabs-explained/natural-ventilation.md index 0724145e..f863de59 100644 --- a/docs/documentation/tabs-explained/natural-ventilation.md +++ b/docs/documentation/tabs-explained/natural-ventilation.md @@ -26,4 +26,12 @@ The minimum temperature threshold is typically dictated by local discomfort near Natural ventilation can be used in combination with and in aid of radiant cooling systems. The most common risk is that condensation will form on cold surfaces, creating slippery floors or potential mold. **Clima** allows this control to be performed with the "surface temperature" filter, which is a function of the dew temperature. -_\[video as for psychometric chart]_ +### Video Tutorial + +Learn more about the Natural Ventilation tab by watching the following video. + +{% embed url="https://youtu.be/VJ_wOHadVdw?si=_cUoFQGyxJD7V85a&t=703" %} + + + + diff --git a/docs/documentation/tabs-explained/outdoor-comfort/README.md b/docs/documentation/tabs-explained/outdoor-comfort/README.md index d958554a..f5f3b996 100644 --- a/docs/documentation/tabs-explained/outdoor-comfort/README.md +++ b/docs/documentation/tabs-explained/outdoor-comfort/README.md @@ -2,18 +2,18 @@ The **Outdoor Comfort** tab shows an overview of the perceived environmental condition based on the UTCI model. -The [Universal Thermal Climate Index](http://www.utci.org/index.php) (UTCI), introduced in 1994, aims to be the measure of human physiological reaction to the atmospheric environment. +The [Universal Thermal Climate Index](http://www.utci.org/index.php) (UTCI), introduced in 1994, aims to be the measure of human physiological reaction to the atmospheric environment. -

Graphical scheme of the UTCI calculation methodology

+

Graphical scheme of the UTCI calculation methodology

It considers: -* dry bulb temperature +* dry bulb temperature * mean radiant temperature * wind speed * relative humidity - to calculate a reference environmental temperature causing strain when compared to an individual's response to the real environment. It is based on [Fiala et al](https://link.springer.com/article/10.1007/s00484-011-0424-7).'s multi-node model of thermo-regulation. +to calculate a reference environmental temperature causing strain when compared to an individual's response to the real environment. It is based on [Fiala et al](https://link.springer.com/article/10.1007/s00484-011-0424-7).'s multi-node model of thermo-regulation. The [UTCI equivalent temperature](https://doi.org/10.1016/j.wace.2018.01.004) is a function of the above parameters, which are combined in a multinode thermo-physiological model that takes into account clothing insulation and metabolic rate. From this a[Universal Thermal Climate Index](http://www.utci.org/index.php) (UTCI) of perceived thermal stress is derived. @@ -24,3 +24,10 @@ The [UTCI equivalent temperature](https://doi.org/10.1016/j.wace.2018.01.004) is The UTCI temperature can be converted in a scale assessing thermal stress, displayed by **Clima** in a heatmap graph.

Example: UTCI heat stress index heatmap in 'Sun and Wind' conditions for Rome, ITA

+ +### Video Tutorial + +Learn more about the Outdoor Comfort tab by watching the following video. + +{% embed url="https://youtu.be/VJ_wOHadVdw?si=hIa-95u3wpeP6eq0&t=781" %} + diff --git a/docs/documentation/tabs-explained/psychrometric-chart/README.md b/docs/documentation/tabs-explained/psychrometric-chart/README.md index 6070829b..1ef00ac0 100644 --- a/docs/documentation/tabs-explained/psychrometric-chart/README.md +++ b/docs/documentation/tabs-explained/psychrometric-chart/README.md @@ -1,6 +1,6 @@ # Psychrometric Chart -**Clima** allows the user to visualize all annual weather conditions on a [psychometric diagram.](psychrometric-chart-explained.md) +**Clima** allows the user to visualize all annual weather conditions on a [psychometric diagram.](psychrometric-chart-explained.md) The default diagram allows the users to overlay the frequency with which weather conditions recur throughout the year. @@ -17,3 +17,10 @@ Then, users can overlay another variable on the graphs, choosing from [Clima dat Moreover, data can be filtered by date, time, or one of the [Clima dataframe](../tab-summary/clima-dataframe.md) variables.

Psychrometric charts filters

+ +### Video Tutorial + +Learn more about the Psychrometric tab by watching the following video. + +{% embed url="https://youtu.be/VJ_wOHadVdw?si=iAcBQpq3IgCNY-H6&t=582" %} + diff --git a/docs/documentation/tabs-explained/sun-and-cloud/README.md b/docs/documentation/tabs-explained/sun-and-cloud/README.md index d0dd117a..ff1f17f2 100644 --- a/docs/documentation/tabs-explained/sun-and-cloud/README.md +++ b/docs/documentation/tabs-explained/sun-and-cloud/README.md @@ -1,8 +1,8 @@ # Sun and Clouds -The **Sun and Clouds** tab presents an overview of various climatic factors that relate to sun, solar position, intensity, and cloud cover, in particular: +The **Sun and Clouds** tab presents an overview of various climatic factors that relate to sun, solar position, intensity, and cloud cover, in particular: -* [Apparent sunpath for the location (spherical and cartesian projection)](broken-reference) +* [Apparent sunpath for the location (spherical and cartesian projection)](broken-reference/) * [Global and Diffuse Horizontal Solar Radiation](global-and-diffuse-horizontal-solar-radiation/) * [Cloud coverage](cloud-coverage.md) * [Customizable daily and hourly maps](customizable-daily-and-hourly-maps.md) @@ -11,12 +11,19 @@ The **Sun and Clouds** tab presents an overview of various climatic factors that **Clima** allows the user to visualize the sun path for the chosen location in spherical and cartesian projection -![Example: spherical sun path for Berlin, DEU ](../../../.gitbook/assets/cbeclima\_berlin\_deu\_spherical\_sun\_path\_sun\_tab.svg) +![Example: spherical sun path for Berlin, DEU](../../../.gitbook/assets/cbeclima\_berlin\_deu\_spherical\_sun\_path\_sun\_tab.svg) -![Example: cartesian sun path for Berlin, DEU ](../../../.gitbook/assets/cbeclima\_berlin\_deu\_cartesian\_sun\_path\_sun\_tab.svg) +![Example: cartesian sun path for Berlin, DEU](../../../.gitbook/assets/cbeclima\_berlin\_deu\_cartesian\_sun\_path\_sun\_tab.svg) Clima optionally allows a variety of variables to be overlayed on either sun path type. This allows the user to identify climatic patterns in relation to the apparent solar position. Data are plotted on the analemma. ![Spherical and carthesian sun paths for Berlin, DEU with various data overlays](../../../.gitbook/assets/sunpath+variables.png) + +### Video Tutorial + +Learn more about the Sun and Cloud tab by watching the following video. + +{% embed url="https://youtu.be/VJ_wOHadVdw?si=mB2xNH57MWW_4CRR&t=447" %} + diff --git a/docs/documentation/tabs-explained/tab-home.md b/docs/documentation/tabs-explained/tab-home.md index e4af9123..c0e73350 100644 --- a/docs/documentation/tabs-explained/tab-home.md +++ b/docs/documentation/tabs-explained/tab-home.md @@ -1,6 +1,16 @@ +--- +description: This page explains how a user can load an EPW file in the Clima tool +--- + # Select Weather File -Users can either choose to analyse the climate of the locations displayed on the map or upload a custom EPW file. After loading an EPW file the user can then access the other tabs to generate dynamic visualisations of the data. +Users can either choose to analyse the climate of the locations displayed on the map or upload a custom EPW file. After loading an EPW file the user can then access the other tabs to generate dynamic visualisations of the data. + +### Video Tutorial + +Learn more about how to analyse the climate of a specific location and uploading your custom EPW file by watching the following video. -![](../../.gitbook/assets/clima-home.png) +{% embed url="https://youtu.be/VJ_wOHadVdw?si=SxvUzaI9rCNIFFs0&t=136" %} +How to select or upload a EPW file +{% endembed %} diff --git a/docs/documentation/tabs-explained/tab-summary/README.md b/docs/documentation/tabs-explained/tab-summary/README.md index 7281cc2e..0f251189 100644 --- a/docs/documentation/tabs-explained/tab-summary/README.md +++ b/docs/documentation/tabs-explained/tab-summary/README.md @@ -7,3 +7,10 @@ The top section of the page provides information about the selected location suc The bottom section of the page comprises the heating and cooling degree day chart and four violin plots showing the distribution of the dry-bulb air temperature (Tdb), relative humidity (RH), Global Horizontal Irradiance (GHI), and wind speed (U). ![Tab summary top](../../../.gitbook/assets/clima-summary-bottom.png) + +### Video Tutorial + +Learn more about the Climate Summary tab by watching the following video. + +{% embed url="https://youtu.be/VJ_wOHadVdw?si=H-93XRhh5Neuby_b&t=220" %} + diff --git a/docs/documentation/tabs-explained/temperature-and-humidity/README.md b/docs/documentation/tabs-explained/temperature-and-humidity/README.md index 65527769..48fad2bf 100644 --- a/docs/documentation/tabs-explained/temperature-and-humidity/README.md +++ b/docs/documentation/tabs-explained/temperature-and-humidity/README.md @@ -2,7 +2,7 @@ The **Temperature and Humidity** tab presents an overview of [air dry bulb temperature](temperatures-explained.md) and [relative humidity](relative-humidity-explained.md) trends. -**Clima** allows the user to visualize the annual data trend through a customizable chart. +**Clima** allows the user to visualize the annual data trend through a customizable chart.

Example: annual dry bulb temperatures trend for Paris, FRA

@@ -19,3 +19,10 @@ Daily [scatter plot](https://en.wikipedia.org/wiki/Scatter\_plot) shows all hour

Example: annual dry bulb temperatures heatmap for Paris, FRA

Example: annual relative humidity heatmap for Paris, FRA

+ +### Video Tutorial + +Learn more about the Temperature and Relative Humidity tab by watching the following video. + +{% embed url="https://youtu.be/VJ_wOHadVdw?si=a1lgX6Lpt8fUXiCr&t=433" %} + diff --git a/docs/documentation/tabs-explained/wind/README.md b/docs/documentation/tabs-explained/wind/README.md index b9118714..4d2abbe3 100644 --- a/docs/documentation/tabs-explained/wind/README.md +++ b/docs/documentation/tabs-explained/wind/README.md @@ -11,3 +11,10 @@ Moreover, **Clima** shows wind intensity and direction using [heat maps](https:/

Example: Heat map of the hourly wind intensity on all days of the year for Rome, ITA

Example: Heat map of the hourly wind direction on all days of the year for Rome, ITA

+ +### Video Tutorial + +Learn more about the Wind tab by watching the following video. + +{% embed url="https://youtu.be/VJ_wOHadVdw?si=W90QpOb4VoqUPcbA&t=524" %} + diff --git a/docs/documentation/weather-file-repositories.md b/docs/documentation/weather-file-repositories.md index 5725c61e..de63edaa 100644 --- a/docs/documentation/weather-file-repositories.md +++ b/docs/documentation/weather-file-repositories.md @@ -1,6 +1,6 @@ # Weather file repositories -In addition to the data from [Energy Plus](https://energyplus.net/weather) and [Climate.One.Building.org](http://climate.onebuilding.org/) CBE Clima Tool allows users to visualize any valid EPW file. Below we list some free sources from which climate files can be obtained. +In addition to the data from [Energy Plus](https://energyplus.net/weather) and [Climate.One.Building.org](http://climate.onebuilding.org/) CBE Clima Tool allows users to visualize any valid EPW file. Below we list some free sources from which climate files can be obtained. The following sources will be divided according to: @@ -9,16 +9,13 @@ The following sources will be divided according to: **TMY repositories:** -* ****[Climate.OneBuilding](https://climate.onebuilding.org/); +* [Climate.OneBuilding](https://climate.onebuilding.org/); * [EnergyPlus](https://energyplus.net/weather); * European commission [Photovoltaic Geographical Information System](https://re.jrc.ec.europa.eu/pvg\_tools/it/#TMY); * [CSIRO](https://agdatashop.csiro.au/future-climate-predictive-weather) projected weather files for future climate scenarios for Australian locations; * University of Exeter [Prometheus](https://engineering.exeter.ac.uk/research/cee/research/prometheus/termsandconditions/futureweatherfiles/), current and future weather files for British cities - - **AMY repositories:** * NASA [Power tool](https://power.larc.nasa.gov/data-access-viewer/); * Pacific Northwest National Laboratory [diyepw tool](https://github.com/IMMM-SFA/diyepw) (given set of WMOs station) - From 78f987e0f48fd0a6888aece410b22342d72c825e Mon Sep 17 00:00:00 2001 From: t-kramer Date: Tue, 20 May 2025 11:46:42 -0700 Subject: [PATCH 12/91] feat: add Siteimprove analytics --- app.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app.py b/app.py index f4e09200..728d947d 100644 --- a/app.py +++ b/app.py @@ -30,6 +30,7 @@ gtag('config', 'G-JDQTBEPS4B'); + + + + + From bd4b57ae061938106d43c75ae51aa088bee9c90d Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 31 Jul 2025 13:04:01 +1000 Subject: [PATCH 13/91] build: reverted to pipenv in cypress.yml --- .github/workflows/cypress.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index 475691b0..f1810e28 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -14,11 +14,12 @@ jobs: - name: Build Clima run: |- - pip install -r requirements.txt + pip install pipenv + pipenv install - name: Start Clima run: |- - python main.py & + pipenv run python main.py & - name: Setup Node uses: actions/setup-node@v4 From 5ae03fa1dd4964c12f7eaa8056f9a7ab271d1f06 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 31 Jul 2025 13:06:06 +1000 Subject: [PATCH 14/91] build: using pipenv in python.yml --- .github/workflows/python.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index bcd136c8..fd21bca2 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -12,13 +12,14 @@ jobs: with: python-version: '3.10' - - name: Install + - name: Build Clima run: |- - pip install -r requirements.txt + pip install pipenv + pipenv install --dev - name: Test Clima run: |- - python -m pytest + pipenv run python -m pytest - name: Run Black # TODO: Add to dev dependencies: Adding it right now From 80f75fdeff3d162a639f896e7e5b4ac075ea75bc Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 31 Jul 2025 13:09:27 +1000 Subject: [PATCH 15/91] build: created Pipfile --- Pipfile | 27 ++ Pipfile.lock | 1098 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1125 insertions(+) create mode 100644 Pipfile create mode 100644 Pipfile.lock diff --git a/Pipfile b/Pipfile new file mode 100644 index 00000000..c16551ca --- /dev/null +++ b/Pipfile @@ -0,0 +1,27 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +dash = "==2.14.2" +pvlib = "==0.9.1" +pythermalcomfort = "*" +dash-bootstrap-components = "==1.2.0" +dash-extensions = "==1.0.7" +dash-mantine-components = "*" +requests = "*" +plotly = "*" +pandas = "*" +numpy = "*" +dash-iconify = "*" + +[dev-packages] +cleanpy = "*" +pytest = "*" +bump2version = "*" +pandas = "*" +requests = "*" + +[requires] +python_version = "3.11" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 00000000..2a33deb2 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,1098 @@ +{ + "_meta": { + "hash": { + "sha256": "d3b477f30d912b7e556bfc1a5892f75a59c464ea0ee66413d3e950573fe90b96" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.11" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "ansi2html": { + "hashes": [ + "sha256:3453bf87535d37b827b05245faaa756dbab4ec3d69925e352b6319c3c955c0a5", + "sha256:dccb75aa95fb018e5d299be2b45f802952377abfdce0504c17a6ee6ef0a420c5" + ], + "markers": "python_version >= '3.7'", + "version": "==1.9.2" + }, + "blinker": { + "hashes": [ + "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", + "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc" + ], + "markers": "python_version >= '3.9'", + "version": "==1.9.0" + }, + "cachelib": { + "hashes": [ + "sha256:38222cc7c1b79a23606de5c2607f4925779e37cdcea1c2ad21b8bae94b5425a5", + "sha256:811ceeb1209d2fe51cd2b62810bd1eccf70feba5c52641532498be5c675493b3" + ], + "markers": "python_version >= '3.7'", + "version": "==0.9.0" + }, + "certifi": { + "hashes": [ + "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", + "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995" + ], + "markers": "python_version >= '3.7'", + "version": "==2025.7.14" + }, + "charset-normalizer": { + "hashes": [ + "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4", + "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45", + "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", + "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", + "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", + "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", + "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d", + "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", + "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184", + "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", + "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b", + "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64", + "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", + "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", + "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", + "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344", + "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58", + "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", + "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471", + "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", + "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", + "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836", + "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", + "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", + "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", + "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1", + "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01", + "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", + "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58", + "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", + "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", + "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2", + "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a", + "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597", + "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", + "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5", + "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb", + "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f", + "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", + "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", + "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", + "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", + "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7", + "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7", + "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455", + "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", + "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4", + "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", + "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3", + "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", + "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", + "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", + "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", + "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", + "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", + "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", + "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12", + "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa", + "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", + "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", + "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f", + "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", + "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", + "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5", + "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02", + "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", + "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", + "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e", + "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", + "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", + "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", + "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", + "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681", + "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba", + "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", + "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a", + "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", + "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", + "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", + "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", + "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027", + "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7", + "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518", + "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", + "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", + "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", + "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", + "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da", + "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", + "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f", + "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", + "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f" + ], + "markers": "python_version >= '3.7'", + "version": "==3.4.2" + }, + "click": { + "hashes": [ + "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", + "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b" + ], + "markers": "python_version >= '3.10'", + "version": "==8.2.1" + }, + "dash": { + "hashes": [ + "sha256:602e7b0047cc9c48a81244377b812e79198678ef759a0039dadfe81023e20e96", + "sha256:8e1a280f1c7be0714825f04786beab41d40752095e116204e64b13ac978b654d" + ], + "index": "pypi", + "markers": "python_version >= '3.6'", + "version": "==2.14.2" + }, + "dash-bootstrap-components": { + "hashes": [ + "sha256:4f95470952ecb9a0f2dbef34be2969353c5c16925817d48da50db51ade80f50f", + "sha256:d7fd69cb2b1e86f9cc4bcee4036302e5d534d0bf102d331b29392a3c355d776a" + ], + "index": "pypi", + "markers": "python_version >= '3.6' and python_version < '4'", + "version": "==1.2.0" + }, + "dash-core-components": { + "hashes": [ + "sha256:52b8e8cce13b18d0802ee3acbc5e888cb1248a04968f962d63d070400af2e346", + "sha256:c6733874af975e552f95a1398a16c2ee7df14ce43fa60bb3718a3c6e0b63ffee" + ], + "version": "==2.0.0" + }, + "dash-extensions": { + "hashes": [ + "sha256:46c4ec7c7d3b42db63032005f7d258435bc0092d0fec6f6ce000be68befeb102", + "sha256:a582f79debd6d1332b4382b575d77a9cfca220860ef9b17c24541e7e6d77d8eb" + ], + "index": "pypi", + "markers": "python_version >= '3.8' and python_version < '4'", + "version": "==1.0.7" + }, + "dash-html-components": { + "hashes": [ + "sha256:8703a601080f02619a6390998e0b3da4a5daabe97a1fd7a9cebc09d015f26e50", + "sha256:b42cc903713c9706af03b3f2548bda4be7307a7cf89b7d6eae3da872717d1b63" + ], + "version": "==2.0.0" + }, + "dash-iconify": { + "hashes": [ + "sha256:564774be6b11b0ac3a8999b7137c3d17a1d351d69b673aa313c7228eacc9d143", + "sha256:9ab0eda19bb4514e177bf1f8f36947bb9e5b44aba6ce947f22a1cf0f1a45fc14" + ], + "index": "pypi", + "version": "==0.1.2" + }, + "dash-mantine-components": { + "hashes": [ + "sha256:3a0a18c26e7373f2c465631a31688d4d25f2584da9770b650bb75da11898934a", + "sha256:dc75fe485c9b5e2ae9c939f8c09611778b3aad50318e63e1bd1eb3abe2a33711" + ], + "index": "pypi", + "version": "==2.1.0" + }, + "dash-table": { + "hashes": [ + "sha256:18624d693d4c8ef2ddec99a6f167593437a7ea0bf153aa20f318c170c5bc7308", + "sha256:19036fa352bb1c11baf38068ec62d172f0515f73ca3276c79dee49b95ddc16c9" + ], + "version": "==5.0.0" + }, + "dataclass-wizard": { + "hashes": [ + "sha256:4c46591782265058f1148cfd1f54a3a91221e63986fdd04c9d59f4ced61f4424", + "sha256:63751203e54b9b9349212cc185331da73c1adc99c51312575eb73bb5c00c1962" + ], + "version": "==0.22.3" + }, + "editorconfig": { + "hashes": [ + "sha256:1eda9c2c0db8c16dbd50111b710572a5e6de934e39772de1959d41f64fc17c82", + "sha256:23c08b00e8e08cc3adcddb825251c497478df1dada6aefeb01e626ad37303745" + ], + "markers": "python_version >= '3.9'", + "version": "==0.17.1" + }, + "flask": { + "hashes": [ + "sha256:09c347a92aa7ff4a8e7f3206795f30d826654baf38b873d0744cd571ca609efc", + "sha256:f69fcd559dc907ed196ab9df0e48471709175e696d6e698dd4dbe940f96ce66b" + ], + "markers": "python_version >= '3.8'", + "version": "==2.3.3" + }, + "flask-caching": { + "hashes": [ + "sha256:19571f2570e9b8dd9dd9d2f49d7cbee69c14ebe8cc001100b1eb98c379dd80ad", + "sha256:24b60c552d59a9605cc1b6a42c56cdb39a82a28dab4532bbedb9222ae54ecb4e" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.2" + }, + "h5py": { + "hashes": [ + "sha256:016e89d3be4c44f8d5e115fab60548e518ecd9efe9fa5c5324505a90773e6f03", + "sha256:0cbd41f4e3761f150aa5b662df991868ca533872c95467216f2bec5fcad84882", + "sha256:1223b902ef0b5d90bcc8a4778218d6d6cd0f5561861611eda59fa6c52b922f4d", + "sha256:2372116b2e0d5d3e5e705b7f663f7c8d96fa79a4052d250484ef91d24d6a08f4", + "sha256:24df6b2622f426857bda88683b16630014588a0e4155cba44e872eb011c4eaed", + "sha256:4f025cf30ae738c4c4e38c7439a761a71ccfcce04c2b87b2a2ac64e8c5171d43", + "sha256:543877d7f3d8f8a9828ed5df6a0b78ca3d8846244b9702e99ed0d53610b583a8", + "sha256:554ef0ced3571366d4d383427c00c966c360e178b5fb5ee5bb31a435c424db0c", + "sha256:573c33ad056ac7c1ab6d567b6db9df3ffc401045e3f605736218f96c1e0490c6", + "sha256:5e59d2136a8b302afd25acdf7a89b634e0eb7c66b1a211ef2d0457853768a2ef", + "sha256:6da62509b7e1d71a7d110478aa25d245dd32c8d9a1daee9d2a42dba8717b047a", + "sha256:6ff2389961ee5872de697054dd5a033b04284afc3fb52dc51d94561ece2c10c6", + "sha256:723a40ee6505bd354bfd26385f2dae7bbfa87655f4e61bab175a49d72ebfc06b", + "sha256:852b81f71df4bb9e27d407b43071d1da330d6a7094a588efa50ef02553fa7ce4", + "sha256:8c497600c0496548810047257e36360ff551df8b59156d3a4181072eed47d8ad", + "sha256:aa4b7bbce683379b7bf80aaba68e17e23396100336a8d500206520052be2f812", + "sha256:ae18e3de237a7a830adb76aaa68ad438d85fe6e19e0d99944a3ce46b772c69b3", + "sha256:bf4897d67e613ecf5bdfbdab39a1158a64df105827da70ea1d90243d796d367f", + "sha256:ccbe17dc187c0c64178f1a10aa274ed3a57d055117588942b8a08793cc448216", + "sha256:d2744b520440a996f2dae97f901caa8a953afc055db4673a993f2d87d7f38713", + "sha256:d90e6445ab7c146d7f7981b11895d70bc1dd91278a4f9f9028bc0c95e4a53f13", + "sha256:e0045115d83272090b0717c555a31398c2c089b87d212ceba800d3dc5d952e23", + "sha256:e8cbaf6910fa3983c46172666b0b8da7b7bd90d764399ca983236f2400436eeb", + "sha256:ef9603a501a04fcd0ba28dd8f0995303d26a77a980a1f9474b3417543d4c6174", + "sha256:f30dbc58f2a0efeec6c8836c97f6c94afd769023f44e2bb0ed7b17a16ec46088", + "sha256:f5cc1601e78027cedfec6dd50efb4802f018551754191aeb58d948bd3ec3bd7a" + ], + "markers": "python_version >= '3.9'", + "version": "==3.14.0" + }, + "idna": { + "hashes": [ + "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", + "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" + ], + "markers": "python_version >= '3.6'", + "version": "==3.10" + }, + "importlib-metadata": { + "hashes": [ + "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", + "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd" + ], + "markers": "python_version >= '3.7'", + "version": "==8.7.0" + }, + "itsdangerous": { + "hashes": [ + "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", + "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173" + ], + "markers": "python_version >= '3.8'", + "version": "==2.2.0" + }, + "jinja2": { + "hashes": [ + "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", + "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67" + ], + "markers": "python_version >= '3.7'", + "version": "==3.1.6" + }, + "jsbeautifier": { + "hashes": [ + "sha256:5bb18d9efb9331d825735fbc5360ee8f1aac5e52780042803943aa7f854f7592", + "sha256:72f65de312a3f10900d7685557f84cb61a9733c50dcc27271a39f5b0051bf528" + ], + "version": "==1.15.4" + }, + "llvmlite": { + "hashes": [ + "sha256:07667d66a5d150abed9157ab6c0b9393c9356f229784a4385c02f99e94fc94d4", + "sha256:1d671a56acf725bf1b531d5ef76b86660a5ab8ef19bb6a46064a705c6ca80aad", + "sha256:2fb7c4f2fb86cbae6dca3db9ab203eeea0e22d73b99bc2341cdf9de93612e930", + "sha256:319bddd44e5f71ae2689859b7203080716448a3cd1128fb144fe5c055219d516", + "sha256:40526fb5e313d7b96bda4cbb2c85cd5374e04d80732dd36a282d72a560bb6408", + "sha256:41e3839150db4330e1b2716c0be3b5c4672525b4c9005e17c7597f835f351ce2", + "sha256:46224058b13c96af1365290bdfebe9a6264ae62fb79b2b55693deed11657a8bf", + "sha256:5f79a728e0435493611c9f405168682bb75ffd1fbe6fc360733b850c80a026db", + "sha256:7202b678cdf904823c764ee0fe2dfe38a76981f4c1e51715b4cb5abb6cf1d9e8", + "sha256:9c58867118bad04a0bb22a2e0068c693719658105e40009ffe95c7000fcde88e", + "sha256:9fbadbfba8422123bab5535b293da1cf72f9f478a65645ecd73e781f962ca614", + "sha256:aa0097052c32bf721a4efc03bd109d335dfa57d9bffb3d4c24cc680711b8b4fc", + "sha256:ace564d9fa44bb91eb6e6d8e7754977783c68e90a471ea7ce913bff30bd62427", + "sha256:c0143a5ef336da14deaa8ec26c5449ad5b6a2b564df82fcef4be040b9cacfea9", + "sha256:c5d22c3bfc842668168a786af4205ec8e3ad29fb1bc03fd11fd48460d0df64c1", + "sha256:cccf8eb28f24840f2689fb1a45f9c0f7e582dd24e088dcf96e424834af11f791", + "sha256:d752f89e31b66db6f8da06df8b39f9b91e78c5feea1bf9e8c1fba1d1c24c065d", + "sha256:d8489634d43c20cd0ad71330dde1d5bc7b9966937a263ff1ec1cebb90dc50955", + "sha256:eae7e2d4ca8f88f89d315b48c6b741dcb925d6a1042da694aa16ab3dd4cbd3a1", + "sha256:eed7d5f29136bda63b6d7804c279e2b72e08c952b7c5df61f45db408e0ee52f3", + "sha256:f01a394e9c9b7b1d4e63c327b096d10f6f0ed149ef53d38a09b3749dcf8c9610" + ], + "markers": "python_version >= '3.10'", + "version": "==0.44.0" + }, + "markupsafe": { + "hashes": [ + "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", + "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", + "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", + "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", + "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", + "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", + "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", + "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", + "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", + "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", + "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", + "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", + "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", + "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", + "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", + "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", + "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", + "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", + "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", + "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", + "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", + "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", + "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", + "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", + "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", + "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", + "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", + "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", + "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", + "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", + "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", + "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", + "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", + "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", + "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", + "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", + "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", + "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", + "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", + "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", + "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", + "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", + "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", + "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", + "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", + "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", + "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", + "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", + "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", + "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", + "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", + "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", + "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", + "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", + "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", + "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", + "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", + "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", + "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", + "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", + "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50" + ], + "markers": "python_version >= '3.9'", + "version": "==3.0.2" + }, + "more-itertools": { + "hashes": [ + "sha256:cabaa341ad0389ea83c17a94566a53ae4c9d07349861ecb14dc6d0345cf9ac5d", + "sha256:d2bc7f02446e86a68911e58ded76d6561eea00cddfb2a91e7019bbb586c799f3" + ], + "markers": "python_version >= '3.7'", + "version": "==9.1.0" + }, + "narwhals": { + "hashes": [ + "sha256:235e61ca807bc21110ca36a4d53888ecc22c42dcdf50a7c886e10dde3fd7f38c", + "sha256:837457e36a2ba1710c881fb69e1f79ce44fb81728c92ac378f70892a53af8ddb" + ], + "markers": "python_version >= '3.9'", + "version": "==2.0.1" + }, + "nest-asyncio": { + "hashes": [ + "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", + "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c" + ], + "markers": "python_version >= '3.5'", + "version": "==1.6.0" + }, + "numba": { + "hashes": [ + "sha256:34fba9406078bac7ab052efbf0d13939426c753ad72946baaa5bf9ae0ebb8dd2", + "sha256:3945615cd73c2c7eba2a85ccc9c1730c21cd3958bfcf5a44302abae0fb07bb60", + "sha256:3a10a8fc9afac40b1eac55717cece1b8b1ac0b946f5065c89e00bde646b5b154", + "sha256:48a53a3de8f8793526cbe330f2a39fe9a6638efcbf11bd63f3d2f9757ae345cd", + "sha256:49c980e4171948ffebf6b9a2520ea81feed113c1f4890747ba7f59e74be84b1b", + "sha256:4ddce10009bc097b080fc96876d14c051cc0c7679e99de3e0af59014dab7dfe8", + "sha256:59321215e2e0ac5fa928a8020ab00b8e57cda8a97384963ac0dfa4d4e6aa54e7", + "sha256:5b1bb509d01f23d70325d3a5a0e237cbc9544dd50e50588bc581ba860c213546", + "sha256:5f154aaea625fb32cfbe3b80c5456d514d416fcdf79733dd69c0df3a11348e9e", + "sha256:76bcec9f46259cedf888041b9886e257ae101c6268261b19fda8cfbc52bec9d1", + "sha256:7d3bcada3c9afba3bed413fba45845f2fb9cd0d2b27dd58a1be90257e293d140", + "sha256:8750ee147940a6637b80ecf7f95062185ad8726c8c28a2295b8ec1160a196f7d", + "sha256:97cf4f12c728cf77c9c1d7c23707e4d8fb4632b46275f8f3397de33e5877af18", + "sha256:ae45830b129c6137294093b269ef0a22998ccc27bf7cf096ab8dcf7bca8946f9", + "sha256:ae8c7a522c26215d5f62ebec436e3d341f7f590079245a2f1008dfd498cc1642", + "sha256:bbfdf4eca202cebade0b7d43896978e146f39398909a42941c9303f82f403a18", + "sha256:bd1e74609855aa43661edffca37346e4e8462f6903889917e9f41db40907daa2", + "sha256:bdbca73ad81fa196bd53dc12e3aaf1564ae036e0c125f237c7644fe64a4928ab", + "sha256:cf9f9fc00d6eca0c23fc840817ce9f439b9f03c8f03d6246c0e7f0cb15b7162a", + "sha256:ea0247617edcb5dd61f6106a56255baab031acc4257bddaeddb3a1003b4ca3fd", + "sha256:efd3db391df53aaa5cfbee189b6c910a5b471488749fd6606c3f33fc984c2ae2" + ], + "markers": "python_version >= '3.10'", + "version": "==0.61.2" + }, + "numpy": { + "hashes": [ + "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", + "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", + "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", + "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", + "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", + "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", + "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", + "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", + "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", + "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", + "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", + "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", + "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", + "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", + "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", + "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", + "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", + "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", + "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", + "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", + "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", + "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", + "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", + "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", + "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", + "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", + "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", + "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", + "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", + "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", + "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", + "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", + "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", + "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", + "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", + "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", + "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", + "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", + "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", + "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", + "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", + "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", + "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", + "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", + "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", + "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", + "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", + "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", + "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", + "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", + "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", + "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", + "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", + "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", + "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==2.2.6" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + ], + "markers": "python_version >= '3.8'", + "version": "==25.0" + }, + "pandas": { + "hashes": [ + "sha256:025e92411c16cbe5bb2a4abc99732a6b132f439b8aab23a59fa593eb00704232", + "sha256:09e3b1587f0f3b0913e21e8b32c3119174551deb4a4eba4a89bc7377947977e7", + "sha256:0a95b9ac964fe83ce317827f80304d37388ea77616b1425f0ae41c9d2d0d7bb2", + "sha256:0f951fbb702dacd390561e0ea45cdd8ecfa7fb56935eb3dd78e306c19104b9b0", + "sha256:1b916a627919a247d865aed068eb65eb91a344b13f5b57ab9f610b7716c92de1", + "sha256:1c78cf43c8fde236342a1cb2c34bcff89564a7bfed7e474ed2fffa6aed03a956", + "sha256:1d12f618d80379fde6af007f65f0c25bd3e40251dbd1636480dfffce2cf1e6da", + "sha256:22c2e866f7209ebc3a8f08d75766566aae02bcc91d196935a1d9e59c7b990ac9", + "sha256:2323294c73ed50f612f67e2bf3ae45aea04dce5690778e08a09391897f35ff88", + "sha256:2b0540963d83431f5ce8870ea02a7430adca100cec8a050f0811f8e31035541b", + "sha256:2ba6aff74075311fc88504b1db890187a3cd0f887a5b10f5525f8e2ef55bfdb9", + "sha256:2eb789ae0274672acbd3c575b0598d213345660120a257b47b5dafdc618aec83", + "sha256:2f4d6feeba91744872a600e6edbbd5b033005b431d5ae8379abee5bcfa479fab", + "sha256:342e59589cc454aaff7484d75b816a433350b3d7964d7847327edda4d532a2e3", + "sha256:3462c3735fe19f2638f2c3a40bd94ec2dc5ba13abbb032dd2fa1f540a075509d", + "sha256:3583d348546201aff730c8c47e49bc159833f971c2899d6097bce68b9112a4f1", + "sha256:4645f770f98d656f11c69e81aeb21c6fca076a44bed3dcbb9396a4311bc7f6d8", + "sha256:4d544806b485ddf29e52d75b1f559142514e60ef58a832f74fb38e48d757b299", + "sha256:56a342b231e8862c96bdb6ab97170e203ce511f4d0429589c8ede1ee8ece48b8", + "sha256:5db9637dbc24b631ff3707269ae4559bce4b7fd75c1c4d7e13f40edc42df4444", + "sha256:689968e841136f9e542020698ee1c4fbe9caa2ed2213ae2388dc7b81721510d3", + "sha256:6de8547d4fdb12421e2d047a2c446c623ff4c11f47fddb6b9169eb98ffba485a", + "sha256:6f3bf5ec947526106399a9e1d26d40ee2b259c66422efdf4de63c848492d91bb", + "sha256:782647ddc63c83133b2506912cc6b108140a38a37292102aaa19c81c83db2928", + "sha256:7dcb79bf373a47d2a40cf7232928eb7540155abbc460925c2c96d2d30b006eb4", + "sha256:8dfc17328e8da77be3cf9f47509e5637ba8f137148ed0e9b5241e1baf526e20a", + "sha256:9026bd4a80108fac2239294a15ef9003c4ee191a0f64b90f170b40cfb7cf2d22", + "sha256:911580460fc4884d9b05254b38a6bfadddfcc6aaef856fb5859e7ca202e45275", + "sha256:98bcc8b5bf7afed22cc753a28bc4d9e26e078e777066bc53fac7904ddef9a678", + "sha256:9b7ff55f31c4fcb3e316e8f7fa194566b286d6ac430afec0d461163312c5841e", + "sha256:ac942bfd0aca577bef61f2bc8da8147c4ef6879965ef883d8e8d5d2dc3e744b8", + "sha256:b3cd4273d3cb3707b6fffd217204c52ed92859533e31dc03b7c5008aa933aaab", + "sha256:b4b0de34dc8499c2db34000ef8baad684cfa4cbd836ecee05f323ebfba348c7d", + "sha256:ca7ed14832bce68baef331f4d7f294411bed8efd032f8109d690df45e00c4679", + "sha256:cd05b72ec02ebfb993569b4931b2e16fbb4d6ad6ce80224a3ee838387d83a191", + "sha256:dd71c47a911da120d72ef173aeac0bf5241423f9bfea57320110a978457e069e", + "sha256:e5635178b387bd2ba4ac040f82bc2ef6e6b500483975c4ebacd34bec945fda12", + "sha256:e6723a27ad7b244c0c79d8e7007092d7c8f0f11305770e2f4cd778b3ad5f9f85", + "sha256:ec6c851509364c59a5344458ab935e6451b31b818be467eb24b0fe89bd05b6b9", + "sha256:fe37e757f462d31a9cd7580236a82f353f5713a80e059a29753cf938c6775d96", + "sha256:fe67dc676818c186d5a3d5425250e40f179c2a89145df477dd82945eaea89e97", + "sha256:fe7317f578c6a153912bd2292f02e40c1d8f253e93c599e82620c7f69755c74f" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==2.3.1" + }, + "plotly": { + "hashes": [ + "sha256:32c444d4c940887219cb80738317040363deefdfee4f354498cc0b6dab8978bd", + "sha256:9dfa23c328000f16c928beb68927444c1ab9eae837d1fe648dbcda5360c7953d" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==6.2.0" + }, + "pvlib": { + "hashes": [ + "sha256:ead96f47898befd7728ab0b61b9747231008e151ef78a26d5e41d0b6a95a3a9d", + "sha256:f03b63e2d2fca13d5ca4c69010929dca5046e935da943d2dd1806e265aa44e93" + ], + "index": "pypi", + "markers": "python_version >= '3.6'", + "version": "==0.9.1" + }, + "pythermalcomfort": { + "hashes": [ + "sha256:5f319fe0e699daea752c789efad3cf2c865a8b24b26fb25ef315462bc1a26dab", + "sha256:78c35c2791ddc01da8355cb3048dee702ee7f7bd645b4fef7a37185d5778900d" + ], + "index": "pypi", + "markers": "python_full_version >= '3.10.0'", + "version": "==3.4.4" + }, + "python-dateutil": { + "hashes": [ + "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", + "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "version": "==2.9.0.post0" + }, + "pytz": { + "hashes": [ + "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", + "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00" + ], + "version": "==2025.2" + }, + "requests": { + "hashes": [ + "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", + "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==2.32.4" + }, + "retrying": { + "hashes": [ + "sha256:4d206e0ed2aff5ef2f3cd867abb9511e9e8f31127c5aca20f1d5246e476903b0", + "sha256:d736050c1adfc0a71fa022d9198ee130b0e66be318678a3fdd8b1b8872dc0997" + ], + "markers": "python_version >= '3.6'", + "version": "==1.4.1" + }, + "scipy": { + "hashes": [ + "sha256:0851f6a1e537fe9399f35986897e395a1aa61c574b178c0d456be5b1a0f5ca1f", + "sha256:0a55ffe0ba0f59666e90951971a884d1ff6f4ec3275a48f472cfb64175570f77", + "sha256:15240c3aac087a522b4eaedb09f0ad061753c5eebf1ea430859e5bf8640d5919", + "sha256:18aca1646a29ee9a0625a1be5637fa798d4d81fdf426481f06d69af828f16958", + "sha256:21a611ced9275cb861bacadbada0b8c0623bc00b05b09eb97f23b370fc2ae56d", + "sha256:226652fca853008119c03a8ce71ffe1b3f6d2844cc1686e8f9806edafae68596", + "sha256:2ef500e72f9623a6735769e4b93e9dcb158d40752cdbb077f305487e3e2d1f45", + "sha256:30cc4bb81c41831ecfd6dc450baf48ffd80ef5aed0f5cf3ea775740e80f16ecc", + "sha256:367d567ee9fc1e9e2047d31f39d9d6a7a04e0710c86e701e053f237d14a9b4f6", + "sha256:3d0b80fb26d3e13a794c71d4b837e2a589d839fd574a6bbb4ee1288c213ad4a3", + "sha256:3ddfb1e8d0b540cb4ee9c53fc3dea3186f97711248fb94b4142a1b27178d8b4b", + "sha256:3ea0733a2ff73fd6fdc5fecca54ee9b459f4d74f00b99aced7d9a3adb43fb1cc", + "sha256:44c76f9e8b6e8e488a586190ab38016e4ed2f8a038af7cd3defa903c0a2238b3", + "sha256:4cf5785e44e19dcd32a0e4807555e1e9a9b8d475c6afff3d21c3c543a6aa84f4", + "sha256:4dc0e7be79e95d8ba3435d193e0d8ce372f47f774cffd882f88ea4e1e1ddc731", + "sha256:5451606823a5e73dfa621a89948096c6528e2896e40b39248295d3a0138d594f", + "sha256:57d75524cb1c5a374958a2eae3d84e1929bb971204cc9d52213fb8589183fc19", + "sha256:5aa2687b9935da3ed89c5dbed5234576589dd28d0bf7cd237501ccfbdf1ad608", + "sha256:5e1a106f8c023d57a2a903e771228bf5c5b27b5d692088f457acacd3b54511e4", + "sha256:65f81a25805f3659b48126b5053d9e823d3215e4a63730b5e1671852a1705921", + "sha256:6c62eea7f607f122069b9bad3f99489ddca1a5173bef8a0c75555d7488b6f725", + "sha256:6e5c2f74e5df33479b5cd4e97a9104c511518fbd979aa9b8f6aec18b2e9ecae7", + "sha256:709559a1db68a9abc3b2c8672c4badf1614f3b440b3ab326d86a5c0491eafae3", + "sha256:744d977daa4becb9fc59135e75c069f8d301a87d64f88f1e602a9ecf51e77b27", + "sha256:796a5a9ad36fa3a782375db8f4241ab02a091308eb079746bc0f874c9b998318", + "sha256:81929ed0fa7a5713fcdd8b2e6f73697d3b4c4816d090dd34ff937c20fa90e8ab", + "sha256:81b433bbeaf35728dad619afc002db9b189e45eebe2cd676effe1fb93fef2b9c", + "sha256:8503517c44c18d1030d666cb70aaac1cc8913608816e06742498833b128488b7", + "sha256:85764fb15a2ad994e708258bb4ed8290d1305c62a4e1ef07c414356a24fcfbf8", + "sha256:886cc81fdb4c6903a3bb0464047c25a6d1016fef77bb97949817d0c0d79f9e04", + "sha256:89728678c5ca5abd610aee148c199ac1afb16e19844401ca97d43dc548a354eb", + "sha256:8dfbb25dffc4c3dd9371d8ab456ca81beeaf6f9e1c2119f179392f0dc1ab7695", + "sha256:978d8311674b05a8f7ff2ea6c6bce5d8b45a0cb09d4c5793e0318f448613ea65", + "sha256:adccd93a2fa937a27aae826d33e3bfa5edf9aa672376a4852d23a7cd67a2e5b7", + "sha256:bcc12db731858abda693cecdb3bdc9e6d4bd200213f49d224fe22df82687bdd6", + "sha256:c033fa32bab91dc98ca59d0cf23bb876454e2bb02cbe592d5023138778f70030", + "sha256:c0c804d60492a0aad7f5b2bb1862f4548b990049e27e828391ff2bf6f7199998", + "sha256:c24fa02f7ed23ae514460a22c57eca8f530dbfa50b1cfdbf4f37c05b5309cc39", + "sha256:ca66d980469cb623b1759bdd6e9fd97d4e33a9fad5b33771ced24d0cb24df67e", + "sha256:cb18899127278058bcc09e7b9966d41a5a43740b5bb8dcba401bd983f82e885b", + "sha256:cc1d2f2fd48ba1e0620554fe5bc44d3e8f5d4185c8c109c7fbdf5af2792cfad2", + "sha256:d85495cef541729a70cdddbbf3e6b903421bc1af3e8e3a9a72a06751f33b7c39", + "sha256:d8da7c3dd67bcd93f15618938f43ed0995982eb38973023d46d4646c4283ad65", + "sha256:dc54f76ac18073bcecffb98d93f03ed6b81a92ef91b5d3b135dcc81d55a724c7", + "sha256:e756d688cb03fd07de0fffad475649b03cb89bee696c98ce508b17c11a03f95c", + "sha256:e7cc1ffcc230f568549fc56670bcf3df1884c30bd652c5da8138199c8c76dae0", + "sha256:e8fd15fc5085ab4cca74cb91fe0a4263b1f32e4420761ddae531ad60934c2119", + "sha256:f006e323874ffd0b0b816d8c6a8e7f9a73d55ab3b8c3f72b752b226d0e3ac83d", + "sha256:f0ebb7204f063fad87fc0a0e4ff4a2ff40b2a226e4ba1b7e34bf4b79bf97cd86", + "sha256:f1b9e5962656f2734c2b285a8745358ecb4e4efbadd00208c80a389227ec61ff", + "sha256:f23634f9e5adb51b2a77766dac217063e764337fbc816aa8ad9aaebcd4397fd3", + "sha256:f7b8013c6c066609577d910d1a2a077021727af07b6fab0ee22c2f901f22352a", + "sha256:f8a5d6cd147acecc2603fbd382fed6c46f474cccfcf69ea32582e033fb54dcfe", + "sha256:f965bbf3235b01c776115ab18f092a95aa74c271a52577bcb0563e85738fd618", + "sha256:fedc2cbd1baed37474b1924c331b97bdff611d762c196fac1a9b71e67b813b1b" + ], + "markers": "python_version >= '3.11'", + "version": "==1.16.1" + }, + "setuptools": { + "hashes": [ + "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", + "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c" + ], + "markers": "python_version >= '3.9'", + "version": "==80.9.0" + }, + "six": { + "hashes": [ + "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", + "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "version": "==1.17.0" + }, + "typing-extensions": { + "hashes": [ + "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", + "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76" + ], + "markers": "python_version >= '3.9'", + "version": "==4.14.1" + }, + "tzdata": { + "hashes": [ + "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", + "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9" + ], + "markers": "python_version >= '2'", + "version": "==2025.2" + }, + "urllib3": { + "hashes": [ + "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", + "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc" + ], + "markers": "python_version >= '3.9'", + "version": "==2.5.0" + }, + "werkzeug": { + "hashes": [ + "sha256:1bc0c2310d2fbb07b1dd1105eba2f7af72f322e1e455f2f93c993bee8c8a5f17", + "sha256:a8dd59d4de28ca70471a34cba79bed5f7ef2e036a76b3ab0835474246eb41f8d" + ], + "markers": "python_version >= '3.8'", + "version": "==3.0.6" + }, + "zipp": { + "hashes": [ + "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", + "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166" + ], + "markers": "python_version >= '3.9'", + "version": "==3.23.0" + } + }, + "develop": { + "bump2version": { + "hashes": [ + "sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410", + "sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6" + ], + "index": "pypi", + "markers": "python_version >= '3.5'", + "version": "==1.0.1" + }, + "certifi": { + "hashes": [ + "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", + "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995" + ], + "markers": "python_version >= '3.7'", + "version": "==2025.7.14" + }, + "charset-normalizer": { + "hashes": [ + "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4", + "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45", + "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", + "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", + "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", + "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", + "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d", + "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", + "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184", + "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", + "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b", + "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64", + "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", + "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", + "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", + "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344", + "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58", + "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", + "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471", + "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", + "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", + "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836", + "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", + "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", + "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", + "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1", + "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01", + "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", + "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58", + "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", + "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", + "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2", + "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a", + "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597", + "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", + "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5", + "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb", + "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f", + "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", + "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", + "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", + "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", + "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7", + "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7", + "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455", + "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", + "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4", + "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", + "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3", + "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", + "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", + "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", + "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", + "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", + "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", + "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", + "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12", + "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa", + "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", + "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", + "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f", + "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", + "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", + "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5", + "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02", + "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", + "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", + "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e", + "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", + "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", + "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", + "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", + "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681", + "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba", + "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", + "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a", + "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", + "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", + "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", + "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", + "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027", + "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7", + "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518", + "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", + "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", + "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", + "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", + "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da", + "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", + "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f", + "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", + "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f" + ], + "markers": "python_version >= '3.7'", + "version": "==3.4.2" + }, + "cleanpy": { + "hashes": [ + "sha256:9ddfa7ce80dd888b597a8b0bfeea3b69567839b6f41b775a4f76f46914d5170e", + "sha256:c60589d5da68527ca0c9151e28ed56fffae69df4ab6c9bfd8c1cf1d9e76a09b8" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==0.5.1" + }, + "idna": { + "hashes": [ + "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", + "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" + ], + "markers": "python_version >= '3.6'", + "version": "==3.10" + }, + "iniconfig": { + "hashes": [ + "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", + "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760" + ], + "markers": "python_version >= '3.8'", + "version": "==2.1.0" + }, + "numpy": { + "hashes": [ + "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", + "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", + "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", + "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", + "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", + "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", + "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", + "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", + "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", + "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", + "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", + "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", + "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", + "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", + "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", + "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", + "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", + "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", + "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", + "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", + "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", + "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", + "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", + "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", + "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", + "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", + "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", + "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", + "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", + "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", + "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", + "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", + "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", + "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", + "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", + "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", + "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", + "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", + "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", + "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", + "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", + "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", + "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", + "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", + "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", + "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", + "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", + "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", + "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", + "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", + "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", + "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", + "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", + "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", + "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==2.2.6" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + ], + "markers": "python_version >= '3.8'", + "version": "==25.0" + }, + "pandas": { + "hashes": [ + "sha256:025e92411c16cbe5bb2a4abc99732a6b132f439b8aab23a59fa593eb00704232", + "sha256:09e3b1587f0f3b0913e21e8b32c3119174551deb4a4eba4a89bc7377947977e7", + "sha256:0a95b9ac964fe83ce317827f80304d37388ea77616b1425f0ae41c9d2d0d7bb2", + "sha256:0f951fbb702dacd390561e0ea45cdd8ecfa7fb56935eb3dd78e306c19104b9b0", + "sha256:1b916a627919a247d865aed068eb65eb91a344b13f5b57ab9f610b7716c92de1", + "sha256:1c78cf43c8fde236342a1cb2c34bcff89564a7bfed7e474ed2fffa6aed03a956", + "sha256:1d12f618d80379fde6af007f65f0c25bd3e40251dbd1636480dfffce2cf1e6da", + "sha256:22c2e866f7209ebc3a8f08d75766566aae02bcc91d196935a1d9e59c7b990ac9", + "sha256:2323294c73ed50f612f67e2bf3ae45aea04dce5690778e08a09391897f35ff88", + "sha256:2b0540963d83431f5ce8870ea02a7430adca100cec8a050f0811f8e31035541b", + "sha256:2ba6aff74075311fc88504b1db890187a3cd0f887a5b10f5525f8e2ef55bfdb9", + "sha256:2eb789ae0274672acbd3c575b0598d213345660120a257b47b5dafdc618aec83", + "sha256:2f4d6feeba91744872a600e6edbbd5b033005b431d5ae8379abee5bcfa479fab", + "sha256:342e59589cc454aaff7484d75b816a433350b3d7964d7847327edda4d532a2e3", + "sha256:3462c3735fe19f2638f2c3a40bd94ec2dc5ba13abbb032dd2fa1f540a075509d", + "sha256:3583d348546201aff730c8c47e49bc159833f971c2899d6097bce68b9112a4f1", + "sha256:4645f770f98d656f11c69e81aeb21c6fca076a44bed3dcbb9396a4311bc7f6d8", + "sha256:4d544806b485ddf29e52d75b1f559142514e60ef58a832f74fb38e48d757b299", + "sha256:56a342b231e8862c96bdb6ab97170e203ce511f4d0429589c8ede1ee8ece48b8", + "sha256:5db9637dbc24b631ff3707269ae4559bce4b7fd75c1c4d7e13f40edc42df4444", + "sha256:689968e841136f9e542020698ee1c4fbe9caa2ed2213ae2388dc7b81721510d3", + "sha256:6de8547d4fdb12421e2d047a2c446c623ff4c11f47fddb6b9169eb98ffba485a", + "sha256:6f3bf5ec947526106399a9e1d26d40ee2b259c66422efdf4de63c848492d91bb", + "sha256:782647ddc63c83133b2506912cc6b108140a38a37292102aaa19c81c83db2928", + "sha256:7dcb79bf373a47d2a40cf7232928eb7540155abbc460925c2c96d2d30b006eb4", + "sha256:8dfc17328e8da77be3cf9f47509e5637ba8f137148ed0e9b5241e1baf526e20a", + "sha256:9026bd4a80108fac2239294a15ef9003c4ee191a0f64b90f170b40cfb7cf2d22", + "sha256:911580460fc4884d9b05254b38a6bfadddfcc6aaef856fb5859e7ca202e45275", + "sha256:98bcc8b5bf7afed22cc753a28bc4d9e26e078e777066bc53fac7904ddef9a678", + "sha256:9b7ff55f31c4fcb3e316e8f7fa194566b286d6ac430afec0d461163312c5841e", + "sha256:ac942bfd0aca577bef61f2bc8da8147c4ef6879965ef883d8e8d5d2dc3e744b8", + "sha256:b3cd4273d3cb3707b6fffd217204c52ed92859533e31dc03b7c5008aa933aaab", + "sha256:b4b0de34dc8499c2db34000ef8baad684cfa4cbd836ecee05f323ebfba348c7d", + "sha256:ca7ed14832bce68baef331f4d7f294411bed8efd032f8109d690df45e00c4679", + "sha256:cd05b72ec02ebfb993569b4931b2e16fbb4d6ad6ce80224a3ee838387d83a191", + "sha256:dd71c47a911da120d72ef173aeac0bf5241423f9bfea57320110a978457e069e", + "sha256:e5635178b387bd2ba4ac040f82bc2ef6e6b500483975c4ebacd34bec945fda12", + "sha256:e6723a27ad7b244c0c79d8e7007092d7c8f0f11305770e2f4cd778b3ad5f9f85", + "sha256:ec6c851509364c59a5344458ab935e6451b31b818be467eb24b0fe89bd05b6b9", + "sha256:fe37e757f462d31a9cd7580236a82f353f5713a80e059a29753cf938c6775d96", + "sha256:fe67dc676818c186d5a3d5425250e40f179c2a89145df477dd82945eaea89e97", + "sha256:fe7317f578c6a153912bd2292f02e40c1d8f253e93c599e82620c7f69755c74f" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==2.3.1" + }, + "pluggy": { + "hashes": [ + "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", + "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" + ], + "markers": "python_version >= '3.9'", + "version": "==1.6.0" + }, + "pygments": { + "hashes": [ + "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", + "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" + ], + "markers": "python_version >= '3.8'", + "version": "==2.19.2" + }, + "pytest": { + "hashes": [ + "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", + "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==8.4.1" + }, + "python-dateutil": { + "hashes": [ + "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", + "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "version": "==2.9.0.post0" + }, + "pytz": { + "hashes": [ + "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", + "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00" + ], + "version": "==2025.2" + }, + "requests": { + "hashes": [ + "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", + "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==2.32.4" + }, + "six": { + "hashes": [ + "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", + "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "version": "==1.17.0" + }, + "tzdata": { + "hashes": [ + "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", + "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9" + ], + "markers": "python_version >= '2'", + "version": "==2025.2" + }, + "urllib3": { + "hashes": [ + "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", + "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc" + ], + "markers": "python_version >= '3.9'", + "version": "==2.5.0" + } + } +} From ffb3d4fa1a85e5d60629de9503456cd7788af6f6 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 31 Jul 2025 13:11:52 +1000 Subject: [PATCH 16/91] build: added Dockerfile --- Dockerfile | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..03e5279c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +# Use the official lightweight Python image. +# https://hub.docker.com/_/python +FROM python:3.11-slim + +RUN apt-get update \ +&& apt-get install gcc -y \ +&& apt-get clean + +ENV APP_HOME /app +WORKDIR $APP_HOME + +COPY . ./ + +# Install production dependencies. +RUN pip install --upgrade pip +RUN pip install -r requirements.txt + +EXPOSE 8080 + +CMD python main.py \ No newline at end of file From 7102dbbe8ebff6f80071b8f383d3e0a7e1e80758 Mon Sep 17 00:00:00 2001 From: federico Tartarini Date: Tue, 5 Dec 2023 14:56:40 +1100 Subject: [PATCH 17/91] build: trying to build from source --- .dockerignore | 296 ----------------------- .github/workflows/cypress.yml | 5 +- .github/workflows/python.yml | 7 +- .gitignore | 3 - .python-version | 1 + docs/contributing/run-project-locally.md | 4 +- 6 files changed, 8 insertions(+), 308 deletions(-) delete mode 100644 .dockerignore create mode 100644 .python-version diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 90a83e9d..00000000 --- a/.dockerignore +++ /dev/null @@ -1,296 +0,0 @@ -README.md -LICENSE -Procfile -Procfile.windows -*.pyo -*.pyd -__pycache__ -.pytest_cache -file_system_store -.git -docs -test - -assets/data/Region*.kml - -venv -.idea -cache-directory -cloud-run-key-file.json - -# Created by .ignore support plugin (hsz.mobi) -### VisualStudioCode template -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -### JetBrains template -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/modules.xml -# .idea/*.iml -# .idea/modules - -# CMake -cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client -.idea/httpRequests -### Node template -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env - -# parcel-bundler cache (https://parceljs.org/) -.cache - -# next.js build output -.next - -# nuxt.js build output -.nuxt - -# vuepress build output -.vuepress/dist - -# Serverless directories -.serverless -### Python template -# Byte-compiled / optimized / DLL files -__pycache__/ -*/__pycache__/* -*.pyc -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -nosetests.xml -coverage.xml -*.cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: -local_settings.py -db.sqlite3 - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# pyenv -.python-version - -# celery beat schedule file -celerybeat-schedule - -# SageMath parsed files -*.sage.py - -# Environments -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -### SublimeText template -# Cache files for Sublime Text -*.tmlanguage.cache -*.tmPreferences.cache -*.stTheme.cache - -# Workspace files are user-specific -*.sublime-workspace - -# Project files should be checked into the repository, unless a significant -# proportion of contributors will probably not be using Sublime Text -# *.sublime-project - -# SFTP configuration file -sftp-config.json - -# Package control specific files -Package Control.last-run -Package Control.ca-list -Package Control.ca-bundle -Package Control.system-ca-bundle -Package Control.cache/ -Package Control.ca-certs/ -Package Control.merged-ca-bundle -Package Control.user-ca-bundle -oscrypto-ca-bundle.crt -bh_unicode_properties.cache - -# Sublime-github package stores a github token in this file -# https://packagecontrol.io/packages/sublime-github -GitHub.sublime-settings \ No newline at end of file diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index f1810e28..475691b0 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -14,12 +14,11 @@ jobs: - name: Build Clima run: |- - pip install pipenv - pipenv install + pip install -r requirements.txt - name: Start Clima run: |- - pipenv run python main.py & + python main.py & - name: Setup Node uses: actions/setup-node@v4 diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 919693a6..1f76d6b6 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -12,14 +12,13 @@ jobs: with: python-version: '3.11' - - name: Build Clima + - name: Install run: |- - pip install pipenv - pipenv install --dev + pip install -r requirements.txt - name: Test Clima run: |- - pipenv run python -m pytest + python -m pytest - name: Run Black # TODO: Add to dev dependencies: Adding it right now diff --git a/.gitignore b/.gitignore index 33964f3b..c35b2a01 100644 --- a/.gitignore +++ b/.gitignore @@ -220,9 +220,6 @@ target/ # Jupyter Notebook .ipynb_checkpoints -# pyenv -.python-version - # celery beat schedule file celerybeat-schedule diff --git a/.python-version b/.python-version new file mode 100644 index 00000000..7c7a975f --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.10 \ No newline at end of file diff --git a/docs/contributing/run-project-locally.md b/docs/contributing/run-project-locally.md index ba9477fd..b6222b7b 100644 --- a/docs/contributing/run-project-locally.md +++ b/docs/contributing/run-project-locally.md @@ -90,7 +90,7 @@ First make sure you that: * that you are logged in with the right account * you have updated the Pipfile.lock. -```bash +```text gcloud components update gcloud auth list pipenv lock @@ -109,4 +109,4 @@ gcloud run deploy clima --image us-docker.pkg.dev/clima-316917/gcr.io/clima --pl gcloud builds submit --tag us-docker.pkg.dev/clima-316917/gcr.io/clima-test --project=clima-316917 gcloud run deploy clima-test --image us-docker.pkg.dev/clima-316917/gcr.io/clima-test --platform managed --project=clima-316917 --allow-unauthenticated --region=us-central1 --memory=4Gi --concurrency=80 --cpu=2 -``` \ No newline at end of file +``` From 1b7618249e0628318199f4092d958c8e37b091ca Mon Sep 17 00:00:00 2001 From: t2the0bi <49641232+t2the0bi@users.noreply.github.com> Date: Sat, 4 May 2024 14:14:47 -0700 Subject: [PATCH 18/91] Restructure app responding to #151 --- .gitignore | 4 +- app.py | 1 + main.py | 115 +++++++-------- my_project/page_changelog/__init__.py | 0 my_project/tab_data_explorer/__init__.py | 0 .../tab_natural_ventilation/__init__.py | 0 my_project/tab_outdoor_comfort/__init__.py | 0 my_project/tab_psy_chart/__init__.py | 0 my_project/tab_select/__init__.py | 0 my_project/tab_summary/__init__.py | 0 my_project/tab_sun/__init__.py | 0 my_project/tab_t_rh/__init__.py | 0 my_project/tab_under_construction/__init__.py | 0 .../tab_under_construction/construction.py | 12 -- my_project/tab_wind/__init__.py | 0 .../app_changelog.py => pages/changelog.py | 4 +- .../app_data_explorer.py => pages/explorer.py | 37 ++--- {my_project => pages/lib}/__init__.py | 0 .../lib}/charts_data_explorer.py | 2 +- .../lib}/charts_summary.py | 0 .../tab_sun => pages/lib}/charts_sun.py | 2 +- {my_project => pages/lib}/extract_df.py | 8 +- {my_project => pages/lib}/global_scheme.py | 0 .../lib}/import_one_building_files.py | 5 +- {my_project => pages/lib}/layout.py | 0 {my_project => pages/lib}/template_graphs.py | 21 +-- {my_project => pages/lib}/utils.py | 16 +-- .../natural_ventilation.py | 131 ++++++++++-------- {my_project => pages}/not_found_404.py | 2 +- .../outdoor.py | 34 ++--- .../app_psy_chart.py => pages/psy-chart.py | 36 +++-- .../app_select.py => pages/select.py | 94 +++++-------- .../app_summary.py => pages/summary.py | 79 +++++++---- my_project/tab_sun/app_sun.py => pages/sun.py | 109 +++++++++------ .../tab_t_rh/app_t_rh.py => pages/t_rh.py | 23 +-- .../tab_wind/app_wind.py => pages/wind.py | 31 +++-- 36 files changed, 411 insertions(+), 355 deletions(-) delete mode 100644 my_project/page_changelog/__init__.py delete mode 100644 my_project/tab_data_explorer/__init__.py delete mode 100644 my_project/tab_natural_ventilation/__init__.py delete mode 100644 my_project/tab_outdoor_comfort/__init__.py delete mode 100644 my_project/tab_psy_chart/__init__.py delete mode 100644 my_project/tab_select/__init__.py delete mode 100644 my_project/tab_summary/__init__.py delete mode 100644 my_project/tab_sun/__init__.py delete mode 100644 my_project/tab_t_rh/__init__.py delete mode 100644 my_project/tab_under_construction/__init__.py delete mode 100644 my_project/tab_under_construction/construction.py delete mode 100644 my_project/tab_wind/__init__.py rename my_project/page_changelog/app_changelog.py => pages/changelog.py (71%) rename my_project/tab_data_explorer/app_data_explorer.py => pages/explorer.py (98%) rename {my_project => pages/lib}/__init__.py (100%) rename {my_project/tab_data_explorer => pages/lib}/charts_data_explorer.py (98%) rename {my_project/tab_summary => pages/lib}/charts_summary.py (100%) rename {my_project/tab_sun => pages/lib}/charts_sun.py (99%) rename {my_project => pages/lib}/extract_df.py (99%) rename {my_project => pages/lib}/global_scheme.py (100%) rename {my_project => pages/lib}/import_one_building_files.py (98%) rename {my_project => pages/lib}/layout.py (100%) rename {my_project => pages/lib}/template_graphs.py (98%) rename {my_project => pages/lib}/utils.py (99%) rename my_project/tab_natural_ventilation/app_natural_ventilation.py => pages/natural_ventilation.py (90%) rename {my_project => pages}/not_found_404.py (94%) rename my_project/tab_outdoor_comfort/app_outdoor_comfort.py => pages/outdoor.py (97%) rename my_project/tab_psy_chart/app_psy_chart.py => pages/psy-chart.py (97%) rename my_project/tab_select/app_select.py => pages/select.py (87%) rename my_project/tab_summary/app_summary.py => pages/summary.py (95%) rename my_project/tab_sun/app_sun.py => pages/sun.py (83%) rename my_project/tab_t_rh/app_t_rh.py => pages/t_rh.py (94%) rename my_project/tab_wind/app_wind.py => pages/wind.py (98%) diff --git a/.gitignore b/.gitignore index c35b2a01..5da7aa3f 100644 --- a/.gitignore +++ b/.gitignore @@ -276,4 +276,6 @@ bh_unicode_properties.cache # Sublime-github package stores a github token in this file # https://packagecontrol.io/packages/sublime-github -GitHub.sublime-settings \ No newline at end of file +GitHub.sublime-settings + +file_system_backend \ No newline at end of file diff --git a/app.py b/app.py index 66fd6a5f..2a2830b1 100644 --- a/app.py +++ b/app.py @@ -3,6 +3,7 @@ app = DashProxy( __name__, + use_pages=True, external_stylesheets=[dbc.themes.BOOTSTRAP], transforms=[ServersideOutputTransform()], suppress_callback_exceptions=True, diff --git a/main.py b/main.py index 502eb7d5..bb8379be 100644 --- a/main.py +++ b/main.py @@ -4,19 +4,7 @@ from dash.dependencies import Input, Output from app import app -from my_project.layout import banner, build_tabs, footer -from my_project.page_changelog.app_changelog import changelog -from my_project.tab_data_explorer.app_data_explorer import layout_data_explorer -from my_project.tab_natural_ventilation.app_natural_ventilation import ( - layout_natural_ventilation, -) -from my_project.tab_outdoor_comfort.app_outdoor_comfort import layout_outdoor_comfort -from my_project.tab_psy_chart.app_psy_chart import layout_psy_chart -from my_project.tab_select.app_select import layout_select -from my_project.tab_summary.app_summary import layout_summary -from my_project.tab_sun.app_sun import layout_sun -from my_project.tab_t_rh.app_t_rh import layout_t_rh -from my_project.tab_wind.app_wind import layout_wind +from pages.lib.layout import banner, build_tabs, footer, store server = app.server @@ -25,55 +13,74 @@ fluid=True, style={"padding": "0"}, children=[ - dcc.Location(id="url", refresh=False), + dcc.Location(id="url", refresh=False), # connected to callback below banner(), - html.Div(id="page-content"), + dbc.Nav( + [ + dbc.NavLink( + html.Div(page["name"], className="ms-2"), + href=page["path"], + active="exact", + style={'color':'white','text-align': 'center', 'height': '100%'} + ) + for page in dash.page_registry.values() if page["name"] not in ["404", "changelog"] + ], + pills=True, + justified=True, + style={'background-color': '#003262', + 'padding': '0.25rem 0.5rem', + 'justify-content': 'center', + 'align-items': 'center', + } + ), + html.Div(id="store-container", children=[store()]), + dash.page_container, footer(), ], ) -@app.callback( - dash.dependencies.Output("page-content", "children"), - [dash.dependencies.Input("url", "pathname")], -) -def display_page(pathname): - if pathname == "/": - return build_tabs() - elif pathname == "/changelog": - return html.Div(children=[changelog()]) +# @app.callback( +# dash.dependencies.Output("page-content", "children"), +# [dash.dependencies.Input("url", "pathname")], +# ) +# def display_page(pathname): +# if pathname == "/": +# return build_tabs() +# elif pathname == "/changelog": +# return html.Div(children=[changelog()]) -# Handle tab selection -@app.callback( - Output("tabs-content", "children"), - [ - Input("tabs", "value"), - Input("si-ip-radio-input", "value"), - ], -) -def render_content(tab, si_ip): - """Update the contents of the page depending on what tab the user selects.""" - if tab == "tab-select": - return layout_select() - elif tab == "tab-summary": - return layout_summary(si_ip) - elif tab == "tab-t-rh": - return layout_t_rh() - elif tab == "tab-sun": - return layout_sun(si_ip) - elif tab == "tab-wind": - return layout_wind() - elif tab == "tab-data-explorer": - return layout_data_explorer() - elif tab == "tab-outdoor-comfort": - return layout_outdoor_comfort() - elif tab == "tab-natural-ventilation": - return layout_natural_ventilation(si_ip) - elif tab == "tab-psy-chart": - return layout_psy_chart() - else: - return "404" +# # Handle tab selection +# @app.callback( +# Output("tabs-content", "children"), +# [ +# Input("tabs", "value"), +# Input("si-ip-radio-input", "value"), +# ], +# ) +# def render_content(tab, si_ip): +# """Update the contents of the page depending on what tab the user selects.""" +# if tab == "tab-select": +# return layout_select() +# elif tab == "tab-summary": +# return layout_summary(si_ip) +# elif tab == "tab-t-rh": +# return layout_t_rh() +# elif tab == "tab-sun": +# return layout_sun(si_ip) +# elif tab == "tab-wind": +# return layout_wind() +# elif tab == "tab-data-explorer": +# return layout_data_explorer() +# elif tab == "tab-outdoor-comfort": +# return layout_outdoor_comfort() +# elif tab == "tab-natural-ventilation": +# return layout_natural_ventilation(si_ip) +# elif tab == "tab-psy-chart": +# return layout_psy_chart() +# else: +# return "404" if __name__ == "__main__": diff --git a/my_project/page_changelog/__init__.py b/my_project/page_changelog/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/my_project/tab_data_explorer/__init__.py b/my_project/tab_data_explorer/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/my_project/tab_natural_ventilation/__init__.py b/my_project/tab_natural_ventilation/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/my_project/tab_outdoor_comfort/__init__.py b/my_project/tab_outdoor_comfort/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/my_project/tab_psy_chart/__init__.py b/my_project/tab_psy_chart/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/my_project/tab_select/__init__.py b/my_project/tab_select/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/my_project/tab_summary/__init__.py b/my_project/tab_summary/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/my_project/tab_sun/__init__.py b/my_project/tab_sun/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/my_project/tab_t_rh/__init__.py b/my_project/tab_t_rh/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/my_project/tab_under_construction/__init__.py b/my_project/tab_under_construction/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/my_project/tab_under_construction/construction.py b/my_project/tab_under_construction/construction.py deleted file mode 100644 index a9f724ff..00000000 --- a/my_project/tab_under_construction/construction.py +++ /dev/null @@ -1,12 +0,0 @@ -from dash import html - - -def construction(): - return html.Div( - className="container-col", - id="construction-container", - children=[ - html.H4("This tab is under construction."), - html.P("Come back soon to check it out!"), - ], - ) diff --git a/my_project/tab_wind/__init__.py b/my_project/tab_wind/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/my_project/page_changelog/app_changelog.py b/pages/changelog.py similarity index 71% rename from my_project/page_changelog/app_changelog.py rename to pages/changelog.py index e6b81ee7..fb90c27a 100644 --- a/my_project/page_changelog/app_changelog.py +++ b/pages/changelog.py @@ -1,8 +1,10 @@ +import dash import dash_bootstrap_components as dbc from dash import dcc +dash.register_page(__name__, path='/changelog', name='changelog') -def changelog(): +def layout(): """changelog page""" f = open("CHANGELOG.md", "r") diff --git a/my_project/tab_data_explorer/app_data_explorer.py b/pages/explorer.py similarity index 98% rename from my_project/tab_data_explorer/app_data_explorer.py rename to pages/explorer.py index 9090c523..edead0fc 100644 --- a/my_project/tab_data_explorer/app_data_explorer.py +++ b/pages/explorer.py @@ -1,9 +1,12 @@ +import dash +from dash import dcc, html import dash_bootstrap_components as dbc -from copy import deepcopy -from dash import dcc -from dash import html +from dash_extensions.enrich import Output, Input, State, callback from dash.exceptions import PreventUpdate -from my_project.utils import ( + +from copy import deepcopy + +from pages.lib.utils import ( generate_chart_name, generate_custom_inputs, generate_custom_inputs_explorer, @@ -16,7 +19,7 @@ dropdown, ) -from my_project.global_scheme import ( +from pages.lib.global_scheme import ( fig_config, dropdown_names, sun_cloud_tab_dropdown_names, @@ -26,14 +29,13 @@ container_col_center_one_of_three, mapping_dictionary, ) -from dash.dependencies import Input, Output, State -from my_project.tab_data_explorer.charts_data_explorer import ( +from pages.lib.charts_data_explorer import ( custom_heatmap, two_var_graph, three_var_graph, ) -from my_project.template_graphs import ( +from pages.lib.template_graphs import ( heatmap, yearly_profile, daily_profile, @@ -41,7 +43,8 @@ filter_df_by_month_and_hour, ) -from app import app +dash.register_page(__name__, name= 'Data Explorer', order=8) + explore_dropdown_names = {} explore_dropdown_names.update(deepcopy(dropdown_names)) @@ -604,7 +607,7 @@ def section_three(): ) -def layout_data_explorer(): +def layout(): """Return the contents of tab six." """ return html.Div( className="continer-col justify-center", @@ -612,7 +615,7 @@ def layout_data_explorer(): ) -@app.callback( +@callback( Output("yearly-explore", "children"), # Section One [ @@ -645,7 +648,7 @@ def update_tab_yearly(ts, var, global_local, df, meta, si_ip): ) -@app.callback( +@callback( Output("query-daily", "children"), [ Input("df-store", "modified_timestamp"), @@ -670,7 +673,7 @@ def update_tab_daily(ts, var, global_local, df, meta, si_ip): ) -@app.callback( +@callback( Output("query-heatmap", "children"), [ Input("df-store", "modified_timestamp"), @@ -695,7 +698,7 @@ def update_tab_heatmap(ts, var, global_local, df, meta, si_ip): ) -@app.callback( +@callback( [ Output("custom-heatmap", "children"), Output("custom-summary", "style"), @@ -808,7 +811,7 @@ def update_heatmap( ) -@app.callback( +@callback( [Output("three-var", "children"), Output("two-var", "children")], [ Input("df-store", "modified_timestamp"), @@ -900,7 +903,7 @@ def update_more_charts( ) -@app.callback( +@callback( Output("table-data-explorer", "children"), [ Input("df-store", "modified_timestamp"), @@ -939,4 +942,4 @@ def update_table( ] return summary_table_tmp_rh_tab( filtered_df[["month", "hour", dd_value, "month_names"]], dd_value, si_ip - ) + ) \ No newline at end of file diff --git a/my_project/__init__.py b/pages/lib/__init__.py similarity index 100% rename from my_project/__init__.py rename to pages/lib/__init__.py diff --git a/my_project/tab_data_explorer/charts_data_explorer.py b/pages/lib/charts_data_explorer.py similarity index 98% rename from my_project/tab_data_explorer/charts_data_explorer.py rename to pages/lib/charts_data_explorer.py index 9211e8c2..0b162943 100644 --- a/my_project/tab_data_explorer/charts_data_explorer.py +++ b/pages/lib/charts_data_explorer.py @@ -5,7 +5,7 @@ import math import plotly.express as px import plotly.graph_objects as go -from my_project.global_scheme import template, mapping_dictionary, month_lst +from pages.lib.global_scheme import template, mapping_dictionary, month_lst def custom_heatmap(df, global_local, var, time_filter_info, data_filter_info, si_ip): diff --git a/my_project/tab_summary/charts_summary.py b/pages/lib/charts_summary.py similarity index 100% rename from my_project/tab_summary/charts_summary.py rename to pages/lib/charts_summary.py diff --git a/my_project/tab_sun/charts_sun.py b/pages/lib/charts_sun.py similarity index 99% rename from my_project/tab_sun/charts_sun.py rename to pages/lib/charts_sun.py index ab5c5984..b08d970f 100644 --- a/my_project/tab_sun/charts_sun.py +++ b/pages/lib/charts_sun.py @@ -5,7 +5,7 @@ import numpy as np import pandas as pd import plotly.graph_objects as go -from my_project.global_scheme import ( +from pages.lib.global_scheme import ( template, mapping_dictionary, degrees_unit, diff --git a/my_project/extract_df.py b/pages/lib/extract_df.py similarity index 99% rename from my_project/extract_df.py rename to pages/lib/extract_df.py index e8cc8cb0..d3f96ec4 100644 --- a/my_project/extract_df.py +++ b/pages/lib/extract_df.py @@ -1,11 +1,11 @@ import io import json +import math import re import zipfile from datetime import timedelta from urllib.request import Request, urlopen -import math import numpy as np import pandas as pd import requests @@ -16,9 +16,11 @@ from pythermalcomfort.models import utci from pythermalcomfort.utilities import running_mean_outdoor_temperature -from my_project.global_scheme import month_lst +from pages.lib.global_scheme import month_lst +from pages.lib.utils import code_timer +@code_timer def get_data(source_url): """Return a list of the data from api call.""" if source_url[-3:] == "zip" or source_url[-3:] == "all": @@ -44,6 +46,7 @@ def get_data(source_url): return None +@code_timer def get_location_info(lst, file_name): """Extract and clean the data. Return a pandas data from a url.""" meta = lst[0].strip().replace("\\r", "").split(",") @@ -69,6 +72,7 @@ def get_location_info(lst, file_name): return location_info +@code_timer def create_df(lst, file_name): """Extract and clean the data. Return a pandas data from a url.""" meta = lst[0].strip().replace("\\r", "").split(",") diff --git a/my_project/global_scheme.py b/pages/lib/global_scheme.py similarity index 100% rename from my_project/global_scheme.py rename to pages/lib/global_scheme.py diff --git a/my_project/import_one_building_files.py b/pages/lib/import_one_building_files.py similarity index 98% rename from my_project/import_one_building_files.py rename to pages/lib/import_one_building_files.py index 415f77d0..32a57c5e 100644 --- a/my_project/import_one_building_files.py +++ b/pages/lib/import_one_building_files.py @@ -1,8 +1,9 @@ -import re - import pandas as pd +from pages.lib.utils import code_timer +import re +@code_timer def import_kml_files(file_name): with open(f"./assets/data/{file_name}", encoding="utf8", errors="ignore") as data: text = data.read() diff --git a/my_project/layout.py b/pages/lib/layout.py similarity index 100% rename from my_project/layout.py rename to pages/lib/layout.py diff --git a/my_project/template_graphs.py b/pages/lib/template_graphs.py similarity index 98% rename from my_project/template_graphs.py rename to pages/lib/template_graphs.py index 6c07e9df..754f3dd8 100644 --- a/my_project/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -1,13 +1,15 @@ -import dash_bootstrap_components as dbc +from math import ceil, floor + import numpy as np import pandas as pd import plotly.graph_objects as go -from math import ceil, floor from plotly.subplots import make_subplots - -from my_project.global_scheme import mapping_dictionary +from pages.lib.global_scheme import mapping_dictionary +import dash_bootstrap_components as dbc from .global_scheme import month_lst, template, tight_margins -from .utils import determine_month_and_hour_filter + + +from .utils import code_timer, determine_month_and_hour_filter def violin(df, var, global_local, si_ip): @@ -79,6 +81,7 @@ def violin(df, var, global_local, si_ip): return fig +@code_timer def yearly_profile(df, var, global_local, si_ip): """Return yearly profile figure based on the 'var' col.""" var_unit = mapping_dictionary[var][si_ip]["unit"] @@ -231,6 +234,7 @@ def yearly_profile(df, var, global_local, si_ip): return fig +# @code_timer def daily_profile(df, var, global_local, si_ip): """Return the daily profile based on the 'var' col.""" var_name = mapping_dictionary[var]["name"] @@ -311,6 +315,7 @@ def daily_profile(df, var, global_local, si_ip): return fig +# @code_timer def heatmap_with_filter( df, var, @@ -502,14 +507,14 @@ def wind_rose(df, title, month, hour, labels, si_ip): df["wind_dir"], bins=dir_bins, labels=dir_labels, right=False ) ) - .replace({"WindDir_bins": {360: 0}}) - .groupby(by=["WindSpd_bins", "WindDir_bins"]) + .ser.cat.rename_categories({"WindDir_bins": {360: 0}}) + .groupby(by=["WindSpd_bins", "WindDir_bins"], observed=False) .size() .unstack(level="WindSpd_bins") .fillna(0) .assign(calm=lambda df: calm_count / df.shape[0]) .sort_index(axis=1) - .applymap(lambda x: x / total_count * 100) + .map(lambda x: x / total_count * 100) ) fig = go.Figure() for i, col in enumerate(rose.columns): diff --git a/my_project/utils.py b/pages/lib/utils.py similarity index 99% rename from my_project/utils.py rename to pages/lib/utils.py index f528c511..3547cc7e 100644 --- a/my_project/utils.py +++ b/pages/lib/utils.py @@ -1,16 +1,14 @@ -import copy import functools -import json import time - -import dash_bootstrap_components as dbc +from pages.lib.global_scheme import fig_config, mapping_dictionary import pandas as pd +import json +from pandas import json_normalize +from dash import html, dash_table, dcc +import dash_bootstrap_components as dbc import plotly.express as px +import copy import requests -from dash import html, dash_table, dcc -from pandas import json_normalize - -from my_project.global_scheme import fig_config, mapping_dictionary def code_timer(func): @@ -216,7 +214,7 @@ def plot_location_epw_files(): height=500, ) try: - print(requests.get("https://climate.onebuilding.org", timeout=6)) + print(requests.get("https://climate.onebuilding.org", timeout=2)) df_one_building = pd.read_csv( "./assets/data/one_building.csv", compression="gzip" diff --git a/my_project/tab_natural_ventilation/app_natural_ventilation.py b/pages/natural_ventilation.py similarity index 90% rename from my_project/tab_natural_ventilation/app_natural_ventilation.py rename to pages/natural_ventilation.py index 70f3d280..b3c6305c 100644 --- a/my_project/tab_natural_ventilation/app_natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -1,10 +1,15 @@ +import dash +from dash import dcc, html +import dash_bootstrap_components as dbc +from dash_extensions.enrich import Output, Input, State, callback + +import numpy as np import math import json -from dash import dcc -import dash_bootstrap_components as dbc -from dash import html import plotly.graph_objects as go -from my_project.global_scheme import ( + + +from pages.lib.global_scheme import ( template, mapping_dictionary, tight_margins, @@ -12,11 +17,10 @@ container_row_center_full, container_col_center_one_of_three, ) -from dash.dependencies import Input, Output, State -import numpy as np -from my_project.template_graphs import filter_df_by_month_and_hour -from my_project.utils import ( +from pages.lib.template_graphs import filter_df_by_month_and_hour + +from pages.lib.utils import ( title_with_tooltip, generate_chart_name, generate_units_degree, @@ -25,10 +29,24 @@ determine_month_and_hour_filter, ) -from app import app +dash.register_page(__name__, name= 'Natural Ventilation', order=6) + + +def layout(): + return html.Div( + className="container-col", + id='main-nv-section', + children=[ + # + ] + ) -def layout_natural_ventilation(si_ip): +@callback( + Output('main-nv-section', 'children'), + [Input('si-ip-radio-input', 'value')] +) +def update_layout(si_ip): if si_ip == "ip": tdb_set_min = 50 tdb_set_max = 75 @@ -37,55 +55,54 @@ def layout_natural_ventilation(si_ip): tdb_set_min = 10 tdb_set_max = 24 dpt_set = 16 - - return html.Div( - className="container-col", - children=[ - inputs_tab(tdb_set_min, tdb_set_max, dpt_set), - dcc.Loading( - html.Div( - id="nv-heatmap-chart", - style={"marginTop": "1rem"}, - ), - type="circle", - ), + + return [ + inputs_tab(tdb_set_min, tdb_set_max, dpt_set), + dcc.Loading( html.Div( - className="container-row align-center justify-center", - children=[ - dbc.Checklist( - options=[ - {"label": "", "value": 1}, - ], - value=[1], - id="switches-input", - switch=True, - style={ - "padding": "1rem", - "marginTop": "1rem", - "marginRight": "-2rem", - }, - ), - html.Div( - children=title_with_tooltip( - text="Normalize data", - tooltip_text=( - "If normalized is enabled it calculates the % " - "time otherwise it calculates the total number of hours" - ), - id_button="nv_normalize", - ), - ), - ], + id="nv-heatmap-chart", + style={"marginTop": "1rem"}, ), - dcc.Loading( + type="circle", + ), + html.Div( + className="container-row align-center justify-center", + children=[ + dbc.Checklist( + options=[ + {"label": "", "value": 1}, + ], + value=[1], + id="switches-input", + switch=True, + style={ + "padding": "1rem", + "marginTop": "1rem", + "marginRight": "-2rem", + }, + ), html.Div( - id="nv-bar-chart", - style={"marginTop": "1rem"}, + children=title_with_tooltip( + text="Normalize data", + tooltip_text=( + "If normalized is enabled it calculates the % " + "time otherwise it calculates the total number of hours" + ), + id_button="nv_normalize", + ), ), - type="circle", + ], + ), + dcc.Loading( + html.Div( + id="nv-bar-chart", + style={"marginTop": "1rem"}, ), - ], - ) + type="circle", + ), + ] + + def inputs_tab(t_min, t_max, d_set): @@ -254,7 +271,7 @@ def inputs_tab(t_min, t_max, d_set): ) -@app.callback( +@callback( Output("nv-heatmap-chart", "children"), [ Input("df-store", "modified_timestamp"), @@ -421,7 +438,7 @@ def nv_heatmap( ) -@app.callback( +@callback( Output("nv-bar-chart", "children"), [ Input("df-store", "modified_timestamp"), @@ -585,7 +602,7 @@ def nv_bar_chart( ) -@app.callback( +@callback( Output("nv-dpt-filter", "disabled"), Input("enable-condensation", "value") ) def enable_disable_button_data_filter(state_checklist): diff --git a/my_project/not_found_404.py b/pages/not_found_404.py similarity index 94% rename from my_project/not_found_404.py rename to pages/not_found_404.py index 9e077fec..84d92fa4 100644 --- a/my_project/not_found_404.py +++ b/pages/not_found_404.py @@ -3,7 +3,7 @@ from dash_iconify import DashIconify from dash_extensions import Lottie -dash.register_page(__name__) +dash.register_page(__name__, name= '404') layout = [ dmc.Title("I could not find the page you are currently looking for", order=4), diff --git a/my_project/tab_outdoor_comfort/app_outdoor_comfort.py b/pages/outdoor.py similarity index 97% rename from my_project/tab_outdoor_comfort/app_outdoor_comfort.py rename to pages/outdoor.py index aab6edfa..93f7486a 100644 --- a/my_project/tab_outdoor_comfort/app_outdoor_comfort.py +++ b/pages/outdoor.py @@ -1,21 +1,24 @@ -from dash import dcc -from dash import html +import dash +from dash import dcc, html import dash_bootstrap_components as dbc -from my_project.global_scheme import ( +from dash_extensions.enrich import Output, Input, State, callback + +import numpy as np + +from pages.lib.global_scheme import ( outdoor_dropdown_names, tight_margins, month_lst, container_row_center_full, container_col_center_one_of_three, ) -from my_project.utils import dropdown -from dash.dependencies import Input, Output, State -from my_project.template_graphs import ( +from pages.lib.template_graphs import ( heatmap_with_filter, thermal_stress_stacked_barchart, ) -from my_project.utils import ( +from pages.lib.utils import ( + dropdown, generate_chart_name, generate_units_degree, generate_units, @@ -24,8 +27,7 @@ ) -import numpy as np -from app import app +dash.register_page(__name__, name= 'Outdoor Comfort', order=7) def inputs_outdoor_comfort(): @@ -196,7 +198,7 @@ def outdoor_comfort_chart(): ) -def layout_outdoor_comfort(): +def layout(): return ( dcc.Loading( type="circle", @@ -208,7 +210,7 @@ def layout_outdoor_comfort(): ) -@app.callback( +@callback( Output("outdoor-comfort-output", "children"), [ Input("df-store", "modified_timestamp"), @@ -240,7 +242,7 @@ def update_outdoor_comfort_output(ts, df): return f"The Best Weather Condition is: {', '.join(colsWithTheHighestNumberOfZero)}" -@app.callback( +@callback( Output("utci-heatmap", "children"), [ Input("df-store", "modified_timestamp"), @@ -290,7 +292,7 @@ def update_tab_utci_value( ) -@app.callback( +@callback( Output("image-selection", "children"), Input("tab7-dropdown", "value"), ) @@ -307,7 +309,7 @@ def change_image_based_on_selection(value): return html.Img(src=source, height=50) -@app.callback( +@callback( Output("utci-category-heatmap", "children"), [ Input("df-store", "modified_timestamp"), @@ -377,7 +379,7 @@ def update_tab_utci_category( ) -@app.callback( +@callback( Output("utci-summary-chart", "children"), [ Input("tab7-dropdown", "value"), @@ -413,4 +415,4 @@ def update_tab_utci_summary_chart( return dcc.Graph( config=generate_chart_name("summary", meta, custom_inputs, units), figure=utci_summary_chart, - ) + ) \ No newline at end of file diff --git a/my_project/tab_psy_chart/app_psy_chart.py b/pages/psy-chart.py similarity index 97% rename from my_project/tab_psy_chart/app_psy_chart.py rename to pages/psy-chart.py index f3a5b56c..2795f8fa 100644 --- a/my_project/tab_psy_chart/app_psy_chart.py +++ b/pages/psy-chart.py @@ -1,18 +1,18 @@ +import dash +from dash import dcc, html +import dash_bootstrap_components as dbc +from dash_extensions.enrich import Output, Input, State, callback + import numpy as np +import pandas as pd import plotly.graph_objects as go import json from pythermalcomfort import psychrometrics as psy from math import ceil, floor -import dash_bootstrap_components as dbc from copy import deepcopy -from dash import dcc -from dash import html -from my_project.global_scheme import ( - container_row_center_full, - container_col_center_one_of_three, -) -from my_project.template_graphs import filter_df_by_month_and_hour -from my_project.utils import ( + +from pages.lib.template_graphs import filter_df_by_month_and_hour +from pages.lib.utils import ( generate_chart_name, generate_units, generate_custom_inputs_psy, @@ -20,23 +20,21 @@ title_with_link, dropdown, ) -from my_project.global_scheme import ( +from pages.lib.global_scheme import ( + container_row_center_full, + container_col_center_one_of_three, dropdown_names, sun_cloud_tab_dropdown_names, more_variables_dropdown, sun_cloud_tab_explore_dropdown_names, -) -from dash.dependencies import Input, Output, State -import pandas as pd - -from app import app - -from my_project.global_scheme import ( template, mapping_dictionary, tight_margins, ) + +dash.register_page(__name__, name= 'Psychrometric Chart', order=5) + psy_dropdown_names = { "None": "None", "Frequency": "Frequency", @@ -207,7 +205,7 @@ def inputs(): ) -def layout_psy_chart(): +def layout(): return ( html.Div( children=title_with_link( @@ -227,7 +225,7 @@ def layout_psy_chart(): # psychrometric chart -@app.callback( +@callback( Output("psych-chart", "children"), [ Input("df-store", "modified_timestamp"), diff --git a/my_project/tab_select/app_select.py b/pages/select.py similarity index 87% rename from my_project/tab_select/app_select.py rename to pages/select.py index a7d38672..07b030d1 100644 --- a/my_project/tab_select/app_select.py +++ b/pages/select.py @@ -5,13 +5,15 @@ import dash import dash_bootstrap_components as dbc from dash.exceptions import PreventUpdate -from dash_extensions.enrich import Serverside, Output, Input, State, html, dcc +from dash_extensions.enrich import Serverside, Output, Input, State, html, dcc, callback -from app import app -from my_project.extract_df import convert_data -from my_project.extract_df import create_df, get_data, get_location_info -from my_project.global_scheme import mapping_dictionary -from my_project.utils import plot_location_epw_files, generate_chart_name + +from pages.lib.extract_df import convert_data +from pages.lib.extract_df import create_df, get_data, get_location_info +from pages.lib.global_scheme import mapping_dictionary +from pages.lib.utils import plot_location_epw_files, generate_chart_name + +dash.register_page(__name__, path='/', name='Select Weather File', order=0) messages_alert = { "start": "To start, upload an EPW file or click on a point on the map!", @@ -21,8 +23,7 @@ "wrong_extension": "The file you have uploaded is not an EPW file", } - -def layout_select(): +def layout(): """Contents in the first tab 'Select Weather File'""" return html.Div( className="container-col tab-container", @@ -82,7 +83,6 @@ def layout_select(): ], ) - def alert(): """Alert layout for the submit button.""" return html.Div( @@ -100,7 +100,7 @@ def alert(): # add si-ip and map dictionary in the output -@app.callback( +@callback( [ Output("meta-store", "data"), Output("lines-store", "data"), @@ -195,7 +195,7 @@ def submitted_data( # add switch_si_ip function and convert the data-store -@app.callback( +@callback( [ Output("df-store", "data"), Output("si-ip-unit-store", "data"), @@ -220,53 +220,7 @@ def switch_si_ip(ts, si_ip_input, url_store, lines): ) -@app.callback( - [ - Output("tab-summary", "disabled"), - Output("tab-t-rh", "disabled"), - Output("tab-sun", "disabled"), - Output("tab-wind", "disabled"), - Output("tab-psy-chart", "disabled"), - Output("tab-data-explorer", "disabled"), - Output("tab-outdoor-comfort", "disabled"), - Output("tab-natural-ventilation", "disabled"), - Output("banner-subtitle", "children"), - ], - [ - Input("meta-store", "data"), - Input("df-store", "data"), - ], -) -def enable_tabs_when_data_is_loaded(meta, data): - """Hide tabs when data are not loaded""" - default = "Current Location: N/A" - if data is None: - return ( - True, - True, - True, - True, - True, - True, - True, - True, - default, - ) - else: - return ( - False, - False, - False, - False, - False, - False, - False, - False, - "Current Location: " + meta["city"] + ", " + meta["country"], - ) - - -@app.callback( +@callback( [ Output("modal", "is_open"), Output("url-store", "data"), @@ -289,7 +243,7 @@ def display_modal_when_data_clicked(clicks_use_epw, click_map, close_clicks, is_ return is_open, "" -@app.callback( +@callback( [ Output("modal-header", "children"), ], @@ -303,3 +257,25 @@ def display_modal_when_data_clicked(click_map): if click_map: return [f"Analyse data from {click_map['points'][0]['hovertext']}?"] return ["Analyse data from this location?"] + + +@callback( + [ + Output("banner-subtitle", "children"), + ], + [ + Input("meta-store", "data"), + Input("df-store", "data"), + ], +) +def enable_location_display(meta, data): + """Display current location in banner""" + default = "Current Location: N/A" + if data is None: + return ( + default, + ) + else: + return ( + "Current Location: " + meta["city"] + ", " + meta["country"], + ) \ No newline at end of file diff --git a/my_project/tab_summary/app_summary.py b/pages/summary.py similarity index 95% rename from my_project/tab_summary/app_summary.py rename to pages/summary.py index d0413724..65a4d8fe 100644 --- a/my_project/tab_summary/app_summary.py +++ b/pages/summary.py @@ -3,14 +3,13 @@ import plotly.graph_objects as go import requests from dash.exceptions import PreventUpdate -from dash_extensions.enrich import dcc, html, Output, Input, State - -from app import app -from my_project.extract_df import get_data -from my_project.global_scheme import template, tight_margins, mapping_dictionary -from my_project.tab_summary.charts_summary import world_map -from my_project.template_graphs import violin -from my_project.utils import ( +from dash_extensions.enrich import dcc, html, Output, Input, State, callback + +from pages.lib.extract_df import get_data +from pages.lib.global_scheme import template, tight_margins, mapping_dictionary +from pages.lib.charts_summary import world_map +from pages.lib.template_graphs import violin +from pages.lib.utils import ( generate_chart_name, generate_units, generate_units_degree, @@ -18,9 +17,28 @@ title_with_link, ) +dash.register_page(__name__, name='Climate Summary', order=1) + -def layout_summary(si_ip): +def layout(): """Contents in the second tab 'Climate Summary'.""" + + return html.Div( + className="container-col", + id="tab-two-container", + children=[ + # + ] + ) + + + +@callback( + Output('tab-two-container', 'children'), + [Input('si-ip-radio-input', 'value')] +) +def update_layout(si_ip): + if si_ip == "si": heating_setpoint = 10 cooling_setpoint = 18 @@ -29,10 +47,6 @@ def layout_summary(si_ip): cooling_setpoint = 64 return html.Div( - className="container-col", - id="tab-two-container", - children=[ - html.Div( className="container-col", id="tab2-sec1-container", children=[ @@ -163,12 +177,24 @@ def layout_summary(si_ip): ], ), ], - ), - ], - ) + ) + -@app.callback( + +# @callback( +# [Output('input-hdd-set-point', 'value'), Output('input-cdd-set-point', 'value')], +# [Input('si-ip-radio-input', 'value')] +# ) +# def update_setpoints(si_ip_unit_store_data): +# if si_ip_unit_store_data == 'si': +# return 10, 18 +# else: +# return 50, 64 + + + +@callback( Output("world-map", "children"), Input("meta-store", "data"), ) @@ -183,7 +209,7 @@ def update_map(meta): return map_world -@app.callback( +@callback( Output("location-info", "children"), Input("df-store", "modified_timestamp"), [ @@ -272,7 +298,10 @@ def update_location_info(ts, df, meta, si_ip): return location_info -@app.callback( + + + +@callback( [ Output("degree-days-chart-wrapper", "children"), Output("warning-cdd-higher-hdd", "is_open"), @@ -383,7 +412,7 @@ def degree_day_chart(ts, ts_click, df, meta, hdd_value, cdd_value, n_clicks, si_ return chart, warning_setpoint -@app.callback( +@callback( Output("temp-profile-graph", "children"), [ Input("df-store", "modified_timestamp"), @@ -405,7 +434,7 @@ def update_violin_tdb(ts, global_local, df, meta, si_ip): ) -@app.callback( +@callback( Output("wind-speed-graph", "children"), [ Input("df-store", "modified_timestamp"), @@ -428,7 +457,7 @@ def update_tab_wind(ts, global_local, df, meta, si_ip): ) -@app.callback( +@callback( Output("humidity-profile-graph", "children"), [ Input("df-store", "modified_timestamp"), @@ -451,7 +480,7 @@ def update_tab_rh(ts, global_local, df, meta, si_ip): ) -@app.callback( +@callback( Output("solar-radiation-graph", "children"), [ Input("df-store", "modified_timestamp"), @@ -474,7 +503,7 @@ def update_tab_gh_rad(ts, global_local, df, meta, si_ip): ) -@app.callback( +@callback( Output("download-dataframe-csv", "data"), [Input("download-button", "n_clicks")], [ @@ -500,7 +529,7 @@ def download_clima_dataframe(n_clicks, df, meta, si_ip): print("df not loaded yet") -@app.callback( +@callback( Output("download-epw", "data"), [Input("download-epw-button", "n_clicks")], [State("meta-store", "data")], diff --git a/my_project/tab_sun/app_sun.py b/pages/sun.py similarity index 83% rename from my_project/tab_sun/app_sun.py rename to pages/sun.py index 4cf9b333..1b3795ba 100644 --- a/my_project/tab_sun/app_sun.py +++ b/pages/sun.py @@ -1,9 +1,13 @@ +import dash +from dash import html, dcc +from dash_extensions.enrich import Output, Input, State, callback +import dash_bootstrap_components as dbc + + import numpy as np from copy import deepcopy -from dash import dcc -from dash import html -import dash_bootstrap_components as dbc -from my_project.global_scheme import ( + +from pages.lib.global_scheme import ( sun_cloud_tab_dropdown_names, sun_cloud_tab_explore_dropdown_names, dropdown_names, @@ -12,17 +16,17 @@ month_lst, mapping_dictionary, ) -from my_project.utils import dropdown +from pages.lib.utils import dropdown from dash.dependencies import Input, Output, State -from my_project.tab_sun.charts_sun import ( +from pages.lib.charts_sun import ( monthly_solar, polar_graph, custom_cartesian_solar, ) -from my_project.template_graphs import heatmap, barchart, daily_profile -from my_project.utils import code_timer -from my_project.utils import ( +from pages.lib.template_graphs import heatmap, barchart, daily_profile +from pages.lib.utils import code_timer +from pages.lib.utils import ( title_with_tooltip, generate_chart_name, generate_units, @@ -30,7 +34,9 @@ title_with_link, ) -from app import app + +dash.register_page(__name__, name= 'Sun and Clouds', order=3) + sc_dropdown_names = { "None": "None", @@ -48,6 +54,7 @@ sc_dropdown_names.pop("UTCI: no Sun & no Wind : categories", None) + def sun_path(): """Return the layout for the custom sun path and its dropdowns.""" return html.Div( @@ -144,52 +151,64 @@ def explore_daily_heatmap(): ) -def static_section(si_ip): - if si_ip == "si": - hor_unit = "Wh/m²" - if si_ip == "ip": - hor_unit = "Btu/ft²" + +def static_section(): return html.Div( + id='static-section', className="container-col full-width", children=[ - html.Div( - children=title_with_link( - text="Global and Diffuse Horizontal Solar Radiation (" - + hor_unit - + ")", - id_button="monthly-chart-label", - doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/sun-and-cloud/global-and-diffuse-horizontal-solar-radiation", - ), - ), - dcc.Loading( - type="circle", - children=html.Div(id="monthly-solar"), - ), - html.Div( - children=title_with_link( - text="Cloud coverage", - id_button="cloud-chart-label", - doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/sun-and-cloud/cloud-coverage", - ), - ), - dcc.Loading( - type="circle", - children=html.Div(id="cloud-cover"), - ), + # ... ], ) -def layout_sun(si_ip): +def layout(): """Contents of tab four.""" return html.Div( className="container-col", id="tab-four-container", - children=[sun_path(), static_section(si_ip), explore_daily_heatmap()], + children=[sun_path(), static_section(), explore_daily_heatmap()], ) -@app.callback( +@callback( + Output('static-section', 'children'), + [Input('si-ip-radio-input', 'value')] +) +def update_static_section(si_ip): + if si_ip == "si": + hor_unit = "Wh/m²" + if si_ip == "ip": + hor_unit = "Btu/ft²" + return [ + html.Div( + children=title_with_link( + text="Global and Diffuse Horizontal Solar Radiation (" + + hor_unit + + ")", + id_button="monthly-chart-label", + doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/sun-and-cloud/global-and-diffuse-horizontal-solar-radiation", + ), + ), + dcc.Loading( + type="circle", + children=html.Div(id="monthly-solar"), + ), + html.Div( + children=title_with_link( + text="Cloud coverage", + id_button="cloud-chart-label", + doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/sun-and-cloud/cloud-coverage", + ), + ), + dcc.Loading( + type="circle", + children=html.Div(id="cloud-cover"), + ), + ] + + +@callback( [ Output("monthly-solar", "children"), Output("cloud-cover", "children"), @@ -232,7 +251,7 @@ def monthly_and_cloud_chart(ts, df, meta, si_ip): ) -@app.callback( +@callback( Output("custom-sunpath", "children"), [ Input("df-store", "modified_timestamp"), @@ -262,7 +281,7 @@ def sun_path_chart(ts, view, var, global_local, df, meta, si_ip): ) -@app.callback( +@callback( Output("tab4-daily", "children"), [ Input("df-store", "modified_timestamp"), @@ -285,7 +304,7 @@ def daily(ts, var, global_local, df, meta, si_ip): ) -@app.callback( +@callback( Output("tab4-heatmap", "children"), [ Input("df-store", "modified_timestamp"), diff --git a/my_project/tab_t_rh/app_t_rh.py b/pages/t_rh.py similarity index 94% rename from my_project/tab_t_rh/app_t_rh.py rename to pages/t_rh.py index aa1a3b5f..e315b0bd 100644 --- a/my_project/tab_t_rh/app_t_rh.py +++ b/pages/t_rh.py @@ -1,10 +1,9 @@ -from dash import dcc, html -from dash_extensions.enrich import Output, Input, State +import dash +from dash_extensions.enrich import Output, Input, State, dcc, html, callback -from app import app -from my_project.global_scheme import dropdown_names -from my_project.template_graphs import heatmap, yearly_profile, daily_profile -from my_project.utils import ( +from pages.lib.global_scheme import dropdown_names +from pages.lib.template_graphs import heatmap, yearly_profile, daily_profile +from pages.lib.utils import ( generate_chart_name, generate_units, generate_units_degree, @@ -14,10 +13,12 @@ dropdown, ) +dash.register_page(__name__, name= 'Temperature and Humidity', order=2) + var_to_plot = ["Dry bulb temperature", "Relative humidity"] -def layout_t_rh(): +def layout(): return html.Div( className="container-col full-width", children=[ @@ -87,7 +88,7 @@ def layout_t_rh(): ) -@app.callback( +@callback( Output("yearly-chart", "children"), [ Input("df-store", "modified_timestamp"), @@ -119,7 +120,7 @@ def update_yearly_chart(ts, global_local, dd_value, df, meta, si_ip): ) -@app.callback( +@callback( Output("daily", "children"), [ Input("df-store", "modified_timestamp"), @@ -157,7 +158,7 @@ def update_daily(ts, global_local, dd_value, df, meta, si_ip): ) -@app.callback( +@callback( [Output("heatmap", "children")], [ Input("df-store", "modified_timestamp"), @@ -196,7 +197,7 @@ def update_heatmap(ts, global_local, dd_value, df, meta, si_ip): ) -@app.callback( +@callback( Output("table-tmp-hum", "children"), [ Input("df-store", "modified_timestamp"), diff --git a/my_project/tab_wind/app_wind.py b/pages/wind.py similarity index 98% rename from my_project/tab_wind/app_wind.py rename to pages/wind.py index 8341d2e6..60b17ec0 100644 --- a/my_project/tab_wind/app_wind.py +++ b/pages/wind.py @@ -1,8 +1,10 @@ +import dash from dash import dcc, html -from my_project.global_scheme import month_lst, container_row_center_full -from dash.dependencies import Input, Output, State -from my_project.template_graphs import heatmap, wind_rose -from my_project.utils import ( +from dash_extensions.enrich import Output, Input, State, callback + +from pages.lib.global_scheme import month_lst, container_row_center_full +from pages.lib.template_graphs import heatmap, wind_rose +from pages.lib.utils import ( title_with_tooltip, generate_chart_name, generate_units, @@ -10,9 +12,10 @@ title_with_link, dropdown, ) -from my_project.utils import code_timer +from pages.lib.utils import code_timer + -from app import app +dash.register_page(__name__, name= 'Wind', order=4) def sliders(): @@ -312,7 +315,7 @@ def custom_wind_rose(): ) -def layout_wind(): +def layout(): """Contents in the fifth tab 'Wind'.""" return html.Div( className="container-col justify-center", @@ -346,7 +349,7 @@ def layout_wind(): # wind rose -@app.callback( +@callback( Output("wind-rose", "children"), Input("df-store", "modified_timestamp"), [ @@ -367,7 +370,7 @@ def update_annual_wind_rose(ts, df, meta, si_ip): # wind speed -@app.callback( +@callback( Output("wind-speed", "children"), # General [ @@ -392,7 +395,7 @@ def update_tab_wind_speed(ts, global_local, df, meta, si_ip): # wind direction -@app.callback( +@callback( Output("wind-direction", "children"), # General [ @@ -416,7 +419,7 @@ def update_tab_wind_direction(global_local, df, meta, si_ip): # Custom Wind rose -@app.callback( +@callback( Output("custom-wind-rose", "children"), # Custom Graph Input [ @@ -465,7 +468,7 @@ def update_custom_wind_rose( ### Seasonal Graphs ### -@app.callback( +@callback( [ Output("winter-wind-rose", "children"), Output("spring-wind-rose", "children"), @@ -581,7 +584,7 @@ def seasonal_chart_caption(month_start, month_end, count, n_calm): ### Daily Graphs ### -@app.callback( +@callback( # Daily Graphs [ Output("morning-wind-rose", "children"), @@ -667,4 +670,4 @@ def daily_chart_caption(hour_start, hour_end, count, calm_count): morning_text, noon_text, night_text, - ) + ) \ No newline at end of file From ec88a44dded8eb5ce8cc73faef811ee5b5f9931e Mon Sep 17 00:00:00 2001 From: t2the0bi <49641232+t2the0bi@users.noreply.github.com> Date: Sat, 4 May 2024 14:42:13 -0700 Subject: [PATCH 19/91] Modify tab style --- main.py | 4 +--- pages/explorer.py | 36 ++++++++++++++++++------------------ pages/natural_ventilation.py | 8 +++----- pages/outdoor.py | 1 - pages/psy-chart.py | 25 +++++++++++++------------ pages/select.py | 13 ++++++++++--- pages/summary.py | 13 +++++++++---- pages/sun.py | 20 +++++++++----------- pages/t_rh.py | 6 +++++- pages/wind.py | 2 +- 10 files changed, 69 insertions(+), 59 deletions(-) diff --git a/main.py b/main.py index bb8379be..ba418da4 100644 --- a/main.py +++ b/main.py @@ -21,7 +21,7 @@ html.Div(page["name"], className="ms-2"), href=page["path"], active="exact", - style={'color':'white','text-align': 'center', 'height': '100%'} + style={'color':'white'} ) for page in dash.page_registry.values() if page["name"] not in ["404", "changelog"] ], @@ -29,8 +29,6 @@ justified=True, style={'background-color': '#003262', 'padding': '0.25rem 0.5rem', - 'justify-content': 'center', - 'align-items': 'center', } ), html.Div(id="store-container", children=[store()]), diff --git a/pages/explorer.py b/pages/explorer.py index edead0fc..0fe68241 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -6,19 +6,11 @@ from copy import deepcopy -from pages.lib.utils import ( - generate_chart_name, - generate_custom_inputs, - generate_custom_inputs_explorer, - generate_units, - title_with_tooltip, - summary_table_tmp_rh_tab, - code_timer, - title_with_link, - determine_month_and_hour_filter, - dropdown, +from pages.lib.charts_data_explorer import ( + custom_heatmap, + two_var_graph, + three_var_graph, ) - from pages.lib.global_scheme import ( fig_config, dropdown_names, @@ -29,12 +21,6 @@ container_col_center_one_of_three, mapping_dictionary, ) - -from pages.lib.charts_data_explorer import ( - custom_heatmap, - two_var_graph, - three_var_graph, -) from pages.lib.template_graphs import ( heatmap, yearly_profile, @@ -43,6 +29,20 @@ filter_df_by_month_and_hour, ) +from pages.lib.utils import ( + generate_chart_name, + generate_custom_inputs, + generate_custom_inputs_explorer, + generate_units, + title_with_tooltip, + summary_table_tmp_rh_tab, + code_timer, + title_with_link, + determine_month_and_hour_filter, + dropdown, +) + + dash.register_page(__name__, name= 'Data Explorer', order=8) diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index b3c6305c..0792c434 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -1,14 +1,14 @@ +import math +import json + import dash from dash import dcc, html import dash_bootstrap_components as dbc from dash_extensions.enrich import Output, Input, State, callback import numpy as np -import math -import json import plotly.graph_objects as go - from pages.lib.global_scheme import ( template, mapping_dictionary, @@ -17,9 +17,7 @@ container_row_center_full, container_col_center_one_of_three, ) - from pages.lib.template_graphs import filter_df_by_month_and_hour - from pages.lib.utils import ( title_with_tooltip, generate_chart_name, diff --git a/pages/outdoor.py b/pages/outdoor.py index 93f7486a..14bb42d1 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -12,7 +12,6 @@ container_row_center_full, container_col_center_one_of_three, ) - from pages.lib.template_graphs import ( heatmap_with_filter, thermal_stress_stacked_barchart, diff --git a/pages/psy-chart.py b/pages/psy-chart.py index 2795f8fa..a1745078 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -1,25 +1,17 @@ +import json +from math import ceil, floor + import dash from dash import dcc, html import dash_bootstrap_components as dbc from dash_extensions.enrich import Output, Input, State, callback +from copy import deepcopy import numpy as np import pandas as pd import plotly.graph_objects as go -import json from pythermalcomfort import psychrometrics as psy -from math import ceil, floor -from copy import deepcopy -from pages.lib.template_graphs import filter_df_by_month_and_hour -from pages.lib.utils import ( - generate_chart_name, - generate_units, - generate_custom_inputs_psy, - determine_month_and_hour_filter, - title_with_link, - dropdown, -) from pages.lib.global_scheme import ( container_row_center_full, container_col_center_one_of_three, @@ -31,6 +23,15 @@ mapping_dictionary, tight_margins, ) +from pages.lib.template_graphs import filter_df_by_month_and_hour +from pages.lib.utils import ( + generate_chart_name, + generate_units, + generate_custom_inputs_psy, + determine_month_and_hour_filter, + title_with_link, + dropdown, +) dash.register_page(__name__, name= 'Psychrometric Chart', order=5) diff --git a/pages/select.py b/pages/select.py index 07b030d1..4c530a65 100644 --- a/pages/select.py +++ b/pages/select.py @@ -7,11 +7,18 @@ from dash.exceptions import PreventUpdate from dash_extensions.enrich import Serverside, Output, Input, State, html, dcc, callback - from pages.lib.extract_df import convert_data -from pages.lib.extract_df import create_df, get_data, get_location_info +from pages.lib.extract_df import ( + create_df, + get_data, + get_location_info +) from pages.lib.global_scheme import mapping_dictionary -from pages.lib.utils import plot_location_epw_files, generate_chart_name +from pages.lib.utils import ( + plot_location_epw_files, + generate_chart_name +) + dash.register_page(__name__, path='/', name='Select Weather File', order=0) diff --git a/pages/summary.py b/pages/summary.py index 65a4d8fe..87529639 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -1,13 +1,18 @@ import dash import dash_bootstrap_components as dbc -import plotly.graph_objects as go -import requests from dash.exceptions import PreventUpdate from dash_extensions.enrich import dcc, html, Output, Input, State, callback -from pages.lib.extract_df import get_data -from pages.lib.global_scheme import template, tight_margins, mapping_dictionary +import plotly.graph_objects as go +import requests + from pages.lib.charts_summary import world_map +from pages.lib.extract_df import get_data +from pages.lib.global_scheme import ( + template, + tight_margins, + mapping_dictionary +) from pages.lib.template_graphs import violin from pages.lib.utils import ( generate_chart_name, diff --git a/pages/sun.py b/pages/sun.py index 1b3795ba..b6b7c8d0 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -2,11 +2,16 @@ from dash import html, dcc from dash_extensions.enrich import Output, Input, State, callback import dash_bootstrap_components as dbc - +# from dash.dependencies import Input, Output, State import numpy as np from copy import deepcopy +from pages.lib.charts_sun import ( + monthly_solar, + polar_graph, + custom_cartesian_solar, +) from pages.lib.global_scheme import ( sun_cloud_tab_dropdown_names, sun_cloud_tab_explore_dropdown_names, @@ -16,17 +21,10 @@ month_lst, mapping_dictionary, ) -from pages.lib.utils import dropdown -from dash.dependencies import Input, Output, State - -from pages.lib.charts_sun import ( - monthly_solar, - polar_graph, - custom_cartesian_solar, -) from pages.lib.template_graphs import heatmap, barchart, daily_profile -from pages.lib.utils import code_timer from pages.lib.utils import ( + code_timer, + dropdown, title_with_tooltip, generate_chart_name, generate_units, @@ -35,7 +33,7 @@ ) -dash.register_page(__name__, name= 'Sun and Clouds', order=3) +dash.register_page(__name__, name= 'Sun and Cloud Coverage', order=3) sc_dropdown_names = { diff --git a/pages/t_rh.py b/pages/t_rh.py index e315b0bd..eaf85f68 100644 --- a/pages/t_rh.py +++ b/pages/t_rh.py @@ -2,7 +2,11 @@ from dash_extensions.enrich import Output, Input, State, dcc, html, callback from pages.lib.global_scheme import dropdown_names -from pages.lib.template_graphs import heatmap, yearly_profile, daily_profile +from pages.lib.template_graphs import ( + heatmap, + yearly_profile, + daily_profile +) from pages.lib.utils import ( generate_chart_name, generate_units, diff --git a/pages/wind.py b/pages/wind.py index 60b17ec0..5231e005 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -5,6 +5,7 @@ from pages.lib.global_scheme import month_lst, container_row_center_full from pages.lib.template_graphs import heatmap, wind_rose from pages.lib.utils import ( + code_timer, title_with_tooltip, generate_chart_name, generate_units, @@ -12,7 +13,6 @@ title_with_link, dropdown, ) -from pages.lib.utils import code_timer dash.register_page(__name__, name= 'Wind', order=4) From f82a174e0dfa9f532d750158e932209d7ff53558 Mon Sep 17 00:00:00 2001 From: t2the0bi <49641232+t2the0bi@users.noreply.github.com> Date: Mon, 6 May 2024 11:34:51 -0700 Subject: [PATCH 20/91] Remove redundant sections, adjust cypress config for new page structure --- main.py | 2 +- pages/lib/layout.py | 89 ------------------------------- pages/lib/template_graphs.py | 2 +- pages/sun.py | 3 +- tests/node/cypress/e2e/spec.cy.js | 3 +- tests/node/package-lock.json | 17 +++--- tests/node/package.json | 2 +- 7 files changed, 14 insertions(+), 104 deletions(-) diff --git a/main.py b/main.py index ba418da4..f90af3d5 100644 --- a/main.py +++ b/main.py @@ -4,7 +4,7 @@ from dash.dependencies import Input, Output from app import app -from pages.lib.layout import banner, build_tabs, footer, store +from pages.lib.layout import banner, footer, store #, build_tabs server = app.server diff --git a/pages/lib/layout.py b/pages/lib/layout.py index 9bfd5f5a..419f398a 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -135,95 +135,6 @@ def banner(): ) -def build_tabs(): - """Build the seven different tabs.""" - return html.Div( - id="tabs-container", - children=[ - dcc.Tabs( - id="tabs", - parent_className="custom-tabs", - value="tab-select", - children=[ - dcc.Tab( - label="Select Weather File", - value="tab-select", - className="custom-tab", - selected_className="custom-tab--selected", - ), - dcc.Tab( - id="tab-summary", - label="Climate Summary", - value="tab-summary", - className="custom-tab", - selected_className="custom-tab--selected", - disabled=True, - ), - dcc.Tab( - id="tab-t-rh", - label="Temperature and Humidity", - value="tab-t-rh", - className="custom-tab", - selected_className="custom-tab--selected", - disabled=True, - ), - dcc.Tab( - id="tab-sun", - label="Sun and Clouds", - value="tab-sun", - className="custom-tab", - selected_className="custom-tab--selected", - disabled=True, - ), - dcc.Tab( - id="tab-wind", - label="Wind", - value="tab-wind", - className="custom-tab", - selected_className="custom-tab--selected", - disabled=True, - ), - dcc.Tab( - id="tab-psy-chart", - label="Psychrometric Chart", - value="tab-psy-chart", - className="custom-tab", - selected_className="custom-tab--selected", - disabled=True, - ), - dcc.Tab( - id="tab-natural-ventilation", - label="Natural Ventilation", - value="tab-natural-ventilation", - className="custom-tab", - selected_className="custom-tab--selected", - disabled=True, - ), - dcc.Tab( - id="tab-outdoor-comfort", - label="Outdoor Comfort", - value="tab-outdoor-comfort", - className="custom-tab", - selected_className="custom-tab--selected", - disabled=True, - ), - dcc.Tab( - id="tab-data-explorer", - label="Data Explorer", - value="tab-data-explorer", - className="custom-tab", - selected_className="custom-tab--selected", - disabled=True, - ), - ], - ), - html.Div( - id="store-container", - children=[store(), html.Div(id="tabs-content")], - ), - ], - ) - def store(): return html.Div( diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index 754f3dd8..9a02b024 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -507,7 +507,7 @@ def wind_rose(df, title, month, hour, labels, si_ip): df["wind_dir"], bins=dir_bins, labels=dir_labels, right=False ) ) - .ser.cat.rename_categories({"WindDir_bins": {360: 0}}) + .replace({"WindDir_bins": {360: 0}}) .groupby(by=["WindSpd_bins", "WindDir_bins"], observed=False) .size() .unstack(level="WindSpd_bins") diff --git a/pages/sun.py b/pages/sun.py index b6b7c8d0..d880ac1f 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -2,7 +2,6 @@ from dash import html, dcc from dash_extensions.enrich import Output, Input, State, callback import dash_bootstrap_components as dbc -# from dash.dependencies import Input, Output, State import numpy as np from copy import deepcopy @@ -33,7 +32,7 @@ ) -dash.register_page(__name__, name= 'Sun and Cloud Coverage', order=3) +dash.register_page(__name__, name= 'Sun and Clouds', order=3) sc_dropdown_names = { diff --git a/tests/node/cypress/e2e/spec.cy.js b/tests/node/cypress/e2e/spec.cy.js index 753c43d7..e423f957 100644 --- a/tests/node/cypress/e2e/spec.cy.js +++ b/tests/node/cypress/e2e/spec.cy.js @@ -1,6 +1,5 @@ function click_tab(name) { - cy.get('.custom-tab') - .not('.tab--disabled') + cy.get('a.nav-link div') // adjusted to new page structure .contains(name) .click(); } diff --git a/tests/node/package-lock.json b/tests/node/package-lock.json index 08476797..c137b1f4 100644 --- a/tests/node/package-lock.json +++ b/tests/node/package-lock.json @@ -8,7 +8,7 @@ "name": "clima", "version": "0.0.0", "devDependencies": { - "cypress": "^13.5.1" + "cypress": "^13.8.1" } }, "node_modules/@colors/colors": { @@ -74,6 +74,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.10.tgz", "integrity": "sha512-luANqZxPmjTll8bduz4ACs/lNTCLuWssCyjqTY9yLdsv1xnViQp3ISKwsEWOIecO13JWUqjVdig/Vjjc09o8uA==", "dev": true, + "optional": true, "dependencies": { "undici-types": "~5.26.4" } @@ -548,21 +549,20 @@ } }, "node_modules/cypress": { - "version": "13.5.1", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.5.1.tgz", - "integrity": "sha512-yqLViT0D/lPI8Kkm7ciF/x/DCK/H/DnogdGyiTnQgX4OVR2aM30PtK+kvklTOD1u3TuItiD9wUQAF8EYWtyZug==", + "version": "13.8.1", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.8.1.tgz", + "integrity": "sha512-Uk6ovhRbTg6FmXjeZW/TkbRM07KPtvM5gah1BIMp4Y2s+i/NMxgaLw0+PbYTOdw1+egE0FP3mWRiGcRkjjmhzA==", "dev": true, "hasInstallScript": true, "dependencies": { "@cypress/request": "^3.0.0", "@cypress/xvfb": "^1.2.4", - "@types/node": "^18.17.5", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", "arch": "^2.2.0", "blob-util": "^2.0.2", "bluebird": "^3.7.2", - "buffer": "^5.6.0", + "buffer": "^5.7.1", "cachedir": "^2.3.0", "chalk": "^4.1.0", "check-more-types": "^2.24.0", @@ -580,7 +580,7 @@ "figures": "^3.2.0", "fs-extra": "^9.1.0", "getos": "^3.2.1", - "is-ci": "^3.0.0", + "is-ci": "^3.0.1", "is-installed-globally": "~0.4.0", "lazy-ass": "^1.6.0", "listr2": "^3.8.3", @@ -1947,7 +1947,8 @@ "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "dev": true, + "optional": true }, "node_modules/universalify": { "version": "0.2.0", diff --git a/tests/node/package.json b/tests/node/package.json index ceecd8f0..cb40d108 100644 --- a/tests/node/package.json +++ b/tests/node/package.json @@ -7,6 +7,6 @@ "cy:open": "cypress open" }, "devDependencies": { - "cypress": "^13.5.1" + "cypress": "^13.8.1" } } From de2310e971897c8ea1d3679f7a1b67e19a64dd59 Mon Sep 17 00:00:00 2001 From: t2the0bi <49641232+t2the0bi@users.noreply.github.com> Date: Wed, 22 May 2024 14:34:55 -0700 Subject: [PATCH 21/91] fix: remove redundant code, adjust Python test - Removed redundant, commented code in `main.py` - Adjusted import statement for custom modules in `tests/python/test_utils.py` to reflect new page structure --- pages/__init__.py | 0 tests/python/test_utils.py | 5 +++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 pages/__init__.py diff --git a/pages/__init__.py b/pages/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/python/test_utils.py b/tests/python/test_utils.py index 1fbebf66..2e7dd86b 100644 --- a/tests/python/test_utils.py +++ b/tests/python/test_utils.py @@ -1,7 +1,8 @@ import requests -from my_project.utils import summary_table_tmp_rh_tab -from my_project.extract_df import get_data, create_df +from pages.lib.utils import summary_table_tmp_rh_tab +from pages.lib.extract_df import get_data, create_df + import pandas as pd import os From 8ec489bfc8b4a579629aad0cb01da7ab066cd700 Mon Sep 17 00:00:00 2001 From: t2the0bi <49641232+t2the0bi@users.noreply.github.com> Date: Wed, 22 May 2024 14:37:58 -0700 Subject: [PATCH 22/91] fix: remove redundant code, adjust Python test - Removed redundant, commented code in `main.py` - Adjusted import statement for custom modules in `tests/python/test_utils.py` to reflect new page structure --- main.py | 43 ------------------------------------------- 1 file changed, 43 deletions(-) diff --git a/main.py b/main.py index f90af3d5..593cf26f 100644 --- a/main.py +++ b/main.py @@ -38,49 +38,6 @@ ) -# @app.callback( -# dash.dependencies.Output("page-content", "children"), -# [dash.dependencies.Input("url", "pathname")], -# ) -# def display_page(pathname): -# if pathname == "/": -# return build_tabs() -# elif pathname == "/changelog": -# return html.Div(children=[changelog()]) - - -# # Handle tab selection -# @app.callback( -# Output("tabs-content", "children"), -# [ -# Input("tabs", "value"), -# Input("si-ip-radio-input", "value"), -# ], -# ) -# def render_content(tab, si_ip): -# """Update the contents of the page depending on what tab the user selects.""" -# if tab == "tab-select": -# return layout_select() -# elif tab == "tab-summary": -# return layout_summary(si_ip) -# elif tab == "tab-t-rh": -# return layout_t_rh() -# elif tab == "tab-sun": -# return layout_sun(si_ip) -# elif tab == "tab-wind": -# return layout_wind() -# elif tab == "tab-data-explorer": -# return layout_data_explorer() -# elif tab == "tab-outdoor-comfort": -# return layout_outdoor_comfort() -# elif tab == "tab-natural-ventilation": -# return layout_natural_ventilation(si_ip) -# elif tab == "tab-psy-chart": -# return layout_psy_chart() -# else: -# return "404" - - if __name__ == "__main__": app.run_server( debug=False, From 568dca328b28d9f42600d8bef444564e5be642ea Mon Sep 17 00:00:00 2001 From: t2the0bi <49641232+t2the0bi@users.noreply.github.com> Date: Wed, 22 May 2024 16:52:48 -0700 Subject: [PATCH 23/91] docs: add link to 'Discussions' to README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9de3c272..e31ffe1c 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ It furthermore calculates a number of climate-related values \(i.e. solar azimuth and altitude, Universal Thermal Climate Index \(UTCI\), comfort indices, etc.\) that are not contained in the EPW files but can be derived from the information therein contained. It can be freely accessed at [clima.cbe.berkeley.edu](http://clima.cbe.berkeley.edu) +For any questions and feedback related to this tool, please use the [Discussions](https://github.com/CenterForTheBuiltEnvironment/clima/discussions) section. + If you use this tool please consider citing us. ## Official documentation From 87a38912780bcefc7cdee20f0593f83d0fc4d577 Mon Sep 17 00:00:00 2001 From: t2the0bi <49641232+t2the0bi@users.noreply.github.com> Date: Thu, 13 Jun 2024 09:57:07 -0700 Subject: [PATCH 24/91] style: adjust navbar to original tab style format Moving to multipage format required app restructuring. This commit adjusts the newly created navbar to the original tab style format. It also introduces Enums to store page urls. - add build_tabs() to build navbar tabs and pages - adjust navbar to original tab style format - add page_urls.py to store page urls using Enums - add reference to Enums in register_page definitions --- assets/tabs.css | 17 ++++++++++++-- main.py | 25 ++++---------------- pages/changelog.py | 9 +++++++- pages/explorer.py | 7 +++++- pages/lib/layout.py | 44 ++++++++++++++++++++++++++++++++++++ pages/lib/page_urls.py | 14 ++++++++++++ pages/natural_ventilation.py | 8 ++++++- pages/not_found_404.py | 6 ++++- pages/outdoor.py | 7 +++++- pages/psy-chart.py | 8 ++++++- pages/select.py | 8 ++++++- pages/summary.py | 8 ++++++- pages/sun.py | 7 +++++- pages/t_rh.py | 9 +++++++- pages/wind.py | 7 +++++- 15 files changed, 151 insertions(+), 33 deletions(-) create mode 100644 pages/lib/page_urls.py diff --git a/assets/tabs.css b/assets/tabs.css index 0be9dda2..1c0ca54b 100644 --- a/assets/tabs.css +++ b/assets/tabs.css @@ -56,14 +56,27 @@ align-items: center; justify-content: center; } -.custom-tab--selected { - color: black; + +.custom-tab:has(.active) { + background-color: white; box-shadow: 1px 1px 0 white; border-left: 1px solid lightgrey !important; border-right: 1px solid lightgrey !important; border-top: 6px solid #abd2ff !important; } +.nav-pills .nav-link { + padding: 0; + color: #586069; + font-family: "system-ui"; + background-color: transparent; +} + +.nav-pills .nav-link.active { + color: black; + background-color: white; +} + /* Tab One */ #tab-one-container { height: 100%; diff --git a/main.py b/main.py index 593cf26f..16ff61eb 100644 --- a/main.py +++ b/main.py @@ -4,7 +4,7 @@ from dash.dependencies import Input, Output from app import app -from pages.lib.layout import banner, footer, store #, build_tabs +from pages.lib.layout import banner, footer, build_tabs #, build_tabs server = app.server @@ -15,29 +15,14 @@ children=[ dcc.Location(id="url", refresh=False), # connected to callback below banner(), - dbc.Nav( - [ - dbc.NavLink( - html.Div(page["name"], className="ms-2"), - href=page["path"], - active="exact", - style={'color':'white'} - ) - for page in dash.page_registry.values() if page["name"] not in ["404", "changelog"] - ], - pills=True, - justified=True, - style={'background-color': '#003262', - 'padding': '0.25rem 0.5rem', - } - ), - html.Div(id="store-container", children=[store()]), - dash.page_container, + html.Div( + id="page-content", + children=build_tabs() + ), footer(), ], ) - if __name__ == "__main__": app.run_server( debug=False, diff --git a/pages/changelog.py b/pages/changelog.py index fb90c27a..39590099 100644 --- a/pages/changelog.py +++ b/pages/changelog.py @@ -2,7 +2,14 @@ import dash_bootstrap_components as dbc from dash import dcc -dash.register_page(__name__, path='/changelog', name='changelog') +from pages.lib.page_urls import PageUrls + + +dash.register_page(__name__, + name='changelog', + path=PageUrls.CHANGELOG.value + ) + def layout(): """changelog page""" diff --git a/pages/explorer.py b/pages/explorer.py index 0fe68241..41d15855 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -6,6 +6,7 @@ from copy import deepcopy +from pages.lib.page_urls import PageUrls from pages.lib.charts_data_explorer import ( custom_heatmap, two_var_graph, @@ -43,7 +44,11 @@ ) -dash.register_page(__name__, name= 'Data Explorer', order=8) +dash.register_page(__name__, + name= 'Data Explorer', + path=PageUrls.EXPLORER.value, + order=8 + ) explore_dropdown_names = {} diff --git a/pages/lib/layout.py b/pages/lib/layout.py index 419f398a..c4b3293f 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -1,4 +1,5 @@ import dash_bootstrap_components as dbc +import dash from dash import dcc, html @@ -153,3 +154,46 @@ def store(): ) ], ) + + +def build_tabs(): + return html.Div( + id="tabs-container", + children=[ + html.Div( + id="tabs-parent", + className="custom-tabs", + children=[ + dbc.Nav( + [ + dbc.NavItem( + dbc.NavLink( + page["name"], + href=page["path"], + active="exact", + className="nav-link" + ), + className="custom-tab" + ) + for page in dash.page_registry.values() if page["name"] not in ["404", "changelog"] + ], + id="tabs", + class_name="tab-container", + pills=True, + justified=True + ) + ] + ), + + html.Div( + id="store-container", + children=[ + store(), + html.Div( + id="tabs-content", + children=dash.page_container + ) + ] + ), + ] + ) \ No newline at end of file diff --git a/pages/lib/page_urls.py b/pages/lib/page_urls.py new file mode 100644 index 00000000..42d593b7 --- /dev/null +++ b/pages/lib/page_urls.py @@ -0,0 +1,14 @@ +from enum import Enum + +class PageUrls(Enum): + SELECT: str = '/' + SUMMARY: str = '/summary' + T_RH: str = '/t-rh' + SUN: str = '/sun' + WIND: str = '/wind' + PSY_CHART: str = '/psy-chart' + NATURAL_VENTILATION: str = '/natural-ventilation' + OUTDOOR: str = '/outdoor' + EXPLORER: str = '/explorer' + CHANGELOG: str = '/changelog' + NOT_FOUND: str = '"../assets/animations/page_not_found.json"' diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 0792c434..66c701b0 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -9,6 +9,7 @@ import numpy as np import plotly.graph_objects as go +from pages.lib.page_urls import PageUrls from pages.lib.global_scheme import ( template, mapping_dictionary, @@ -27,7 +28,12 @@ determine_month_and_hour_filter, ) -dash.register_page(__name__, name= 'Natural Ventilation', order=6) + +dash.register_page(__name__, + name= 'Natural Ventilation', + path=PageUrls.NATURAL_VENTILATION.value, + order=6 + ) def layout(): diff --git a/pages/not_found_404.py b/pages/not_found_404.py index 84d92fa4..5da94903 100644 --- a/pages/not_found_404.py +++ b/pages/not_found_404.py @@ -3,8 +3,12 @@ from dash_iconify import DashIconify from dash_extensions import Lottie +from pages.lib.page_urls import PageUrls + + dash.register_page(__name__, name= '404') + layout = [ dmc.Title("I could not find the page you are currently looking for", order=4), dmc.Text( @@ -25,6 +29,6 @@ rendererSettings=dict(preserveAspectRatio="xMidYMid slice"), ), width="100%", - url="../assets/animations/page_not_found.json", + url=PageUrls.NOT_FOUND.value, ), ] diff --git a/pages/outdoor.py b/pages/outdoor.py index 14bb42d1..7684432f 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -5,6 +5,7 @@ import numpy as np +from pages.lib.page_urls import PageUrls from pages.lib.global_scheme import ( outdoor_dropdown_names, tight_margins, @@ -26,7 +27,11 @@ ) -dash.register_page(__name__, name= 'Outdoor Comfort', order=7) +dash.register_page(__name__, + name= 'Outdoor Comfort', + path=PageUrls.OUTDOOR.value, + order=7 + ) def inputs_outdoor_comfort(): diff --git a/pages/psy-chart.py b/pages/psy-chart.py index a1745078..cf3bc5f9 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -12,6 +12,7 @@ import plotly.graph_objects as go from pythermalcomfort import psychrometrics as psy +from pages.lib.page_urls import PageUrls from pages.lib.global_scheme import ( container_row_center_full, container_col_center_one_of_three, @@ -34,7 +35,12 @@ ) -dash.register_page(__name__, name= 'Psychrometric Chart', order=5) +dash.register_page(__name__, + name= 'Psychrometric Chart', + path=PageUrls.PSY_CHART.value, + order=5 + ) + psy_dropdown_names = { "None": "None", diff --git a/pages/select.py b/pages/select.py index 4c530a65..5b6ad57e 100644 --- a/pages/select.py +++ b/pages/select.py @@ -7,6 +7,7 @@ from dash.exceptions import PreventUpdate from dash_extensions.enrich import Serverside, Output, Input, State, html, dcc, callback +from pages.lib.page_urls import PageUrls from pages.lib.extract_df import convert_data from pages.lib.extract_df import ( create_df, @@ -20,7 +21,12 @@ ) -dash.register_page(__name__, path='/', name='Select Weather File', order=0) +dash.register_page(__name__, + name='Select Weather File', + path=PageUrls.SELECT.value, + order=0 + ) + messages_alert = { "start": "To start, upload an EPW file or click on a point on the map!", diff --git a/pages/summary.py b/pages/summary.py index 87529639..c2a5ffd4 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -6,6 +6,7 @@ import plotly.graph_objects as go import requests +from pages.lib.page_urls import PageUrls from pages.lib.charts_summary import world_map from pages.lib.extract_df import get_data from pages.lib.global_scheme import ( @@ -22,7 +23,12 @@ title_with_link, ) -dash.register_page(__name__, name='Climate Summary', order=1) + +dash.register_page(__name__, + name='Climate Summary', + path=PageUrls.SUMMARY.value, + order=1 + ) def layout(): diff --git a/pages/sun.py b/pages/sun.py index d880ac1f..d5a80976 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -6,6 +6,7 @@ import numpy as np from copy import deepcopy +from pages.lib.page_urls import PageUrls from pages.lib.charts_sun import ( monthly_solar, polar_graph, @@ -32,7 +33,11 @@ ) -dash.register_page(__name__, name= 'Sun and Clouds', order=3) +dash.register_page(__name__, + name= 'Sun and Clouds', + path=PageUrls.SUN.value, + order=3 + ) sc_dropdown_names = { diff --git a/pages/t_rh.py b/pages/t_rh.py index eaf85f68..b8db5f78 100644 --- a/pages/t_rh.py +++ b/pages/t_rh.py @@ -1,6 +1,7 @@ import dash from dash_extensions.enrich import Output, Input, State, dcc, html, callback +from pages.lib.page_urls import PageUrls from pages.lib.global_scheme import dropdown_names from pages.lib.template_graphs import ( heatmap, @@ -17,7 +18,13 @@ dropdown, ) -dash.register_page(__name__, name= 'Temperature and Humidity', order=2) + +dash.register_page(__name__, + name= 'Temperature and Humidity', + path=PageUrls.T_RH.value, + order=2 + ) + var_to_plot = ["Dry bulb temperature", "Relative humidity"] diff --git a/pages/wind.py b/pages/wind.py index 5231e005..0820576c 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -2,6 +2,7 @@ from dash import dcc, html from dash_extensions.enrich import Output, Input, State, callback +from pages.lib.page_urls import PageUrls from pages.lib.global_scheme import month_lst, container_row_center_full from pages.lib.template_graphs import heatmap, wind_rose from pages.lib.utils import ( @@ -15,7 +16,11 @@ ) -dash.register_page(__name__, name= 'Wind', order=4) +dash.register_page(__name__, + name= 'Wind', + path=PageUrls.WIND.value, + order=4 + ) def sliders(): From 38715327986d592436fbc0be48a8dbf113b3b3df Mon Sep 17 00:00:00 2001 From: t2the0bi <49641232+t2the0bi@users.noreply.github.com> Date: Tue, 18 Jun 2024 20:09:58 -0700 Subject: [PATCH 25/91] fix: adjust navbar tabs behavior and callbacks - Adjusted the tabs behavior to mimic behaviour of original tabs, e.g., initial disabled tabs and style differences between active and inactive tabs; required changes in callbacks and CSS style definitions - Added multiple version for user survey alert (Dash Modal/Alert/Toast); final solution will be picked and finalized for next commit --- assets/tabs.css | 38 ++++++++--- main.py | 32 ++++++++-- pages/lib/layout.py | 101 ++++++++++++++++++++++++++---- pages/select.py | 74 +++++++++++++++------- tests/node/cypress/e2e/spec.cy.js | 2 +- 5 files changed, 200 insertions(+), 47 deletions(-) diff --git a/assets/tabs.css b/assets/tabs.css index 1c0ca54b..6fcab5f6 100644 --- a/assets/tabs.css +++ b/assets/tabs.css @@ -34,22 +34,22 @@ .custom-tabs-container { width: 85%; } + .custom-tabs { - border-top-left-radius: 3px; background-color: #f9f9f9; padding: 0 24px; border-bottom: 1px solid #d6d6d6; } .custom-tab { - color:#586069; - border-color: lightgrey; + border-color: rgb(238, 236, 236); border-top-left-radius: 3px; border-top-right-radius: 3px; border-top: 3px solid transparent !important; border-left: 1px solid lightgrey !important; border-right: 1px solid lightgrey !important; - background-color: #fafbfc; + border-bottom: 1px solid #d6d6d6; + background-color: #f6f8f8; padding: 12px !important; font-family: "system-ui"; display: flex !important; @@ -58,11 +58,24 @@ } .custom-tab:has(.active) { + color:#586069; background-color: white; box-shadow: 1px 1px 0 white; border-left: 1px solid lightgrey !important; border-right: 1px solid lightgrey !important; border-top: 6px solid #abd2ff !important; + border-bottom: 1px solid transparent; +} + +.nav-pills { + display: flex; + flex-wrap: wrap; +} + +@media (max-width: 900px) { + .nav-pills { + flex-direction: column; + } } .nav-pills .nav-link { @@ -72,6 +85,10 @@ background-color: transparent; } +.nav-pills .nav-link.disabled { + color: #c8c6c6; +} + .nav-pills .nav-link.active { color: black; background-color: white; @@ -259,7 +276,12 @@ p { align-items: stretch; } - - - - +.survey-alert { + color: white; + background-color: #0c2772; + opacity: 0.98; + font-family: "system-ui"; + font-size: 15px; + border-radius: 0.25rem; + border: 0.5px solid lightgrey; +} \ No newline at end of file diff --git a/main.py b/main.py index 16ff61eb..9e6ddfbb 100644 --- a/main.py +++ b/main.py @@ -1,10 +1,10 @@ import dash import dash_bootstrap_components as dbc -from dash import html, dcc -from dash.dependencies import Input, Output +from dash import html, dcc, no_update +from dash_extensions.enrich import Output, Input, State, callback from app import app -from pages.lib.layout import banner, footer, build_tabs #, build_tabs +from pages.lib.layout import banner, footer, build_tabs server = app.server @@ -23,6 +23,30 @@ ], ) + +# callbrack for Alert solution +@callback( + Output('alert-auto', 'is_open'), + Input('interval-component', 'n_intervals') +) +def display_alert(n): + return n == 1 + + +# callback for Modal solution +# @callback( +# Output('alert-auto', 'is_open'), +# [Input('interval-component', 'n_intervals'), Input("close", "n_clicks")], +# [State('alert-auto', 'is_open')] +# ) +# def toggle_alert(n_intervals, n_clicks, is_open): +# if n_intervals == 1 and not is_open: +# return True +# elif n_clicks: +# return False +# return is_open + + if __name__ == "__main__": app.run_server( debug=False, @@ -30,4 +54,4 @@ port=8080, processes=1, threaded=True, - ) + ) \ No newline at end of file diff --git a/pages/lib/layout.py b/pages/lib/layout.py index c4b3293f..bc1cb56f 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -3,6 +3,80 @@ from dash import dcc, html +def alert(): + """Alert for survey.""" + return html.Div( + id="alert-container", + children=[ + + dbc.Toast( + [ + "If you have a moment, help us improving Clima and take a ", + html.A( + "quick user survey", + href="https://forms.gle/k289zP3R92jdu14M7", + className="alert-link", + target="_blank" + ), + "! ☀️" + ], + id="alert-auto", + header="CBE Clima User Survey", + icon="info", + is_open=False, + dismissable=True, + className="survey-alert", + style={"position": "fixed", "top": 25, "right": 10, "width": 400}, + ), + + + # Alert style solution + # dbc.Alert( + # [ + # "If you have a moment, help us improving Clima and take a ", + # html.A("quick user survey", href="https://lnkd.in/gFheGNrt", className="alert-link"), + # "!" + # ], + # id="alert-auto", + # class_name= "survey-alert", + # is_open=False, + # ), + + # Pop-up window solution + # dbc.Modal( + # [ + # dbc.ModalHeader(dbc.ModalTitle("CBE Clima User Survey")), + # dbc.ModalBody([ + # "If you have a moment, help us improving Clima and take the quick ", + # html.A( + # "user survey", + # href="https://forms.gle/k289zP3R92jdu14M7", + # style={'color':'black', 'font-weight': 'bold'} + # ), + # "!" + # ]), + # dbc.ModalFooter( + # dbc.Button( + # "Close", id="close", className="ms-auto", n_clicks=0 + # ) + # ), + # ], + # id="alert-auto", + # centered=True, + # autofocus=True, + # is_open=False, + # size="lg", + # ), + + dcc.Interval( + id='interval-component', + interval=12*1000, + n_intervals=0 + ) + ] + ) + + def footer(): return dbc.Row( align="center", @@ -166,16 +240,18 @@ def build_tabs(): children=[ dbc.Nav( [ - dbc.NavItem( - dbc.NavLink( - page["name"], - href=page["path"], - active="exact", - className="nav-link" - ), - className="custom-tab" - ) - for page in dash.page_registry.values() if page["name"] not in ["404", "changelog"] + dbc.NavItem( + dbc.NavLink( + page["name"], + id=page["path"], + href=page["path"], + active="exact", + className="nav-link", + disabled=True, + ), + className="custom-tab" + ) + for page in dash.page_registry.values() if page["name"] not in ["404", "changelog"] ], id="tabs", class_name="tab-container", @@ -191,7 +267,10 @@ def build_tabs(): store(), html.Div( id="tabs-content", - children=dash.page_container + children=[ + alert(), # alert can be removed after survey is done + dash.page_container + ], ) ] ), diff --git a/pages/select.py b/pages/select.py index 5b6ad57e..c92d3b09 100644 --- a/pages/select.py +++ b/pages/select.py @@ -231,6 +231,56 @@ def switch_si_ip(ts, si_ip_input, url_store, lines): None, None, ) + + +@callback( + [ + Output("/", "disabled"), + Output("/summary", "disabled"), + Output("/t-rh", "disabled"), + Output("/sun", "disabled"), + Output("/wind", "disabled"), + Output("/psy-chart", "disabled"), + Output("/explorer", "disabled"), + Output("/outdoor", "disabled"), + Output("/natural-ventilation", "disabled"), + Output("banner-subtitle", "children"), + ], + [ + Input("meta-store", "data"), + Input("df-store", "data"), + ], +) +def enable_tabs_when_data_is_loaded(meta, data): + """Hide tabs when data are not loaded""" + default = "Current Location: N/A" + if data is None: + return ( + True, + True, + True, + True, + True, + True, + True, + True, + True, + default, + ) + else: + return ( + False, + False, + False, + False, + False, + False, + False, + False, + False, + "Current Location: " + meta["city"] + ", " + meta["country"], + ) + @callback( @@ -269,26 +319,4 @@ def display_modal_when_data_clicked(click_map): """change the text of the modal header""" if click_map: return [f"Analyse data from {click_map['points'][0]['hovertext']}?"] - return ["Analyse data from this location?"] - - -@callback( - [ - Output("banner-subtitle", "children"), - ], - [ - Input("meta-store", "data"), - Input("df-store", "data"), - ], -) -def enable_location_display(meta, data): - """Display current location in banner""" - default = "Current Location: N/A" - if data is None: - return ( - default, - ) - else: - return ( - "Current Location: " + meta["city"] + ", " + meta["country"], - ) \ No newline at end of file + return ["Analyse data from this location?"] \ No newline at end of file diff --git a/tests/node/cypress/e2e/spec.cy.js b/tests/node/cypress/e2e/spec.cy.js index e423f957..3c48fb23 100644 --- a/tests/node/cypress/e2e/spec.cy.js +++ b/tests/node/cypress/e2e/spec.cy.js @@ -1,5 +1,5 @@ function click_tab(name) { - cy.get('a.nav-link div') // adjusted to new page structure + cy.get('.custom-tab') .contains(name) .click(); } From 8633840ccc16eaef82e43fc51f7a05d31d56ed22 Mon Sep 17 00:00:00 2001 From: t2the0bi <49641232+t2the0bi@users.noreply.github.com> Date: Mon, 24 Jun 2024 11:45:19 -0700 Subject: [PATCH 26/91] fix: adjust dynamic layout behavior for tabs - Fixed tabs layout behavior for smaller windows - Selected dbc.Toast for temporary survey alert - Adjust test files based on previous errors --- assets/tabs.css | 1 + main.py | 16 +---------- pages/lib/layout.py | 48 +++++-------------------------- tests/node/cypress/e2e/spec.cy.js | 2 +- tests/python/test_utils.py | 8 +++++- 5 files changed, 17 insertions(+), 58 deletions(-) diff --git a/assets/tabs.css b/assets/tabs.css index 6fcab5f6..15590dff 100644 --- a/assets/tabs.css +++ b/assets/tabs.css @@ -284,4 +284,5 @@ p { font-size: 15px; border-radius: 0.25rem; border: 0.5px solid lightgrey; + z-index: 1000; } \ No newline at end of file diff --git a/main.py b/main.py index 9e6ddfbb..764c023a 100644 --- a/main.py +++ b/main.py @@ -24,7 +24,7 @@ ) -# callbrack for Alert solution +# callback for survey alert (dbc.Toast) @callback( Output('alert-auto', 'is_open'), Input('interval-component', 'n_intervals') @@ -33,20 +33,6 @@ def display_alert(n): return n == 1 -# callback for Modal solution -# @callback( -# Output('alert-auto', 'is_open'), -# [Input('interval-component', 'n_intervals'), Input("close", "n_clicks")], -# [State('alert-auto', 'is_open')] -# ) -# def toggle_alert(n_intervals, n_clicks, is_open): -# if n_intervals == 1 and not is_open: -# return True -# elif n_clicks: -# return False -# return is_open - - if __name__ == "__main__": app.run_server( debug=False, diff --git a/pages/lib/layout.py b/pages/lib/layout.py index bc1cb56f..3921c794 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -26,54 +26,20 @@ def alert(): is_open=False, dismissable=True, className="survey-alert", - style={"position": "fixed", "top": 25, "right": 10, "width": 400}, + style={ + "position": "fixed", + "top": 25, + "right": 10, + "width": 400 + }, ), - - # Alert style solution - # dbc.Alert( - # [ - # "If you have a moment, help us improving Clima and take a ", - # html.A("quick user survey", href="https://lnkd.in/gFheGNrt", className="alert-link"), - # "!" - # ], - # id="alert-auto", - # class_name= "survey-alert", - # is_open=False, - # ), - - # Pop-up window solution - # dbc.Modal( - # [ - # dbc.ModalHeader(dbc.ModalTitle("CBE Clima User Survey")), - # dbc.ModalBody([ - # "If you have a moment, help us improving Clima and take the quick ", - # html.A( - # "user survey", - # href="https://forms.gle/k289zP3R92jdu14M7", - # style={'color':'black', 'font-weight': 'bold'} - # ), - # "!" - # ]), - # dbc.ModalFooter( - # dbc.Button( - # "Close", id="close", className="ms-auto", n_clicks=0 - # ) - # ), - # ], - # id="alert-auto", - # centered=True, - # autofocus=True, - # is_open=False, - # size="lg", - # ), - dcc.Interval( id='interval-component', interval=12*1000, n_intervals=0 ) - ] + ], ) diff --git a/tests/node/cypress/e2e/spec.cy.js b/tests/node/cypress/e2e/spec.cy.js index 3c48fb23..dcc84470 100644 --- a/tests/node/cypress/e2e/spec.cy.js +++ b/tests/node/cypress/e2e/spec.cy.js @@ -1,5 +1,5 @@ function click_tab(name) { - cy.get('.custom-tab') + cy.get('.nav-item') .contains(name) .click(); } diff --git a/tests/python/test_utils.py b/tests/python/test_utils.py index 2e7dd86b..92672cc4 100644 --- a/tests/python/test_utils.py +++ b/tests/python/test_utils.py @@ -1,10 +1,16 @@ +import sys +import os + +root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) +if root_dir not in sys.path: + sys.path.append(root_dir) + import requests from pages.lib.utils import summary_table_tmp_rh_tab from pages.lib.extract_df import get_data, create_df import pandas as pd -import os def save_epw_test(path_file): From 414ead11eacc16541c618ba099e1a98ddee00757 Mon Sep 17 00:00:00 2001 From: Federico Tartarini Date: Sat, 12 Oct 2024 01:32:30 +0000 Subject: [PATCH 27/91] GITBOOK-54: No subject --- docs/.gitbook/assets/utci_image (1).png | Bin 0 -> 150010 bytes docs/README.md | 12 +++++++++--- .../tabs-explained/data-explorer.md | 9 +++++++-- .../tabs-explained/natural-ventilation.md | 10 +++++++++- .../tabs-explained/outdoor-comfort/README.md | 15 +++++++++++---- .../psychrometric-chart/README.md | 9 ++++++++- .../tabs-explained/sun-and-cloud/README.md | 15 +++++++++++---- docs/documentation/tabs-explained/tab-home.md | 14 ++++++++++++-- .../tabs-explained/tab-summary/README.md | 7 +++++++ .../temperature-and-humidity/README.md | 9 ++++++++- .../tabs-explained/wind/README.md | 7 +++++++ .../documentation/weather-file-repositories.md | 7 ++----- 12 files changed, 91 insertions(+), 23 deletions(-) create mode 100644 docs/.gitbook/assets/utci_image (1).png diff --git a/docs/.gitbook/assets/utci_image (1).png b/docs/.gitbook/assets/utci_image (1).png new file mode 100644 index 0000000000000000000000000000000000000000..cda064ad84f26f01abce0faab137fbf7e88f971a GIT binary patch literal 150010 zcmb5Wc|4Wv8$GHLI~leJnKe-<8Om6)Qz9W1DnlVd$UIMF2$>U-DTI_EDf2u_l2kI! zA!N)PXX*X^&N+Xcv;TNMUTu3n&wXF_b**cybv=G6N^&$*tW;!VWHj;@WL3$?$a%=f zwh{?j@tfm62cyZzxXI*YWv)8JOmsVH2DWdIOUGSFe8k`nB+3d~<2ZTrTmE%go#EDerv} z9`5MuT;?$2p^!}FO=9x5{rNG9e&6}N_fnkZL-nEZ^78rt*^YBR!;h$!HimBBT7e6R zmvY?8!y|aR(rf+is(W?o83NDOo}SbA|NMM0iC9j0`u?%8c7Mv=BKs*i>9zaB8}E!- z;!Zg`4L?m)ihU+(&M89-<5fQGw&u`A^DH`g+t#hS7#QeD)+3*~^UQU!ihmb5&kWS~ zG4MJ_#YIGX57;BD!T+82*uV8Im-cWAXbw{0GrfEFu8B#Na%V@!mVE*O0!NQ# zl$6AFl_n((as+=InOmIdjSmfdKz#e=%@VmF1?lDc_q!t_nnxCYhdN4eMmC-!*xK1G zd>6!4l+I>kWSDpUY>F0L3h4R!VKMQ`moGGIC%L_-X=r#ApOOhuY<~agvqU`?J3dA# z&kCZ}Y+uE;)A7owt@Nb!_I50EqKTzXZPBf44J#|Fr7b>HW687Y+cZrNhbts6%=X1& z?Ia~6_IhFK+HThE-|6Syebnk(k(w$`6nV0PJM#}2>y_sLpb&-d%<>BXOReMGdfw-*%>`ehgG z<>l4Yr8%G0<8MFnvx&{vc&zl;cCwN~f`XT?UyoX2NqO<2zOj*wjcuP-+-X-1b>f`Wm;LDEb2jde~=PV-KFUS3LG zRu&c(2S-O9ueWdCV%O+N4RzuvcZFOei9B(!DA zmR-AcO}P$bu~f6f3s89@@>;%sU%$d!U%!3h@gjIQEKT!xRvZ@=uDUE|W;XflwYsp7kanp{ zT577O&FJULN#{e&hD7gm)i*SRMnrH*+L@Y}nVFiVM@B}5hwmo6Oin%^At7<#z%aHH zd;Rn0&)>g)ppP!%qzt<0g?CfkhPAMZ36Ke~LH#s>) z&Wpdt+pK!@7!VvhGUIUa{vPXgoQ@!qgeqHnWY+m+vK~Oh9Jxja)?d7zux2qoh zxfd`qo>kUU?8v}lGe6prSHmI~%t^Cz=h^sQC8J1W?S;1E0RaIPb=+h+K2<(+0-E_2 zV_%ZR!WR(%)<{;;?(6+kKJ+B4^G@bt6So)R-B!|=^#ZUZ=lq#YoH)Vbm7b9yy}wq> zb!EQ7laij4bUyGGPi;N|?%4?wA}KB|POLWHZjyC9V0C51?Lt6kc(}JfY-#B_zG3r_ z#DDnk!O6*q6wq>NtBV2hSV~%&A5m8*YTx;QTS8HUgg0-FdvM>laf6?qpGd;@=H})u zadhRHvGrt^mb(4=nx;FI<>x2w;vywORL|6;zj-b}<#$)XS)#0!m5`&zmwd~?SFc`8 zhyMNhcZOtWXviKGBkdl??VFgmS5#Cqn?s!aQ^e7mC>lFF5Ij}A+piuzAtAxal~Gve zG&|Iw>nz9;;JuUi6k2P{rXkJW>lrP$z%J@ z=}O!2@BIflIyw?#*>Jsk_Uv(p#uE2?r=+ALX5BL}xq0=fSKbb4>gt`yyTOrbvyB1{ z4eyLwWktDZIHmFv68g7o43~;wR}*u%ZE6g1!X7`KuGOaz;^P}-c#@HrxV4Pkr^?va zcv*c_Df%SMl=IrsOz`F0%1Y8&)Z_5*qeqWUc6%zvh|x2z4>g3DcRoz-a(T9xNQr~0 zqOLAS$k516G{3m@jDQo4H>tk4IjguhhTAtio!?94%$af|!D$M^=D5lJXa=77MuD<< zT}K>)`T2PwiIM66KK(4Mw~x+VuN1|oFI5Kl=XU6j7wflWYB@CE>!e?hAqNj-os=Qw z=aKWg$jS;H-~asehT7anq&F!+OhV%4(9l&iwUp%K?GzOF`N@+f++GutlShsmsmayJ zyVo5hY)B;8+1WjK@Zj9JbGL8b_V)Ick&(gaU0GQvEiFw;OG6F6diYyUkG7ng+{KHp z5k3eMycntObi6Y@`;gl2(W63?s+Pl?f3ujk^Xxxzr1SfCCPqfD zNTa)VCB($Oe*b=O|9+7ZC(M4lkwJz#5c!m}>}Lq_7cS%%79yT|%G|lQ9yA#YI_+vFDKZJ{-><*1{?~zx zj*jkhy!*(f*siXw0~aIqA2@(>eeJc{HFfoUUIu_8_qy^MBL%pm-HPA5u~_`wjpcWp z>f(L7e|UJQhaJqdU4qrKhH$q5%PcXg4MkXTqKAt*>+_l1Ll zgNKKwzrR26_OH~0moHi9=_>|jaG~v{3%qWB;*5=qP`fM&H09+RGAkC`QMNw3R?i}m zR8>`7wV249Xq0{~FOS4qj!3Pa?s{|kY+HaX4NV|mf-nm;Nq>OrbiA~FeTcF~PtVB6 zh>vn^o1yF1wzhat^Pc33k<-nOo;~Zx>veNRd86|FQsi2x!{94;T1Ser`Nt23^7~tm zPEi!N340|a*@90lOiv?Ey&J8Kii+Y@j*}%^zj33sx|)xVkJ}3<&wXvC4m&hh7re1F z7>sa4rbbd%JZL@lvnjvTVydS^Y|yB}W4jM6M^={O`r_U_$E$%1@^?aO)dhDabsLDDL5IuTqai5mDd?Me|X%LPTn>PbGF62nh1 zme$t$2z#ZZTt3B!<1E$%bHz*AX9fj*3f(6+y>K(vba{Ps3J~#2@_iB!&c!>o)|Pe^DbkqtgKveaYJC5 zn2b9sXIzY^uBjRBcy9tQ*6X^+5oGtFv9TsM`|aCD0Rdk>e}0*kHnXts>Ep*@$2kKf zrJ%sTrcNaq9xr!_pUv^+CMJ^l^~3}fqg%Hu5d0eshsC`~Q6k2V3kt;DR{k#bqM8s> z$%lr9fL;98DF?dBVq)043PddW&csal5Dx=SW@MZr;9&Y4{A5{xAlFP#xscQX{AKxZ zdUm++$rHw|k%LE%nwgj!k#ODWS@9+}mx(}5O_4Ot@&d>lsY_~aWls+$@0n8nfq?-O zxA(cZU&d@07#N(LSMP~lyl{c<*s&DVG+J6(e( zk(H&4t)S0G=FQ2mZlg0tpaLHR*Cna&9X@;*sp0a~t1fl3WlcD5NI8cXA0cmec~vLL z1zUW0k5n@%{{7px{vc)`$2)iKSPA1zl9G~E!koeCDk|l}R(q%)W@iiT89e2@K%MG0 z(Cz=|(PmkC1swD7qoSjueU8BA?81X zAHk;5lLl)8qwzu5xpR3}Ra6L6R4aAc3{c$e+?iQ+!G|Hi3FW9?MgDVgl89`?F?gSs z=N_3hgh1tza!|Q`9pHD&rZ6h1{ke>1&!7FyFA@_yI{!FvW<3oH^Gpp44GUwqbnJqH z0`7hnl{Z3EcSkacT0%ksJw3hH;6)vsjm2Jfz+2U;SJ#5`=Ks8Cc2X(78B(B1NO=16 zDVO^iJr8g<;G4mx7(e2dmX?O*=Ea4D+Ri@BLz-D60xH~gO3LPFQF8>3zCt0up9+w_ zx7d5X8r}Yn2NG1o3_h|HH~G@(({`bjoP+*O3lr^c-wLf&{In3Osi|@Btge>r zYCe9e{62D-2e;i+PoVwvLYqS}M2Gp^r24O48O`s{I7qcH4_Fl-LW}GsSAIsD*B?&7 z!ek1S>STTeYM6eEY}|jqeSMi%+rv7B;Gt3KBCh?`?XT0U(Nfbn!rz%XE^?WW-X$M8 zu3EOWmGLZxhV<(>(~l9^n%3`ga-8RWUTWUBsB=U}{Shf4#PJ?W6vNB(^nliF^_ppi zuB4DtQihdWL$aJ3X*M@E=Oqjv+vy!=LFl%&hBc4)bJEz${pjdG5!ppfziU_Gt5=6t zb{`fHD05l1n(duHB?CS+J$X}IU0p=|*UVtuKDi*qzlTs62(+|J-6@@#X(*4&b0dw7 zjbV~PQ6B~MY8jcB9>qD>+WrA_s?P}v3HedxzVR_i_&R@tmzS)jCc6v~D~Q0w=gP^A z;E#t&>w-9hMMM}+9n#Yq^z|iJdE!F2xVW-=FP}P9g09*sda*J5MvehZbdy0&JKoK3 ziNP>i15GIocU5$(l;bY*T)W8*^lo9P4F)+QP0>ERtRCElj~oHYY&Xq)`_{_M?euaX zT3dVoKhyZCvX-zNJ5$d=MWxb}8^ zIRA6>@tzv}%qQO7=bC+9Wo6M(QSJIGHb34*=k9Rye-zA#^9BC&$PA=ZboMa#$zRa!UD@9jWK)%`8x5J82pc5n*tg1tFeqKq6LSi_mM! z%6*P{_UxgLPnet=;A4I0K6=vS%a_SzKn83lCx5HDZzK)f>4y=wr!qBF`U7?5l!B7 z9)@u;1iwJ4_wU{n1D2CU(Vbz@wmu-+Jg)VYBd7%;tTX~s-*XI`r?(-KzKt235|*v0 ztIN_ZSpi&-;-u4Imy&Dk3|j7Sda9)UZtR)qRE2qO(f);yjDVv~zpkrla#ZlgO=um;}xSCf8sET{{e$Fi?&XwIUGz?}J z-^Dvp|KS4?7LX30!c=Eo){7TIYs+)yY}#MCx}+=zsyp+{2UAfGkQUK^q0ob0Kz6M@ zTZQxpfbM`wT^yC2ZHY!53e`m{&bpsUb!C(r(U`>>-M^JA^wzkkQlmY0`*{P^+Sl-hC-nu%%l z&qvfV)3dVB0anUCjE#+z3t~t4tqk9PEYe?8(qa16l`CQC>FFSt5cv2Dw~fE{2`Wo< zT<#&fKU!LN7RHRs%xa1T+cTq|J*#hJCV<)^6WGMVI_`hHiMl!M;3sf`aH}Xwz{V&# zCC<)VWTz=+f`M15`tKu9VkjOcjR1*g za^r>Wd;qSKK0f8UmN_c1%2uzoCxHQWLgZe5@X-JI~?$gk&#gm z4KKbdpXMF9K$KI2O=yIjuD|JcOaI%ePGhEe)`TC_#ee?%8PZ0M6+3QZ zYPx@lTvbP>*E;-Jy2?orkv>+>r^Us^#;qyIDJioKQqt08xQ{Nejw->8qXQhjjSe}f z({zOf5-V%C?Rle(ou3{@6>wRppomXEW<*j%1_=YeM*A`p%Yb#Psifl!&cg>HCg$gD z-mcQp4Yszm)s=~89*Yck*IVr9NvZNyBYiW`ds%FMQgKVO#OeL>=iK}D4I&1Yl5E!3 z9Hs23#1}={IS&#EYUvsewgrHY!37>XaG<8Ty6;8*%a<=Xe-GuG_nv0Ig_gLtz?xTF zT>QX+?do=oA3iW0)AUUmo}RW~zhxm7gRT$cmHwSOG#nC7)=UT7celLlKY&b8nc;c$ zux)3q86DSY#fulKb`v;*eUehpp`3D8T^_qrvz`0U2NAI0Jr8zcUc zU%UW}?$>`6@4jB}=8ek4?io2DFbI?^N7T|C7ACF(Z7L{G-E+FAq*S{&wL(0lcm^0U zIwnTZ?#~2BB&4&Y8ITE<_xlG1W~ckBuqvQ_vNiZm$Cuen-dH>LYjQF+I$F}aN2<87 zuflUD6;-`H`RAi(bxe?0FJGn~IiL>SIyYB{g_@a}nKM{Ut<r4IVcdY#K^77*Uy+T7}RBs&f}?x2fq!_7zdyN>RK&R(-BG6#q#RatA*<+9qsLA zUHO-hgDzb%TVGptxUHqdk!i2=Ok{^Zfz>dT_T#v?prURHT6SL_mp(ocA0yJ^L0;Y~ zX=un&dl2a$?M|LtFiY~ea^=cYSAi!{Rb5@$Y5t|3-wvOunfZC@9Xl$deE@^eqiNZL zxU9KM<4s!r)20KA_o?3k#9|PabWkVj;Naj4IoXh*#rO2w{+m?Pk>BS2`@7cL#X#o)VphLuRE(2TR03&CX*^E8LNx)kW=}}R8PLtZ)TNVc= zCf2Ixq|un5$~~4|)171{@BprT{;YE9)T!;;y=(lL2fuDlP;tP@%+sEFu+&@i`Ev;S z*4LSt_Y4eD5)xXzeDTo9OiB5Ph9a0tT1{28qrln_mo6i-Mcki*EM6E%9V>xX%oq97 zlM+E#DTEicJmrsjM`x#H4l{tVHj~%qg~?7z8rJ>1ya+BfcJ=@wz*QX$mwv}LWKbT$fB&7IE=`V(mbY5ipL2U@ZEZc(U8Js~V{T@~K~Fz}fKE(I1bMi)a)RfMfdR;= zjO1jHKBnD;;fl9@S`{q0q+PvhWb_i*+rwjP918`{{L+jb`|@!X>Xw*mhr_A%y#KiXR-w$Tu==Hby$WXu! zCv14eg%m0Ujl`0=B@p1`Y7ddbf2q#L(H`3)r{+UB#$+DBYBUw^?J zps|s1n&&%kz}xi2M22$6=;}ENGC&)AQlc>@w30RT!u0>WtiUi(9q_HXiT za%rz$L*@b>h75+5bnx;KPfEIl#l^PP*70X|x0W*pR`mz42$D>-sn(8uZE2Z7@D=Yr zbt=}Brh1Z|z_XU&!M09M+P;0ezFaT5fSGMlW|^aMyWG{qBqaw^H-k}75J$DJun>jZ z&CN|WsnUt#cjS4(<+Q6-vPVeh_$F{>2|N<9ZYV5WN(u=o_2zz{fWm4hHmD%YaDW)0 zSjjI{q7^z%pkWtNQBZi5nHkhY;Z6+(5}88zArm+xdIEXAc-OaYyGM3{1-W>!5u6h} zIsNX<7gl^2M-&PXKn21doc7^EheBz>P^GxbET?z1JiUz#yqS0JZ@&*08tP4m=eg%& zSJxVXD^WEK@FPFz3?JiwHsR8xCg@-k+qa)Md2*nb-%8-biT65g%i9-gk7*a@Lp=d~ z2}KBLn81UpK*4F*8WL<-TL2^uiXSIT1(jSy<+xrgwN;~h*uk`yFC82laEg*Z9szwJ zt0Xo&bLbP;B~QW7WVGuXk-Ytn3~eEInO~urW+7)PxD-A<->Uw;^OjzpH)YV&UtFl# zob_?>@yaKMAC*KP*WoIlrhb8h3#bSnw&4(vZ8fZbfO&A{OtLB{GMol9e??8L3}#<> zSF3T?kPtQaBi_B6e|b3xSJ4J`iJ95(_U#Az&R~PBNFH|@Be0TM#SZk+M}aDi9P#(_ z!?}`Po309$UjJdn5xM5gWHSFaBH|#!TD@_Q?6>dV2h=DK^V!0C<=7#KQju>Jnp3eM zP@PW8^x1uGUFVOKfWp5HQx1%z$3M;ZR4jQ-tcu2`^8B$cg*`m z>llD99aMn130oT*#(z5tocQJQ=V=^Vv1r=+S)RGMC)=;{JggfG-Ax^H{1C;+$*{77 zp|1Uj_4j21!opfNajsd1mwguU+pAX!^nM%C|Hlg;FW-))AD|NyV_w{d=daycAKXI0 zhNx82OO4aClp5bSJW$FiC|0num=XX)()6S=%KXy0y~BP8z?+-_cKAGqb58NF26i!v z=Z_!nk|7RVjYdC-&K;=R1ZOzY8fil5>G6JaGW$*TzgEk5m637j;zeMER@ADHkmo9V zsF%oi63z?!aTQ?-$sIp_@ZWeh8f{)$;oN`72GW{_yNQK`1$O!D*|VrB_*-LqP==oEs+Gd^XlP0zq^aT^VHXGQqKB4L0MT-ApEK-E453Ul3%_Y9Up(4mgWfN z8*uMa(5sY`T!3q{&fI?aFsGS^={jiFgsgumTK(>bvz5%(vC+7A>lU|C1H!dp@w_a7 zW{;59ns39058C+_{ip%xxD-+9K7FdK(7$q}73v)Y9p}XF-#vd0)E~6S7Q7(E8B8rx z#PRdO`gb&1k4}t)S?&xxpQM_mPww%$Lj#m!-XV?dU4cC{FH%xgR#yjUD5zO(VN=Y_ z-G@JfgA}xvS}G#Oe)0}DQdmo&n{z1S1W)#A+D>m$OTXciSe7Tk2S^E=whN#exDG3i zN2$v3pJF8!QFlUEx0JFvyz;!q#{BZ?VR2pSZ?CSQTg$&p;l3S5p-)@R>iD;T8vmw` zAE!_LCs^CqjE;`ltQIm-4X;j>#Y@=!g6jc=J~|@e{Lklc;?}k0<@eb`{t6=ZJc*11 za+X8NI)5Jf#}rRke+6+GYyBRq^?L2CoOV(Ibmg49pWpt{d9V!TBxPp0^;d3R69{@u z%T7CjGL8&+_3-YtbHJiXu@c^O>b@r}@WZ#ZHc?*Q)XYpJ5~&ij zF}7&Vao8p zB75rs&BNh`C9H$M`g?kwMVo)^TKF06&rqwHc+xU5{wyxyq=Q_BE2ipCE?gvC4?n;W z;W__8KFnfUj?wBn1Tf&QcCiE6Vx9vB&h>Dxvq4mfj8xaw=8RQ>P8#{-NsPGl!PESu zZy?NCSX%NB2F%jv!AwHR>I3~!#bfaCsi5V6EL!9PXclT~emk+-+b`n!VXD9$Lo8`& zthsgEZDlOgymUDwF_Cob+HUvF?bb9MtwFl&>h87-0J3f|6W@@m?^y83dH0T8#0a%c z0U$yAad3=s1h+358W2R&%WHrd76o0vR^TWmJM#u*W4*k{@r{|^bcIfTY-m6+9HO&U zQ)7zzs4XLt(E0Q`AjG15IF5Hj#N#VR?GZG&c3vSUCEg^#itT3%O2nN`^| zeJWn)`bifm4JZlKehd%8P}5uZcCb@<^o`X*6(CP-Wz=&y5HBQ0%t<&TL6H@df(CyNB01QosxzNCzdz_Qy9X*m>v49^n4PYeB= zS}C}9_wFkY0_o|RIR;J05%@fqA|OI}F;rsNHw0@PrlKzcL-iF{SQv|%b%80+H)r&ud80?4h1kYZu^*F1t!!*< zd+l^KvqWA~hTqueXmLhMqK_-syuysVsDyMZ$NSoI3{fT^m_nOLSth&Spn9j z&FZP*&(2a4;FaD8u1yg|qEa^9;S@UqG!`wNg|`~~+2)CyL9Hzigkzd(z@$A_u5C9e~<5N??br$|DEiIOIc0rFG4S>b^ zo+0VFA`-NgXV&>J`bS`3;EYdqZ!cHy8^Ft#IXQEnbg{r?8|$n0cr_SK=vEV4T0epz zKubJOTm+kh?74G1UZ8t%8XJByqt{()j*pGKq?Eeq$18e7K%h6*tP?4QNJ6q1(y~OO zOix0gLv?U4C;(#X-*XTma+mGwk++7P6FUl^NQ?HmT+z|NF@6>k6W|km^TrJn2WET9 z3XX~jck)=J#hICkZLz2|5)jwm>p)3C%7lAE^YUdu$jLw6+y?H1MTU@|Rb&V03V>_c zp%}=kpR+8GP4v<5%z(qDgbfD;zOE_poIkkkm#Let#i0fUcD>@p)btlG_De|>k6Xp? zbyfu-SK;izL4=G2`p{u-M_=D9+^o8K8@MaTc`!6^h+BCz=mYAlEG@mu%fk+Eid(%f zQP}FhOw_yIOzU5>*>{36KpMNKpis|r2aGG&t6<0?^dubs(rOnXDh@r7D_SD5_JbJfrA{HT6{>u zW>LxG<^(hi735%;BQl22?&xB*safriP8-+Ha|?-fzq>$iWxjsxj2>O;N(@w`xLXR~ zSw4NreEHIs+ZU_1nK#L|S}m|$*1z@>mI6RXEPAioYR0QqchU5KZ}p*J`#Cn&aO*hK z&uLdT$Ot_V(mTo{UycN8tEhYi6ElN0d}+x+D#Hv*^SdPd_3Nsmf3Kt{w&$7mwx%k> zn8nV)aY{-G6z1>VvS8w?moK@kpEgXf$0!aD4qE^EddLfT&Ifn~ga<^ThLbz9ANY1N zEump|IB6ybRCn5Lf~Eae%O7&fXaE90ubscAy0i7_P+lO?MhNJ@AN6Tu4#)-Ow!6m0 zd$0$oiMNll=q_q)->CHIDyO?248cbfO=ZWM+g0G|%T|9EAyE5Ue(AhKM`8x00OwL? z{<_P->0&MoU0vN~wlKlF05k<5#XBAb1t~!}8C|%Ns#NoO)W*ri=H2KkI4{t0+y7;{ zcKLFF)1SjV6ur^Ac=jJX+TGQq_#5uYp3Ki|^2(v@c&&iw8mH)Vmzt%zT<3z3~m5CEDCmrh6C|T2Bni$jR{@I^@@NP+a`_gk2$l2Z1viol{u&EQW9lG_F8b zj-hg!EVV>zDNJIVALhF4du{)kK6-|XEkgvuur!8hJRJn`w(4SS5XVJMv#&2NJKR=L zA+9H`!cd0vTODK!OOmGM+G%E1BKelZWs3NtSP`73{vEH%w!-#h5#QI7n3h&xKcy}8 z$;b4lb2Kv2;4+1&scF48+VQfr*_|>(xW9Ch035rs`><63X!^wEz>10rkfo4alrLSXNs2?8x3;z>>u$2i zG#h>9rt6JZ8fZ!27V{&j zg32uljew~IbO5Ya4~_^zOM;3& z|Gl2#W%N@bA|e+qTv%FOrYFCxt80aOyBK+FlXa&HzpSdtDyR!CXW!}Ro+>hzgA)@I z)9Ue$9#M(2q}_zy1#CTh(x$M^#krV+M?$8ck6o#$sNfM7U&g@%?SzKeM#2>=4HT(^ zbD8L(Q7P5n5KC=s@J1^&H$4r*-wkAaLqpzhh4*jXut?fXfEse<3x?GaH`n?5V&mY< zbiFC^Er+BXkzD4}_JOf6KcCPLt+#}PkPsqv0%p`hhZ=^h;3NXi8xs>Voz4m89a8YO zZ?>>lCh7?E^(GSI*VbVio^EG7arZ04V|o%=$;uPm%{oR#qbO`}Cjy{e=&3ADNvVai z73+$k0c~*?$Ix8O@+LabfkBT_X$vJjCCbx{nylPi3%)I+qcIyNSy^)0<4+Y0@{BLX zXPC5Q_REBU;930h#}!2q@jMd0;beC(C+d-rt&batbbx7^x%57J zs&w_!moIPW{O7Y2<4?yHo4J6>9G&b3qRch#J!ZG+D?lqQqb8tq4W9VEq(tDGw5&$l zl&haYa>SD-SFc?|m$qs1gnYp!dT-6+*~ciwl(I7Eum;9WH_X`^H=b4XqaOs}wg!_B z3bLzKIo&lK9i1yz^so-2qdz7CX}7?de(#nPjMmQ)R1Y6gcyQw&myW7!K=bJO1s+jA zVw4@?G@#+8D95{@^sT|ke3+mAV{dPKS08E|go-AVz>JLK z)-h-T$Rmi|NszHzyZ6j*{ohnTdEG8n)E9p&JEV+G}#J}(dAjcnZ-uN@5NNO)0Bkv1X);7=rxNDIHa z{kKtr)IvTBWETew$;2~{=tuilTUWPe)?eZ5`MlN~2w4je#oK1)A+z(ezYYnc5aHPzYC5ja}o`t@sIg4)^y zVE94;iw03s>?lmSaQ*u7_l%nWDnrMB;^0byJ6BLdWTME!lWyyO=6SLu-%Rb2LR>N| znZQxka}3OSOJi$vmzS2hN?qA_L|_-hYW}g^BO~BhV1jrcBP*8!v>BP0Kn8||+|bk% z1pe#S0I78*$X=`OZ2FS!|DQY6M9&JgWa!F+GoRbr{kU@4Cr%$`T=Kp3QS@IdEWH48 z2K?lvXlHakYezN9zu)$SJq`^88($MV)mzs4<;&i}T=X3R0$BndwmZ@M-){oJi1f2$ z>E^Z$eyaW&xlB4X5x5l!*%^3blT}j{qXq^C z_c2nTmsC^|wgvFQIbSputZ@E6(?ktw74W=;GpHWi^Xh0zu(pQ&V?$ zH$>poLd`zUGXej-jcoK%$dT>;J{Lc3CWL=4Cx-vsF4_Ga3{3p@%jcpp|Ic6je|>8y z1-T5GUrZ5TJjCgLN3?IQa&X3Nd6@ZRbct>aRm(6MG2{yoAt6-ud(Coi8%o*!`U=el z*DGxxzafL$K))7w^kxct425feiZ}rW30rxpZ*68hIXRy*+~p=vNi@-_Gchf|)CWFi zQKq0MA3Un2W*1l@K)k|kZb%T2Mxat5YEaYR0>6=O!Q)d^VFLUNO&0+CQi94!(;v@a z5@l!a@~;7Tbz3NEsIdfahx0F7H^K%V!LO{UOTuamPwj0~A5@ribxAi>@_$Q3#Area zps&9E{@misOj@3ypFbCZ<-{DOxk;nYPq|X>Pf;-sA_?d=(y@y@hisOa*^{Ls4Y7kb zU?B!X2T>37B9yb0!C+}32^G~@Nf=xcBgDpF#BjU(NJyJ)q&dF$zuaowR@4?xpUh|MnDSqwek+D zaFfc%q391%07+%jukOa};pqT~*gX927HssGBz$ma3Y_}{U z0XkbEQBYRigcJJj*OA8H;sJ{T0FK%LcyNXQ)elZtg$oycbat8;8~ZW+g#HOi zWb1=7a&mQ0$&{wjg5)5IFjw9bVhPAR;Q$T-W>(}|Xc*#>l8yBMcNjdG?vpV#PQ&M; zpDOr;YX(Nd1t4XDL5I|fBb^P2{_53Y+i|A&W*l}j2mMwB*hYAea68z{q1)TH%0RA> zm*0UQ7F^-mx4)K;!oZ7uEAsK z4GvzMo;ETv%6H!=O-vksrhlDZ4TDFRQV2W`=h#yqt^z%v9GJ%tD$z~3@7{?B3d#`r z$Iqs=Zmf(8C@VKZOdAM9#DdEaXAcSs3rkGYZ8m(cY5juY14}us-7hEzvjI7v5|InL z;gvlqEv*A@yZgqvuDKeLm9FjpuA7FMy54>RXB~^qAz~DQ37hM$FaZHaO6H99)cAPN zzbtsa`J%IPk)K}!&T>seTU#4GAb5{3g*4ow_PA6_%gZV%Ay1yj5{_yYKW!diAfExT zy3Y%Hk0$29dR)dkleS~?W$)s#GY|n=jYJ@tfQ2_ST$~@fQXGY>3PTVy9uhoSuOJjb z!@qj?8W1k3E}%6Q%Hd)$Ofc|q^qr0XEe#{%V{TvMh`nB@*-m2Z0A~BVkeT-t5+i$? zo0S2F^FBvMMGcRP48BJJu(7w7BQ!KN=Cqqarb6pG`|N2{6bvjpUh{wcz^8>+1*djN zQ4#71e7w22cKZ4+V1Iy4;aLIYK@48_p1kMJZy{cn?)Gs$g;Hi!py|OqUC#2B>;>ND z^%<>gVO$#)77K(~HyI+-N4T8p3RDr;nVFg7G9ZgCkVt#cVZeb9pXB3H2m5M3U?9BM z7RwFPFOfE(@E(s+!~(I$!L$&{?TbF9-vqM!LaVYgJ$cLL&&R>1dW8V{{xb;-U`(*E zWESk#7 zHk<=M73hNa#>3&i`ud>3ohO2<^2XUnNindsJt0HH_y{zI?(P!ol+T7w&;PJRd2g{^ zz=QF26i$cb**h3o3l65co=`Wq2L}v8hI^JCBH9^56#LfPuuHAN_oNeFFnexhx$gjiReUj|)Ey#MkqL-OS7kNoe*N0(#*)Pr%0lXCQ+6ye>!pJ*N|h+oZAIJ=!ar$-HgE8>)a9FtAwl2zeT zgl(wzpXPYlix}2`A&XlOr>WLU$; z4<9P~H>U&AuKq$33{xLcjEk#ll%fGrQk$w@EejY1?BkI4hvw!w^(3&K+=Q5BP>Q)Q z@&fsFmE>UAJh;K;Cm>!x8V)Qa7VjIh``m#UTg zZzT7AnPVMGRYQY3IAqP}r%!!}5|#txGi4GIoWiGES8Nb(h&roi>5a88Zh~f7jja*C zloUIUm!S(Q#J=KU7i()_?f18q zrI#VX4ZIrS7J%O2@kQXNjP&$>nS2Xr6h1B6EUv5E- z>+Ey}v|yy#&hrkP0#XYIQh4ytXG*!RvzN48(9Ld@CHQ@EIC9{?x0V)M_w3LP5}%03 z{6u?>%jtN9J4Fm|vDQ$!kr-nzWziXMZE}H%XmFY9gUMl}sf|}SJEdDi>S%7P0pe1qIQB{Yr z^`IBf2c6wu^9)jsZypI_=GnV<0EHcDB-jAHCt&%2a@_gp;+sW}ALouA@MIvr48luY zT@=$PR^f;}m_6KT@E@)u zpW9~+2FSCb{Dj+ONPi_ITJKhZc-;=P?e(%K(fJBi6a{^=RDtnoO5ldT`68+6o;%?f^)JqJ9rY9rOWG60$W&uV+zFAjWQNo71?~crQ9zt*jlT-1@Q)3dzlv1ldper0ko@0( zIZowH=GB1*ZvV#%a14$6bq$RGHc^=TegP3^HGW6iN5nDb-lNtsHjl6?8%)2FI3>0` zRXsREBqg?~%s+P76W;hKdK@{L)gG?f)Ocu*7yS3{8V0 z2rzURzMqII5F1cwy+|6(CvF8a8BkJC{O-(~YL?yrlM^ebN1>ewfv~;30P_u214%%Q zP>cr&vJ-tU^&&IE%%YL4d(PR}8Oi6?ty>xzKj6wh#{r_t1zu9ArA>PZmajxLd}3k3 zVg`KSbiKg@L>>JTg(l3OrL_+LTSCskIBXUYCGu5%4+d<%x$`H$EdWW>#%3OUN%hJT zKo;mUQphiH!PCd4`p|Q34$zK%-aL?l1S-1V3g$grThv!9jRZ0Bh5G-vC|=Bxq^0$j zODDOra@s)(cnymc)9&3LN29q5SQ8KwiYdC(f%k|m_`HS!G%iN)V|4U7$O@1wi}1eN z-E0V^{QX}`%8jG_JR`#lSBO=1kO%Vsw;GWIK(dx67cF7?>J0afcgA68Cg8)<9-W#} zM#HQe9~9<+MjxKA0@L=lxUo#ReCNe`pm;gmxOsRUjBs94Q-k#mB^lsOtQ=StQ%0n2 z+thg~5AK-QSd>uoigBCs|98#Jb&4HkFqz_irL2c8Tmi;AIl`eMM+V7~Fqa7Jee zHmxm~UIEVp$_&Q`iSknP$wk`fLcFP~$52M9d>O`YZE=VMkgZVjoJtinOXz`HGAij@@# zVyVN--smQ`mHAB{7c%$^-(B{xevzYscqRdw^;luUAjDcyQWBc+e>0NVuV0U2{QO~_ z@!h+Xg4CsNa4ky{1dsA5#uc5tF&dX=JVBjp4*Gw2Aupzj(7;HfufPA1Lx(_RW#{Ie zC*VOD%CQoEuu}+nsEUA&5WBFA=zlRM?vz(Txzt_Zjv;MhV|rIx&Ev&(AZ!3-(T1&T z&g_^?*K(>py*p6T0&#Hdf2R0&o+2G@8LZxFm?@ zqi@23y&eu8|F`vL5?jX@4k{i$e%#31JPSsfi3xTIn^7pX76Myp4K`7wR&Cn#uGMQ&G;BMoAx0Wi_W%PWBM6l>Pw zkPr(yyMyMxc=zoS2CRbqj#7?c5oC$qKeD%RA%WL*bUZtsgjNO*p%C7`zq-8q4BSaA zEujtiV6h%CB^{ygOu)l9e*X?K=@}wnZWdn3NOewK-5wrI07G_KLooK} zJh>^K=V=yj@pXLiyCKiDXLoo3E{Vm(zv7Un^+D6hU_I%QRWk6(?4em!?=Ul;X^Su^_D5 zWxaJD!K2B*UNUQwh@YOB-f(1?-kr!TJ3cPtVycq1^%PFnho&ai*&+FxH`&4()(#s= zq^R&|9#e;QjNzin%B{V;yYaV6EMBDk_a<%dYyePK26H)E{iTR*TBCB;l%XTvdfHRa zVA5%Dqnm2*HK<63*YEzl78dNVVxW^In@*I`im+y2tMQVQ?1ZnnxSt*${JDsHYXxi; zpvxhBD5yR*Ii~fPM9>Y`V5A~75w11IQVLQmHRI6-U|ld+g$5B@rgLY}n}x!&Vsn_S z2W}lUR#w=3RtshEiKR;Hv~na8SV#SwJ%pCz=Ujtj-jyT5Px)QA=CfD=mMOY7Ym=Q_ zF39D;^Q!{nEKgPVV1-1Mfs+rl(N$}6xybGd?5<`x(*=bF%rNAYb;*|Pe%u5PZZUQx znDr3DD}`d4ZzR*%?<-i|OYp9QrGdh8W_R=6KfwTdB-n?~@i8vgUjO&5QYp9;${bNz z@uzTNuY1O_?DyctPk5LKo&wa{cIO}8yEF^pcReLPf2pj?QwE9L<#ZV+wXB{0o(D8N z3_Q68Y|`X@H)gT(AMnMAS}Eklmz1{+Nb3|WDQ~_-+JhTQOR1D{p6K;&6jsOSIVrUj z-tUmg z6+Aa5kb7WFMuvw4S*W{qp|W9A2|Ryh=4q#SBS*&qn36&3czBR>m=}7JxMg_N>*c8B zf6BRXYzY?VRIV~u38-UkZi{^K<>|)T^=0!94O4x4y+$X3ozM4i$w*U-a^z2uP}{1i z^xy%5(F8VU$bbAy{&*OW?N|?kEHtEPt~R)A5VsG`JO@L943?N!shdqCp})b^nHw9= z%+A6p`S*9x6ebK-3*U6_gU3u>p;D{~p7%5Ig zRi@>j#lv@xUfplIlyWxj;(Bn&Tu^Z0DA&fviQkidx{?=3heJs6qa_3u0>_iz$}diB zRoyLH#;qz#zukpTfu1qD#e_gkPTD~vc`U1uAl|F+lV$1Pu?yUvHYl~dvmX#mOI7%B&)nIB({=jgT<<$Tj8*Ci54 zvA07|mUT=2NH^T5kd#-{c0%^JfB!y`Pl^bMp8e#4H(+vx~P?*cC*YC zy1PQMRn4Q_WcT6Y$DDvZ8V10c6+Pbl1k!OosjS^?o+Dx(D!B<_oPA>D@;mM%h|z{n z?YP(~QX@>Z1Uts(_s+lK{_gioY8I$K~sm0xDw(P#aW*~kC8*dG-UV5EXasLAMff_QP6r2 zyMv?;`P|QaTQ=qEMX18RZ|U1|=Eu}S+FFHd{&M0iaa`d+9*u{|8T_RCBkNuH^;ftb zx$X@lt2&Ny1IJI%1|C3_1utbyU7f#@+}_QX00(u|%WuEn-vKC8W!#OYCcV0LByI%U zcSo7KyK^3dfKAUQy~KGuGYREe@gPGTMmjg2V&U9#WJH5$D^XncrA}Q?%A*fYVtcp1i6iA3poC?V&@isj)CiE8TB_Qmbn= zMNSg3I)_3Hy$8jj6u$$m`q)k{E^cb$j;B)4eaXtMzyB=a3K0b2C^TpUKAw&dD`ETL z?t9k2S_~yRi(*(7i9qh^z4td@!${jyiR9&5#%!7;RYOO_g8GXDR^Bw-yz=PEy_Y3Y zKKI&Ftn6i2m{ymW@6liCEITC(A0sDERPBS1#@(Nkp2~i)UH1s>ox1m>gz{|8rPV)y zf7f=Ny`EN3=u{p#?&)M}n0~dHj=3&#S@o4A_2?JduL91Ue@D#xD_Bn4Rczk4NxGb_ zQ)lOS((P>Y$8QA%S^PKm{87n-fM@`YPZlo&YU{NGSmJ0erC7$vp_dJ z%|*KPtiAgf$lRveZgwSA%D;E|oML`~(x=#3zQT2-+U$o{=$B8{BXXsu4^K@v`rfW8 z?H=naeD0l|uhbFtI&m>EBN0~Ti(rVr`(oY?9>vcC{r#ZA{)iskBv+b#i;0s*-Gu)p_FLEwsmcZK*2zgvq`l zJI+6I0&*jtluo&49~0$W$;e}5z2*~@^D1Xbv*Vp#vT;Vd&Pc(WFdbLorSq~xGBN^v z-*T^7hT_VMp}9`8sPaLH?UW`i#U(fL*6)q>Ju7x`y}fQy>aR(pNsy_WPW z*%BbBOJDg^kIVS4??;<_s4p#*(UFm}$Pfe>KUGwy%9-6UaQa=~AHHX-gKXpLHp*z5 zsfWiW)_xwn!At4;2FKeyPU>35W$cX2pXer0yQ$h8U8!2D3+Bqon(NutMjFVNd*p(C zZrDlaR-34Le>^JXp_gAdG<36U?S?BaL)Zo4!MJ*9n#3TU+4O0tEY-9LIUxvUc+wbp zZ#ZLJR~N!B>?b1=WI6k9+wN53xjs$LPAuddz42}dRg~`$ggm@=!Op)XvBij0H})3G z`TB|_&4^V>eoDx8C)=tv8(Kx$kS(*WUZGqo-dEdRBJx z>aH+Nj#oFg{$q#^$rL#66iJ1uv%=Twq-`)eA?B$~s7jiF^ww=hB(H9YzE+SrEx>qG zx|>xnQ%jXVpeFL1*`dQLCC; z9I7*YDhX}LhfMqBmiN*Tc%m;#iA1*Aweycu>-}Q5!{Fa*U3VXFx1iwbTHu599 zQD@*qX?gg*WWqedZexO3g5<-i5_jc1++W#H5JrqXn3?LwUU0MTOQbu}7tFR;@iK3E z{;Bp#Nc>+Fn$^6&RzH&En5*R%no?ig_Dp?tNtP*XgufH6AfJD_$V9;l>L7Y(5O-_+ z=2bAmz*p7Zw~dV~_za0sx^7p+;G72xP((xo)oE|V&E4ODYr?v*gVZ)y;Ertk{%qHy zj>mr*#-rn0NnzzjuRDC2awqtDESF5WeBj|RIhIv1Z%5#1+%SGcxzCZ7kWi}@cW7ce zy;*NbQba3zm8^Pne5O6AUd-2rpy1D4N4mR(z#XVakd7~$oZ2B`CRObmpnM?q_|@VK ziu+ynCnwI6>iVy-5f%NV0*k*){Ap0(mp5v1BE=3kv?au5_0Cq>$6aVV-Pm`*$Z6qn z3RA0l@;Je#PR4e!-Q^6)t=K+ke&)-OgE7aCUG2L(^j0xJaxQ49{V=Vv+P7Ww1l0j4 z!q|7V?-#2}F0Cg0bxY(+qD&wpyV6lh#Kc~kS`Lxqey09aWc};&El2vFqDuR;xrG?) zx$uiKR9>g7j6I=dzaU;73CYf+%qT2L@$0LZohtA zxurtH?S#Do`=yrydV+P1oCJR!r2~GX-vpu|O^c1zx|8eA+?O9}dDsJ86Qz$&E_Sdt zd^<}SMB;gP(%+3h@b}kJZL)F=<2Xsx;nn0AxP2q3;*Z(<;faAKJBeZ-5%e}YU;C1$ z{&g1LLxcZR@_4j^1KXy8GD%5N^Ic2epQ&SBTQuMLBKm<^1>gYGAsqoji8vAj#Ld4 zBw%C(l7^%>;V+{jKp=O>7$e^co)g$8GKuXXkuFjM~s`!uL!6iOWN#wbdR5S4I=pUG|^SpWU^q;at>J=Nn96Y|&X) zJ~Zz(Oj4YgJ)kS#+|I{*{x#nxoA0ON7=Gl&7MCpjDa?%dZc_V&Lh5vyfz8_5+{UeU zM!JHL>gRn=hDM1p_p0T#Re!SkvA1od5m|*le;sMCp`t2rps(N+{7WV#qe`w9c#n$| z$L+B$bqH*r`}p_YXv&Gk$8E%V?jg;B3+G?}$9}xTxaQob$xK{yYp(OG5IaZRN+Zu3 zmJ8aqcF>7vt7o^tN@#?Czq`t*(qyqz_0rh@mMD>HYdO!KeALt5jnToUje>11vtGli za}FDSZ&<6w<0|hux$W5Ak2R9VmZvAr%)NXkQCC{;#29#tk?G6p% zNFK+_Jg=`0!^HYi9TK3m`~crok-5FOSsI;$lA2I7h!Zj4AN(9+7|;&eTs^ol)f zSTu;j|EVp?av+{8R&xK<sJjra;P%$f* zi_#8DCyyKXaBFtRHs;F8@+bvH@p2M;`6%iLjlbDxNWR2j<88lWm06X8hK<5fjYIUy zD=qnK1q6qT=wdad4-kkv%B;(t+B=Dd*&T)3on8gR$Ou@U7Kw=$x|(!1V%Pl#F#=5D z`47vVc0Mf?IL~1m|5RJGQ!!MXucX4nT;Mzp}Ul9}}xK(P4oUiV-libOjKo1SxI;ZmAb{9uXYN9sJV zuTIRLM^ihRBv`8>N!+DO{6*MDOdh^(y&pkpn8cGjE}d|em^mOFmTv2BNl*&2c8fnL z6(+{6;jV98E=JY(dnHY}-D#TZB!Q6ECM4_^p=lG^9g}iTjE}LZK9k{{T?Z2rH~YgT zYRzjq>ztHHUn9VU&hNjN^6T!A{u(u6NBS-0zT?z&!Jg0Q4o|zY zi2Xapoy;66A>P;52f!Ne27BYtx4X~ihv113$Nby-uQ(QyQ)* z`s*zOnCZfW0R;|34MZOQ2uoJ*AK2J{9_tsI9Qw&Aq5bvqXV6HGA3Uh}Q9>%rkA&(U zkG!n1@*y7#TaCY1iDSCbTjdTilHrTHg~GXAb_gaAGaAP*bVueHd23>A@l_qS*?J zf4m3G3x(nBXKRw%6&Mfzj_cP=&(L0blTPz&|tf z1l=DWFT;avY6Z2Y7uxfWCD%+VMItdv2kVTP`JI>+CW8QRPAVyF^r^@*>o$^Xtw2iv z?wz4GacTF!kQ7wvDNreAsP85yfaClAG6pY&Al}&(<0iv(^uWVn+fS-NmH4zvFTsiN zO&$O&KRZiDz!Yu=E$u5KVLtWmCyVi2p^gO5rkehHadAJ{r1&LiN;d78Q!ril zCZo)8Qh8+STN?>*+sFQ4_X~JU}2sMa0f+3UywaiWoE>%@jvJHfY zkW{f$bVwv6CzGA4tIgXNWCJZxP{aF~`91eyLl|xfpxZ#pw|oqEYi#U|w->w4f$Um! zd$r`_(B$NId@nE!PpPU_R8}s?Ucjq2GfTwuR!2uiPY-stHzCSOy%EZMC=ikdC?ZgS z)Lh?9zNKg0;HOB1g@%RUaM@t&Hvi|3KM54JhkSd$fz`|S>-{WDQI}Cg*Y|PLf~bx{eJN!v0#@HNh!uU0N=PrMKAK5 zQobQw- z&~RC;1zr^RaUZ7^rNU@dn#KvwRP!&)okAw9F zn1$;44h($=KBuz|Vy-46RM>4VdTysP>b5Nh9+H`ODF4ppc0pIm=(UA%OOGTy@c#ep z2-tGl%WKx1liJ%(DhyN;-9)#yfOoG(&99yKf(t+!wpz4@d%_d&7OY`TcvdV!JMg zF1R@UI^%DL%de>MMsoWjNP5lk_+wUv&v(J=cf%2fargR#HS!E&eI0+c?piQPZBsT6 z*uC5Sw3^z_zUoT2fEpX`X>=m;_;R}?i=vAY6x{XaCg_>OE*3`rQkf)?qesvFy-Y!< zYcIT`K=jw)jTEB=M-yNBv;mB7%k6q7mb4>gm3`C$0l5bYL@#v@qWQGDY&qfRoHX_ZkN+% zn27{R0>#Uwo*Eh=!Jqzbr+ab@7}-Wfk8$;_cv5>U#K6VJ-Jz~qfD#Y4#oT=~y2h>h zOv}leV01VT2zc83QrSmnVG}J5$qC*hNEU0xKfV;4uMD*Oh7b`zNhI=wHq3*KxRXnM z4HOBW4&kh=PCvk~^EXM5Nt%U))8(Rn9kHQ7L61*(%LiKWsbhLMj6Y8Cnx|He0+agF zr-f|S-?6n$KX4^imS$E9f3h0p79+x=8+g*^x7jfux?5H*r$nXB+HbfOL8o46XiZPgg+XU-M1JCnf|1 znCOo@_};T8(2{i;C!Jxo_^XopFQ%hwjEGbCS zsG==dmz3$9Y3o5&Z@5@1HhrhO=5B??@U42Wyywqn!Nq_k)~-x61qDDlcjAp|$dxge zL6LAq#!N>o&oH;`G7TMF9}4kDg8F390O1XO(X&ElhYO69GbrG_1O;k72I!aqZztIB zOmFjHIGM&V+`qMb@5}8$1rpktnVm2%M1F14# z0S0B~pTXOKn@tUh4XnRV<6|i5)*?}b4I?PJS4ybqb^i;lj z#YlMLl-O)lIK@C94G*VFTaddPLk8_dXP|+m^+URJu2dY1x8VJq! zB6Zh^X~BqUSvyk?HXbl4$IN|4PlkZdi4!S&>L^xE^{1%b2HTEq#?T8Y%JQvq4`Et1?S-0!QVqeOW3U@w4=75J%Jur9$X?X~ju zW4xPuh}NHwra&|@%{Rp0)Au$ZBI>Uuw^)b-r_{C(zHjT}Nyy)T19w+tz{gPGlM9^R zCEKcOZ!bWf7#kf0BiNy*cZV|{KX{~~k3x<@^;6U85T2739w7Dyamq+I}C?AobS5U@d%X)>lQD%~z*YCAVvb-4Ple zYC_ERw6Na=l;d{;wlC)r(eC`N(96!SHIZ@x{Q~y(GEn8=4+Jz0JfyB@r1`LWiWMa> zl_I2f{1OtcfUe=JTxE}pgLSfm;sN_}#yLtcQ)4q9AsXiQO9^O&Ub zc>Su~ju(Yc0&6|8*8#FSDCi&pQ4q+62B3-OoXv%abUDrn*sSO|XgOqFfro_uoqz4+ zj_W~5NhuJrG8%iq`NoZNc}9bfLiSv*IfN&k-T`mM-?ArgzR1VWq!_GeQE6#+a3E#d zVSUG`^f*4=W3Vm&g{a8+3OIl*f?T>ex4B*e`ZfJt$@@(mt3=YeQK3 zOM&ggoi$8*ae$dg82^At0R^rDP6>eko*(MCZtNL=SPFDm43wF;xI`JPel0CM6|e(S z5!kZ603&TZExXi5&M#32z^Gb4G^Du>J-Q^~3oa)5bDUjd7;o=Q_@EL9N@`^)OAjLW zY)fg7mH}-dBJL)KGeObugqZo zj2>p%o8LV-OJFiUuOlTAWC}Pab$4_e78afc9=@HPlO5t{sMNvO!>*(*yW2bF1hHX> znYjcnm>MrXMZOvr4@9f>OTWo2Ph*`6~Yi)%%rKOKy@7_vC z6X4$M-*+M&iD6=Kg`5v$-6E`weYw%g!es*6gz#z|*~!M(w`;)@TdHPxI9h{%KXZT|- zDaSYVk~GVB^fA_R;TGjwG1f=f9`v5ehus^@*Pv5EzEm>4T5k6I>lCj0s@cu^kY6Cs zp*)Z+aS7%KNkhv3n@7ReR$vTp$H)$x;^*bvBqJ2BQDS}k_>t+LA-De@k9C|s%pNFO0+uRtf`T>is+_1WO#wlRoi?)>RE z1x+OgsNMLt5Ai@gINP-E&lgXV{n?nSTU zyWq(N;1(CM0=E&A)&Z$I;o)st&J?597+3CxL`02e&4%M7D=(kWEU3XW9ywomFR{8Q@Yhmu*N!D-5F|}aTgg^8Npfo!n!ZiJ@VB7Dk}F{NFKTf-r~Q1r zc3+?OLiWe?)7`6PS$93b&+>7#Uo$`)EeY*&1_s}*|Ann3h?u{E5*DTrsG-zpu__D{ zpeAE)*Uo{w$==u@v6JVm*t8{op=^7R@WE+=AV%+R#YQp3-sPeuTIE4ONyq|N+5?ML zb8zC>OQC@PaTVgZBE&9e0Z++b4_3RnuEq6>y9oI60= zCJ-|9$PX8Hh?Y&_XJ)PhD2az*goU$=QlaMGSOURk5N#}skw7@b&t|b_D|o#!i5zDF zTNClPWC1n5VX2~SSwIlDLE!KI#fcTwG;t)OWwD|BUk<}6W)BC)%-Gn0s_IiCct5Ln z;n3G5L)C_W#vgB-+tn#?t*D!%wA>VX;NisG!eU?q1-*e=w4P9R^Gw%&JC)`=?btUh7>Mr6}k3!K$ z|LO4TuGj9#Ung7^yLeV#0eVHU#DSddNN=oP`YQ`P!%v)4C{Rw?i_+G^f1;b|K745) zIWI3OBQhO~P_&syquBj4f%FcW zmqw241qHT5wt+3X$3lYEtl6cVsR*RqySMO2B(!0j2QDS^#uZ8eI++d|#?vw=0f4$y z{BKP|ADS1$O;jwU;Kw2g1Ez(p{t@(Hz%7R8QK|^P4PWmywHLrJ3wIFK^F|de6W$vO zT%MH%LLBhO!27}s>mNckJc~vknnq>E3F&0pWzRI?&Ol3S5<*pncE8_aDj#92O87$? zB7u=x^0-tGiImc?$<67J*SfGK7a0I}3cF>ZuCPnY+UY%?o!I*g=mR3=hv=!G4h1do0Gi}GByA~*RFThczc75f07-dVw{-gd3lVS zGn6PXz^n&t!{KBg4T%aqAG~EEMEm~NKw8asBmd0ynyPKH-V``;S_9=bTsKVQ(kx=Y*z#546=20oNzQpB2%d##gh7J-j7lF#Ij75xXZXc{ z*gGdPSd@U*4Q4;|+XIU5;b9efg1?1hD%`r`t_y8FKz{geAjZ+LidlfXOipA{ju!Bb z!4D()lK2=bm4cOrMnIaDq#%HlOJ zb5$HH31{}N_;nGbF|UUY@i7sR(?Zc?b#Oxiq@~xfSI4YKO6)mAcC){*`gjrzQ<7W$gltTgzJtF;I+_pj3Ia=SnH8eFWFOFJ5lN|6qrCO z9+YkY$|lghx{I3U?Tf5)Vs5Wf58At(iM%qFdW$xB9LfO}VI%HDdiZ*v4TTBue~kup zp5c_7bqSuk=_{MxNT>Mx1C}0uPf;-AZoc~ZucX@^acG<0AljfU1OdhnRIg+u6IkMW z%_;vGl7CE2X|WQg-abNGuf`XJCxf0ECkz&e8h+tV@frf~I5b2_5Mz6=bjgjUx}w*Dw~9%g$(Q~0-=R=2S)rLlVa$%ug*!v= z0kDKM4D*N#YXg4tNXm$#*p>QY;5cZ7{WbL66zx} z9MRwc=W{NHP->Vp@aF6fX&IUC00S{HMptPLa|ry>)32|vH|}|$>dDThWP|p!pnB;$ zB6Ppo@Xi=(c+o&?x`~%`VtPY@cc=Tv4fb(3levzNHGBFg+Fo^SHR|uTzq?VVfhmk)wCRSqSKdGVjgp^ilmfHRi!pY)BJb5L$f_s?k4R&aTu9X4mGYV+S@Q|n zW3MEX<=rP`!-kESCAr5r+mScjE^OTzIZjdH_Iqgi9qwn$;AsO^(mFu2#1sSlR0mAj zJh^xbO7FZqLU;nDI-Ek#+tM!>4@uD}J^MOE-sxg5$@YMl3cUaJd^0})2Bj2~RB)w- z+HD89JrIkn zno2mQcvo;ST7_o=*}e&VATk+?vN`;3I5@5yd-6%57b6(Y3AvCo=yGB1hD+>;zRb1H zonKylqjK)`CDt)u)TP^HU8cThA9zScS&<(;VD;ck@mH8S0*z{4!01O@ypwZv7YE0o zXbspWK1xpR1FsQir{wj%HJ|_}k$@ug_O1gRl}UPzlFcN)1YjKW)nvjIsP3@K>OpE6 z7#IM*u{JdqAm7qOUvMJMSGbhpvte9>cL+D^KX|`i!jIOZpv0hVDB+BXiZaX+Ucf4mU9;aja5j`yi0^p_d z^42vpsFY~{pF&&rpQ2kYn~`S(mfYycF!Dn|O(JoBYr#)I6$4FXy_nLVQnUtiz@H&% zN3(;EYL=;I$qH#W827(U6m7izYK@zZu1W?=u<`^~0)EkQB{z_}fP$iELRdgz;PcOuSTm{H za|%l!@eSZXzrMB~9SGC^PYg+D46_qG|4w~vBK*tNuJ0R(L;w0O^w3Yw{cCz2(o@+r zcWLh5spDrp`Ge&B##<*qR)-3CeRX+dnfl*E@Yo+0AJ) zfU~S}d*3U53a3Ph`pooqD{K23A~!|Y`cMvaWE(6 zo%Rv-MikcgD`y3+0x~NoxDG`1N&Al*H!ws%Gc;RXFQE;yA`B&iBZqj~!vS_cLFSI$ z86V2qVxaqoFA2gMGFXM62C*b;vT%R*E;BsX+P*-h|6;Cn=1k3|>xvkX1K@Sca1M@a z_f9(~#25m!85i#rYDVD2*9TJc!T<7qI)Si=XhPO9RbD<#Z&YcX6C;0fr+l)dd!Qxs?!qtO zl-RKCFcoP?aGM782j$AsNh5z&o*ge(8r-vT=IXXaXJ$4AR)SBKvD#2x9QC&soElA4 z$NK9SsZMo(nMUA?{>9$d!zxg_^<-np)op|!s_W@Ibg7R<>?Pm)T1wdG;<>{yP%tur z?p~Pd-+ciSC!(@;wQwmyqK^&}_{nF1A~~@`UF#OCmrKQDw_@~o@>>dBZS|n#)d)4d zoo0ReTryU7(wyNFkXXCBKHX#`4%rjVR-l@ulEe~nk|R4dwjm*79*2^Vy%FBI(6oQ< z@9*#F0q#63ChJC%cNGC&cvm_xGy>x{QmmzP?nkIWXf{ zz!88gmNDpR7#;0h7kU5(;j_@MtdDn-P=7|lS`NT4Oy#l!-d`y;A@|>)UBWvg88U#b za~E!R?g1&?T#6=h&&LZx!=}YIk6cL|)!h|Ov87f1f_7O&xmL0e4NcJ~i@};}(oy~k z9lT1Y5YHQD+kd?)B$ztyYH*J>ALskxg?G}83S36~ecP)l*30bAhNsQ}{gSCS`j3Rg3vFt>I@5!DUQLwc9>i{p@|nGw-#Ju5 z9T#(7x~RXDbRa1_TWf9KIo?je*<||4F=m{p!&mneJDueAT{#lWbiq4+ zwy$Hft+dI9Fc+N?BfJ@upC3R)Aw@^{DeI6|@%3N?w-(DoA<645oU8FCYm9=#mZj$m z=K4lgR%~+0KBivSY&n#m9UIkq`GXEV4I6E=>9GWn{U_N4dtTjSlcRHW$!KjAdfjKE z-OA@jY1WshyzTFe%bHyuW&&?q=@_6SstH`u>M9N(7_ffr_geQzqdfW>6nX*M*4TaghD95?_G=`;kLSin^V$JYU_ z{Z_sV;xiVksJCvxH5_UXD4*H!AED;(ZD&#>DP1
Ev-1Dn*2i_|N*uF9kjjcGHe7UW8oA8X}I!A`VyzscB zJKNyc2V(&Oce@ioX>K$nqBeDZuKeKx`~T(lJPC5Rs&V@AO__zZ1Ot@^5(00>AGW_b zc`xo$CsB0n!0}tZWiPz6+^ryBF;(hcN#p#{L)3ioW7dVmGbau+F}=QUC!n%Iy{ek> zbvK>cYv)$x_qDaIZ6;?+Ib~bFEMJT&^L$o$t3ZyKCj9dMaREFYwX`pkgs+t;vts_u8nzQsqXFfr@xZ-{x-!zy^w(=+gEl(qe}L;AY2 zMU(pYjjX4>m^k$PS-n^-TqTVaz@>NmjvlZ4^Qrxb^5kZJn}1eT`}`TlE8|hoxx%Fm zE7$wdY&h!Uu7$4!Zgfe++gQIOLt(s;YH+08nEQRG)(PC7wM#puBzKUM;u1R)X%)&>(n9+ z&CY*kk!o)ue*%02_6%<1F;?0$uDT&2KL1!{MKkj<>`g6+QnlG5Z+|;Tzrm*xX!pa8cP0jrc2zJJ*JcRd!#` z`l_l~AKziU-@oAc?+~(gfKrnafr2k~`1A)gq5OaU_;bp>MI}3&E+3Qdu!;Qr8xRdVU_cy<@)b^InU4U-=N4S4S(BS@Uvb; z>+H>$*VWp$W}ScbG$wi++?){{dvsNYx74~<_tsB?ir)zp>GvZ1Dm+%12|P%u3nYSa zR9VrH!O5Hxac-_)wsto}cy+l=P)2E74Qn_U|976&^z}yTQ1Ry3QrfSyoMvl-_gvJC z&yOC?dO4ifcwhA2pWW{j_x+x~sQs=px^+bN5P?!HlXt{9=+fE#J6BtqEpN&Q?8*$f z*J&f8>gM(r-O0wa@mjzG{cuu{8;=h1X|_Tc_m3JUcsWxV&=J=(5=A?M9&0__ zcA)LL3wPL&o2uvQsIBjvHc*sr{1{kNQ+_%wq?zgY!z=~bVRv&^@w;79hs*B^iIj|8 z-c9v_DaOblGsp8!hGs~16hrF+13qv<360XB#TH{doHQ)|@=doJ8Kyk8sfti8t59Tb z1U-xJmfF;T?yNx&1@~2Eht{^iJ=-2$@)`fx$zW@9zjQD zS1%tpwZ-g^^&i8lUpVTzM!l2kiHEmw=<5e;A?#=PV4=+5`)je5o!u-;t2RSCZ1_*s zX-5yaE8^0BCWkjZ*jH4UaqhCM@}j2j8F5x4J=SQ5Z}YuI)x7^~PpOj9kc@TC_z(A( z>%$yr!p1jvfmYEclQl_T~16 zA5XaMSS$wSobT_rr#auBJX!9j`{t=OyUJ7;*HJU6yS+h%!T zAp!nGn5QC*V4d<9e-7oJ*vOO=dcv=3Ab8eh2!5t@kEv{QN;DIp$F^eI3e3nqA6DjU zL)8N?0?wBZX~Ll(c?S6k=~prO&T9WgQ+Sw0_}~f6FHP*klJ`z?RMRRwo^Y z`BJw~&a0?D%hw989Y157duy5s>KtKNsCNmLI=mEbE>|X`&wk&c&=ha1#S{5=>(8NK ziuRMVe!j4wR0z7nTJcJG&!=s?Z+_+z2;yQ9M}E3R12vop%q z_=vF|wZ5Rl%k^t2TjdX)h}^b){62>!6Z7BeeY8?x+ac>YB7EiL+N@W@f^5Crz56dl z$1c0f+^_U-d!I18+EDR6xz{~#l%j!p?_H8QABm9jECSAxt3x;HuaEQ49bI27@<>Xv zF3Yq1^rNF-i&D<)_wFl;Y{`X+_XRd;=zp423roFd4x4j}7g=|cHjqDQ9quiWrUroz zZV~P^zXq=$@BDQv_?SgW0w5lx>Y2!bB};+m7Bu%{Z03Jf9qZT=?uw3@USbKjlS zYo4~}K@`JEq-+D24Az0=hZ95en7=^FN8LIjQBn0i^-Rt8bx$6`iaxcLb>`R2+kD~o zoj1I9qbeJ7#{yRaAp$6S>X9OY{%>1NnK@HW++o}pG^3>$5H@TZ;JGs;*%TjWX}A4@ zU3%+5LgSDL|JiX7e-jFHr-Bqzq`UFwD{;W|Js0J3;KOiZZsnrRtE-|rY0*L1K0MGumlUD3O^QyTNmqE!z+BDm ztJ<3zkKa*PNS!xn(WR|(vUT%Mb7y_)sFm$26~-;rUuLN$GgUrt+j{j@eA(STO?lNF z`%iU{E2nnPaL1b}JGSZ@H$RG&4&OUov_c~;K+5jQYkqHF`0H0()VGZycbnU^k+C!h zZA%KrquMTm3wbo5ZKqb({Pf~sOq+o0b=bV>whw~QS7eF}I@cM{9587^k%XmT*0{ERsTG_gnD^36_8V%x1VJV-HA#*No!R3Z7BiD2}?O=NEjcu(3{ zUdpH5hlK?|M!~(Y!_al#HMNnHz=n&)u$a-=Q-Xmi!3RS}7$0~6TE&^gKo^4>e1U=M zHf`|0jB5*T@f=%g zVB48y=`a6Ok9}ld|HDU3>7yJ$sjAIRDoIU}`tzTO^q}CwiKp{^6vk!Q{%BC)?5cBK z7rG)bFPShgv^9uu-rVxFlXh2mZp97$Hw*hiS&ZjK22Hjnk86*&-!P=%rOwccD>(l$ z!#e)?f!xYl`mPffTidVX2vPV~)NkK>Hq*8*m`cO?sU}NYyd7VpgY|Idi1up-sLytmBS}# zPqIp!S08ITpuI_vb2>??sQk}EYZrHEe}#^mqlBt|D>wOK<3!F|*j*(IJQ#0}qPY8g z<7MYSL`cE0FB~#&49+E7H#>K8B4@TE$)3Te-2GB-ZTi?jmW;N#qXmBqU)g0I_ja2}DO#(PJ1c@95CEfvVSLYpxc3!2n0106%F|$2A8Me5y<1qL z)r~;42*Tf+8gBD*Ve7fVZ)rOYYckg5<}l3lyc4l6&&|qf2heFc9I>1T>0_Q zLqBv|CtYM)Erqg2Z^cWG%0xv*a2q9U6;!Wl$hTW%P9D5F-L9{qrt!>o&9}SAiGTZ> z7kM9u)>JQyL;Di8k2=2*`!X$DUw%VPWGidn$m5B5>rbPtk4+jT0o!ODD*o}>`gM%& z2w#+@QT6M;ekwlObLb9-rN3i*AAkDX=fec5>o6!XlGB?zgDUD_ zVzbfO;C3A)3%l(7M;B*JuMe0U(m%Idic??xTDiOX!vjNfcJC|(Y|XvbSl>AHU2f$# zwqqAVa;kU1-~&t9`bP!pInF)Id=C$I6_d6+Rx)An*782EOMz(g^sgU%#3LUDO2V7Z zUzn?xImbrcnC2%o2Qu>c=%hI6d9N@&?<(}zay|WU@n?_m)3tv}5A*Llqnp@AJ4*Fd z>=!Rv;02;G$8hF2NTL}1yn&Adyi|Z!uK~>{udJMMsX#Y7J~E;eC%*r{fk4a0FfPLk z9xHjhkfgk8bCLzl(5dKZV{G*$Kfz1{0)`VHRPAmE4GpStdDdkYtc(`eCO+_&tzGJn zl!$>}b5oNP{0afMVPg~pVe?@cup#d^+ToU5WD@y^5%M0|mR&x+1&qq#QCC<^EmI4xdG?EbIlKA7l#M+j$W~v>^MLYV z@;s48+SvP%*j@m9?Ju`&;U)Tz1}J#<`wSZu?NR6R@t18?3iD&2wlEG$<<7|xwPZE0 zlx;2CR-dvvim$zpq~s&UxA}>RXFBvq9Ji9%zg6|^dTRJi9&*Y=AwY|2Q95-uRQLJ( zT6ReD{q0D8=lq8czXWjvC_TQ&$_AH-P85OX@=b1HW`|W*o&;+QkU)qafeo~KH=gn1 z@lwueb8loJa7btZ1UY++n>6>5NFTwnA6#Ro7puLb@hoZih3;lb^#WaZ+RDmb*On5i zW7X#vh!G!o{5k!JscKXi>pCwjQmH%5m35V=9vujx^wrzCRXIZBEI02V1@6?$h=}Bf zif)mSxG#?Bnx@G_NCb*&pF!4WCeAE2Y614tg*AKWpC#Z~9D{ zo|!R@L9Xr0TE9boF1HKN4saTDNR8-!)OC@Gm=kKkbv~dh$9`b}4d@LMzbZ`wD`@sR=;=30 zvpC^~MMeW5W&*d=&SdSe*3MeHVld8Ju#ptjZvGJ=Yg~eHS!oG;A#Sl|Z$SgW02h|$ z4F6gDVUNgO(N`^^)GE4X>*xe`q^VKWrd~RAb4DhPYmF;5)_Yu|tCUOgoH)@T<(Y=p z%~3i=6+uQXbMwjE%>~w@->-i5^ip>k8`=ECC9m9*J?m_cSz{ZrK3<*h^kByI&E$B? z*}s}5H?KvTZY+fKIxQW)^{{;RytN10vv27iC9c6=&%}f*s07x2PS*xk8Tjf$&Ju5K ztlfoWGCtjMVfdiCJSN|DZ(uQw1y--%;$JW3QQc=Rsd;uuXl1vOTXL2y#u2*thP#z8 z?ldomy)$(%g{^(TpJcm7LK}E)wfXg8libH5QTfi57SHd$KN(=jn$32<+y0A<#PZEN z)pWQn&SPN$2%MAqQrpychf!Z&tQRW6fMpjMv&f^;()q2yKE#VjBU!2Vq(8? z3ts0i(3oIh4ED$L?bp%BHh`k$43sUyWzZd%wXDTNX4io%hwsg>XHPle2?o^g1t$;m zK_0^b5{Q)2Oo0{aWxF^z{VR1siXvCdppy~n&{1)ovFFs*mq;qLQJnStr#vucZy`%1 z@Sw%{(CEsy!>=?{+`@ic47j8?Ca79+IN1#pkTo1B37e1PwG(U)o}!`l-uRa#5CX8f z)ODkjd&l0PpFdguGs#;`S!GQg$6ztVb5^?f4Xot)ppjKjP(U)L06h|0vM!W6XO@*2 z6;eR9l0V^zJITUFs3<61B4L(HWuXe^q|oDPt2k&dR!?sCc)-rb^WMw&3E$A7xH()*>%10^u4c5eR); zeR^C%pnyQ@c99kG{NOHupgoxA;of8MAfB(RfWWhPQ;@W!JeTq?S+%ej;E1lwxR?Z9 zY;bru7bs}G>nFt9#X$x}u_)y_o2Zf50!>(JYo*JC9>|l}ui^sN?GOj(ztFF|4!wBy zbQX3=MsAQb1jFw)Qxy(NWV$JD+r}MWku9i1+;y<7)GFD-YVU9`*?LYj-3gOvOs!Fy zWW$6Hd~J{`)3v7Z*tKUzN11l-zJX8xrU6_mlO$iPT1|@uA0A~3-JhDmXlWS~Z7 z1-b?_1>oYy{~hqNdoQwZpMBRNO@q^=c2}gGO;_72QW>OsgjZ4jq#7-l*!fe`+fO_AoE^ey}#=<;^=k=3=BA zoh5X1clSHDAu!?5lY)1gO|%vf64J}<3ctL-v)|x{h}6{R=uNDY*|E3&!-p_yX|mGWp=(LI*Z@ zc=KngtfoM;1{YTf2I=a@qFSy)07!tZ8%`_i(HT{`vh^G5>+7w=!Er|M2__;mr?{%) zU4O$gFEq2wOS4c}LSO>VEL;sqF0&je2KWXW=3W=|;xTr@6>V*APl1HT!s1CA3-^Kn z2rZ_+s}~j*;k3B|CLUV!JOd$Y(h~ma_{d!Wf=V(M0cRdn3KB2$3Rt1h+23CShBh80 zxDB`^cr4!Qi?ISBxD0oGOs&)7pkqSR*NbdoS1j)bzW@naTfI5!LLMyareZ?AD4#|DhpSfE{%{x8na7j=W-DxYuI1FF|?)eQVLxW*t& zVCN!cxt~jlT4ftx_HgcXDe!l)uNg*wpusZ9yYGp!dl(l7E@d~Iy9nr5Q4-*n0xJkK zZTu&IUI_3R{GSHZ{!)|dw#lyrHSm{273{75v54BAq)3W~CIWqQ%dTg7CRkpP!)|^0 zlMDRpDl1oE5)b~EVPr~MVP35TDrg1JND#)m>~4b7pD8{FWgVCkL|XEfi=!jRx@P#X zu!jZ>EkNy&c|rJt2XLf<_5?(g*c=B~jb~(Lp0Bx+G%RI{Woe+=KgO00*b8GDZ-ONNGKF4)M$OhG> z2ayz?1k#gZpo30oPmYbfTkyh;uWKI$YXKqmiaoTXtN6k%^E32tB z%}8!*1@E;z;TT*?Z7>XjPQ4`l+*tPb(kWeN3_$aJDXcm+v5${WIjS&1jFmR(ifMWW zYz59KyX<}Nj-PkunZ9#!0TTA#K3;s82-R-QD0WFkRBvU!Q4GqZev$ITtJ3Ks!YokNT0%O^oV%WN*%qsAj@w9x(6D-DkOMw-qOAKoNHh=yy|gmXoPj8b6;>F~U>N3}$Jw<$^`rU*!W*cp zFcl5-_y4wJmecn5@FkRsch(obqP&=a#MJQeJJFgoUZN7nh}c8{6V_-~q#clbN-?kj zrxm=&o&!f(nhfZ7Jh{t%JYQpM<6o(_OSH!Rkc-#^gtKgGE9irSPeG)iX>Do22E&su zp8jumiI(X0E6wM0^Is+f!OqD5I7Ifp$EzN#R)YiqO*naiC|mXb!ziq>My$$Zg-s^U z@#Ch>9KU2!qN7K#r@?(Y+@GL$1?dObSEZ6Knk-|4S0EM*>&_|VSPFL^VUoLk;l9;P zgIPn}veF&2^?>xGDb~Y!t*kgH@YKt!FA*XbV_?;~CvXpVSXLH#MyA=n79;~K zM$k%QIF)f?0iSpPuFD)#2<*HY&vj6BLY=cU}|M-V{C z!X;JJ?^db^c{h?GI3%VUj;p0K%QldB9|w!gm)wHSe4#yxx{O$iw$XtbfwmLIDL07) z+)uUlW|Jv&m}}!sWDl@L;MkM%yE1(!(sBjU4<8a{xrRVjB7PRoFYQ?lrX<8j2De?7E zqHe`PRTaNYufE8q#L=t3I>{Wum2 zr=8z1kchP;AgUvT$iV`)K5B&85ts#;`8CQTIN>JKpNoh9IFqBBrOMm0+b!6M(OUVJ?`eQ;=*N zv;XFZljfY5p{1>$slU@eCud~*YCEg%Gm6`lQ(v%605Umb5_~K$ba9rV%$#w{VMG8W z4rno=q9<-mKSuMs^OzVnPQ4s5V9D877@U)Rj=y9e{e*`@*3(}_+7VD^C7vYbY3Sj2 z@F<)&0Nn`Q>j`Xq80oGDJ;!dUTCiJ{c6_?{@(*<5z=%;ka6t6~s< zWKZ07Wh!B+bk1{}|HlP*^w(hg5{dGk>gE|nDp%nJPG;uI#U`NtQYw(BP-CbUl%SeL zq4X7u%>Bl)K!*fbssi`*u!=9=<0c|7MVHDWVPYWaN?!}w7*YW$E2eGX)z#Ji5r-m% z)2))Vk;S(?an3hXB!PC1hYH3KT9#kPp}itI&s{@B5TGGAb~FUsR6P)A`{(*Ej((4qRC>fPB~Lm)iGV}kO5(x9P1;)oVGv+0Ip|zS7aLE z!U9SNK&z^LSczw&z*4EV6Q5r?J@z_#-X2SHOVM#+KLi*o9AJ*cnRL%e)8mbUcEtJuix<;&Hol_vkHF!vEjGc+>Istttb~WAp4%m2 zT5^B5?C8}mWNloDep8dIyTOlwt(;3T6{aUz8aSfQCLiqoIOleQpXyoX>DeC-#|1<$ zTpl?7=mQ12>pgi_`)r#LDWN>+UU{}O$DaGq9t@Pa89nl6j@eV^W;%ygCO0L0@w%t~ zFk7DdyMuKhq*+Wr)wBHUkg0JC~;C8*pbJ-#oNU?Ev+PsJnl z_-9yeT%i;I*VHu%7y60!ieoTb52Y-G3F+W|cd1sFLUS~J{@>>C6(~6}NS`H)B zmB)BRX(Y6}cBEGR$u#@%mL}`%$>WxiwdR`3>{|DZ^0;>oy*|V@#rEiEb=~>4Q%jec zesIy&!w>&)o3|;s(njnq{rV{%buPG#(f^O9?*OOz?f*aaIyiF3-XV!YLiUJ^tdi_e z5<(I(%3cvlLMk&#G8(kVN|ee-Rrb*Y|m@#&OQ)bARsp{eHb) z0SM8dh=_-_Ta4eYnNN?Qhg7}T?x}2XMPobo^pL%SKu}}D znDhiaChS(E(I-F|12&s2%Z-zIaq;m!`RiE7^!l~qwaUr^2LZ67CO6-``{dQu(h@?x zm&}WDCtbw?E&YL`t*>0H7+x0-YZckrjKsQM2!3)rHC*?Wq;C1}sd4V2-v@a1HC2Qy zDfdr>EdTS0-*`PDo#)P5%39KfQ=5f96s`r*Fpmj0zjEnNIbvv*_C=>=3$LH2L}c@U zYVw?CVy^DCF!fuG(;OejdwFTdmz{a~b8VLL&vtFhi=9(GY9(PT`9UXt?UP{EmDoOs z(B~32e2#eE4P1_Cq?>eNyU#P{sobZ{s62N{&ebBuwL5BETX=0luw{?No>$)kgG!yX z2QztZ7j8Y6e~(Fgv1?8V@AxctbD>`Q-Lk-A*RxgbV>Ep9hc#0W#2#miEG_U*8vS zdn;-tY9uqNU}_)4Ab*90WWu@0$Ux1Tqb&cjMr*WM+P?@^KI7jZ>0y6Te*aZ$Rcz8L zi!+l9uz%XPuz}gbO;FO$ReZJH^>+a8o}h~6_?UvTNC>VpE}l?>lE z91MdW&wW^(Dovc#HTd>BGez;DuQ;_dU^221>Hwa(FM!C(ntJAptt=T;x z=+|dvhQYvjZb{6T*t2)9__b4AS_>vgJK+r>zH>tiinE%9g$4A;ZHUacw3})=Pf*Qn zImL&hXNYv?z)ep9NQjK=`8G~!(+$$HX!NQL*j1gK0Mh~MDG8*bZ>SycJ>+@gB}xdi zyN|8_MA$_`=23jXBJ8N6*kejac zk{Vap?e*Nk@OHn62aU@;C1&x96;is%L4@6md#aQ77{2A;u@c%@!S!lcO4sR(uBFn9 z?vpEgyhaRCk{A1Ela#Y_4JwMiCfK|Rn91uVyH$Ba5Djc-szONJ!q=R&-z8QKT0Jg{ z`>IdYrz6tk8yq@zvTTq23Xkcd8@~e{1x?P`USa(Btj*o0hv>~nY?h<0wU1(4M!y;M zzN+4Bd$fIc-^{0#u^rD_PCKV?=R~(yUaqn~aaLF(_VOB=5ku?D4_oiOlMfqHS9H1J z3!HU0mpMZ2ANtO=L)7r|V-E)ewIZ|lZcqwD9oOUI4bcu;T4oITNhe@v0jPFvj>?^t zJ2weT2X<;F6f49f7vzh2y#p)#5-qO{{jaq{cejjr&5-BGf+^rdPi|B7eSXSgZGa^M z{%iJ#NUlY6;u^Yr$OSP6jRn(&n)1@tR{CBNK3ZJO5cB)pv**9~b%Af^5JSPwd76%6 z2mjavxCSqT&ZJm{89dn*ZML#OtcbZ-RzPlhm1WlZRZ5|hOObOUA%>vBeok+UW)p!#dm-s3JNfb2?lE~=@8h4N+pfoG3^Uclr;r#@ z?gbZT`%+?VUCz#BseCN)w%VYjwTAbF^o>yM4biO+L$+e62oGBP7(6k&>!<ni1rtPj9UC;*w(-=(MF9%oIO9ic76}3DG(GHZirK?L@z&*f4JZWB{EWjtDq3 zVEenl$A_{S%%s?vCuh$+D04qj=Lk|6bo!84)Yw&GqId-UcISVMW1|=&LxD-656(#s+8*`FNVJkOHf1A6ExDl5FHHUVll{ zkJpS-5jhjDH5?9o}#Hebf4@W7^S{L=(zNAc$YU9eY z2%dZU&g!xL1J~g@4!gAscRaKvvzM_di1bwG+gSVP^=8Kzm`RvE*!e4pL0WQV=ABai z<|eV{_jomrYV!!mJ^jwZ=^t;vQYO63M`599{F84oEPPruP2IOD zyv3+r_U-w!W3~3peH6j=C+aD2Vahpf8t20g%76G3l1lQJ)XK_fRlL&jcrEthwRou? z#HQ~~5BDlQJa#bXv(=4n<}WnQCm8J&Pc8DXCO!U_6@FM*bAqYWp~c~Mq9}LTh0B_+ z7NajstL0tZVXW}9#WQA)K5=sO=CK!fJ~OR_;>;i2n4Y{F6=!a>GQT)3dSIT}taLp0 zlz2e~4{ua!gH~#OQ^6kDgeTitpB}1>ZjIV@L@e9c+o7D+pp!arw6zUZWF(01Bqh|= z)`GV^Ji!5J<06VL9RC+8X;>E$C>z0-50s3(;_e z$*@?8zk+h9$TrSnGp8Hh;!Zc#x<5JfJPc`Ea_p0Cf=`38LfBb(WLbH+Cuccj)P_4H z8FpA7aaT2%+POmCdO-SO1%aXL=c_~p|5$@hHToBSy?j+a`(FF%W?$OdqK4)9*5r1| z3+(sQK+d+dwY9amJ_k|)W@zvs25=%#ao>IK<|AG8tz6C_+yc{D;5njTc3UiuY1$(GGC#u5T<&mnU=A{zZ zbYrNMFpyjtJY0x4BSm*}aPUQ2mXPvL5RF~>66?C;_t+Hyz4Dw5s3WJO}Cg9 zKiyM4{HMKW+Nqkz8W;0ZnQ|{f?rpq>UVqQK^Q|YzpVqY>n^~kDEuwI;Zp!xkADJuKI<}bYLeOU;~u`6 zd(Ngc_)&-|p?&SK?K`tJ=-IgQ6ip`|c3+jj^+Cp(d>11V(|$5<|FU!})Dxp(+4>}^= zG$XmAR{eLym;q`cY8LwTbD%Us1zg8Yv?0TDsxT)Q!n`p zlb14uTvGo;Iw|V+U!VVKE_f_y)0EZEJ2Diyk>bKRcI(w&ZdnT*#eJ&>FSEupxNo$XS#||3ona02q-MK0s zvFYD>Bx0!{zeH>+Tn4oMdsVwN6=Y|>t~;{7?qT_UmTZ6GMJA(S>y(nb?Cjv#_uH%N zsHZqm>-JQ@QWoesT2O)X5?MJi>yBqmpB(_omGc)61u!1;!n$a%)- zOGBSE1zSoS%L8uxn86MXVmR03sk|M;7P#(0qRvBK3{n)bxxlYLE4nsw zr<*$8-t-P>A|e0Q*U$~YfJxyZ8SwtLL(j9NE%Gt#r6bY96#&Jd z7SIZucSjq3JVogdtnuOC18ZevD^)T>y0ALivj-fhvBr|Q)sZ9+bU=i0JU4Lzj&on1 zVyuP9cgZEtS)f{(aQL`3|7mvBQ$)_JENg1zYuRp-&j~Mc7wylQu|H2`R{odt%u@q% z5RA$2z(T!_nXjBYD@YEooQH56re_R{jF>Tlux*zy zy3tzP0%m|m(ynqqs~Z%l_!d3pXV0HYB*5ZpZ_Qg+s2s(~f8jdJ?^FY z8LdI^gAMg40BIa8_~?)ktQ>q4sN7AlYRhS0+{v%)usWps(DEAWc(G_e^zv2#1*H)m zmjI6{P~YIEv^50m_K?ufDBH9uou7vdK8w;bFP>9xKmcD`1p zn#N(4gh1O>a61VYg@WFMX@G#LA2?87=jId>U%*!r!Lu{Y z7lVxo>V2ReOJ?>aRu6m)RBR(6{$OmxCn|~!X%2fZx^_5;fO`RE1%MV*!?<~Pp%t0l z_|&(~EF+OsN>B_;CG0=g33gznhrbG{8f1AeDMfL+4ct{&41h5TF9~Ojdbl@XhJ)wC zzwhd4Obpr&Y*tjfCTmtXfXD)j(QHb40rt0bbXCQ%^!sp6KL!Y@32BFdg7 zH~q{qEkK!%VbgbQgXp9)-y8)924YLNci(@$qz2XA>-eX~bm?gOwm- zP-9T9b3UDFR+OFv7t! z1M_GMRp5(6Axb6+AX1k(Zov0K=!<;~9%g0Ap!LB77$805*Nh}^VFU#c#cr=HXsGNc z7oeNTX3&Xw;KalP)q4n3UEG+%hoN}h`ao?Hr%d+(&;HK}mImp@3m?-It6@t5k7n4F zZ8NgLDwU8pfTkMmF&NM#SzbHxuGRxIQkcHM<@W2BFD5qGo-UXTKOY&<0fihVAu1e< z)u^I|3-`CP0DbxXef`6UQJAw((}vK1P-&HzAi*H-4K8W&i7HP|x%-|QIKA4cJ0&z< z7H&=v%enI)V(zWd_G>1iL;OPk)N%clMcJ0v$2t^*b#82PHI^+iR^0|OVzr*VJqRPx7UW_H6K z3f@5q{1o1_c0bkqXKHNa%2KC6$wkO37>xg%jyA%nW8I$yg-?|om6VV3a0lqC)Ke0_ zuqg03nil)aIinmrJ-=HjYzjEw-u*mrs&MO0R(F%8q4&taRZKkqhTqj({JRGcOE(7n zasqhqp)oM_#yzmek*uMfT(+>!kl3-qq2}!omHfjLaeZY9sQg$^%8S$VtfFdDt^RuS>65Y*oLKd z=hdRqr`~{DpLhVrG`mw^FE!NPkNr@$(~7C%7`8*ODc9h^gW)*}bn2cQ{?oJ0(FOMj z9h6|Y@fFMc4z9EWi+>(SxT?^e!?0e8XqK~Vd^Kpo7kFAU*0htDcWNJxlGwoUgK`n_kuEz!73 zCsJ+buz<-Au-efvxrh96;TjgFn z-8aB_diXzm!)BJHi^|)i=nP@qj>Hs&$saEyQa@sbr}KB_4pI{^ZbChZ!tL)KF$4`} zsrWBF-bgm;RXGLoF#^vyYW4t_O*po27QlXYz(Y9xO{NkX`la>lWWN=C$h z6jy;zgc2AmRM30xJ~Ct;hySK}&LOu`OZ%=^s=ZQ$&e6|G_;& zYym=V;XtUn@`-!1SrniNjMYvA1MpA*QJ>9a@P z2nh&)-%E8l1h*h#0+rgL(+LHd<-pH>#HJ-Q;Tdv5jhn^yW__zY#uYd`FAvaFn(VGt2 zU^_l8dFf1HK>89sjzo!yj%_>G7$7AmI^6aI-A>hE zw(;!6iw|%+;RXQO<8h(G=anLtzGBFEld8Ro8=IR${6Qc1>G0FXP8SS~A37kAPWMEp zb>05o91?UG=fnXnZtl_n-^x$n|77|y_0S0!8X98ZW90i4XeHH650CtdE!8zUAr+r1p_cb5v-%LI1uh`m~7F)v&)O)! z4HW@h2vqCU4$){RkEj+=stzk324UHRIuN?&aqv((X1+#V4azxbZQTRQF=f8mjodW{zoWJcaF$LsW<8jyW2*by+rC2t0vf2%f3Y zv~-((LuYy#iK4LIe+^v;)^*?Szt~E%_7GS;t&1#*9s) z*a!4B$B3tPv&C0GB{wu&O3mmi{RlM}p2BZSf2dD6s&x?b#Khd;_KTlpiqbpcg}Oa)fyajQpr`6MOl8K54h*w-_;Ly*{*`+fwffTVPG*K{p!u z*K4DTWnZ5BTYY(p)q7no+wrnJt5KcY5r=Tf@41IU2{GKwdx{FP{l>LIK__|r zI&+K|GtP(^026H#i1CI2vSM9Pzk9^u7i>)clI+6eybMuO;2?a1kp#AaNYkj_YFA5m z339ufz~~}?1Lult2^cq_S-KAo+l&c->b&yyJ>b{iQY)g1^Jq%~bYyO26%D64*eBxh zi4RDHpIylMSdL&1)PGm-Wcz=3xLrXZ0Cyx)nc1Q|$-T}Iqeg100P9-gbOBiF?kBg) zh4cXS*pUI-+$@!fqcU5zU_)}ql4!-$OC;zN9qk?uaS~}v^cDBC%h$qVX7mX$GV=jR zV^e`MTS&C5XQLSB#v;YlIAtXCVoM0LHai;w#OO^UTY92TT-VqrM*4j(>5AaGGp!xL z2xBZ^>8iV45^)DeN@RYSv$=sxk>v-BB@8?=%h%3E(I-}1m*qZr{JPx68-3$y-Cr`^ z?)~g~t|k`}nHLt=-vXhap3zUK5B6L!yN*l$(^FEsYyLAQI0_vKoz&5VaU6D*{_nj!T{|Ab}>v=(ea8tdD>hh zTajv#1tu1Hq6+k}?1HMpaLR3?;BcZA^tisrO5U(><6E?AKn)OD`WcH~RzU%~_dT-O z0C<^~Ru|x#{q`GNjPW$-9DXHK+WY%wmm++=UwkfPzbKHgr{W}TAMR|e*GuRy?!e{D zwBzP&&x>NrW@UY^UVR^B^l*R!+=qVZYj2gEu6#&XMsi~EK7wN0M2mSe+gQLUH3ku0 znmFb(!gyhN1M?R5-Zo!&m)eKV^CX{d>bN&gq9<|jij**0##~?$r*OYc)Bo_D!cEf4 zj#xil%^J2%{?Hg9wpl`tOo$ow+2a#l!dyuxVB+$a?D^?(|8?SY;X&5bmzx~u1GVV6 zih8_Mf_26^U!ExYp|3yY!6RmNxP|V1xN#iw`Wt@JYw{u6M!!lQKiU~Z#Ex_E(#-q1 zw5gRZU%anaUVHmZ^eK7Y&N3wxJANk61l+6a zkv05n7Z(@6I-^*|AgSOep-MNPn+8o6)y-pb{WHE|?G|8I-o`NwrPd#q>H*oHYB>5! zzaH{?g5m}bhNOhV)m$i#P&`dyl!7&f6(+CjM-sK__r>V z_G@s`Q~qUiul{R?^DO|NDIxg~bYBA7$(dt>;uKuT3h3s$_T+@Y=Cr`=JB|?y0D{_N+g}-QNFhZVfmR)E|Xq(eFu*|6?p%6$knxAaR-#i z{ct^X@OVjm=yC4;IuLGV@T~rx4NI(^^&G>Z1I@jNBKt*`#P16&3qAKs-tKw#$grx- z{K(h<^XBAnSJ6}ZTVCDYh0pJzWu^Jdbi$H|Rb zgaGHx#f_bfF|D49_Aw7#TGGC#Zqz?#GR(px+EB|Z zCu2>PWjwQCaqER}30jqhQL&ErGY*NOIkVqJG4n;<6xMliLw?by<6U94l9*S5r=nxt z(Cz0ejv_vcqM>0HP0E@3_%&+ye(HC(;;5td-#ld-H7JbV64_g%_^_L)eI)on=jujG zRZzV!5&%oRD%2GvlRr&QhyS|aKM-Fc3vP_F^UeP0fVbPh$6i4dfPod2FA*tv2UQt~ z9$*|M#fb*+Np&cTZ$AK`0iu6Sw;xzRGcoCbmBlJoDtg52ZbM8xQmMPSQBpyZTsa9yV=qyCh%Ans63J zHBQPZ;2N+z#ezX}2kLa9QV{oRU4 zwMcyFHi1Q>z+E$Lac8H*gVY}~rc2t~lZoPwn_-hRd-9Em^oFUUX;*u0-ag3*`ufa7 zF6$fCkG5E@$Gj;$8%n?NUhIZ((b#3~bGJqJpOE}Uyq)FI+1lkaK9(NuCVo@RQlb4q zY0qtf-p(Gg`)}gKMU6~NR)p26Tb)zigET{uko_ySpkTGdW931A<}37kdmG3W*$jRC~v%R&Yw z@D06Cfc+v82^Z=a)XZA!I1sOkB+=cVHAs2pN%fCMqY0nDmsE`*x*2Gf!o#*be@#zI zN0%$_AfZi*%*@NG{`s~!sNFGPKLG$3gC1rUmWwJr*uDBET;&PPXPE@7eXR?Uvx2V^ z@)?fO^ZiaL9owMJHbWRmZS@ROi7`mb`qpWlz*|eWC_^H%L~iuf9v`6Bo{IG-?vc>t zVr3+!-`qxVDx&?VD1LzNeMaH{bJa$A5!M)+xbh;x@HfL^{omGl^u=dSh+O6(u3Pe6 z^C6eTFsZ~;FvsyYa(Xw8e-Ki9L1$rDY`D(Qqmo)vJmD$3oy5Y&u#?Ly#;dsYZ=|64 zow2HSI)*2WZK1up!RT41c^qS4C1)MCh9zTV0<-58n#w1| zKGvE=-6u#4SB3Rww6Z^c9J9SE5UV;@EJfGxIBtMooN?iy%>UhO1R5Y+j+wdk-aR#e zbXCzpQi)w9h8gIpAxKJ2-ZgmwL5tYeSHVdXLF5L~PMSKDfKqXaiHYf5>jx+efg`BN zNgg-enE>~?){oubgnSnwZ-9|S!~7ih#1*7@%nJVcYtVP<#@=fg#-*FLHa7bDhjhMt z`Qg^V;S~2o*FlqqW}D?1x%`ItaXX&+WG7Gt8*qIca#CSXuP;8!Y)yjIeE1 zXo9YI;JUBGg6H_`tTq~$A*zECwgAz!h;_Pr28I~g_82dkm=YQRl1)BMcam5Pk2N7< zz4JHQAFb;`MXyU5gtUs52vrFVoRu^i@8PVMeH~cAkS<6sQgk$<;!W|WWA;^#?v62Y z%9u<_s|C%Lf@J3GsTSKabbD=b=#mnnw`3oRUB4DZuDZXH>ZR9Pn~f*($xHPmZF?SP z+Tx&?U6RE22@|6qXCl4~j}2{zTk=}b8Hr21Zz5q(ns`{!kDt|!?H47ukli?zdzAK* zofT~=jiJ66g{MT8Uvq@cTh?~|2!o+WT%qBN5MSE3K$7M89)H!07skira_VB8Bwx>E zJl1vhw<1eDJEZ-c0feppws^q%#O9%)zaF`F|J_sftH@h~$n*O#JYLY_8ke%Lu^r?~ zI^<9=^=~Ur%Yj?!>gpyxLUXEESVK*?bLv**zu1aZ{xuS(Fhyvu7mC-e8eR5U(4U#DgxvNY?|rk%75`+ z(!SWn$wFIiy)6OX|6W*jC*PlE3)b=0WK{_1@gmL)2Zl3M3n#kkFXqnsBzZNGbjW9D zr6o70mP}X9`_XG2-T6|labvUTM5P|Vj=f68Y+EzU(t$BSnmI4)!Pt^R}QTjAO?(BK|lR7%OZFWlR0k4E1)}^n*-Ev zRQ1w!ftN16k^JdT(Y|8Wgtz;!m1TsG}a^XklN!?DFq^Y(fV?;S!dS6VLJ3Xx^qOXL2{>C11f2*u{~ zE$WX1ytK(G2d3h@NNWu0bU_~j{b=}D!>9DUWeKNfmd_YII?e2toK2@rVPI!!Zl~K{ zU)pc0BAU{+aBFF6aDM9J(Pqbtcu~ov%%-l{UOjyYTMd(p^j0a^I%{in4K{Mm8e5>W z=M{q)t$Aaig4j+@cey8%{^}3-*#C%4pKyMk^hf)gVDgs8=G#RM_DkGi!}`A zI51VDPWShk*s8yZAg#pMFH_mwG+R2%K5zrOKvn`B0q{H!ZYVFs2l@JPUW0^p;1)ug z_MNL?7(+@a>J}9wtmf*%g31fzdY z)d@KLe!X!zszkhl+8vM_;_S2|=;F(#vGJpPiVO?0U9N`4!%~)&7{6_cJ|QQzdAzxU z5StoX!ThL$mi}M&NO;FCo2q~Rb`KS7eOB(DWx>>E!ki#w%OX5|Jw?J*&xZ6QRKG@7 zFL9n>yk!5*=R95eQe*mf438EkCY&{ksJY~KyK(o*+Oc4l^CVS<4Z$NdtG6y*(LU~V z;_%eUp+ogR|c)kl#1xeH>`x~CUm+l7`6B*M+iPN>NM8m9x3%g=HEj!afE|( z1Ok**$7Kj(Y87XX(~=A5)J6FO33Sa>iZq89Uv4V;txO<}xfkhhuBvm+Pd1m(d1*J_ z(xolC&v$&49cVvqRVEuyh38WLZcV+n5GD$u=_tZo2)R35%}^6NdyCC zZ^`@jd7b|ft;==gqliowrpKeLFv zPquj1y@%Iqe)+$wr7`{_i1UiA)eF44e!gvUr@%M|Y1kFEf2`N|42sW|PyfB}v=Bht z;lfj)AD^)7Qh5rXke&c-B%z5dFEkY4reY~rEeM50HkmS5I>p5vko|xUkI;n?oV9k9 z9~vXoa=T`Grkf3{J{+2CldS`&g>Z!<)M)FM$h)esQECC z5&7ZX9R6P8<~_^N!yeP7e0(Y?v;=}7ZOrLHN5D9qOKWh4i1TX3lDwym}IUVj@L%DhL zrhJuM*LWZx5rjL8hvt{xxqTb?(+`{ja5>Y?JRV`GRI-0x+R&f{+DCP@Woa+Qw`)8{ zHaAlcem3*@vnrJ4knI-~aG7&Ll7+;xOP2(b+rqDVZCQQ%VQvouX@QtY09U07hP$N{ zpag`OC$@p&r(t618EPsjRBYwItuVlTJ#k=A0qvkNLhgF^qKiRf@%r;EHNlI)?ze7o zG=7b0zG00`d|`R{%{213$VP&Kf_0Am;PxQv3*|EA&CA2byAfhXD)$ZUI;N>)b#XV` z1Fqum2I@wo4eu&%Y3*ZNW>h=yOEZ3C>eydFE_(N9Rq|{HQ|Hl@(WW(~g|3!D*EsjF zsHC>Sa^HXVm-J`r#oirtrKe}GcVcZ?;+nlD79GXme~9s(l6>FO08XZ~_Mp!%_~XP- zeS^A!;TktS7m3tNc>(R@rTl!%&{4ci_g@24+WYqocI4kUz zKEK1;_%3(A$faW08z8*Y)>1Is6c?vCY;L^zvS&J&ot+&cE@y>%!d(MzMymUnvP`51 zF-Ra(EkqhA74}C5iz?uv4ejz>680-g0I_eYcN0UsBsR-Wa-Z03nZ1 zIC|aTFTh}M>BkQTOx@sijd%;I+2-c8IBIyzBkTXGfMqCQ_d9N{Z1FE1ax2}x(0QzMX+ zG2SG^Ap;)0m{<{H!f>2K%u7u=qn*)5h|2{Q})m}R~I`8cG)_+sTnMPpHaF%sAY*(nwkZ;3y9 zoEUB;MxH=DA4RQcWOh_uzYc*glp`HzHj2307*k6iVlUf+X?duvzP^9Y){UA*HmIj$ zQ}O$e6?A*w9XWC&naF<|RU3vY$-RwNOPiO!V&aQ_XjaykH-{-sj8GdL-K2Z9N-;h& zlQR{q70?-}oJ;H;V2fPyvj_HQKm<+-U9^}hQ=g#Sqc3$J8*QB|3 z@WRiq2JrTM>)zw2A3a(Kky($5HsJ6fUb0c~Khipzl{ij_`ub&X^r%Wz;PUo%cB&ZS zV_V}@lZg|9O9~DE_z6VH#q)dF>3hVc<*Mn~r|lk0w*3PcFZjv%R3VbzPf!PA4Nia8DfXF@Y_9)amA zU{a?1~+;l&Q}5SmPISE#EL+_v_7TVnNkKh=I#| z=;KDmD&AFv4H`5J<(|s?1ihNK_wG#Lp+;Ol2*_Q?1gKk5tF5S*hW-(i(ei9FSTv?t zJ-?WhF75_@Lme|&pw)O>G0sJ=y849M3PF$2A(G4nHm@;-ry+nhCafV@Ie>lO_sPfh z^4~w^)25cxu061q2L-l1Jo2X95i>JZf;)yhShzErsApimN@fQeyvg7YkDz7dP(&A?71HoE%?b~mSrK|hW~jDw{1`2#&MW_I?;Ky_;19j@!b z&z~Z*XEf=sK-TFO|7z~opz^WDP5WtZnAj``y=E-P|xcGs2>L_s;&lk@We&n%ZEA7gu+=Z)fZ( z1c=~&e6pb2C$)q^{8k;Z79d(18GB9;RF|7ie`{JJE*)I|vuE9%^pCPz^rICzspAG0 ziYmLj~U?2Iw#3AmwXy8v7bIvzx3C5bSP3H1A;ehV6~mB5z`pGa3j z=1S9_ua%gB){QWA{6GbPLkUeTAHEP?RX1-Yi02v{mgwm)eR6`f z(Macx7OQu5$+w~3z0#)2TefUL>wzpHDXAx($}K-*AWp^5<=4_u;sHtQzBuDDquvWA z4)DoG8&nRo3O1}6=a-|O#|vu-0MFZR4*}cVvZs=0VAWw)8Cm=y=0ds1uOdZWrvEXI zGBPtaGSmM`J8*!{Se}MwA{|Iy0t(S14K+04hmu+D>T+0XNJ zsWPe!SS(f8zIpo=)wP+#IfN=AB&DOInoND(Vm$cZ)bF$^Wf~D4j_1d|*!r?|Ngh#ure!#iw;_2u)RS&z z2b^b!+l{9lwr(Rzl9&?=JX~Ca`vVSt$J`5Py)&Q*;nA?Mv8kU0Q|%5I^WgBE`f8+! zfYw{~4YVr1=#9(Zq~3@2zJ{&w+cEtmC)nIOQ|H@P)k9>!zt&_@7=q%pN^Xk zeFW4Z;Zv`K`LgHqAJnjqaahUJcg7OmyRBSoU6H*dZ`%2jF98T85c zmXHR0_x{(NkqZX&`?N~VeZMecRn=eIBJle%GDo^Sti+N4ODaFCVu{m@}P6_ zCkRS+NA5g$z)t|R_4Q|U=D7bMb0V2jv$Ote{suO-rMBU0b>KV-4_&zBYv-ytVI<`|-yP z8U%e3Dh!-(7@7Q7T0*n4RZgx%HdTxnesrkg)YR1Snk{Uy&C2BdBj6|3XMXz?FZOS{eE!kk~fAauPD|U6)FjEMLyqZIo5!?SYNNI zqx0CXY@=+d2HX#KO0clA&qkDmURMHV5e#r7r2tf>A%OF9CM5-%MdOak8>-QxWFbMV zUWcBIjV(?4=2Q1+K5H*NJm>udC;ztqQL$>^?to1PI2;BJ zYTLKpqKz-hMK6Xm(+jp~dq;=neS3~AH$vXuNP0zj;B1-Yi3^9FPkXnRtqm^iC+gYTnF_=O4g<+$O|WlF=JHk z>#PjIVe=;q^47f z&y;xY-OEkTQ^-A0-K|EzIsDG@5xjcNtzp>@!UqxcBm4=`-owWMQD+rFKLa2ECshr2~Ox@qOkqkcHZ_aFJ%yAjeT{IT=rc9DBDb z=4ECvKCqKu5HBxpP64%gx&l3rNajWe9Ja&_>hL9DL6}QrRVN4BsSRd>f70`pFR59W z2+F{Ux1(o1(2``yN9c? z-|4OApJ|+O&QrG}?SytLp79S@hXevPB^Y}(j_L#+2zt=4w3i}%S+Ay(4r6{s0wV0Mh^2%kF%ag%8s1nH%d!rfvz6mXYY(*Y9>mGU zM4(`p0aqwABTQAGqJQY=fzD_8I8{H~i zjm*@~sQ=e)qmd2fBytx*gY7G9-&@`%t3=peZ29ROBhYq?d$3#yIb+tcGk5*jwYD22%hEGRo&f- zc1rYqTgrIBL~y@lkDBI3*z)Y^Lt2Y|#&{YdxN8r(5vc31iP3M?F((J7&-rHu4jy#k zBQZltf=M=BC?MZEEBTAYiKz>5m}a571(AH~3=6TXs261SLFEN`PC;)gbEysUVmAJC zp}(=_PwF)Fms2cYBZlt>DN}WQy@#UBJG5y z4UEdt(y$-YuA-z}T3SLqhpw14k=B=}-bshhWw>`dq6c!KaYX?<6*amanJ>D0N^MC;Z$2)n>WXmnTmqY4uzz(A3zP& zngo7f$3%bE(x57;A095fwi;M7i4GP6CSqihx9nnX z&&SQ(zy7LrxPM!>R{txVTFPC3YPpBg4m!x^P%WDEpu4OR;!Q2V&jw2x?GjFFAPZ!p z9rl*L;OruOo~p_E@G(@6^u$jUl_4lDTP{2g&3YRtLu~U_5s206xgal`TK)4Cx}EdSCaYzU`4 z?AhpOlhOj!g{T3$m(Wus8+-93mBN9CHOs|fz@4OFG}pcnp2-YLw6$!{SS)n*%>biAAC+f1wGh0`V4o5@juf^ z>GN>teScyU5km?DF^F%0v2{(u!ke0TqkPd1bp8(;hE1}r+RLwx{#(aeKBc^vxPUk@ zm|kz`Qjl*f{GS>UJXao9X^4Z&cJE$=y9d=w5-=;U-7vTy03U>x2h!A0Z#i3a=O&ij zYxX7P)B|(zcjTP6Lskwuqv5XYNiFU@-3dl1{4-l8C#o5K`BS_nDsS9U z)D}JFgvvoLFE0t^4ZOU@{VMV%K-lJ+pMh-t;D0WvAp3vV2#-(~bV#FzdQIocT+4p1k*eofEhb3D+_9cvi7=%GVpSjKpq<9+eq z1J$SSVT<2*u*`kM5tKG?&MmJ!0CyN^N1x& z*L+xsc*NTqc7Y4Oe(k4P>a@RnX(4eA2C%w;KLMu^2xkPgvl{gVEbf4;wZqaqX7s~O zSU1b_;y{MDqRUVmRC7R%Uy}d=E94$g5rBy&#KZpgP%y#sAT){}y`W>2l&L`a(~d23 zDT!srYo&$FlVjPaL~UXJCzo<_ciE61MYZ)!fC&KA3{2*nw!USGqgF-Qrde32E?OE0 zB`)rPYVYb*dR302=RxDOzt+Fi?Qz4)>W#}F6JL7ig^CmfZ|jp5%1xX`p33mjeW!79 zLfsYJv8^p^-No=Xemi2Cja_y*axX41ERgHprnBeORuA|hee<1tMUxze#4fiEB+kC7 zqcRvIjq#*GL|vHBHno5isQK0iNo_E0yoP`cP_WU#gsM<0l3Vfro&RDfNPfq-sKYJm zKNa=G@XKn1+Oo_Jfnm$c z%p67^!1QJQ$+3pF5$as@*lW?p!+5?iG)vxvUjHlv+V*NQu%Pe>2yngY&6V4gI6@o> zkTU(bvKo4|+*+2Jm5Fn{0Hq=>R)#{~ty-r@5*D^d1vwQkXFza9ZEYa1 zjQJaASfKijXW~rK#IpIjvQl?E{`@mfkd`G*of=f1ShUPo3@ z(W=5$LsRpF5)VyIO`@({mAN_El@pkovL+q?4?W{dd9OG23h>P_^>x}!^gV+$fPo|W zL0Mv;5Z1zGS=l`8$og4l68$+7DM;BI=Gn6_+RV?_fWPcQuuIpK}qFdMRb+za0`O%zNkFssLm9mY+qs0TtcJ^;s| z+5lr7haMCrFp!-t1N=ko0|aA1p3NfvzJ_vbJJd=N+9etxcILE75QQUYj2PB1daG%x z@ZVIxIn*gXtj(*VXaG)7NpOG$AL4L^s|81TC`21LG;r(pl9xc#&E((q8O!5)5A!m0 zKLAJy>aGi0<=x`qam);;BC&J%`HnU=O8BD0HcT{X9629}s#xHs1VN1iIq}T&gH>?~ zt{xh3|2YqEOpYD9l}3HBkyACURO7SbAo5oEsLo+{b!^YK>o%pBxWaV8vEd&|s~tPi zMYIK$d%`}RmH{>l04c)E>#Wq~^Up8=>N}nBS-w#yFNBb0sX^;!0K*CuXofHDezUai zWPZ7a;A7q>BBp3Hu2sF>km-FgAxjX7aBySyQ&6y!ICu9AA8ZxC!~r!?`aD08(SGB1 z-~@!2ygWXU%xeR_;}LSJ1^2jVLe^Kq^UJA@O=MOawjEXv>6)E=p%Q_TI$)&X!PTpu za2?;HqiyzL!G=XmF|Z*sxbV}) zj$tR>5&Q+2*S$bn@J#>N7;0fnY^SIsePq_7EnB;rBVAX)Wh7!JPo(1`w)}aR@b=r0 zyLRkJ!*7Z11j9Z%p-rUu+*1ppZW`rEd`ZVbL#bB1s4n~ju?}6sCoiatgiV0jJZ=3$ z2k3ZiG8a8RawNVLec~wY^#-cp=r#^q3IK`F$p&P~0F|f47`p7}DZK_z^x;G#@1+m z4U$SMJ%z7y{CL|jp$J>Q83?CA+QbK}YxmbZi$TB@%Z)-p!x0B$QvsGK6A%8|j+{&o zFeJ{wla};Qax9T>u=)EnCGUKl6W;-iQg=)G!4d0tE_xd%TXF%=uHeLlGyO`Y&Yv%E zg1|!!{g4~%XPgz1Sp+#Fb^K|stju%c;LuiP%Gm`I1cG1wpnyo`FZ-Q_!CZ_K1A`~} zJ1jmMliPDUZfZE4Tn$`z)mg7KJ7}ZwY-)Zq-KA;b)#oD@rWke(`S=js1If8RFNg55 zo1VY<;){QZTq@c$9@-tk!X?f>{`D`!H|X{M4?l9jzzQG^QFE6H9V zGlZgul8~&lL?SB*36-o=l%0yQw+P?o`?`Oh$FD!``@Xu$&G~-6j@NNK*KwRRZV2aV zVbX!os2oJC&3&fM^AXB##d0 zgN-I#gaR>B=$o4O4o2$kKl2Fc`OACb-E$Zl0g^#=*1b-7J^P~nD>fyVzY3BClMmN0 z>kl2EHZT)ny9uG@pRV8LVF#*uZFL2&*@1*3;LQjC0cE6SMhOpLoV2QJk(@3p98VvU zB@`ua?xO}*{@nI5>(s0RH@_ZBIr8Esom=6bp{>VX^2~G8>$B@-G18L?^&PgHxi{u} z-|^$I6yck6QBta#X`1zo-m8hRYu{j3PuW`>SyXfG_TC#wYjt-8&gESZHx-g-uIUyD z-|5KowT9H7V|MIjyN(&9#zd%?snxZ9Kx5lo?w}pSUw&=FS!1&IXskr|4apM@FF5Mh<*4hXNIi22C(?}k=Z z)%d{RCHy%&OcZ!z?}S`1o}zbgKg0oX!PnwF=z0|tg8E)wD1E<(2`KjJRpEX6y8)Up{5F_2!qkn@^TA;j2@{*$Q&3mZ4moo~+29Ajzixg1UYzS1`cd>- z*|pa2_cajFL2F^~Uo`!kSz7W>*YlhC@eZ#6{HK!2%IgFnEcFKZ4j54mvR%OnlS5uV z-s%OtYU4v>PvJI7zWVJWNtU!eR^$)1UN@Y{8V3lHRJmUe`glL#8D~GR))dC@fp8KmQXCzH5bJ|Oaj@WqCM)hJ z8&oh)o^2Z=>v31+F*}p-+%DX4|{;lm#(m~g`Zr8P@ovs{enbUriSDxdSn$N&VGb$#n zuLS|A_XkVdl{X~{fV~0v0|?*fi3!}u%a9w8NG!Cc3$1J6n}kVx5&I$lb_D}gAWTs3 z!RPonwn<(ak|DF{A;d;hH6t>Y!G^T!jiyn<>qfee#7r+;?W(M(2v6;A8%b0>lcd8QeEX!*Zj`u#=3a0w?Zn?d=3CCaG3e1q5#Tv@ z{fT>o!Kuf4-x!2Pc+v7CBzy{#xxlHNlT>8lc;fn#jK1sx?d&?`MI>$e{H(`%R?W5L z9N%^hy`X)+ES2^-@{hR?$uX+5^^G+JM~r>vcK2uQNd?(zC$3U|>Uh_9cx-a_%%ejm zJ@rMx`6bmWH}*KsozlwF+q>WgoIh=r>)1ZNhBAEz*26uAIh?Yu()|fE`WC)Js97}l zj(WPx$L#wrwN6xxzd8LvFM50*t*WVH9KbWPQWpaucJRpzq%i~qptnSL2BM=TL1E$m zoFAy6y1jW3)DvMvKo@;ACI<0}U^Ko!c0w#1B-&X(b~vQyb4;EgQ{!*S*}Ju(E|Alp zXM@RPZ`=?B1Vj`D9!`?Z&+9wXiLZ=j0}>C7&ZfgrHh}8?NqJ)%3q0LGi17ke>>Jd9Ag{3!ihC zXmN%Qa5N!R1O2WN0wty@2bqsw2>q}X#wi!A;#Kc&;0%> zng+BB-&%=Z2PGu7&>Q_-S{jhCw6K6)px#zY(*W9Kfp^Gb1Q!E@0mmmx-+y`TTphe*Cf~Nw#SP*9!gB#_1Ps9} zptaUt4G3%ODaiK-pbbell9F>!HhBf{;AaD{aY)_y8h#LetQ5r9m2%7>6rzWcoM}BY z;VK3`5VB1*ma0E3Ven%J}MZ> zdKwNK+uQj!>0#OWjiMHfR0mpR>5pe$rqvW1SyVSDM3v+Xjo<|mvI ztDAcSx0OGoywr0(x8OUYczdOqw*Lyp`!T_jQ=V(b7hf^moM%dmobs&QQ>OItbnu13 z;mgT8lRLSca?OXo|9W}wqm9i%G<2$g%Y9Gv=&5Z7lQQmdzj&)Y z@YnE1+PdGWMQ&H9>6EvCYcA(?%9L#w%@wg`QK_cN|!9Y$!!oHh2g6+U5-bw76Ulh}U#a2AXHi(4>%b!5@(Z&LYHHNj#g^oReR;r~s7S(CaRePOz^Ap=&^4kh!_SPb zIf?O~L=q;f9UpUXg)4XrkYcg}sw{UV5QU|{Y7MhBnp-Ru+{kVRdk!KjP4S0Or7%-r zaFr)2h2;f0l1idLbltPX%{7d>mwro1O9Oho0At_OBNRugyw7Xkr$s+G;ie`|Fm}+y z#*VfP$Jv-5FU=?qOtF)dHHpM0Gz+#o9=nC|rmn*Ig`CC{JS_zQu2a}sBcN;QV9=3f3ikL$0@d}>~)UPP{afdyq zNbj4PfT7|nW6zwKGg93xEG?BFfBy1&z*TRY{mxWN%nedD^Z1^3+x|yLLqER>` zY`&DE`GLU(3m&1m0IY?7)YzG@8mE?ZTRAku$>|R=)9!*qL{(U6zl#QM8Y$iY*bPQ#Jar7i<2`$b;%xEO5_s;%|!d@MrCKF2x2S~lBKz3E=x zl_7h9YuxVtj$UMvzS<-EX8&OF_Y8bBzsE~&{ZhIk($$qiY>jO?s(3!9Tc1ASR@Ynd z&OL`@#yBd?_~@Y=CQ+H1nQ#Y{M6 zCNK4N?mKv|GqKDW4M@YeP4P}Am^!MrV zfLZtpE0%i495Qj#STtN*gn2UR2C*RA0SZy*A%PK7qI5HqcsG(sDjptF0ATRl?~OsD z{=TI}Dh)R zUC)u6q;ty5%=t#R1#P1zQb1Euv{@iQ+L`dy_8Cl;5Orhqs$rHs6n_r(_H*OiokrZ- z5Mw29O>5Vav=5?JKRZPZa!{gVXR_*O69KF&`C>;5HqZS2jZ6(-y{4wAS~)hm?yt?j zw?gb3MXuNDOB=*fKCnKZ{cxw$G#{$8oe2*s16n<0cq+`9)eDIp1DZd;*?MVG`RWmf zEWa)Q^6+bbEe`w1lla{JV}rXwF5YNX?RBVqhzG^cvTU{Rs!e)j0I|O7sA70+s)%> zhK|5!3V{e*3O4zoU>ljJNiqDLc_Mh@O&qvPwh+*#R%OXQl_PbCk{@NNekjhGT|Hy3tYy{#O)Ar#JCkpc<`N?JX(;ij z)$d1IEa%3eiK3yrS2ezg9qV=+-Z$GY{S$(yX}Vr~WfR`msnSr_&5q zvD%LWnefS`HF5oKiz(Mb-c(+c0;k@GNBi)W?o;;};5s`B+P&NB^O}g)*m>$$Z zr^`KdxLhUqL5dAG!KE|y_y8dTx3k-I>@D_gMn9@4ZHJi8v|kD#bV%wzVpj;VuYm8H z6u$kX1rcc}t0{%SgSEwTPY8f?ZS8lkXJn0TH(~^Z9OkDF^~-s+Cr10@8;jF-k$Izh z_vv=vr41S1Xw+l;=p;~MFiS`U3m?i6a*|5J0BdtxI-GxO#R3VT06|<4oQdE@xpd~f zUs+WvHM2$9%&GVP2E96qyS~>r;BiRQjxIa zZ(M@m1sz|!A0`HbSJgB(k4J1)8X0LKoYkbui1k$5&#no;jEaQV(zqYj!F^2yN(C_y zdOH#(0I0iSH?`E;dcvy&lo9a?%K#sqnLsNE2|FMhS65dE>hOSs+5DmPwhCQM63_;| zg=XO?j6xwqr&NN@go#5~25J-2e4^{v!ordX+rMf6eVr%|b7OP!fkTH%Uc5*J4Hi#D z&ODbI;Yq1s5sJ>aAa=KB)uj@ zWot9H?BdLOum7OT$$))D&d%AULs6t*&$+y$B1VrL;h7hy%PaOPp>zE|+z;n28DFn@(_}*+c(9CbfD| zW3X0N+GvvpH;>?U(YM}?Dfj(H$2D?J#Vh`s*dEFt_fHVmquB@-E)KXzfbt+R?$m7> zSMd}~?Fhnesw{}PDY`Ih z!xPusHE!#1)Zc#zK@kzlXSSMYJ2*YI_OCs0T{I|v;lBPuUVtqoU|}8u!Mi8Aw7ij{M4#Cskz6N^;EyRDmB<3MxlrFW_?x z4O;C)48F|EPY2FIZwsA4yW`j<&9Kvao4Bt1+P=Qjh|&6P;6z1PU%-ADnXZIPuC8Fz z;uHKVm~woMiqq5Hx0jTZL|=X+?=wuNB{`9aP>AX1#N_2Uo0yoyvIs71Nk~qP=Evxi zIrKVn{@_0CM3f9r)zI-;j2)&r&dKo(i)(*lEqHVt&~N-g+vfI6;v^TQKIFH<+~68& z57irH=qj*F%4n?dl#o270Fh+gXVi%8z9a!SSaLtQNY!a^Lf*r}uIfrJNzFpND^>ST z(0SjN->=xwDLWo$-a_M$AbG0iX8f+u+m|Wf5+swoOkr%4@UQHQQP#8#@7zCpF+0EY zMPl8Bz}@Cw(;~h&HaGvh&qJ;qU%aHv%ouK69eK90%j#nO=C$OC1A1aJ zf7_C&NM!Y#>S|h&b?yPwV0p3aZWZ&j2~ABug32ZZ*V)GT{EGi<+$g&L46X`QeV*O= zv^kuDIZ7oN-b;})_Y!}FuN6daXb3l-%smjhW}I?=1TVCt`ctl=3%3_35@+~+DK1Cs zcb|B*^3~&K`KDcdLiKAY^XHy4jBvbI7A!bj{0kE_F&yCzyv+X=QaR{`h&W_mTIh|O z4(#0>^X$QqN^Ijo^k zv)g+`$sE1TmAE)x1wO6-dVX5qRc zZ}WNO%(n2Y(enX{x*dj(*dJqA2K&Z(AUPg9h_1x)Wq9*a;a-i2fm;K}dFyb7!i6Hp zo1S)7mY1ts`MAT5I>fAtr@4AF1}%WxbBKr|M0`KJHql4yx+SejZ0rX33df;q?~%&= zEVMfkjyR@%aVh~N_ebM~F6E3Qq}}k#b8Jy(d1Eb#{sl-rSiyoPBipua1@3rISoi~W z*2Pu`P3`0r#mF>(NJR5OYgKu92$}-VPSW6oUPI;;CLgd&awsDh&(7dzi>Hj)oDJK> zNbSCp5U;L^R(~#tRNh9W5lYQ6pj{R59)4f`=9IdWG-Wiq$F{oZo2A{u@3x^g-OrUu zE+j-MY=1F=2At!g2F>y!D}|>v@BG%4BEQyn6R8?SBaO>HsTb83Uum*j%HI3SFCx-@ zph#S;LsIjGC|isTDa!hMRBGC1XM%Q#1_{WEv! z)Qg!U&rx?Hs|3$8=cE3lol4e@`eEDim-T4b{k4n~$%Lz0Nj3H5!%ZPqykh@Wvvy0I ze3T`U5UE>n>RS={)F1DxgOyBM=?(iEWydrI!#*rOKd0)g#I=xt^O*VE%B#L2@fS7q zs7jvnE#HybVfCU{zq*uGr+@+m5=NBa{`EBoK^uo)rrgTT-X@uzmR5=VchHofwU5b0 zZf(u*ELL`stU@4wf-wEt>;3~AOfR>1$lQVaFG)u;@dxg0;9+%mV+lbZ;%N^UwP$va zl(1|xr(IG?3hwFtRv&)tj~w&zK}#MO4S?!ltbLsYIw=QO5AOu2SboCqOX=AS;f`)@ zeT6nO$68UZ2rsF3t0Du5BH?h=L^uR_Mp@mBj#$f;u^t2ie-;*=$&yHo(}{^}>3WEA z#zvnKd~Lwj{*7uZmjtx^olxJP;Dhc6tHUpKbOEb`y)2)$5T66*7Ep^&9IeBufJ>ad zOkmF*e1$`bid8h*FFYvpd+@w?bCeDKI;I1JNCcpkYa09~0BY{;mY0$7hgunT6Npb8 zU0wLj*coX7LxS1F3@yQRP5eegA9G%lS$~*ar zL4BNI6W1rXr!P8c-fEc*wG$A>7DW#@y>MF~^+S+-quHJyb>)kYK0_w?zN`vRUse`G zMX8NL=T{$#r8VO_#FK$oECcHc?<0FxCR4F+at2o-CMsnF?E==x6e3LTQ^^cbDT-r# z!@|(HP%&;E$`DfU7jQoq$3|`L|3Ku7@iGUk>b~c@M-$gC?z8; z)Eiw>=Z{g=f` z`s&pE18$O!i6Ptk_rXKU?-vgqtT>rq&p&_m=<%S0(sm85uR&KQvZ3n0lRCN2Ot@|{ zMbsL?2dkjzdV-IJeP;M>pxe8|-4G5z-3XN*6ne-6IE*+}94+68p1;6&prrdW$6pKSSDaq z0p1(E+oCq0rvSvddaq$PY;0tRgOC!r1q=B9_>g+23G*0yaah&T^^B(A2$VftdRyv5 zG0K`n*(Ik0&MSarW!@b!h$Kiw~EA>8Xt%!@9no3Q4ExiD$51K21uWR|d0sBzJ z@$*7V0`?D*d~;LNZ)j!U2E_6oe7-Q~oM4APb9i(VM&)|j3ZLI>G1*!b23t%ZMK4>|L z?M@WHRva-hBz~|!Y3-r^3yY~3nb-%gs@ov?1pONXYl4DW{472{^X?`mGm!r0CbiIB zih|yP?Ftb|{^`>kj$l6+0)(swTMM0x_D3W!SU|#ngLu#4uUBnMqysWcX+|?~jm0Z4 z9d)NaJgs(KgodiInzH;aQnA@`S>17PoJ^eWYVN)!F>&(acLJl05}g z4fXy!H&2{>_~>KOtcMcA&s&mP+sO5ebX?xEgD(}y*8Y?Xs$-rfUR`9#{bD(6ZO&6` zvnCy7Evdf8_-@rnJrITU<%P-h^M7Jb?%pk+A*pUW`u>8qN1XVrK%Qph}L=3lvbt z4y3Pxye0CGv~tAcb1KBEd;Kj2hjG=q?o_pZ<5hs9% z;V^sb3Bws&6E?%=q@~NyV0{}~gRF@CE;C65fH3s9eVQba0-rmCaURESOH6@VMyCe} zFLwLXrJ6#)c^ya=LeUC}ilFKD2J4|u=4Z53_Wlor0uhB(0`EmXz$tx3{3`%bC^oEH zU!m7`hE$9?j9*xoOq#+uL`M4v;L0vj3{>azn% z#s}d!S2w{9xaZUX1kg2Enj;OP`qNcxbWgL?Zl48dxNG1 z1C9CwEq0Z9q%O9(q#VO!%)@*Z^c!)E!l0Jue3}8&SS_nCzRTx2Kqs`PHQ$fs?}^HO zh@?74NsQmnY3%QxMQ9ScCcHkp|!t-y`@wehAeFKkpRns=f% znSZ@tU;o5}&(FI4FW)0^Ou`YKuJNAijiE;stQl|ePdHmjP{OasRM(5B(NT|wblcJL zE8JtE=}9ohV`$?ntjz7M7A5YrJ^ihbg4gE*1=E(Mwxdrel`}Xg* z3i!Pnu8;dmsG(FO=-A=*)XUqIdLOQ7=;#4~N^kVKeQN&Qo(z5Vc@WD;D*l}luk@#& zmJ0Thx%TIkS?54b4ahL}dvM%Zs6g@(RI#v_nRe476hy4+Qm5O+k`wX_Pd4fp+%y-F zPC14nAR0#q_F?>jnm*O=(Q}xSVt4cN0~tC1VZMUGKgc>@G!r^-0NaHa)-$SR* zIbk|-iR0su$*N=G@8&{=`}()XUoF;V+#n#pIkB?Y@&^c!vx$3KFSN*JY$kzU1q=-C~f z(D@;N{r%0oK!p3f$>cigoL{cbXVgD`!v)Dk+kQ@+y&xZoHWCT5k6!6f2+h5{i;oG8 z_FwQLU2J#63kuPu=vNq#emBO}JqYqsN*UDJ5I1CTK*_}9g`v555L((VUyK&Iqw9a# zcwd#L*0U>?ZQ!MLUCKOr#MG24xk>laoux0!Mp~xna$4hZ-}5{(ygHyCMKhr^E5bSy@yhBzjE#pm|(TaT}XBWi1grnRteEzv$Oa0RCzQq9K9EbX%s z!X!N27QK4KMc}e;0VCw8?HjrtjOgfWg;v&?x}$S>tWEh-{ier8n#TPYb}l(N)UKD!CIZ z2Y%`EJnilj4d;j3o)SfD$JBPq)EC0E@8KbO)3AJe)Q71~6pC3KC_6~5 za(>x;Qb=2Q(DCl47f&NYz^{#_i|}~fzc08%btrRl<9F&BgSy(d@W?YY#G~ISB;<%y zP!YWKQ!u2%5&<_ddOrLQjE0Ws`#EBp9g3SBBI-9{y$yB&cKjyO(`2*)U;}{c!NSw% z8J%H=3Zt%06O+xu99=hvA2t6B*32mI9T#AG*4HO;{K5sS}{^0`vE5# z85%_X#){!qu8*}mnNMXKaN=R>aB|xB$z#-Ke>j41N^)`{T8ELi)}!r4Vv0gs10`yc zh8nZ$OJG+gDtkk4y}~~Pn!3Nj%=hqNEDD%eScnQ_S!Mgvd@;jVKwIu;baCMvNz-7= zcym(~Db22s3_l_UVXWt^xkR-w3aCkGNeL`Ow5s^N2+EWr7V=hDw~X>4Uu4K2!94Pv zeGzo&5LV(f3J<4_HqC8!ytHe(#rAfGVo=V90I4x-Ewa%;=0EJdZFMk`VcCSjVvqOY z5or(9WfH%Dz`f&JYV`+#&;Z9UsAD$gHu?2`|!NTztRRzp3)Qe?cIC!0B^xme*fM*BnY6R5djs2p$7KF z{CwRv|pi)cavRlGFo359b>N0;)me4>(|-w7+Pb{&PG- zSgMp%b(Nv#_BKC~H~E=K&*YF$%k5?drHpl?I>CoJ-dma6Ivk1)8?~IOIK=C;8$e3(VU_)s6bC;%>y z%kI-n_vrwxE{NZ8RR9i2y+uBg`NZ|qsn2kFfQQje{D74oLPCHJ;N#$7W~eK~L3r4d zWt0c^%dul0(A|+pc$pE%BrUy-L)~qG1K-9OB1*-Ts_x%vAyrc>q7<13qwwJ- zBtD;_BQkO5DX}I*P*4!BEVKUc>Rz|#4s#FPo}=)1LuZH8sqkHX&A-JOG~FSon`nP< zy^UPwQDIK2DyNyPFT_HNd!MG44vvURYJ&)K?b@Sl(#wtTO~-Ay7v@SiCrU*ma)pE0K$i>=g;-N8%qlP|hi zY(fgVKLQZ-kG*FjJ>LKDAijM0f+W)qo^NcQ3FF1XiuZIch{|t?Sa7khcwl;5oM8pI zMXU)#cg;pS-3bZ!WaxUJsi|qTLUI^Q<1|2{wB}zE6Mb!MC}e~cQ%#5+RR&Ia8;a!i zH=+Q8D|`Dsex$C@ zgXp!r05D}{8la|G#vmBIewIQUCL#9sKW8efi?lnQtMJ3hjKU4$i>w;)h6ZvF`V12*(*F}MNxJUB=L z0fbSJh>c>{)c|wiq%(g6^_Y0{Y`;*f@{!KZ=DuHIk=VZW`|>?BITUxghqn3sm5oQS|BVf{O2TZIiF%kRaI z3##AL%xsY6(PyU{?=BIkVNrSZx2>TyNjV0K)R9CVnH#=UNb*|+71q($hm=3K9Dt;K zOUsoCs`gp7cRY6vy7euw^{dWto)U;jGZ(P}dW`iTPR`DFTv;+QK7RzX4F%X_`#$MY zeJ9{upONdcAz|d~Fta!tgSY>AHA^+_tc@ zv-9#22Ajii1|}vj`2qhxdfxWHF){xsz!l;t!Z-ncUqKKKX$@$mimx--W*$7-u4qiH zgL@ae@y@Mm^E3|)=_3D;J;`oSHb)X~zOzR`0qcX~`{IS^gATwgDwSe@qs3D&^XRij z77s~R%IJ0o-T=!Vc<;=}UEGP-Ee1J^UtYD31bj@7PJ*&W3r>Iom(RHac{k0!Wkad5 z?Cbh)H_+KRPb&vB29Cvc9E*=jmvhlwCuxe3>Tlf z;DcBj6BzsT9+*Lb0#yk#&vOBaCrw9pusO~Qplp5mq+xK=E}y*kYqCgSzivm>yHQcH zM9Q%?=7=i$SL`)5usGT~Tm2t<|TQ8t}q7SKlT(1LFqofbahFfS)O zjDk&Rc4TSrT!g3N&67Rqr;j8q6*OE`iZM%dGbg-ByTPf>^=9|=O* zEq=tQw@9eQzrw}<%xUoBc?{IVY~7C4Xt+k~3tUenrKKSDySf~u(y;RkrOt#@gD!kNAQ)}kWcARX{+LSC2k|A?twegv^8D29joW#Bhc0XE zbeyA)OsjNwJC%xSnsgBtF_L}>|C)|oYo4p)7gZKwV*Qs5&p3_%ltmjO)fbu}A#zFd z;n}-?KMmQ~FLiQ@u$@{#;ZDBY99frGYVr4L@mc5+hfu7TeP!_3(lCum z$}&Puhs+p!oFo!nd)!4}@sL~Qm`ce0NCOxS8N+MHZ)$Os(ou(b!!rK~O)28=iy+5G zPgzz{LZosLixR+JR3U_nR0DPYU<%%Iu!E*Yj{-0UBu|>om|V>>l^6tSTyEBZ-1kYqR(QYhNHqT^Wzt}jkMkru(l38N1@9U)pz%56h&msTCq$u!g*~19?NA|O4tfcEm zbb-tgZ6+>pUZo()geUU}>rqBk7D)eO?m({v!kQNwxiBB28lsb_GG|uHaPNu}Zxbsf zb3oies7xi#8G;`_cy=FSxTCYPSyjM!)DXlXL1KbHr-6rJV{b+qgJ?7jV(Se}P0?rV zPi}Jm`kAIR`tcJqOuct$j&8?N284egg|olE-$kkuu1Ud0n2l_obva7P9i|Q}i~7|~ z`&bDrb4gj*S$q4M2z*1B^TBU!+^~VOTs2j%W*dFC|JOjFm}OYTFa|PLGn_UtF=vx% zr0^gw3;U`-x=Wv?SO zQ5lfDgg=kHg;Ri>WgR5UNfEV~C-=eej1L^LYQi$tw3|P=4!|HJs5Z%TA|Jphxkz=x zpRKvm;NZ%Pb|OB<0Yp`RvBsDKAUioVbs1HCvcDRcA_Foagtv-HmBsI_OQz6cqda_u zD2zZe4;=oORcr~b{V_qnd1Hf|wYv}2i56-jpX1LhuFaCVv;GPQB{|IGHuSqEmr`F1$y zllbN317RRo|2M2$$;>TNMt}aBv(K0kZX9c*mTnLIH%(vzh;)XPC@}NxXA6e+-{eNB z?j9u7*o#-(Yn5gKHFy>^5Q)Mlo#Q`M=$`u!%Nt~QTA=m~h3p9b)!)=Kif?TaQY@V8 zg%1*~L96qfXqi7cN(yLij8qfa?GyNPCr_iZlhY@s^Voo^!e4$SZF@{Mb}hepr39Ph z&V(KpD&Pk;##tBvO7Eh>$&3y2E1SMtZ+qk2J;O)%D9IZ+V$a~vp;y@}EKIEPB3a`C z6^elzaJ(SNiiP%3UY;GL;dFOQ?iR(jmqBg%HeBVz{ZZYtV}2`QaxfZ$;%Of|K_qE` zy+x$yix)4>-o1dh91o9O_BNbOh0Qq;5&zp4iSjf0^JfyaSZv0@L)QM<>SXBR;-X)< zrv!PnmSOwV?aCnlILcm*wYi+5gv00vhy@xkw3^JmsJO3zC+Z4z0COM4^SPZn!O0x$ zjb;n-{Z?$jf~%G>9O$+K;tO$?AvozRiZ+t*3Q<;RRpH};hhh)bX=7y$2M4Z8t&ZpQ zw!ia|`~ffNcA(}==-vwFzx>q&sVf+DfG>AT%b$FM!ag%SJ?K?|EEHN*cpZ@QfA^>J zV2rs)GOjvILNb$Lb75aNW49gbLq7`+72FlDD{PBr-?GKqk1N;O`y0L$DpLAC!b^vnq}88i<#JIE+86=Lrzsabx5FGKL<*PKZt)BFVtVMwreU=vz< zczZAr`$_m=kDFQif0sNL6ioyff>(=as{<8@RuwvE!aj!2lexHy7@KguTQ~-oc|@Lj4eeS(yNt;(HjS}fKl|ig-+En@WzIq z)j%r-xhSAV@GFpzz)fg{Vu%L|f*o0Q1cM>vYO1629{yq|wIN!Sqg7Pq@Od9mWDG%J z<~DBH!ZJAZv;`8~@*6#QCPzk0iXC(V1LdD5fTn=<7SSO3-z$dCKtu>AhFk|dygc*k zl<2INE*N%+CKn1)e3np0AzmA*TfEEO>P>}5h12(~$@c}+t z4>Yj2HA0YtMeMiCtbW%Gs{vL4PT;6Uorgq{hg^rKRa$q8A{a<^9|aV{vl5|L^QHCl z1Z&iUbaWF;I20BZQREQ}M_`0PtLWuYfZ>qnm0%Tsd0-@fPK5Bdric3UegEzu^ThFE zGe0BsvP%yyxAm>csCGwl$p68#I*6xaO86X3!wKZugDS}zWV%RM=e{{g8nPdT!$bsmwkR4o>h$tgUVym$b2+43KEn(D;UI) zut_g}s|8J!kOgBuv1b81Ok7z~a`Lw1rhJ6lHs>OY!7Yu3A`*MlS}F~EXn}c!A$$Nl ze%HRrHDux@dIsJGlge-e_@Nb4MBc|G;B^pxkAEIr*oa-9cV@S(W9!g&VYX_opE|?j zOSxN+7YS*1%n{*&TIIFrcm&)qMygd=D!cW_AXCorK77TCxZ0r|!gG3Ad{eEgys{D> zB7RlPyx))ChnfR&^H97Qpr#RpE{28L7cY{4p#p3k7_d%j{&I+(T!+pM+2RNyf$F0F z=r~n*dBxX{v~{AGZ=acb9~#0>1mFBCygWdCk=b?g)~$v6tJ;D$s7O5L!TiHCS%x1F zJFm{5vxYl@u^=Y1@Zb}=ETI>$`65ZMvQRD3cQ^W-B^*N3s;EbkZA8LRHQW@q>j|GZ z(Y49nS+D&Yj5heso%!0y?|1XgLQx9M6qHcpI?Sja0JsgPo$;iS=~2%bBC zzNA>O(77#ezAr59#{}G3gg68dbD2ULErgN$>V7MY5`|C&EPKlmTK;k4_`(`%mah|} z{}_o;){5{ifHk;v6;ky9$bsMIpu+42AlYR%YDheHAln5eVJ_0?D7i5I&X-QJ9b$jT z$q|SuLB^1VVOcN=Dy|+2R^-bo2!Cc+z_#R5fdBQH^fjp!`j(S%fMzwNjM$TLjN{d0k%qCp%}2| z3V|0xs@S4q8i6~av5xU2XEi2iCs{NWdFefD~H6&BjY z4IeisWxyKj!PDB(LddA_NfSn&pH0b7NQaW{eyNJ9w>{_RNC~gpG1V)FD;TI;V{ATx zX&s7Xn;QLK!aNJ{3uZttD2G8rSC0u7mhY2F%P%SUoMjYkV}dRo-l74m&&FMH^i5UN zJ~~nBjh+esSK#m#YA1M#|iTu8q=RiE6&Aq{Yxo^==oD2~l z)4`WW|NXq;KOsn*J73?0qBBkS>@Z8>e_&p4rw`4NY{TUR{sT#-%X3VVF%X#`6aL?N zajR10|7`WR$k&pLEyFt4Nsv%OR)QN4JDD(IEWZ$(Sa{kn=%EM47Cwb-mi|OU6Er*} z-_SM#qQ;6_?5T)UV?}U6o!!@QUo5B?8ae?dh$I*9J0cGjDPk|s31Wg&fQC)I%`nP_ zf^xyf$Glp)65v-J6Un6Hm^-{w4FgR0k;2E=a*ez{H0p(Zla_g({|pZ=#)tm11I)L& zddWn^(QyPF2>j|YU12^A(;{(jk28KpQyPMnnMCRjT@MD2jAbqEBGh!IQP#_E#C1G=ltVkQ$C1{juMlOH5Oa*kOPMI0Ct@c{_x-SNlP_DD4`pL;tH zQA{WS-vc3t4Kk&!PNN5Xe| zJ{|$0bR=sE$F^;UJD%s5AS53V^cODtC5YS6(e9>G@`RxDB;+zaJ~diQ!!;_+k0UgA#B)TF!mAbPZpXP2}zxJBd$HtHh0<_)-0oF%uf9*0as z$=-nn425FE$;Bb7%c#^fVOxfI>hN+}YBGl3J|?)-V`pbaz}`@618)rp;t;-0$fIEL z_5Sfkoq<;MlG=`*D3&W41U$RG+P`k`D(I_I$)EXU?B|-Fk!$IuWmFp~kF2S%ep{7i zLeC0?J}RxhIPk2u=j6Z!+V2)A_H zDPw4`LH(hI1H+6yk}CG;TZ+FzUt5ooLEh)k)ln;qGem$sK3aB7f*em22$WEOvLJ2< z-&dY#eh3oLVP00IFNNO{o!a`(gP|2T(RY&KJb09m&_Uvtl}*8Mk(X7`+$(f6xHy36 zAAR_izBYDpoIR3d&OmsjXwRtsY@<$(l%%9glL4}^X8m8HIVz}V_P|<;*!skT1Ot72 z7B;q>ER0J*N<7QaCGwDd?Y{V5-WD=8P=>X!te|2xgn#5ri^3C>dqRzg=PQRdH2#P{ z0K)A5t*#EngE~M|vcf%bV-`F$c1jwC57u?wg2SgGf1_`auCw>-20w(zJmKAU9z0~_A z0X1%A)wus*&$KLYN_zTq5{)BYz(^M0oW8)ovggnLLCOi;ODMMl@Tvt+pc-0Qudurv z!MDC5S<5G|u~Rz{-*McR*uIWKjTf1ILdEZ)&b^PHAMFve(KI9=L120i!jN)I(Rhbr zD%_1!B%j2AOVpKUKTsszn%?W>6ZNLRK@QxGDDG%MIwZ%1+oXLcxP1%FY*YrEUt#(g z?f?(6jQ($BdATqBp&JcPqx@GZIBXOlulMuP&EYraoTeJMagj6R{XOBb&EdQLaI_6| zqvYzQN^bbJ6EFr=%Q7D{*h_p+BJrqz^Yak2Mk;?Kgt}(>`qtujxscwLfSrHHrg2GS zW2D9Hh8=;t;l2FAy+wG3ApQiw5CXqGtgN;a2XHhz%ny<|T+n3Ut;MxQ_^6L{_R1on z0&Cx2i;h91P9~Fmq_}_(qbtNzc1PiYm%hIKWFz;$r!Xus3=FKSwZ5*C8yA4J4G4TJ zENtuT<^4s$C;&eHZEGuJ7znAwn}ig7qeErFy{j z=O^)a2esOOiXg3mh;_Bk#9n=}I-Oz$o*=Zf7{Ng*6?FVYvzvbYa5SAuTja)0g;fL?LrThq2akx{f==jGe0-p!WGY=>Mj+zfzfc)gcA384eHtwsiZd3}BlCcP zT!+%n#mbt1+xsY!6uTxSTO`PelFf?jwUy}?W?Pl9)*~OY0Fdc)(^3zfYMqX&P8qgu!F2M_j;GO-uB(C+sJzIf>%*QJ1w zU8Wd}7`6uuII6rDwx*~UTzm~yq>uW{#Z?aKt?2dv^({l|ctPQ=;Ui=}Q+%CP>f8@s zip*U^oIML8PyJ#;Lj&j|avcxdVOrI4b3~$7`;K$<7-$)M>hIsK7tUmq#0a987IM-T#@vn4b6y1)fY{J1W0yZRi;t8gXL*8vuwqpm%1Tv4;*`D_pb zY=|^NYk<{0*bsG`OIOt9msf@0jYlUGsWjVhoY4HhLD{H>Ol1Y&OcooL{VgkfH*s0b zO|jTMyN%Tyot^U07Wh;Uu)SpqqFkeLI$c_k1OzzjKTO}c@q3X^-XdaL3en)=VDwaO zpl51oo}QXQ%#0<}QO5+2;{537aE)DFCPNZXE>5lkN*y+^7-Mf?k>2bu`eGYX6J|B<9KjczwZ-07Sud7zkHiYn7vOin0O3*xB@tMdu3`gHE?HV%Pn4Gkwe&ci|w% z(m8_db~=V}6E)@khbpbS$?$2T`Dt&r5vAOrf%G&x`?Xt{{EC-aPCQaxNh-FsJ2P65 z5mRKEulR!!cjsk6K}*HIk8N$%W!;TeId`#dv=DD(#9(syvJ6CC4^-K2g$;p#^VZU8 z*m7J!tSb!Em<7W7?d^Y{QHZ{uJ#@gvo6NDTQLL(2^r}woAK$P@xNi*NG`DIey71Zw zVN$XTT|J+qGhe^W3XiWQ!s9%n^u55@vR#bXh&qGW1qh4&WQi5<=k>p>p)9l)pgr*9 zS+`2ip{*-~z3Lmp_7V=4HL|-|9K*Q|r{Cod=SP=8BAvB~0IGxU7J1k)*)Z~{tEnM~ z6zy=p+YChMdwWBYJ9*Qt=9~qj%*jDkUE{vMGcfEysDtskmbr0$dU27E1Q2=$o6e&7b%_7|{!7ch&O+;Hzst9>cb>zr;0^8uy$q$*HucSl5XZ+w0A-^@ z0o%~y9zWtoBEfiwbail>crYV_gY8z&h_nFUFu?fM2}*0LSmq06mx6KU9QJatSp-1) z$fXybla8{{%6Wzn@W#kBbCK;54#!aake(W4gHhNS=KQ?8QB{deF~TV8FaZy%vdY{A z*KwYk*-w2tvTPi7kTP>~&lJ{q>hh$?qyNF()j%lC%cr3j>H(dJA4-+w4RB}Q*wugk zUi4nh6~b~28IDwJtn8BQ?UBd$3X2j{%7D`vJh9*Izb!Hl5gHI_Sg*N3`YL5?p0D45 zyelO4YqsOrfU5By2n!(-5?9~KV3bv;H&3L5*A1FOjtQ+dMjy=iWj433WeaI1raLGg z$<5DAaaQ}n*0IxUp+ zi`$v%3`)+KSc_6{iUC$t;vA}uiv!3_j&_wh>v;xW{I?GV8vRr$D1fnAAv-%q zHd}~Y6EFu)_7VtmcFj!zablkquJQ^tMgSYVfTUeGkl}TZa6G_ZL($4<|1UoOBc`bI zbgUl8JTBpKy~C*lUwO)XUvd*v*#N$=?;f2uD7EGNFTtH+G(nus@|6Rb2Mme>oX z(=mq`5Ao_5?720Yj>>?LB_X9WZio_v+XBf705J1Ts0u16@sY0o7i!4G%X^LgNJz-q z0V`wu)zU|gHmrd@!!NkeG2&XtG+qqlFO3sZxr(i00O41KHaEY1Ej~w_L1X%W8%Tz& z;fdhC3?hu5(a^$TQ*slr3gF+HHyVd;TrthZ_*W~66|%J!h7do|($g=Tv)k%)8+wU_ zAg<^-Jc8dR*btSjoft83F$9uQfP0E_ZR(hpy&j~Yexq-&u^B{b?2AWjW{JFY-Mr6A z5F~+$t$q!~ahg`n>8gMdWIyeRV0K*a8)@57VSYC;@nfL7JA=OQ#*GxpHLXTZc7lI6 zz;P6gtp~iJ3H?Rgvq?9r?!q#b_nmS^-5VpbF-iW{L09zENWXC^ z!4WhPU_%>p9L>kJ*!l?Gggi`J3SB#hcik25`p?Y+#~#NXfEDc^re!9kh?&b+eS+Q= z2q5lBIQpUG6cuH0>rn%$=Of1(U6)08jW^jI8bVMIVt_(2sB1*WASez5-WZ^CKu&HM zS`7j1g+o{VH2z$2Rq2?+x8cDd2k8=vOqyCTXMA{xwtP>ZF9lzIHiDJq&d82T6MOjHi? zD#%ftEutb3>%Fz&E7g6@gY8Ri0fIj4RRO`#e~K`O!aiZ6U`YwWeR_o7!GkMhE^Ge< zMX3ARH|T*iST4l|%g7X(2m|lF3j-mssPb?yz5Gr>Mq;xWSJl7GK}y@>c9H{(kA;S1 zKWui82QPtu1nWH;qR@)j^cs!B0gWsUPo8L<6Hv?9#_hlj|G2WMv0~S{!+-H2$|KFz z!B{kH^}^>}M~4KYX&t(Z;VMj*k1PGX)HqZ%+alkx@u&r4-|t#l?jCYBLc&*QWLfC? z8ZLWWf>?+p+sRpTE#Mo>M=Z1{NES%9pTW{UnR%TZjJ8r~Xm~b+d${pM;4*+@4q2(# zsPpR=2RC;mC_S(c_)R`9@@;J=NiP3;RaL!CB@oE6(t1k7PYp8bcF?NU@C*SLMKdR! z$wEs-_nlszWer(j)bi*3{lS&w5Ms?LM-RLJ=g=}0jeMB4N(6lV6_(R zz*+{QfZ4(eB9~Nv4Lc@)WX>OT!DNF#o59(?Fo2IdK5#VA9-e&ktiTvSD!2#cGM_R+ zo}v)@)DCR>+Lr{p9GFvK<2%mVcCg5S5hkf1!bX#TdKIMy&Oc@ajYvg;_G3dh#WXKF zq#kF6>(KlEGN8O=Udfw8G2z+*dxbL%nKe ze3<-_wF)c$jtwu5#_bUB7;N78d#+7n<(cFdD$cF-9YR7!ggN@7zBjW#powXnNHPWV ziucl?_^VqP)Mf@K?#S{X=rLek5RWhkr!@;E9O0oK6jn!g=a;8e7)yIlJV6AleLb6j zZaHuEdciw;!@SRIS7@2F7F7T&;wC$7Zcf-cLaxB1WQXP!#>{7 z+4j+tPAKIFt;`zCgvL!Xd|K~&-}6wmhhB1A*GuX+nft&ms+DVhL})Iihn%_Bnzt*M z>U>k;!8Cia1yQ0x`L&TIm~|tz&{>5A#67&K3P8vRY!V?xd>97d`+&?`2EYfh1LB6J zPVZubNC@4b^vJFj;q>e8?D2UojpW250~T&;VtyrnE4)i#WvR^$aIYshxnFn$r&(On zPeS)~!m7g+%RnJ)Cz0Sm_V3K1GjZxdN`7VK3e2~Y8b?V|;?w>LNjkU$PG(hM9V5va z6hiO)O`yqrD|wlXVX@cF{Rl&o%%hLv>imq^C-;S&=s?f`O*p?_BG;yE5HPvA6v+9$ z7H2oX`~XfA#)D&m9o7xCq8A-PS~pP|ZU|)e_%l+M-YmHxA(kx)-OTlzkky|x;rGQ2Wie9 z$(imv_ePAh{))uTttoE(yX(`0-o)}q_4eNrlDF1sK7UCN_S1tI43w$f1MezHQ1;lY zS=J4GI|9(}{EPB+pjDL-bc8w=1qoJZ>&B*!n=mB-UxUmFWI@pUXXE*RRZM5=SU`v4 zHWJWYa_W&8h*4t8Dg3U8rRcXg0`natQ5jGUz^KzH#hoYsfRsqi#k-1NH~HUna3ct? zP5%u-h)dyMQ%egysmAkWK}{mkjYuTi$1nz=1;Tyo>vY+RUM@)9A?#as_XUN!*lakS zgeDP}VQwzgITXDx6sCy=$BH(b;V+so+8FxSa+#Q#Y+6kTj#MpD`IpBBPNb2dNHO%} zq2^(0Mrt9h5an-82@S6i#BsomH8bnG8{;nO@Dc0GiY2S3nhd|LyDRk z_^8=Kf_E`yI*LRk$ZQ;|&KPT`T^D-UH5SCf#O7%uKq6T`psJ(2QcfLr;=1ta8x|)9 zd(NHK6e)PuVb;z&xi3q9B!IU|C}yGX3UUJ=6TlT@ez4waH{Ni-IY|Ec1a52wvqaC! z3*NvCF`l+XTsd!yoGl{S3P%INg~H>0V{b4Z{%_ward&DgFWEwPZp5NR5@Mv3R9VWh zOh-upKcIp41>buW`xOmYuR?mP9yzj9UL%kkH>&U0Vnsv-~bhgKUpXO8S;TU)O@ zDBPmnW?%TqD_8K%HFGYJ%`6^DT-)XxjMBfTttwUNwoi)>{%H>}C!e8AIY&4-r@VH1 zu;=d)qpMR52A8sb=Wlgwbp96l6oF+zD_8YfJMDt@(z8E0W?)-nK6>(x4TY5<)b-}^ zt0yn+$TYWG6Mbr9d0n!;`DTNFnb4#rxQxauP zQZj~88Qyd4@B4G?fA&6hVLkVA-`9Oz=lMD1$^T1>|7)1d;fQ1y-oi67rCw^}y4q(nd z(F9GOkPxOXbn)S?7G)WoQ1s7fymUZYa6qN02Otuytfb*o8;09^XX+X7N}@wmGfAMU z4|C_}O^|@Npx7wm! zq6kaCpe}#V+k2s9oLqF6gCMqV-2S=!aGSlIr_n^|okL3jfit&x zCO>a@S@%9~g^zbgTD;}g-~px$HM(}9vQgoe?g~6A?lC`^H(;J=#U-0ICbuT!X~$c# zZ&0@B>gTLSwwr%1bOdDyM($`y<*&KuZd7$8BRDy-$%uXPZ}!eERu; zTzAyQ^Lli-)2VM8vw}u1U!6bGGG@@Q#a?@d9>sKDMb1pxm!cTfei!pcF7sc%*K$?O z>Bq&cxZDi%3Hj&6x3Mj~Fq%6tIA&J8_hG9!U-72}3I3yuSJn8$1C$!Q^c_x=4Jw^ z)dJi=>!jk8rg7TXd|z7A?87t>7AE0Ko#`tV0^(8h`NZyqx&wltB+eBb&wzHvG$)8J#SC}cP6`jyz--G8+S59emva2mumCQ zuE^@xxQ_0ZCh_&eF8gy%XCmuVd37{e+>0&pHmrZ7ch>NP(KD)!gVeeOy`U|&DXX_i z)wsXSj+wNztxMypmscuLIcnxov?QI+(|*vd%I<3UCU#%{O``V)9FsT}rMFz_+|=cM zGCz|zlBsC+O-4%k*ubMXAKuavuI>(9_oUp{&v9xDP1lxK-X536DMDOSJopRCjcwh+ zg^1)$SEmXmRX*KUHu)u<>5v-#N`G3ZZcvJSVMd% z{jNZSUAD@)Odmprv#s=#;YE+NL{NF(46nx{?=$rxA)2AF1s%sDT6Z&rPChmrfBZho zwyjQrk&9*V@%_BVM)8>}%zAW@qAc>gw|O$xbENPZa7cb`+RA0GP0}(}e&ls5(x+rH z&ug?<|240^e~mU5mA^~)QLIbxxU=Z?-+%UhjcWRI@L73@^NIDuytZO;)~=%6OwCVb z=jZLa2jR#8n-6r1t5=~F>u5HYxtamT{r*FTR%wQbO}k&=K7sk!svV;v;)%7MlQE!=HCCf}`Qm1}&7X}0b(7?G9vL1UII}ElaH$%3EoZBa6(&=qf zPrQ9)rKJc5OETQb!Lk<`S#&_4f`E#G8m|3dOqCQAjrD`ZbZl%3j4#`^(OMMU5!sZN zOUJfcvaU2ex1)DSY-vZy=~l+^w?3+UdYT04ySPt_&q7p9-%2}o5ZpC3Q?Do{_3|u) zN?5(;nu%A>7_N%=L_Y1AX!Rmp6pTD8S{=A}=amnGNRiysy1D_oiuaEKT&@Ynho{Z+ z+`F#&>h5Z~>6$PRa##=F^)5FHp>1Ox^7##6bJ6y<RuOY__;gaU51U&_kZ2%MqLv+Z19-Yt&8~q{h1G1DmaKh zK3MoE3x6r{sN29XhiWJNZ9dKp!{rh-dV)vmjcMs~X?MQaVfZdb#_Q78?bkJ>g zMlIWa*Uj=l8>yaKjFKrj%k`*9>i~W*bH>9bTu-Yr_D;nwJ$U@mX8*I)b6ce-Dz~FG z4?}yMK~FF3+Hu$QgKMqaM9x*^LqZC2CZC*(>6wNkwfnw3(&l6wczG&E|LVo~2)~t2 z_Z2@n;fdE-ae-)-ODI& zCtTD@p#S*CUk58DsN06WFFbN&w+qXW&+pHxym_Wnc|1Hqb8~2yi;;q9!|ux;+%hLg z`)7R){7lA12AUKdL4NnR-1Uj;KXdJno8g`w^M5!8+^zDRlr4o>ZyKuf45$9`yPa!# zw&~dT*}sM>s=GgT{#Z{-MfbOUAQ(}1<;M%!JW-}V=luDftN(&mrw3P8d@(HOo37gm zwG74`m}I%SxkXCp$G zeLf;17yH-m_SXpCVJ1RW$f)(T_9rN%p*;NEk4qk+801N4t^nnJ3sQuSt}C-tVKDvt z{3=FRMQ=oBd zhat{R+^|99HI8zg#EmaIe7(lUm<@9Ls!qCf2Aj4o+NY2tiU#?QPFV>U%iKBQ$WP)4 z$)u*eSD31HTsyr#^Y3qcEsh;}a?QDciha`CH6J}QiNCwO&Apo;M5fZDR?JFyXMN57 zbAdbZ%}zg~p`P-rtClqm&+6<; zZw4Db_-K~LC|R6mH5>R)&FY2RJDlSk0njMvBGH1~nnp@5>dxY3Q z<;$(N-Lf<{KYQ{VBr^xaH|=yj3ZA#Iaa?k8H~QlKDs`2Wd@Acb7--4u$ z5A(J^&}Xu(Tekt$pAQ)GL(>Ff<#jWNo0a%u;f`lMu)Z53XiRgF0|kMIc~XU~9Ub|d zYf)gr5q6V#U+F#c%`V+z+q?;#-`$dQ47zHkIv*B!cW+X5=%!<;-m_WvTo#$>j>`w& zKScND8>M$WVjw?~w;Zf6T_2@H{M2Vo8aT81{66P>7F#mGp_1}I>Eqt!bA%+lBh!6J z5&77b+N`5=^4uI2;jK$;Z&Nq%+7V$Z3ZbzCaec&Fkow~oK^Jlc*KvUsr!MuJc6#nL zl0VNKjyXblJ8<<`&9OIw+vPmC5zekqlXh$!2Y64KoSL>wS66B){`}jNIZjY5hCc;G zKu7|8I&-Mz40=8wz9D$;?LGB>keJ`al&rh+Kbi^!jn&@w?;}Pk46~3|SAF}XZh2{0 zSwmz7WS+kLBN8kb1qD;+e_-N^ZVkc=>)vj4O#M3RM~i#&`bzpQ`IZ$C8pDBCGeSBE zKq=&Z_+d;vh)E(|O5-sAJ}@W&bzo|WEgs0mNIMc!cynRpIps7^N->TO3aUjvz{_jX zf3+YJsYbzbM&EGe->L$)yr%opjZW7FjaXN=*5B2AdOPTIdC89nAJzK1%KQSpTAU$1 z<#ZjBo>Z-|69Ogu!HgdSS%}8iG{KzR&6>;cY#V=4o9;I^#R+a06FC{k>%d{V^*QZR zVa2Bc@%bgxz-ThjB77n2eZO!AQraib&0*T1to#A|9)v+Y`9L$E;7VFE|LRl8*+b{f zO`eWd=YghZClzVRkd8ppgR5+9U0odpi%}9xDE*$N!1E+{OI!#8`+IHO&SmvYrB$V+ z^Y|bkR_!TsG4Ez%AUFgBkZg#UWC8sl27E@z$zGR#mZ75v&=oY+;eu@InngkhCan+l zU3G~^sCtK`bqcFZmrtCeu8Lugtf_xlx5L6>YNvmR?$jQ&A%E0{G#3`OCK{L@IxEx# zb5GDXVI*7uWI(?T;#EP2-5_@6-EMjF&K)&~T;Y9L#>5swm0eX8rg<0`)$A9uL|~f7 z`Sg$>m4XgOP>`C2J)qIpoSOZwdvmXI**-lz#3e$a11HTwpyHiIk89c3RAB+d3~%vF z?N|Jo@QzgZw0JVI-Gq*>;*ozDgGvG?;w{T8(ZOa`72vtmz@SAC{~q0Rw|=rh?Mj_9 z;{UKSY*g7cAnSk6h@b`rh`@VQ#v5}TGNygHx-vjwS54PKQMrf+kNZ70jdQ9nnb#G) z5+A=Stp!#;_$k|(o26xBs^LI`xf6Y1NVn4?BO(r_Dt&(tC6p>}r=4T-ou=`Jq7WO# zZXnK|pHc-|1&&xhjJ%nJ{A|(PU*5YN!?K&HU`y1E4)Xsp+-=Aa#V*W5%*$shLgWhL z-i;e}B_C~tM0sNVn+(jD%98FhM90K15Gv;9EiCqQAm-1?YGQg?n3GeX=P8NY8fzdE zuJ0mL%>I8`fd3=~B@i2V9Q}0~wb`vp@QFSlO;`kv2gd);VX=Yu8rZQnuw2?Jb|_7bjq&pE(AbaIXWQGa-@Qwl3c~%5 zSsnhNvCojX02~#1{6m~1|}GP2EBnZQb+%LKs(%3R=DnN>4t8)zi~ zWh1v81 zKRFd(9cW=-_{Z!;A_##sp`wR@#lew+=&~>!y+_i3mMAF9Vi*XmOw{g3xI}eDkvy0! z`DRpK5$}0jT`=^jSdk|zEd};P$jDsB#EX{21plJJfdg357SCLdqwUGSt|s5(`Lsd= zyhWI{0qaLHoIIIC10P9CBi2S>qT+8zOt2kJS$WB;&#Uiz6D-DQiwz#|f9$!KDqR4v z9+eVG+ka?F!`0W7*K);zf!b{6X!;OFCZ+9IA{OJEK{sFVzro-i>C&efS?(HBpB;F5fkwH9C=DkI5rWue_7Y&Bgo#Hd9ML))x-vW%1#j8DeG>2t<=Y1_Zb4&_ThCjt5Vxl%X49X_V#J#8 zQ~&x`TE0aYk{f&;5KY6OVw|d!N2^zMV2OQLQJ`@c3Jinr z{)#TnSGV$!iXXuaSnG(cCMqCs#5;v;?$!f6B!V537r~pN;WLq9mOq*JP9f5ccHQ5h zWVDGC30@DC6OwrV5U*xLc8tvwt-Xl{pLGcSSGU1hbulq$yZ5eTVL^H;jMUsON6&*L zB4@rzqV!;4wICGt=vf#=ODv!Czv(~~1SP1wg9CKf(&FN0MEJ7=Fs4S1A?;O#2z-|+ z_0-eSXU~v`ctdP$zHB@7`Pql2+a`7b;Y3d}7~By1*wH~3?7jR)AW~@Nm;$uugeJz# zwow$tQYgJJN^3oi32_zDb{*nqF(^o6fN^dM%Xzfw(FsNe&xD&pY{;WQ-9*Cv4+Duo z_mFT7x8Ci8;$s&NkSMOon`o*WehjCeJ+D%0<-m&ve_FDQT7S3zVooY6|G;F3h)*ng zXr)@Wre?d3I4c?bUgAOSaYintj0&U@G@}y4m>oZDq9sK`vVjc{vn`BT?G=xg=K%Rp zV=c$cib3f;e?0c`>IzK}7FJegN5^*OGI{FE{5&*2Mj9c~;O!}LBxRUX$mFDq?%WmZPiVnCd!}5Gq}nq~Vqw%m)+&M#;c8L&^5!#rkLxo$ znh}+i9F1kC5dT|yhEsl_f=xq1CnWVc-WjyqAlZS#O+iB=7_#~Pd$6&9_4J>`12{0M z{9{OXZt;%I`XN0QXx|I;TG!FO#cmHnD!feWj-F>E@yTIMfs-Br=JtVsR_v*4Cfs<5 zpFDniK>%#L`Gp0fsn=9i;f2?UVBG_b#7yKK*IrIjF~$&pBQ`s#8r@g&6InSUfpI!A zwm*Fm`1=~|d@-)|a!*xhzpGH}J}+!NaDF4$P=HN~gOaP(Z7#!e0CE|8VXg!GOM06y zO1&xW%i5lxhQR6pF<_NRm<}}VvpCYQ*Fd=g)fq?^p#dtAR%thH&g0&UK|sF@6~lAb z_7xSmDDpNoHcu_AplEK6fu$4iCO9-Dr_|A#rX{Mux_bt(2dXW9=bA{!#e=663MXv% zI6}#Bsq-=nRH%XKpk9HEj#GAlO-L=}`t|m1IenFuHTsJCAJqNMJ=?GADmXImqV;}m znPAAhSLe00wed7D`J)bdIHsbV5_;|qg(dmq&f>nchNG)zr{9jG<<70H-j+_xcY2ro z`%-$TW~ix~r0qt!8!ufNPS@1y9p{c;E{-lL7?aA+_&n)u&dI4|xBJa;w=8bM6NYU{ z>hF}DK4lxExfBcv3%7i@m3cFJ+rnI6h1(g$x!Z@F#pi-94J3QwEC5j+QxXq4KfmOg zn+*~&B(*po4uF|SzzZ4eYwd01Fd$N6|-Mxnz65gsRo_hs7#>j>1$ zH0>XJNN&NS1r`@)B0M%OM=oB1c#{^QipNMxH$Y0UTe$`vOSkH4cQ@fX^?G9a zu7tLylZ_jVSj9O~JYrcU(X&EB2Bi($iORcnQ4)N^chrQ^cNOT(H`*&+f-lae>ZEKk zyZeC*)tN*o?yJxdqQm*yZQxo(Wo~{x0|7-YC!+$3&FL_aYx_=P;(%wRt^Gp8&-jk< zS&SXwvPE_!+$0Y12-{X`qiC{)iNFl~@!dOcWPFqCfZT#=WGB$Wkn9f}`x}}49oCQS z2uwoo81h`EPS5`_VtpSa?^-PPT{?Pjqv(xq*ri|`2}JpW%86E(9+h%;-LJ)=qBX=A zs=vB)Z)G^bCC`vV0ci9_Lc-?@d(bPzlaxXMd96PL?YjGR=Sg z{4s)E1sf-fO3CWkH8q07&?7%Idj%52XvqUfb^t^Zwr^7uiq56SzyN?~b{ukOQ4Ltp zH7svYRKy{TM=;p7XI(~o^vlmQtE0{8-)>~Zd$kz0voywr*jbpGzC&{bkJtT`KXw0U zkszIm7b{@|ljIiSB#Rj!nY?Z9`!!lKzkbO|ONYPW`7FM5+qNrWO^D@i@2i+ae;Rn2 zqJ7tKqn~*vK#qirWO2rh(Dd<9jvEP|5sci%2geZ+f(`!UFY=;bKMrVJksKn`{xn7nBT!5Y3+m4%heoV|jexHW4hhNfu^2f+#YQxpOu!uo@7lQ2QEM zLkc_0jhi=>AQ=^M7u<7!g(yOf5umWcsdKS*DKKm97lXjS5A6~#;jvI>*ho4M&i@WQ zU(30g3Bg%mcYv57D*>twuCG|u*h^_rgn)y@x-gL>L)@>sob`5QyTM%H597xngwU@(WJjq&eZs4QvwghkPPDxHZ@!yWf%oIevb0|81oot0pDMp2&P{Shit zxp=Rzun_VXm`*7bhvN2&!723*0aPGYG!{=F)1d-Yo1N7mc08Xx*1rq7OOQ>oRrOEZ z&_t{pK4pLswK#CMfJ#1GEmWvpQ45j>r)z1vBuX~o$YGqTfWjP9B{so5H`CMWQ2b)& zFy&54NKiz$5t46!qCyKCV~k2GSsrMmeD~Zf`p8o)2nULu1(bz{G1M^5Ig;7o+EZi) zzb9&a>tg#Wo!(r?H5KpJpX~*9?wZ8fBQt4Q;W1q-Z%p(3%Is*lh1l# zTYW#Wy~et*%NY|@{N%rx?00dtpIEbsR6;F|f?!JBl?Gc{g6;s!KZu?|UJ?q!fna6? zoodbtVh32IxRnPucf^fUAhn15o>Nfpq^T*?C2AF6EQvZeIfc`tH%IOTjsaf?`+yM_ zYGd5#)v$ADX*J+18m5EU=80KUL|E! zT2J`l9vx8Ei=JA0yFBidhj#BSyyDWzt=F*UJ;&l88&6`$)HW>+TbCEmP5k=hgIk8; z?K{R}5Klvd$rX!0}6YB1#p{&WbPTDNE^n~ZqBd%L|}ZP^iu zv_#LoB6p{ye~q}z@1T9n;T8|wB!pZwezO_Cbv=6I?B*s5v5x=zH+%|EXhBKh=$MO( z7P?AM-DCdz+=X;+Y;71|gtYiCSiYEc7`|CUN>0Vbh(AN~r~A#G>OKL@FQhbzip2rv zTB4&_P{hHTk99Oe>Cixf#5^C`w0H zmjjJ|0GB{Xha=(v-v3y@tr4vicPk0Te&rLcxaP;l}JZSiwZ*8jHxjwE&1KJ)!u zYl}Aq4;LkUN!NY%SEioPKScjSyEwy1?g8N|PV=v$qcr0QO*59?g&$0Qk0WS5@I9U> zX`rY2_IsP7Pm0xx{RecM<32NMm=uMhwSiZ3jx_=;O8?Q9boi1aL#tvQ_ zy9<0GOI?2#}r-7x1K`4k) zqKK$R;w2h~h{HK51z_EK=Q8aF_a(2=F1n-VE3FF$apERf^1+wIm?10@8#*Je)`np$ zDo>$*C;+hi+Bsg;7mr-*53?g1b^27{jt3|vk52~F_cG1T&qt`l{or&O*+!0p;S3|) zW!&O%!#l|jO)3}B9$>dZ3mf@SPz6;IWVK**)6#L#j2r#(MGcA#^XH69?{#+c_QHj9 zN5vC%c6^B$yaIBZiRlHG9nDO5|GEgvi(^WpS`NlghvSiUh&qpX2|ZeJDjrhwFe9E4 zJEhnay!?Ytco-X8m4R~{RuO{FnTsoThm1e9RBH&8QWkp_tn*gzb_f;&_=nY%s*?iQKRYW+?}=v1`Am#|06?9+oh2 zs<$f#g?wW_Kt>NczeC=VV`*y5E7KfDR?rr`0mtz`S6wl13)$bN^s;1|cjCn;Yl}3^ zn|JkkE8Q?xjmZSs)cHwvWb=)Ad!OxTkcZfR9WgXEwo7tOZ)w2un8w_^E>7hzC8igN z9s*-fh4Sz)?Sf<-Eh{UdXwb)(mzV7r)#w#cFvSmZ_=o~4O>{baM5WA9P)GX;7$U9}5aC zwY6`5MK-jnwLxbQH)6}rM1*AI3i0K$G!AiW+O&>1;b}x9Fu@30)#bYuRGd61@5k(39riwR;8JiUmU#g=yc8 zscjCbPkt`~ESh$}u{tk!dF^<-cP#<;wV71X>!eV!JwA36Mi>+vzg4qMDe}K2>{)1@ zZ-vfkGnAP;>iiDl#=?SN>QmEQ^pSSTX-1hH7*)Ub*r34j*y8DDNakR+4&(H<&0T#i z!m-TkM2eu=9#=+w&Vp58K|y5fE5683hh22U`ARi(G8T_3M{lMn2rpbHHOt@0AFERr zqIi@Wn-ze382oIdxp{u7waLE?xmMWy13NL2pJ%Ez0Ch1w4v@&zxVU{aHJsrg`^8M5 zw_P<~6I$VU{gtL4Q_3Vt%k?ESRnMQp@Hy;~AO@?$dL{-nMZhw}6W6yqza2fyYSals zCH&R23uNlTO@Amjf!YAt6aH4q@INuz1W?^b0V(Dspv~Y!q^A$gRIOhdhS@Us z=1NN3qo+lfm@RJCca~I$%1Fphv|Abt`871n-$@W5B)ldw*jU(U>uZOHKn{BG9|zF6 z5KmkyJBhnn$wwRpLnnadKqrFIR-r}m1kbc>uKGuSO5+1baG&>QcA#>HH`X5`Yn09L zYCm7c$|b`<3UUfsN?L=jL*Dv)3mNgdbg8EEJG^2u^Hv<#jMr(7>M{Ro`l~@NH?ySq zX*o#WM)*bf z_njXJ2TB{I=QV-dT)%eK&C*4&ks}+@`3XWwE>sgE71y`t5=HMFSG43Hy5;gTam6Bi zVbAVwFya%e{yVIo5?Pu3#lC_g_|IT+Y&?sp%AIbDu!k%Hl*6B6n91+hjT=2Kk6nZX zd|dN!_YYV(P72K7qUik}wKg;%=n1f()YW+zPZ2|Yp(AVJHV7EU@iGzw zKh99g1TIpxnZbwRPt>L`AKBeesd%W)8L zENl{E_9|S|_l9frxL&x&czNEj^*!(5Pij8k__HfVTulGbW1m~RjBz6uwLDh%NlY5v zzw_so~Msdmw|Ay|G4^{zh#GQ*g->0_bq|XR zMn53dbgDn98Fk`H+tk*UuIOM0D^5LI(AT$o31XR?qH!Y_8=`9>*TTvf{Q%O6 z2ystR;?<@ekF5H2CzZw9&G`k#TZ-x~Je(+gYHfPh`Eg{WaD$!xLwd?~f z(VMfJhL;aAhS-*sh+f?vGk46t~4#Q?n=kAa#>}$YGb8^~-P0Y|x zTQ7@amueVsN5*%0`|OqtOCciHqK}N-E7+dzl&-o}s?nx)EW)Sr_e&C`C7r*_cHhCA zq#IY8PEwtH1oMae?B2Z|N(vTe@~ke%I`HJ?g^%RO(QRBOf_}e#Pqra6W14=o?z$Ws zS+F&R>$FZ%kd)``XUl1QSIW5|LvrMinWYr`g4ixvQ{4*POqi^cd9$9lQ(tgXoALI5)Y5$ zS#oXX8@E17i^IKi8u!D8-y1d!3h`~?d)QunyvmgndbHvo>iY1K2ot+q*5${}}glfi5S9ft9~YDL3mKV)h08q`Zvk7OpmNvs%tk zFx-7t&`xHVJg&bnHsmGQOU zxMwFd(s|SNFp=uzu1cShlHyh+aRrf`AI!BAOI}6w3idsI>p=j~b7bG;+?tnR#IL)L zE}ivyU~w=g;J)dThZ5Oq*8FRdC$0OHh)4Rovg^&yz`X2pO?MiLh|2OiL9TXEai>@I zCKzl9Z%ryAaBP}yYuxeb@}%0MZJo~o4Dx)-x$zf-Ejc9aTN7MVR`L7+1GT}ts(E0k z`C)}eR`@VEj(s-KA(kcZPs!3!j6aF8cfgv9x8UJxn-6b1N%Ar_76XbGkG?(-e|F_) z-%@*hVJH8{NY z5AXWKeN3yDJ}xF+j-7Vj?ks;n>WipO{=#>SxVO^ncPn!>5(eu%)D%kdLr8+{>1!2o zXVeaLUO>`$pR1D-qFRzSqH`xeMFQFJ<9vPdKYv~YC^^hW=v0Mha197SHBBf1|P!o1X($f|$pV z9E?Wjsauttyycbj{F=YN8~+e{B8Ge`O1vsXT$g+A_AOtssMNM}p2@K-!+!Th896T} zG8%JLM2lA2i!xJAYhHi%&_XwG=H%>0({KxXJ3niVnEiPXN+hXwy$`8Cg$M5 zD|!)G%?3v*q8s_*n2Bh;hj4U>MC&oa5}kNbG+XS{qvlrZ`%B$nhY=LQ_{`}&{-U zLtV8?-}&Nrr>b@i;|GsDur{|~+Ha*l@$$x32HzWg2nh$D^^q|cst#EqUwUzxKQll7 zKxW6yRYPo$=%om!+o`qvSny|;edLYCv67ON^7;F4+QE12k==o*)Q1nsD&72&7Pr=8 zgvi1Yk_KoVNd8&!j`2Z(XeA>hv5C}ppKkqclqH)-)uE83^%Go`_|fmKGlL-*cA+fO z^%KXy*o0z`^5O*sc#$rOuaGIhz`y{MubR3b<0)I*033WasC@N*&cztHb|~=nW(CvH z%=@w8RfXj$o3$@Kc;}awyEf@|0gX$= zMFyKQ$#D|$*Tg&`H!fXz82o4Cbzi6Vfe#a_ZBvRCLbt}&&{N5gzABaHpEM1B@kwuA z@gDhJUbp8EM~VnbyNBr7>M}|}*Xsvs zDxBGw>V?m|Dwc^KN$F}(B*oj2^@bSZ?8s);y>4Db=~ESp*uHB&ns0t!aBKKpu)NOZ z%;cK$_6?m?ZB|Ns>Z(Dl52LqwaSbjO4;AqKs;&5*v!*liw9dwwP4uF&F;ug!v>^`Q zK^WyZ+MsjLSz1@O3dnK6ZEKjgDiLA`VlNG#Xs&S{O&DA#aOYH1{?p?s!0?@Ia zFEHBfAi`AQMnD)q#I3x%({+iVM;hx+2LLo2_bj1i8kV4vctc)=^7oXMcdR_Q1ziPJEkR9(zJ<{V~BdWuY}9-CXi(_2}5hcA=yp za|=60BJlah=d|G4^TVqxxsP|Xq;YuSMBJ zR7+0#rnf)y-IE=^cA4_FC-ba#Wsb{g<@tB5nLB4W-+beku90va4)hQxQSC7rT&s~}ob6Z~Vb<<42^D>o4Z;Lx;^5?oFBwW=^3}RTWzKH*_YbGq@^w7YG&n{_i zNz(OyI=N>loGlOM?;rYLFTA@~{?b85{hZ9aF3X(q#}&Tz*VEtHKa;9x6OIVRZGtVuD2d~i6MacRz7#yA>{FGBqajic2;S=I6Yri%Y z#Gtsdl)2UHjK7@S=v>?g#z*M(J$R)8=(dByD93xUOf!`F<{JGNv`ISz_%c&xAt5d; zRZ6*e`}%d0lP9;cya;)}qAyZlj5!Z7;c*Ft$lOY{&NG%Si#~2dHec}H_5yWvYiI^A z0R=w;V=MT65GoEH6mGQ%36TBq_*yjKwhwnDQs}w$kdjF!JQ!X--h=-rK+Vd@VV@O> ziQc@XZLSSKKn&6L2K;g*LhxFQCYb}Z=#&bqI`lHXZk|AHwch7d^@6Qyukj3%sKM1W z54GHH80`0wn48*lc|FrQ%4}O*fYe!S-J?aP2ETvm>n!tOjyb(JsG**?V{@Q=n9bqK zo@0k&j~+C9Y42pzYqN53;#bpQt=f0K%DnfaoD<%CY0cy(LJn$w66`x5yj+cYM@alU zRjp-A!@AnrRLqET@T-};j9juQJ=4mc^tp25X!7m*1&?;sRQ=ID5Y=+*hJj(?Svh{Q z+DkHLPw7UdPjm|Mvfs^1HptCf^V}@)(%&8vPQB`i?_+bL+Xt-YKOSJxNjoMZx)Na3 z>ss2{%6fM9)aapB$&IBC`lUqij>Vl8kXTwy+|l_eBAc2wX*^XX$J&})9=(5Qvat26 z7_(pV%Qabl*dj#`Wyw5kxf6L`~c`p&azBj>QGrg@acc(+v8vix^mnuu< z{p>OmhlK+E%uLMMt-bLmCiU4R2^ZBTq<7>?ji>n;DDuchbKFn`S|7A2e&FmE7AVM* zGM)aWKHC;)2QRd&IR)K%#JINi&b70%gjKw=T%&iG;e_ETtgv;aA}V@^XW~U}?1xTs z8&Aw|5c~!Yj&Hd<_NS;wK6lpk*)FPQ_tNzK1bZat?91faeO`k9v8Ly0}C@tIFoN+ibAIWBtKn-i~_sm`_3vJ#9Tc1n6CVswP<@u1#@ z@h8kuDAPPi3?V$6iV9ihlV7`O*)aPeUVo?R&qoS`E_X-fmwerW&YN1MFL}|?I@gaw4{&|a zGdf|H#Yt##Riub}klugWBjwWjQpw#8fkyIema&3JJL)DX{ih?MRc%zI$URWZ4g3(E9_%zKPl~n7mN4qH!f}`L!70 zuY0P#aT&SSu94tYB0`$%on2i)rbM$5$CIQa(l;wSdfvT*uyxpu0|_z-ApJ&^06}~Q zsTrz1kMTT?eGP8$o`}sam4o@xa80R1o4pVV`eQJg0$bCl;!oE#R zV3u=XS=&>f`%-M|*dGKfAt)^=3EZgsg`I74(4$ZO0;dW+b6lZ|ixp85q^!1IVhnG9 zbd=d90-bHHD@Z6EIs=}@vErHl=n=p@JAb}-zgXDvB9MhQq!|>Tx#tlUu7d{|g&?F) zI^GSQ0;SV5G=6c^^7`%Pb2_)N&>v;~g{fLSsoiKF*LxTK$<^Cu310A}80DrO71B zsYlElAa10H6GRx%;=olazV^<|`O<{7^oa(tb1FQCBXZN^IU;PRyaW*<8hqMAXRTI3 z3hx!l?jaTNjW(0p1_Ka?xLwq7Che|#yZ~0CruGKH%F`*KqHp8yS!VpH8+G@jXv?y? zH$CH@@OZFlfDu=RuUfVOqZR-HzfI@Sia2>D*DU`ezB|j$fA%tn zGN3U<2mh7>M8-A%r_jLu;3GUKACQuh>;AC-%}`r2O zsV}@&ovu#2e!U0+Bv{lX#Kn2Ixsg}`Rxn~Xe_$U3wjtuV3ly(Ki|-IV10UQy|M6{v zCZeBEP{K3tpK%M*SO}E~BCX{YShO_IRa{(L5b~$m?y7>52XV(Q5T!8c{R^NsqB`P! z9C{8EDXoy35zZH&uEAJ5;uM_Fh{?>9qvvO|e4rygK=sUnejfuBy@I| zyFg=*(}|A=5EQ{Zr;`jF;t44d#o|`ij|CD1`Zj!F$qzi;2U!d@dX+c`h>)8OWgzn6 zk4pWgq>0pUguJx7o0lSQv8@4Ja`*w8n-9qFOrzY*&OU?YU%?P1H0$wWlK5$odF+G# zXnFlNq~po2B52guI&eugJ;n}a0$4xUDG@4!Iq z**2jt5is?d?Ei;C*^Mzc=sE;}e?1iZ_lT?O1V*Tc?F|425))&vv!S0W{I`rKghin8 zAD<|nf)VGd$`mA1n78u6avFfnB@L4V#{3;5&_KvH@q^ZXKVf6j=4k~w7RvP}-9Lt3 zP@qMCa?9V}AHXnBy_5h51JzYaHhC3c0mxx1Gt5fe?R6%;2??j+4Mk8BOzn)bW$M8V z?c`UG48OTqsN8KuDP(c)<*)~y8chNVReNRUcPkJukjjLgtrk+G(_|myUjf81il&Ky1{0GG$2%Y&K;TN7U*L(I>nYND zVusi+@KE}*6rg@tLy(so59d~aGx%WSvtdn5A#TrVZLmH=+wfUMmg@?JW>zn=Iatc! z)rJla;`hC)OJ4WJ!o$PuiZ&P|nCH3aL;rsmBQFsVTWBfqg@uHW5@pPvsB!&J?|&+D zfqlciyf+JYR;i@@VvJhAXPI;J2;}@*7dL|0@x%n?pHK&ciBz~(&O?Yyq99?#7(&fD zdkjol(>2iPfrDcqOWEkK?`^pNRhB65H|t#Co|ClW(;L`5&p^HSqLlwL2OcX>!VdPzVSou z5J6}JuOQ!!eb+QTTReyI^71Hz5$NhdLye(&?p9l#sLt=ec482X_KIbZElzI8R)Fn5 zhABRRXBek}s1TP7+>%=SCw$Izn~G5 z?Xmwjf^l5DUkKxDHZ|#{cRIIUnJ`gYKXkjs#^LtU57C#O&wr&vm1-B z6pg#&D-8MwhLrVrR$flo_lf4ZK+u=7JQ?WJ>{W{-C_C0IWFhU z)f9X(DH2XM&Iw;$#Bw;SvA1#PzG)uB!@yT=yN5qzY-9xC7Ngb$ADhfEVmAgDVA239cr2X!zAQWW{l%#xA~X3=4Jf;D;v+;t*E0KSGg1cUhX z>bIwcV9+K)u*VK3U!&9LzPEI)aIXHF8{{~kP4iCA|iE_5Rg;iUU42PG0 ze4@y|O&WiLm>1+j@t9qvS?C}kL!>Qbd_Y?yD4ePk35-olOmCS>GIajV&UiEpSUxmV zTQ?ojprF)3wI%>s{voi)E(` z&(L7~h`0KJ7+ge!^nUo@(-KxU6*5vWGvlH1r}ixFWxqzG<>aFH!6ZlEQ@W}z8%EPr zkYd1`1tkyMRoSqIBHUxZynXxAs$K09A-_-$vqkCTckK~~L-d5jMGOrg-A0CG%^dJY zXip&=1mRv?t>qc@Z;DWM=1S{|b~vni z1tFHNkB_AcPj@DSXBQ!KOcspI{tlE}ewP%BHWZ0Nyf5~NF!3Gwid`IYOcrA1=TC&v zcfwdw2`qBe)bn*h0RKat0ANRpr+`ugQpNzaMGQ=pZ+LO=QT(KnlDTjix_&kvcuky{PAz_JdTv@Y&-(T+ zjk5rc6s?yJ;Ten|grN&--3gvi|3IJ9tm+c1Et+ zK)BM2Lv`HJr0UK0n$t3z(Zf+xo{)geF3$p3r&BX=wkEhHVJp-}PL6nBqr{G}|B2e%LX?)*; zMYy!(PCT0^ZuT=T-tpXKtBOhmS~m+Ha4=!PNXO+z)mOaB{;>e(Foye9ikUFAf+8q; zpyuCq^$Umh?TbX)5!SIBv;4vVzed6OE2v}}w(Y*#iuO!U{63Bd2m~!;Ja%l}3@D!+ zk4zjntp2)K?7mLQrjPpfg&BOx5XoL3zG*pSt$<;E7d_E;R4~piC7hZ4>dViNowL<> zunADN^EBf|vXOPo__fe)!hp_9@&yr_J97m;0rP3a8&Xwhq^SR>lLvy5_b5WhJ7w_%Tpi4G8aXv!SWuA|N= z5Cne29!S|v`u2lQVTu@BniLInGmS$zz47-zqVKSj4jo~E(-@6hlv;lvFy`gvMovs0 zq-MrxBNbEm;p!Q37Pl_%-b@ft6uNj?2?d@z&6kGzeI~aa=rh+xRFW%4JXEZno`AFk z$6R>#NgV!K960$e;_ifVrlg_Gdg$T+)S&QwxS{D^?t#f|9+Ohw42NintOZiY%X5D#VG$08Plx4Mkt zZAghrH*}Z9B_$hK_0S4cl^xz@b<70^D|6LDkS`mjL(?)P?c#5Kv%9EQPJvuzL4%6*8=-cHM zGTq))VDh00+?uv)8uN%{*jfwbysS3;FA*NF3UEi@x-F9dulaj1mi z#x8z)FX&Nfc@B77$UJc@N7-~)S}>@if`?{*`}P*v1e+sT`bF_$v;6)_FNwDk8ySaT zOTkY{%Nlz66uA*aF&!pn7A@XI4>*>6lB-=jMccZH3iZEoY}~l!93TOR&n#KE+iIVOhTi-okdM@E)pL)C=5tT3Y}!J5bn- zWq}e&QB5rnN9&%uZqZRCqOl5hkdg(W%c#l}&Gr)*#ZUEw0}EvlCbXUX`=9yx+G^CI zaaFM`nFfT9OjnN}n}U``Phe17SY8Uxfo$md7-7xW4)!P7hmRT=wZN*YgyDcnI$aK! zF~s#-x0p`ESB-Gpqo;cI2*bi7F2_NWzH%$xc17iful6jNBtvjYj!JC|!jb9UtO9Arov=O*9o8NW zU|OFGuN{ONxU}>rKwlMFen)Ti9;<4 z>Q^7$R-SAfkw~Pt4wEApYXHQwt_DVo1gM$Pt7wfnr7wc=-IWOthbOZI&q?FE7;P z=q2(92@UwhY9%~uu!a1J4-TaZUOI<8Yiy2Q%|OLQ+c)f9Fz#Aoh(-7P!gE|Z;EI;n zxv#Nqz4`xX0TA`r<&0XhIA<(Ob1AI_u!K>xe?S7zv%yQaaH6%W zoL2bb@qh`pH|Q?7@&G>9tUOlf`4%PwTX@Sjz=FPg4XLjpEN;hk%AcYQYl#pXP*u~~ zEREiqdpxo)XN0i4G+ZvQrHj8&_va{qMmbl}e>iE!l&j&~~zKWI~48r|l?BSlgsqZZuQ zLPBox$;y;$acY2hsZ6z84)cGT+65aUFp7z zxLOqfKu?s6*i(6hDDvbTAL748USej-866t~60@gFFqF*7QDMn==8faFcjP*03m%gU zW1?W0d+4Ij87eYnT3cID<3k7z+PI~qVZs*^9x~x{%}xiPLv)y`ox2{DmeM+0!?y;L$=$nh>8#49xF>7%kfYw&NKJF`C z1tfd4clm`2(}jLpKX20g{(Xqezf!9?Zbwa&b_|4n%Cqh=SN2V+D7&1|@tOLuZtd3R zrg;w~ojsJ8v^W5ZN0o?sUA+1)a&>3F=jH^lo%}}VtDiX|{&?!LCTYDNafe>y#;e7X zn+cJSy$k1)nn&9`Cr3wVank8QfAgL{=f^qp0-XoE@c|P4r$vsqrW9eLgu@U$8nk!R z{(MP9c;NSm2}VL`=gy5hJUAR{!guqfTpLC|W*6F~RL|v@BrZlQ88q8*f3CC|$o#NA zZUn*Y{7^2z$%U-BrO&T9br>Dfv|~H;hs;YM!TJ}Li2Xe8C_a!gB@45)A}pU#Iv`pT zyZ@BSgo(L1GXV*@cm9C~svf&(37aTVFP=D&_Rr9j5;2Xl0!_z^oh%V&Lj~C*kDqgl zNZW60Y<%cYR3QvqUy;_Fl(aKwX%V`QstmnInb+|O2jMnFDxp!R(hSLAqy8xj&478aIOGumA>FsIN1$AE=p zSNT=aEDc@*15UPvMM6*Z&l;h4-A*`@DEXIC;Uk1kb_4=Sd>W%6pmR{%T@^oABYgCM z#w)$D;p4NkFg7_tsCuUHpx8p?d+j{ma}sP~_q(J+Q=~w8V@8Z=}RtJV6G2>{%jU+U(iGhU^+y(y3GT z!LxV@hw}J10((OeoOdo#Ds12GVhAldDhw^Hh@LCc!}=_X)^-jKxEWms7+Pg^h|;Z| zpwfu(Dc^)D8xCtuP5QYnUmn)D-YSN)ADsMPwZCh#d)qg~81Ye?jq-7%%TP51wn)Wk zQSvk)xCU6@P(Wqj^s+xlk>=37I;Nu}4V!W(=0Maz8Rsx$C6+K$ zA42Q?X;)XmnM?$h2M8Hgf7}GS2$2~2a^u_Tor)pcz_6mKs_H`< zlbANpymhz%{pcE}moN?EphElv;ys~-|8BEH7xuv#;5GW=1VpiSIxLVl`cR$J30tBD z0JZ@VFl#-jrGRfpjqnwK>;SGwG!~=~lT?dn&tis4@Eex79}VVcus)&Bb`BHT8sUvr zCfSxo>jE};CA(h>!a?Lp0KI1mO#~rp;%VMLw8t(!Dl$@xi3Wi!(R|Z5B|AE1Wm6tL z?5K=T_Ea}BV{diCln zvpgLQ4Htnx4mvg^enuRf^NGsqY>F#PH8&9rjsAj^nvQOSzp(w`e_}^Y6owelZ8uwrapz)f#Z*MU2ZIuM|GOZBz#8|Y>w5b zQzc|HHHe5qv&X){q{~*N>d^f$Bk+K`B&%@q4v#|5ec*##5eLGdeEbo3?MtRq+l_bO z@kvNYmA-lNAk+TInbC^0bMToXCzPB!Mo&cx%eU)ATmiJWO#NWf(gU1q-e&`|&0p77 z05Z&!jU8z zbsyCX;kww`PpUMwOMk=oV{l)E<)N3;3+F^=YFrY>Rw`()Hm_73!kzq0GUdPY7)&UB zJa>!X1{{Vt90_V-4_qe-#_=)i@~pM9gmlsbP!EBFi_5C2UL8`)S{tSOb2Z`E@aXRm znU3spnWSbcO+wIxQJkvZzUdVjK}j-@U!p~Kv*(JSfWV_q*4RvB^75#9 zIRvS{GSxgF<+B&{IfaP(z&{5E@z5r&Rp{kRzIm`Kny>)3-8^EYDCm1wiB9fw=q096 zH|ZfZni|U2wH0Msx?u@>n3@K$T;oDg-Hq{F_U#)!ew=~;edEywpX%$?`5BkmtT0Mu z-(cHF$Ikjs2a4i9#>P;>^dp6KJK$~bW=J+X3kz4TUGq$dpsfFksUfOL+}~AIiV~(2 zBweGNB8wH%Km5jsD8{{4-GtAIhG`mAxdOtu5p_8Ie(tQNPL<8hA;|!)q)@?wlCY2R zA=msc&R=qSY-QV|j>-A+>FvLDfACk|%F7edh-r;>X$rx_{I?qfO6aZz{#;NV(k5Oo zU*9X+f}BuTj6xDx@x2dN2F5XHJ!2b3rZ=mHC7Xr6z+q_z!Qj1@3;JdbDh!dFH21;@ z7HiW^?C6mrr6xIOer`BRoVKD-!4O;y{`UGS4}bmo1rsI`tUz+n`22_uTI0!Y+0?-n zZ+d<}83oLJON=toX2w6aW@voiWWP=B?uT_%!jT+dSXY72C`^k^(n%a@z5A^`L?ed( z`0?$8XF%WwX^(^#7kipJG%D^Ch-*xJH$C_^Ps7_=s@?^JkPzKz_${C~2Bvr9=uudV zU(9OXMtFMLvPx+8wO@GS1j4FmFE!~wZxBZOXjMqFJT}g4075@EH$$KJ>Ist|>z*Bv zdRaU#XWBUAf|?eth#N;|)I(byYU!gY0?bvZvE{d0HQN`76M?gi5q~khhRM)Va#~f- z)$XB#^)C7^{~|}`T_uHa8_t*R$eDPRoxxRe?~N0*%u1ZM)x0h#k^DiaQn`a$W4f<6 zFFoA~XBW3db(-_**?}N3t1lL}XRv-eO-QSkE{QR&ya^ba{J z>f=U(0x&T3fMiq++t7gLu3%u-KCHY{!OE#YfXn%6gvlPUK++tty@Y-szvs$p=jxNu z+*E1spMuKkl^#-H-2$og-t4BP{?qu16SPKBs%R0CW#om)(DL3_PDNOoyXOf?H6nN) zU{Jm~bJV!RnREH%)MxE#EMWX~;I=4J;Xd^Le_nfAuh45)|ftSKt~gz}f^a&TUAwLq*FCjgg&hg*H6S0tUORKC zN;IsHAk^LM1vEs&s?JSl>J}a+m1l?;<)t97BiDi*k$b4!zOg6nCA@2-1d(axJQNK3 zy;Ko~UAG#2>~Je3y(0C^tx8>GC#|nJv*sajRXPa-m;uM6e12n4?e3oj2D&!>GvvPS zw5u9SM<6k#AScIlS$x#bvt<#606Y6J72iY91@qwyYOE1G6_Qpnm9kA^8n?&>OYv zpVt1{nRRJ#vBKvJJwFpg`%NFJ5a7TB0!B(Wa0kgAm|UG)lbXrio*|PMxn{!5tpV_gP@yoV^Vz$_4aK$;nsDhrBo_Fri)iUDv67U65E3SV0 z5%zrP$*=ICNRjt{hcxq8V<*HeyA9qlse1Srr8LQ7;f1*1&m|bactf!aY675x;h#TI z6`>iufDAZ%N8FbBX5L5SxQ9L8}g9yp*HTyhXUop5j zky&LcayZ{GXvd*XrglP3~+{WH;7 zRx|>f4_c>ZguFz(XUBbdhs@Jq(G? zw@tm)pS;j=(z?PZ5FSaPw0EjNjn0PIibhisLdyl ztS?4KVW2XdW5pBxb9%aY&JS6RT+F~9-oXk44iC~vx#|&={=KTzux_;w2GVdRty}$L zoMcpEQxl0d^HeDWDO?>Zsq&xepYmwNJi`P!)&{W!Nxs*A9*3sY8&wawm+YlIAYMSQ zj+G2IM;y5`UhyzR`)}$YSsuBq&OqQ*?qc{`<7IjfE4JH)5*+{`?4H!5PTvGJ9R*V8jG46&(#A)U{)4&D(7 ztEd(}gR;la@Ja6~LU~DMst8kx;BPFCB8gHd{YEjVGyQN5^1SxQ&^!heCRVsjD@Q5u*G20oG zW}!h$_VRJKblv&XsgVuW`a!opv-;-Ua<0>g*Vs4QBSP2Ta|KS8%#H*e6bQer=d*C< zOYfNZli%zX4b8cwL$Q_$8;`fXI?6Cc9$KG|==zoTywEV?e!5E0#=W`d%4-YGUH;1{ zf5<}muNVBueIn}IYOy7x(6U?^IQ7+qU-@*b2#43=2(wk)t&M+LfB(I(aw_Hed%yCk zgJbc=pz&PVCBGA2`SQvw8}5hLwB+{Z3Z6Lap_XkkxaHQ?Iz)Ct^yR1kt1Q2Z;^<$8 zddK{*Mf-LucJJ%|^51Ie=nTlwM*9E!AW(CPChX3_&Ydpo65@xa>{f24mHGU*4^c$E z=a>VVvDiZ0zu0=VZhiyR_@fE_KDTO_RLzNwLqNhha3v*Ke5|kUyHdoZ9yNdT%RO$q zLSTXF8m*Y-qOLx7HhkO>&}4WTc#R%opO~ydEnx?N3BjaC*(#%ML<`7+1^j*KyzWSp zsu4yBaX|c^>-iQFM3UrOq=4bWt$_irRBlC?Eb`j{5|HnhUfF=|Y3qlpnn6}!TgbvelroF(N`ulUs zrlpao)>wdhrzMi#@LYd?p~Qgn&$)~0TPqXawmE$lst`9$w=^>=6%}t@TCpu+Z+Y=p z=9=#5$7ANP0fob3{cAG`_askvZANZA6c_sv|9EyYFn6$A)I26XD{K0}(s@tThH}df zvvKALYi+(e#=fvKF#h%)UN1ayG4p1CDwn`nNmh@Mw&p>2ow)y?0__ZogQ$;8cUmR32?XPNJl@%>kSyI_~?x}^1H z_3}7f%Aad2ixUxL@&<*+jtkHJ9<#j}T9Wel#W9gnLid?M##Q(maZDin8rD{7cem}` zw~t;Kb^L)MK3He{Sy;vR6r7lB^&CCKwQa(V^3!3;wl0#fjO1m6$cTKi6N4Kd+6w@O zh_@zm90{IV{5G!aFR$J1zmy_-{I~<4Hk>v7`4_4SCNU<0KMgcpOa~A4jS%BAMe3S& z3wN9_go=wG3}V)?LQtzvR;a}(Tw8|v?9!Zi{*4-?FZ`|zdn1K%;~7+PMK5HZt) zw(G!W&!b&i;zb}SO`y#D-Mb~GP$#D%zxn38r5q+17ftKBwwG`?T>iVSvyEQ9AO|61 z6b!K$OEZG2mX3SKgaaW8azrOFnLY0EI-LD2#3eh=vC{s-n4X$R zalr33mHoGl+;ut~c%m#b=dFEzZP?v|XL$;cWqMQNzHflwnU+`1>0*^^f>N*Z9&eR9 z3n*7{tVVR&=atI@4yLY&MqgxK&v2{%W}-n<)u7Lbq1s#jb;PF0JVzr>wCGEUTy39z z<#L^61S9Q5hFh1PbE=RjtawgjUen20nTSoC?49R&loD{y$l#L6!b}bArvKM|#~tii z*(Svkb^&HwEiug3uidA+J8q0|;@Ft2wY6jvDIW*yw(05VU=FuH{ZMX=U=DI}@-K-} z?erE61?yM@s1;%eA>%t7&Z$Z~I4M%cT95Ytu&Lf!Szvq;VZa-_h3|aJ($hmMA6JyI z=bhc?{QSOIPE>?DSN2;9QO4F`cNQh`k)Doa8~mLEOAX;i>ey31TX|K{h$U{2yH+ zEgry9_or7pW%w`?;TaDTXYF+wf}W?e$eFGL1}3t4Q7Xmz`!(*Xb5p3D@^0LFnQ0eU z0~41bOLD%^RFDYA&283d16LONUNMEIA!Fc*Pn75r>1pGlq0Tsulj8C0URSDBbk3gJ z_f4^Bn-Yh{t{Pnezuwb~o_|wYQLl{SiZ&x6cg*yZ_^RJXNEqEHrtM#f+pT_%uTDGC zYwW=-5s`|670vJGMuh^G}0KX-%6w6RHDPw@od(gh|AqCF*7Fc@bfdMwf#QNe)HZt@?Ypk zM>xv{zPGmi07M2pUu_0Wd?s8LQC?VBSb#P3=B$=U6Kpns-omiN(a{m4y>eRlWeX7c z!qYIuk@)VFj{g15v1EfXk11#gphWV_-_iHT^kMn?h3u^|ZrQJY+FOax zNih7t5|wI4PM}fwE34Ah5c)AC@N7nAL#tnOeI;T&RH^Vm-e`)bEzs28xw8`vB9*rkSx{IyOJlcD7_8wPyejoK`q9C_{)eJY%92`b{3>r`cM}+Hc_pXXg;O4%I$dhKDUK=hf0HrM#;AG@!E9X6wsV8n{m_^=9g} zy+=*^d?Rwm>HVY9cno7{4@q-}qdQCUN$;=_#R=3SGJxU_bw{6XvlT_G;A-`h(6^NSu0I)17@A) zG4aL}4+r=@awY#7=|5j4WO_k83ox(yjoLn;sjeXBoz-W<12%?5phZ7>W(~N+|G;B< z{r%8ckQi{YzS}65H}|G73JOs&K@eXYD8PlfTb zW&-1Yu$XANv_w`#MUiPiBa6i@>brTem47yGAN!Ih(^{jVZ`PaWpB6PX{{H;JmZ2w} zxr+1avB0j(z%3Pz3pq>YTeQtz+r5s9+cfYQ!P)M>dws2-_;jSwdg{zk5i#w?0zb3> zIF(5ZfRd8u^{`397wYZEQ|Zv2IXQuo?tePF6XhE`=HWBv-gEep7z?%}CRzruQ|*M| zg7I|K`Gc4`Mh(NFt89*4CCND3us5pI{lCc;u7iO}TOHyBNE|^EpP!#>+kQIw=Iz_+ zs;U@$zpSVTs0!NFEFf^Cu^?WP#H1x13r;7VU{3Vb=+(YKCk8bf+El^9=tOOk9+Z5R zs&H&B%FBz^JRwPuMz_18FK?!=V&sBJaG-DYl%2z^9{$%f{#*Zc(-Y061gc~2O8I&) z9=<6V%Q*G5J$ge@mbXEn_1=Sv#upssE!=l3GTL6g&~vpQYUKE5qs`5#(fEm?=Tvz~ zeRC52aZ^)c*T)rmvS+?n8M?n3F1{B;IkDjx(6i<`ROM9}|M}ZnuKiS;v6*Imm+Mlwv^Uh-IM8~1@`ic% zHNz4?dpD{U(I?EEBO}_mk)s!f4_U8N?oL&{Tzu;U&C=UN>g@R+-hf1?$?>Wz7RzLj`A`hw4KB5l(;wtuJ z+x%WMe+u)zA0}nmho?dPjB^%S-`e`wuo3ijd0n1aCeb|MnDGG_V{`^Nm8h~{a6d9S z8hZP-jKsnHR;+Y%P?P@FQUuotlM*&IHlOnEhKI;H!aXKNzbJ)JN{u9vyOgH$JR9l` zeqZ;%+ns@9cg?IU8NoYi;*Cz{gyC7@>-^M^VS9oFvk}p`w`~_Q!yY@i7S5o{4>IyU ze3_$BuP0V*v?@34ji>J|2i;Fs)|1cQO<*-|7Q6M9)$@3kJYQDm zws2eKaAks`3yG%CLrH5N{NxAfLRQeJMu@{bQE zm>DQ}b~oxWoU=PVW^Fq9$$^EgMVNJ`aN>xthnPh{xPQ}+^+0|vt z&qzy48#SD`Fcd-;!_ABF@yCyt@YL~+afk5{l&j`ww#M$HgxYrcZ)dnAt)NUi-ME!+ zNJZ94GxD5Mi+H+5G+cRWRqMAcJA1(J2E}goDYoQZ?SloLGyJWaQW|?XTW2FjTe{S! z>^J=a780Wr{w%xtH@@L-s%BpqVIr&fnp??EtT}eO-Tb5)*P)H9Y_6MU*}2=aeidu1 ze*m&=A^dzd&+7DX2iqVB6F+~}1kD-t!B~IK`wYrD-V&D_iouWrn0Mg<)7?{rV~Psg z7EpFTo?4$Q7!1MqFo+GG8yoSxrf{4^yu)QTGNG#$!$oLBd4O3p?g# zYivsMhr*Q$A_slk2S;ZLc955Lc0HkGDIn>{l!jKCM&$eLL(SobTD2XNq2rbjQE4hb->{xdYT*Jg!ZCs_b0iYyQT$_oJS&E!q18)+)tb^;{wGV*q6)o^2Y! zdzij#&s*pRj2gJt4ubnax-0*{A)H=>R@##WS=tHU5LSTdkM(#=@F#D0wTp?Oq9Ps^ zzQ%5Mm|8p77-#8nNu2zY%xy(Su4JN$Li-&HyyyWrLm%Dw%~RRG9N!OqxJM$GYMG%l zAt)*--AIpUEaeTqRNU7Y#7yu?wq?SS%TWJl+4)K@X{~0S9^L`4D24}g0LW5jBSkt4 z8ZiinwK-{aT~|dYB0p4fil=HJu!yi+hd|{gs}vuI^Kw&)$O!4mY?^&X0~}9_54zGkFXf1lql3 zUrqn0PuANBg-Pb-Wr*Y1W&uqA+IPc3_KtJA9qznB_1H}Nf?eqBm(6p0)!@)0jRf}9 zl7HTUyrimgU%;)F`89tTLczg}x3RW{IDOIn;}@-)7~D=zV}Tx}Sh(Yg5h)ykAZEg} z67Pe&@WZWl(`}`9{UxM4HYHtD+N5aMf8P7_Yfp-Z$kIRtl7rDEg{Io6}NILN9O=j*+bkywVIVFEog)4?1F1Y)5H z4d`1DMUJ0sioXZ+OYoV79Qmx_XI+Mm!0ocyG3=1uvnv>cyRqNDUv&LRDl`pGdk$?g zJ`oO7d4UBjx&x7xOocifAK9ps<^}%IpP?gfBLnl0;0%>3HP7EjI%TR)1^rFG;cbCc z1V9FgXn>)w!tdS%wP+HIdw7#1T@*KpwZY+wP!wEA>Q?tKT*59vWr!h*?bXjvaX?UT zDIa+dQX@CXhY&(GWmJ0I1_NpP@5&~BQI?W^FwkS^BtbbjQ-+Vpbxhz7Ha2FbrhcUyW*JuxHn4^ZV{zuK=`vE@#6v+5)tFB`$AT@d?55g2!LMA@tK;2`T9!x^uKc{a0 z0tV!`Z0nesjq35C?pmUtO}wrg?3`P= z3PhBeXK(99?@kA$<0m!VrSyGLQ&-oC7lX&TfP_RBZ(oLRoovhP+qe0`e};*4#4+yO z`!k2ZAUqAk3TOui&oi#?{JHm6&WKWQNiCo{m=X2_6+I8@f zA-$YlDPFZ3TY-8DC6>$z(}d{?60*_wZC>j;fpP_h9fYv>wvri{Ab!JbM@-w+);3!m z^E^6buxp#+uMrt+3XMScgbCPbGWlQ*L37}G3ear-=kdM{sVo$DgdeD-u#Z1AHp;k9 zz&rK>=wxpS3IrEVx7_N;J%Z={1WZEg1k7S1K331Y=hm?GV^MxyUOoT?Oj((Xo-BhJ zJbY!SIrtkHv62}Xk8*PZfR>u0a8uFMZG~7>FVD;$TvAv|;A}8U){S`v_93e1>S|>I z45%=G#fAZOvRSk4DbGvXF)v=&kGG~`ZKKx0C$PnI1VRW5UmW~Nq2NH-u}KVHeFIYy zFneTv)8#(!pO1V$`8bDkr_7&$k3R5qjB;S2AayQ{UKxIHY0ta<9NN*Dcuk3}*>``e z4Z{3D4nPC$OEi+}cWc+CN`*@VRdDOtmuqT7oRl~m(XlmMcE$U|x&XBSrHNkT2jMgJ z_Afv+8JmYqJJqps5mzO5Ijf&KwOvs~B_t&!1?dt5j++?74Ph(6azJE5M7Yz68SWf16jYD79^+b=wL0SG9y>MyM$O6YU* zggacjHu%Oi8onvG84^!UvJH6$z_+TkXEm_1+yea?URLvwx1Cxtch~!a+`}+8J zxV9KbUkq71E^=qY@)_DW`ae2CtAUA}Z>eLm?>H5H&KWCAk#SDxVfu9m0TB z^11xn`<;WbEw-00!(juc>Un{BH{zERg{_P@vtGoAfSLb$V&ZuC&y~PY{*JHb930=H z{zZO|8KrSmHKzo545+|H9D~KfN_V4EIYcc-s1Ui$~b&Q+)xz=v%#;iEgbHg`r zRujf$M;7Mi$q8?I{Gu{w!qdK7p2JWYs|=x3-4c0E%VPNr|3OK%1%)N{VJ6Q@iPC*B z+-QyQwV#3Eant3<{Cx@c!Ki%lgf~3x-K$1Y6IF4FQ$L8Dt<+@l2Hl2c^{(5SZnm1~ zZEl<3+REWMb5KFXIbLuKl?X&Kua?{;SGS=Im;(VF3TrE-QO%9fIwq|c2B`H>Xyn%` zn}`F5Eriw-cEX4&Pu}EFMuJne$SG%M=Q#D2q}x?y8O6Np)vw*vY zV+9pnLX*dbEC`zG>yL48Jk8G+k&!WGP=nP_@)*v6nR<3Sw&a9_ zG6)jmGtor4CH}B?!?$#VA;v-2EQ1qjS zv?{PKQ5UYGN=0(y-BVU4o=8Sf@7d#_>56g;XJkZpxSRCA$o1RNCGXLd%g(o|;6B2e zCOOr3CkKKBKtM>|v1Bhe1(38x@G_dfJ&t_?eR3JF)QKZWhm;B1m zNg@NDh5H6|gE^_g5exYMGoZHCR@8Jl(IRxpr_$S;iV?fvvF8}&9@a3C-0W-_E6vc= zFIabERgfF6U0;@a1AR52`>p*0ZjEw#2Tm$wnXcScj0GW7NL8aPfh~lOkDzf;PDzOY@gbUaTu&&}zd16!0UpF`ni%D0acf{2Tmm&Sf#dLD z<-D#Xc@!j|+o0CkUIp!UU&0W5ZI9}yQ}Ub3!%SAdVe<_@8VP? zUihyzH64>yASc)+7`U=}0)588gro}dbDz=y4w6PXpR6&U(-)H9dXasLAnY}30Rrgv zF_OhiM+dBL*s&9M%JH0U%MiEPbImI^!9geWqDawMhU3UKMVLUHT~ha?##W29X*pyi zOrz31-?_e9%jC%EW{JPY3iwgPIf$`jU9l+fvyAjk#`&kCsd>1s?9LlzX*V9i!$*%a zVmfTBIF+AQRIm}={UYH@1ZQ`5IseeqMe4zPs~rS>q@d^R7(6f0?_5Lb%h5+0JC)uV zE}?E**E{Bbmq-XA*}YHJU1>LXO$+_?{M ztZ*IPpE))SlHHKP6UY9{el>EC1@9G!Z$v0X>m*?t_p_*rJ1hY8!cQHnCI}P8%1-DD zkn*I?Lq65}_zA~Z35zNM9$I2zB6>|hia2f!Y}gE*myKH~nVClk5OPV%Me0jvPW8OO zZDP5)%4QV)*n(_A3z?hz#DzGukk^ zcgyT_7;8%C`u;taM-!<^s2e|7!^q*B{!tYb6}SfaU>pl4q02TlQURas#Ne=wabvp< zPvdpKCE(f;=1=6^ft&zKfAd*-x{4{o^k|csx{~q2;;%<RjIsR(Zef&dx$D##j)jet;;bx8*nB-gRH-z|U}X(n}#%8Xpzi zuXf*tnX|Tlzp)^Vhm_C&D)r|Qx%SeZ@=A&KNt1B|p82?Gw~I9vpdFuym^*h~vLpN) zTnU^v4U#MY+@eB(Mj_|r%YREtGoxCrx_#7avR=A$H+j!z0*jCy*~k$8@y3AP=vD0e z5suY{lU*inun29Fy^)$O4or-FlK(k6N)nOgASfI*M49%|Qc^4U z{|l8IdbuVmpll!?XA9*r>Gy$tuG#;1LGLN-@kpil$2{SnmH=YfO+z#32T`;FhMQ?= zhfkjDv_4@fVo9l#xSM2?3GpY7%<$9iZ*I41o__I%Ez7gO%CHExl&GjHtUDl7_Luud z9UBw#4+fcA$l${rH~rg#H`WGs7BX$I&6y4y7<*_<<4%LOMIOYd5<0(mliWodzpRe@ z%Ci9{{R6XyfZPPabD}fB@mCRplBCH_U!$;1*n~mK2*O+Ekt%1ZojadMxssUb(lef+WavEjj0k7FXUn*8~5JHhk>Fsf(v@s^JTZ#mf*f_+UE1UM^zd5vNhEiFlY z1K5Fd5&|li*7wlMGRH83AZ*vl0gKGNIJ03#MZ9o9RoZW#>LBs;^%s73xSTs3?L+Hk z>%D|6?uK?ctjz5pelRFiHEXy?f}?49!cX9z7y8tdLM?z8NJ~^$cwdl>F3!z8uvs}i zQ>tWgA038B9g4H8Jgeuo=78j|s;d&ko}9r<07dxR0r47Pd=j+d0L)RtY>57&UW9`l zF2xF%j-X-(=cM8HT1E3I2y*LimO6j4pi{=9CaIW5a$NV4w|zWvg6}~7Y`QC*=%i0O%Xk z4DonMuq&ij=KjKwE9{Ux12`=_jaBAI$AB)0h2PrUJ?>TD!^+ula#zQwUs#R%g`OqTwa$?=%1YTZdMLWp!9G?WBo)Ohez^DJ(~F8LQ$$_i)AaW@ z?~9NLHzbiAc4tB;Mt49>3~zD2^gxu_!mZ)6m3LhuO989SXVKfLV&qi@Z^)LpiN)yT zbBzrEd`*agPZsoookJnkAfWt|^ygdrEj8%i);6+sF^LCMMFs+Lmj>BGrVD|b+#uVr zT@mg|asKAE>5L)JupyBd<&o#-DawVSkAMk=mMj3jNi&4a_BmNI;2aBjVuCtsX z|69Da!Nh$p$0AOVa+m6|Ktr5jUC<$RGpc(*YTH!J8jn*Mzt$#C(0y|1|F{5mx>~6j zGjHBp`)pDFl#0XfLoR`mz{jXcCndlYZ1>S&%9lUbjzT6)iE?Bq1o$`AiKcVgyI*lM z%#J0B5w=Bm(0L0)@QzoBQ&^EVgid+TN$ute$w}U2b)~R(KLuIPD$hp_Lfe(1aF3k? zsk1y*J-fEow9%*=E72*%DzeA~%r4(iQN71dwYNe1bp!|3uAY2riW?)$6t72?M2z#F z5=IMihP&))Jm^);>R;+`%{_7Da?c2J!d$1O#;WzH(!gMV|2y>j;7!3i8-4>Z2h~lO zJ9ZPZkkej=-9i-%tDds?c3r-An3AdHb(sfGmA61qIgQGZnu_YNtoL<^H?QpCop86? z*&)W;?VEIHylMq3UjgMe9+BmuI#zJ0FOA}X)urI?0mH`Gmkymg9MHD=L9%(NbhU!> zN0o@`>Wzae@q^yOeGhKkYWrYW+%I3AUtwdS8T`>fCdS91X{^uY-T|So3vcK|@rSo$k%H@A^Nxn;Lfz!8StSBirhJR;h-@U3=ChXsPy3CY?fxYNw z-i)d``71B4`yaFitsRUP+G5{6IHq1(Kj_^4>*dS3p1HbNmc=PtEsLh=3|0H;XiWdk z9;T)3GG9u0-S@FBG`7&OSX42aJyh>XG}Ruj$xlZDHrd3L`j?wFwG;MG889*IciB?u z^7biv(#P+pcU8uD{_$!xJt637?Y$u#dEbcQ;=bvmdgb5^ZCH^HM&8^XYddXLWKsPr zm8Qn}GQ<0YZ(Ff~i6WjK48oOl;4K*6*%vz~TABG-()PpSV)M081HH}k=zeK8)wfNT z%{P93GhJ(EewwAN`EF#9Ial!AH@!LanShj7{}Y_zqj^i_2kCws(fW75(bH+`Hyt(2 z_7f>mq6JY(iNMR#c#ttFVDQ?aHjKXfcOI1RC_};Nu6ykE>qst*Z33TFBw`fllp)`a z3IQGpX;OVX!qn9bz$C|I4FnQ?=u1n7%}kKSR*y1op{ke{uPhYkMp)yf9gYWs1+{t2fq5C7QIbq?T<;U)m13m znfb)j%2!v-<;qRjz30pGD*HF`x@MSV| z6?}8vzn%VgpzCqdw?{(XrF_o3rwL!>wWyEDDjOQiW=rRJ`>(X6=!*36;tsyi7WT2J z-$fI1$N2SzGUB>M6ZzL>+i&lqtTTAEef#U)o2^y%3jd^t7aN=V*uUa$bRCO+)2CKH z^O3T;NkV6>alqAVt!p((O!nXM-TD)| zULI%p?!9!QyW~e!^7?qaz>B_ZTVMJd)-YbBZ*Dj?J$-$?;l&!8ff#(K zjeN{}6Gm2SyZkEM9iQ6%`e~d=!^Uc6Y_*GOwtsmlfSii+8Dm1R(NsQ}O6Zktc?R)dg z#ZJm?T+p!W<5L^_dT%US&d$Mbh!JN z4jUWGXtDKQ>bqC(9y=Q=)*%6x0_p>2e|e4e9MgAim^xN4mvx5SrSa=;q00GEC>vt( z$~C5}2-tx3#wmUp85vma~tRUB02$lJZ$d_P$~D zT>pm}_3G^y4*gx&TD!h*U`zW9lYi3}qR@ux-S1C73GwgPHQ?9x_CShx1RaaialvE7 zUJHRm)7>5huRj&=(1z1SM^hux-OENzMf>g@vah=)b4V!P6aGe+lJoL@0uq9u+@166 zp_b_8F`|dBBxVX89!1~+*m9D)^hgD)OA^pncz!ww^bvW2FcQFlF#?`NCgOSs;2)rZ zLe(Rk)O`8Pixz99OcXfj>BHAwM9u?rhvykWe#GSE&FGq4X`?gkU+Iw&u@w-9J#ino zz_B*IgF(@0&oT7F@C3AgJ?!%D<7~OY8tLzj@5%QcTTHjA`=}o)VIZLF;IO#Cm)m7m za?mOM)Onhqe;c7>g@s3xqOLhO%=-trsOG2G0W> zjny3!49NtkevECX6Mo6WHp6*m;+}xCbD^T6mDOC(HhI>@;|0~PFL+&0)X;c!vB>^I z)2>%56#Kt_pN`t+aARvmU&cD8-=ptj@t{aqQS2%OAC?ZBGts4} zx*fXqXMX)5uSWW*Bf1RMY)sdotk^)x4Zl0c= zpGU7?#)SmlU%xO`paB=8am!MTh!iOiD@p>1l|P|$G9Q(A{BTLLyJ<&wZk$>qXZX*+ z%{lh?OrQvw#E#PQ#n6QOcKg$n7YOG&Trqi2br5FJ8{3D>io(Ge(DKu#78t|DUAJ`$ z9zQSUtl*@vd-vbK@wSg^OE&RSnO}eQ9zEYT9KE@=Xrm)!zSx`pc0KeRBY9XFUE>KV z9-7aK?6f+`C;UErWj2;zFZd+R!b70TpPZWZ*ixbHdetNEQEfij;(uxOUf2#c1ODR$ z!5>~MgT4$ z)l98!iqr%-J6*3h+Musrjm*uX6$7swd~1~IKk~qHz<(@lax!fjncskzAfsKX;=V)s z6I0ABiz67c?|77Q6vO&VQOiE3b$ivGaOwvETXYx@a3Cg{l}Km%#q9oxq3OuC@AU_l z6B530G!_g9ZlfZAlwLqXFgCi-*U_=K>3eY6WOIJ1(QMhi?|!_4)z2R#j+c)NmQNOn z499K^|J9kUSn9>1t62H=d!}MzYW%OKfZ5yYKc5XX&OEucL#AihUmR#nvtrD%pr9bY z!T}|*Fb(&iEn&}9qie=M4C9Rcfq?~|jo;>?!5nZHmxj^|vDi&bO$78doH!(ghYxxF z?F`R#h#VRl>uMRUBmj<9aQ;yRtu68uTgth+XLC5# zX6XyP^)}956ED;4cadGbF5jiXI#Zqna7 zFX|Af2>5#^$4yP%9;_VHB6f_WD7dZ(%x%tOtkvn9erO|a8;=53yT_Vu3WVs)q5sydyBuDy5J}6`)ReEtbA_ncuv}*%;(R3b=RiW*u{Fd zw4I>YU1dGSIMbcH__cC6S;BQ4yzB6TukGiIrMfjaspSH%cSeYq( zeETAObK^JHzWO734zbuDs7*_AAZK`C=_)=CUw;gw6it+2H*RUkStIP^8XnmbdIU!qadQ4k0@*qRe z$nB1;fc*0Xd<+zSL%9B(g*om)Aga+B`#C6{7#Y)U#fVya{Tlz}_29)+X<$yS!6lgF78A8UR__A4yqCl5kFfh(J8UkGBt#;R{7O%}CfQxOOLU!<#ZwzA zUe3>FY9G?eS4>~4T$$35q3xSgp4d!8MMzAChnLsTlat;@Y^3$HM0w<1JWGAULOl9H3n&s!y)aIbBsEt!a~>)KqfaWzipSn~GRrjU|Cpi%+}GF$J}ME~M<%oGs=M#Avn*Sm;{!aVXo z8_5J3CUr2p1qO^v@Uey$7KTOOY{1$V`ZoN7G9W>KHsKQm1TSFyuR458n62!@pj~|r zC!gB7IzYAexZUXEB7n%CCW3_jzcB?yFKi{(M%LJ;zNS;#%Z>yQdR}wfcutYzbABLH zZ6~+Bk!|c%h9E|JZvMWi%x$|v>_nYBZA?Acy_P~eD~y*GJmkK*OYf&L*sIjal@yu3 z`-)L3$I~1R=PC}x6Fi5*e4brQxmM%BY%MUWN5}N^WY~dx?x3e+0=Mo3=TqbmgfB1O z^VR3~njfNNG0rATn)_|WzDS))t*p%6kz^I%R3Ag-A(yd_p^Us>j~GMR6@z<CYztS^_H+lvBRNWv0t_et-K}|}I@#0Lpn1lr8dJ-p3 z-sj!!^5nXUixQQ+7)v}X>s_}Mwp2P>5t)Q z^O>@4Xr&Xb@iR7}Wd#)mqub`cXQGC|#RmTh&T6D6y6uWs3t*yLzkZ@+u3YFiPB9D# zKvA*%@>oe;F08SEloPQr-wr_jnBKV$LEg20ADf7_54TUf<;Q`mxq=#x_u zBvRdeH0s?#kt4Up_o5Bq9sMaf6s(sdzwj6+1+fm@W}IVU*hLi6W!)S;l%>RKp-cFq zL`@}ZLD@$0ZQFL5AiA$sNmS>E8W*w0Z5>nAr_1Ds^qf0GxVAi6j2U#34iEy$a?_lv z{)PQneAQIo3;3Up|K`ZyS&j=V}h~6ElYg8;I3NIUD&zuHDWUapdGl z5Xl!!BP|P!I@hm{B`0#MYU}C6rtyGRhpf);yvncsUuSRPPF356jc-ColB8`+B_x%3 z$P^*U95Q7p^DJa4Ll2dyP=q2B36UXlNJv6bgv=x%7k4FoHSZ*2Ktl^!z{j_d(kT@XtV8LVE}woIaQ5=l{x;mX#3}$Z2V5s1?6y z56dUoVia9-H!_mGdXSroYw+)X?n~W{81%xy>O#9jt;|lPpCz)h73VPHK;f7UiSR=1 zMvx-cp8M4kpZ3<1&#P)&yD=fX+_iJJc-b?@?b~vWA6xrvznyYII!rs*E6I6dx|375 zz2JNK(8{~PU+s0v{TgcLW+DTJW$s=+c||*_Si5@Gzw-uDCReaxcjSxoG!lu#aI(w(E`k3aE%*wN1k|kMQMi3%Bh9fNvkQuN2{dEL0mPNIDg!v+L^Vnu^sg544 zI&sF)-QBX>Z4k8UjyrF3O-w4`F^TC!ZtZ0_|6fAp$+B6dJ$My(Nf`{_Aj6uKOV4&p z$^O2qke_@^kJlu!q6W+%%41MDvkjaC%LnlE`1pm9dJ2<7oO^#zSs!);cLg(NY8o04 z)y^0iZe|JoFs#h9ItBYvqEv+vX{bP%K{(q8oA?tA2^7 z0m(;B1L8w)!UkLrMD33IgRjGf5AS;TjxL;u`pcDVbqNZ$9Pf8k|=-ils#!#t&;;z_1%bmk+%@R_g_3G#sA60X?>Q))G&9~gRrNLPr_ZTiNly-s1K zZ;qglklb1Z9z2lfFfGJH?JRM|!@H&m#;7JGc3>g>`YE_8J2!V4PF@A@1Mkwwm0$Qn zODBA-?JT^6mA`P7bxFr&If8|Xv;t7!eP(|Yyda-u*|~c+i~!>j61tCMJ!gWL`Va{@ z2O~d|JdSF_8df8gGBvw2tP%$;h12g2wtNlnymz>(H%sQgXNIVT5tybIJXiLwPDeM zvV@2#(kTpMLKkDNX?YpD3rG*x>CnhjVcLr6G2tu-UMrH+YD1DPPzXf6b1F-+a*|Lo z&jzs*t_>j5A!vn-f2&;Q)WpQ2@*gnsl(&0xr&~(>k%*eIG9?L7B1?@$N3d-X_IKxO zY}8^o{c*=h_XQ$B(1E|sKN*({351l#;m(1I)i&hp`}e`F07u>;Et2PnGHphb*OhG< zQNE62nH7;XplI-GrQlMC1Ky(^BO&9C|E$g>;rjaO<{pf#zfK#Lnj&@^ zR_5xOn&0kT$>^QwEik)CEN|R8&@FEMl2nq87n;ncK*4?6)f^I9rXO)mBmj z1Dn?~S7q`zjIRoeVB8eoxE$Gsi8!7M&}`RMSF4hr76_a?f1bCw0+~uA08`tUm_}x1 z2HGN#)?rd$BtfqJd1?)nj(ORW%pe95An66f(jZc#6|oo;^`Q8`9gy(1hm|^PNWjjZ ztjB;d6W+#@lqf5D;B5t?w6@}!fTgbhtudTJBn>hh0a_h0d-f(S_tV?AKZamhGv%pg z_#LJ-gLb>`SVvsi#Y9crq!Qo6b;jJQ_sv7mQ*H0ePl<`O>vxOyTC{)q^)u)56X+bm zDCq^NiyKLe-!3E#@6P#rZ`aMY2Ev`cZYXpNem1wcv-s&k$KvYG(*XfhAbo^TY)ZQ& zl!=>+nj=-(b8nCpor&OI(Ep%e&cn`u#LJG>Ry_e0)HqL<48}ix{0KreLh8en=fN&9 zvE-GN4Q!0q3fvavjhHKeCt3Op=@?x|E5K(1hcp15>c?o`ms+*7{ize?EMA|x5VViQ zc~58fWU_f}+@^FC`s=H}X<(B0-pL1{^=-vVPy8agaQV$7h)~u@8bTpK$cPas3Ld5DT*2eVrZs-^ zkV?TX60}qg&fOw5DmFHIKUIUMNKD!*D`U9jO$Beqa!Ad7{gCHz1~eG3-wqx;i0tW| zoSZctqc32%TYnZV1c09Kgm8k_4JUU*k${2gUy~$7U1BZN_Ds}Xt(*ttW>6OM{8>9n{w zYaFIA><@JXxD|p{9$3nxWFx!``v&xwZbLP{$2iu#;mHqZ9>NUbzq+&EUE3FU_!Jc_ zv%35O0wnDYooJ*D6F?%*t5>uP4BkFIYy3&DvQ)CT+w07EoQFw8MWxK8=9xa#8ZQ5{GHCJLgS}l zwtaYZ>d)f2;RGY8_D1V|=GKmTvNt$oykI?rdyap}SB+xr&e6GAxl1Gdfn$|r-DP;2 zQ;-QC6C}+1v9oU-y&aZtReT{52$Ms%V2cO<2D{IJ&N`uB7AwIty5V@4w)du%UVL z3@qe-%rz!3hTL<3;9>LTtI*S@boIcc|kn2y{IMYgA<-(XcBLOeCjc(+(wN_ z_NO}!5{}&Jb7}VcgqFLlt*xmkd|dkWN&J3%f}a8M3>;Q(NH_R5UX0KC$)&0D7MGRn zysiQl$L(}vckof}Y3CryFhHJjXXqxbvY~%kR&3j*DYL^cMvXB%#qvsm}nt|v(}h{<__enO(YN<1S0=jwWUB-SYP>3-)}2APav-d ztI;hy8@-*CwRvh0A>_E85J+%wB4!n#BWLjjp`XTR4T_hQ)qax?>hUbs*UjF>Wjo&K zGQ{Wq-~<9q+u=La(;AkIoGlH{^Y7gw)PfEUkE*NxqKFcmxK0J)K6|`=U zF>jwNL!xloPKT7)_qhnD1E;~M><^@iHc!bNUd_U9;4|LdzEvRy5N!GLD4PD|%mzA; zA+$jOy5Qqeke{!4>QvzB%2TzhP$^q^dTWX;{1-Uw2VMO3!6kEV+{eDY)=94n-B(kK z{&L(A)*ke%THT3HqL(U*jv`J;Sflb^?1osfra<$tG+@6FngR(`7L{eHvloJ425#F*7f??1F$u$s(-x zaC6ALztGnKW-+6G`DbMX0|A&givb>h$-q*Y@vlAmTRejlEP0rWnGGorl_0_%<0G5D zjav@<<~1-sM_+qI;RpgR@<~w!DfO+GK0Y%Jt9@a#KEZu*3G_0#emtu6R2 z9FF8%$@u@n1u!Zwot&O_blk&W0C|AFT$<0o$k^4K-;VN9b|oe}Or@YIBmO2k`!h}n zJVWrr;<*+P71TShG$pN__W{Be6kK-p_E+k{j%C2*k{*jhW$$*R_=b`gL+aBxqT7WO zAAIZ>`IDGbR3Y1EIy4rl~Um0!u8#-RnzA+TGZ`+}8jnbU{YUhTud z8-uW(I4pN9Dr%-HYxAcxWEZ5T>*&2dy;_vE8L67~_Pw=%7y0>fKv6eBC z%H9J)LPRPj3K;I4JIlb6oXY6KUm#qfSOje;WCz{dJ1*~|B*CaGD?NP*@*vR3K64*VOGr3vYWfE_ zm|>m~QixX3nqkZS(YK0q?LJtApI8b;t`!_x;Fu0CaMWFMZ!;R!%RQPTb7erK09GxB%iV zg6mRo+jUpHv)uw0KWi=@rB<( zELJ?7ngvD>kPg6rxwp66?P4;|H6Yj6TA?|9eO{DAkcS}B!Qjzg?i?+Nq}7c>0b|L{ z+qi?kkZr5Ds)jH;evM+3GFzvvK(i!xJNv-{=nB^Hj^}y{hmkaQC|H|<1PUE~h+)eX z$o`CR4h$a1U9w29?Su3M6*)rOD$zJ16%TAa^vPHt!HmBmh*L$la9lbfDyj_?CsGaa zFU0!=2Et+(W0IEQCRCj9Ewxx+unbN-tDpORczCz34thQm5+KOuVVscM{`u`&-CP4x z_suroTp+m@=FDa#b}`06Mo2>owZd6IsLe=-2uHb$aSq_ix8i5@^%r2{teqlG2jcyE zGz-W=T}1kxjI3-@Vq$+^-}jGCkLMatlCZlI9N;&}<6pl%?w#>DI{0U4X5ZeuBocOM zgrK4=#14ov0zf)jW{LT?1OoT*t&ux0ky zp*inYgT?~7uP`2qbw9?1BMG?KiHSX^Ns~2==ECM+RM9PtunX!9qaVMC2!!DZ&wXHg z^YZ>@O`f58Evy%cBLkf`+Tf(r)ERI|i`{b>Q?hp}1bu;x!1T1q#ml??v%aG%@W3$u zs|%g=M+$T8OsOTC_WS^1TWw@-b~a_LqkKI zle0_{eUP&rI&i=daP*I?cHuXK)&I?#$CzG1;dZFIX#0f3ucOUm=~#C#G>zWj+M$Bm zGZTZzT8h;_{2Ij8*;75_Hy(pM*7$25lH@Y5Ca@`B!5uO!MI6AA0vEmyCn6EjikS!I zZ0O%m010hdv{LKE!%93s_#uRaFa7#eeixC&gj+Z&QN)R3dNxBDM5P45Sn#_C8o;-) zE#a{33123pBT%hEd4X#My*7;aB6=0&P9URVZyb6>^aOVmE>($8MD)3Qp7+BcjUR6q+i90nN0evAQC1Ruz^bn{% z;R8({uFS(!{{<%p6ju#NRk0YB04ztfLP9IkZ#*Ii1B*rq=tw+_kQMSMFYn+t z_dysRpi|oL5Z|O^aTDI=q+{R$55D;DLq`QMd8GP)g7+Avkw}ocE@Gu2m=oF0Gb1CU z(|40KvoR!x?Hy-Y8qU~**L@R0#v>DXcL;yJx}7Dx6D2f8_1KP9agqQ&#bg#E_I^ZN z@}G6Zg|;j4M~|YM@xy(})r*s#HR^ZJW%QQ?7d-ZMr`FkYZA$Aw`b!nSj)JY27+ZT$vD2Dg6BHwjg1Ic1{^5MZMVCN{im?0 z{Z+}%CvJj8xj*WPCX0f&Z?u#^h>7j&*RNesO<1G9=7!xhL%ND>Pt8o-Vo}A4YxuLt z!D7d|PX=dzk_CWvA|W8~Du8+WUFq}U>>G%s#d8dZYxCyKX3KWx&tFr?q}#y2lHNdBlUQ09($BvO0gh?dqh*)HY1q6y*a<|A!9A`g(5X;Ufv(I0XU@<;10V1H8 zq@<*WxHJ&9A0rJ~1FvZAld~#A47pN*qm{bFHJ9nr9mb@!;~y9c!Gi$@0FsTmy7c%G z8XE567(;C+CC5a;e;wKASHr_UMDF+CMARI9!$z zx1ubra&((J@;|f*79J3lc=-8MsO%+FoG{_m&D% z@C&>lIj)IVZSDKK;d4X00O%O>=g2t1l{9`Pee|d)cLh}2;Gp2pMLc@8v)yss=q__e zDGr-g=pIgz@;RPHn|Q~=(-UcOa@^aRckYe5wvl9dyb(Y-o)FR0_N@$TXplscDK~9G zW^X|jEkZI_=rOSH^~KfkLrriYTEyyQSQ_u1jhA?SDM&coC+7W%BC^zZAv^@AVT>$C zTU$bv2ICc7-J479ri5$5+|VWLgTS=}ZlA>J1%zNM{Z9Wy(|OTLh|oe6Mi`PK+BYSr z`8}rL4k-#COQ5=-AO$H5L-`^tExmWI4W3&RScMXvr50GSmEP0ufFRPqRp=FEkWgEs zN+#wCn5N>G+|@*Wbt=2d0lyI{g4e>DcoWz%QPOdf$se&F!y!B}bL10zT=V(&G|p8A zl*r{7JVs@Lw46j+0BlybIks*hUD{piw*Gme0@ECrIXE~7Xv8PB7=sYlv#$zkm!T0@ zB+EITV4QaV(4OR)%}XJdZr%@tRB-izC_n!c79r^NTQx6)oqj;{8QZs8)9rzgJ(?<* zN6az4Q}%$PQ4EzfVk*;{VWR1p4_IbBCLrBWI(AA|7%?8oYaW3VXqP zv0L6{=Icb*55TD-2M!?FRyji#J@LSR6klmkh2Q*M(g+0C0Px@qB$88aljU*G3^SGz z32vozx#7N^~N=d;>bR0d(MGp_yA(r0FG|F>KS^n|kQJ};< z?TaYJpbKMYBp?S1D(S>7T~mjN;cL_(CG=w8S*WSetf~>TY7dW!3sbS^>L7K3K;+>S zmF`lX9O@pE$JIPVzmJK~yfgO5u!q%=CoPO6- zd~zLE0ZSVu&xo)nE{@Nh25G*kq5|=R{J75 z2zx{^%oi*Q3m-iqnr46$x`u`lIL?8Znm-Tv4N|9TS7sC7cgIVazzBi5mLn+uh0A5A zIuYAD8$=g74*Dl__jtg|(O#mYg3m?W%hbGdcz^pmjmm+$4p@Y`0zVfPoHPgJX&Z%4 zeNg;Kr)r^qU_Q?=m9{pBDuGI3c!9?n_zwmzC@Rq}J;&=2twNtm#PCIk!lh%&VbL;6 zjg}Fs9I>+|zL;8g9NV_(D5V^4{f=Ftdp7hL7}rySZj4I2z;HTQ&($+i74e_wE>+IJ zp38Jy1^68{YM?b&bZ+Y|3qFVgn&OZ0kwk0(ctm~&tpkCzId={<@|i-5K@{Z}qov^v zp#{LE1Lb0Y&mZCjV2ptW3z%pR40kW*zPFJ~o%H&R^yP?cyVp?<0Hv#RE4Y4Ui>e+& zBRW~N%dbb?!fN6Zj&@-NJZR%lZhOMS^U>}hWG-~$Ic^!EGD`!NoZ@+bckwc}0;!d9 z?H30+1+W&&N%HQCu0O3|TRYsh+_wm2-u@j6Be4;e>p-Ma$Axct(erb+Qd0wQT!2edcdbTTj$5a|C>_En z+-(%fFq%gc0w(k*6r9S~TjnE5eo>O(We719CS`}_FT$J-mj=dyOO@mZ_z%ILR$5jT z(?~_beb)46W5-qY#u*6SR_A3=1u7>iD=Xb~N`Qrini>OBeEX;Bi-pC-%ug(XU+<<_ z=!bCi2G084CxfUVa7(8~Ms(3SL8OKb#GjQ)g!Y20PaiQ`Yxc{Mo};X;SLxKmap4unaB~9RaGgPd|PanaEdn-)kS9T;QOcA0&QU-i~x zVYGv23QR-(P@hfD6hP^Yo&^Arvp9RLMW-->rupCUsTZUytiVK4qk}j+hVb~9<|Oc% zK+0udG5h`dz4&-aMfVs-?)>|Rz5D|EB+$l>^_R=vuy$DwnWD|WPL0xapx&K`QTE11g}DhirWv*fe}4boPDK&y z=$H~QPjk2T7vLxCR$i!WiTvdPLHR<~k6BJD393y0=?c7`_C~327Jcf{OfQ~=6bWZM zK0SBqRN{Jd!4g8hb_l5ps^AEpsPZVRG}T&edRHYRb_4 zo@_T-056?DR|crkywd(2M=hJONORp=^c?WdxBza_F4G-|d`d*v-YIMDfq=F@J(MNt z7()_(J+qYu=dRtmH!w=8nx~7f=1Vw$0}_$8$n3&b44XZ=Kp?0+hgMWL3fYu0WbS1zW zPHJgEi3}5_Ng}}IhbmqJZY*M+V=u8wa7%mK$3BXh54-b4_|$oLjLiE99~al^#$TY1 zbp@ymdW=}%RKGA;nvRNcMv!B=X_0TVnO) z4F!q%fKx%3gahqcgXVX6Zg^Yw3w-$St5+|6c}vmEVe?cP%9i{0>m3kO*u2X}@d)+Z zw98m>a004uiOUpNX*f^D1RNPCxTb4>0ZAb+n0S*WUoDgGeS(R~(bQ*?DfUndpXKC^*dssu?~K2xfi7lS8$E zSj|xEA}j-w%$IG!J(Sh!K?;g+^x)-vc~l580i@&V@Z_4Y>kQ8`!&r_Y|q(0zl)35W<>(W-Go0PII3C#Ji;-}})zqdfBPfPO5( zSuV|&Qtg5m!r0JL1ugg4qbPC00)w+27BLJ2aOIrJa4LX)MF}U>q zkWn|@-jCCn=w_IiDQEn(VE&F>P~LyxJP1b`8m*|hu!!8a>|h&>P8XYqB-BD~m3SjS zGW+pwxDdFx=uI!;_W~C=@}!-!<*Cq?b2K04_-S7Z4$9;D!FcnJ`t6fGzZYy1THhaw zG-GIdV|M`!Jj&1ijRb2?DU8VCxaj-%(Y(q#H#75vK2Iwymn0* z)jxcvVzGj-@jLxGhdI1*{X!;(2J&~hq)X`=nRw75omVk!HocXvR|qO_EhPe<(F~jSj$vo zX4>EiWB8x3)UO+^>ZfSu`nKjwzsR_D#Cig%iq8NSQBRp9aUJsg`-{KT%b>Z zx8M<{fY&cG1@K5Xtc{N9ISXt`M#@(+8tgnl{{=4pH6g?&9$jwz=y0soe+98@4oueVPeSk%9 z9h*0kNhBB%vTM1xftPPiUn&_;c<1JPg9) zVpRUU86_T;bE~IdO8~XNCFp^AIAB49Ox+ewV5$J6Kf!bbXb?)Fw6Cu{Dvn_a7OpJ4 zwbexuQ)pwMd^f3kwtf6fWF*rPT^m~Hhz}oFR6!I=Kk9Q*h!t}n_!ywNyTEzI)YM6s zyzbj^lpz7@1fM!HD+@-~D82}T)OMd!LdQMaK44*@A2}_t^WW8)khuE!q=a)3W75*8 z83p;qRg-J$C`tZTo&^?U2AUT5qS!~cZK{+zH|ail5Padli*YBlc#3PG*hb>$HKLn< zB@D8g@r7C+9wn?HL$iGMeTubvICI;5kIxa5qc~bT%R^D;7w>X zP6W|1@s{q=Gfuk-8!}DWmJ`}5LC>+hBeD@#3{V6Mi?ovu;YGV-K|ul;DvH$|&jj&U z>|{A7?o6DfE=;skJt7f6x&^9KX|Mt!DR}!N zQ9RPRVFKDiwVPv*gr9vAR=t{#;qpIa4KG|K?oL@6lKi}U_7y3&lI>5@HCer zjOPd|u`L7bnMo>p++t=SX};yn(9rXqqcP2>^I$!Jb8>Cq&VQX#o2jYc1O!$&D=RBT zSK#q%tY2E$%+g^Spwt~>oQONqC>(lh1oaGVBI0A6o!g3CusU#R;1I!$($2v?f>>1t z@rdwne?LDwyAQIn@p7UJ=j6M#_&*%PV+KDq#OyFMf+d9SLMPsM%^Tw=RM{}QfK}b4 z&odV|%Q0-lqyWQlv?V=(nlPzC6N(!^)Ap5!yS%c~3QCK&v@#2YS8W&`NLEB=U_6G8 z5-t{Hb!O&Wr{QGnI0=6-b8Zf^zj}S_GDeAO``MMlP z6moL70boK-d*jIgA`B%m_8_|wHkxh19eXt5d9J~B3oSn$`_%T&Qxc_N;^0Ij9kC?J zUCeQjOQDb+{O2dcOn=>QkfLqfPD{m%raDQ{P&gR~xseeB1!gm}CdeG_@0awrhqTl} z^U8PUMKj2olx|iL8!T#njK_f_i~E={O*AQs(C8zT)FV<4ITc8JCo|v;@!39>YDR?& z!qf=Uosj>-1pv`yV4xaBA7Jo*ID}#@>yX%$3~JIbUgkdhQ)MNx0skd~1N3Nn{TjRG z==);+oj*X~5cLf;^4ds+1xf(Mcesl86oW6r9z}@>7#qr{qZen1=MdD^lCz7b6VXo7 zGcdTUn&2}44I?Ir5f@dMCM?|nFM=AlIu>(cXu3{83X#c{jy)(Oq^YBW_^<1ckzcX9D`$Y&`F>`pGg0|5Yoe`nfC=bn z27&-pfNR6EBi8`y4R7rqqYCF~*vf$C^?;-cJcXs8xMJtQ!ug}P@%zTrtD8Rw#Hl}D zU1$SxR5WyQkyTu)(KONLxJ^RqgYN0C*^&`$mq{cLkrv#e{uMNDM>X8-g~8#YpJxyBU2_LGJbatr{wN{Sdh=hBGu ztmEG?p*9Q#02g5w1QY}f$Pyq!Jjot!M;xn{Jf5?zg9% z_ul!*$4Gk4)NbTp^T<9zZnt#o;_p?=LlC`Lip30WAr3yo_u)`OTOi6FiHr;r^9>z2Z#jw*8J;TclK$ZYo zO0Zy2^bW|*)r?P1`{Sbl?Lcw$=kMRFmb*AhcX77JA9#Se{qmm6UYq;AFJ709=oQ;a z$4Iu)`Si+@lcvaYv-po#oqp%OSE53eFL$qw-VcX9bLjX%2QF$G z!jZeE2=fi(dx$TvB(#?L&5r>%*jSxXL=U-%1UGJgco_R5+I1TQPuKs=i|>NtL11i1 zen*eguw#_{0nk;12xAZO4-DKy!r|PAYu?n{%*ZYdazqJ_@#RyaZ6LsaLWne(?Id9& zWR?#FI0JZtQ9L$Al$RfIn1lSW0Y9e~&z9zfhaJRmk}U1c?t@qBsBwptIEiFjzd);S zLx;_qhDLaA#x==qwSEUu^T?wS-?L%obegXpc-rdng49fk&{@!k6#p8-fC`g}XGK|o zbS$TLlr%+Zp@=>41E-=4JE?x3Kg*3V>5lWl4Tq%bsVN&aUUHMI9GV?O$4K@Rr)+q2 z>Nk;_?>JuRBS|f2D3HMQDKKq&%RnRiQJcXk;C`>8H?PvF≈{TBS}3MiRrOkj^RR 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 If you use this tool please consider citing us. Betti, G., Tartarini, F., Nguyen, C, Schiavon, S. CBE Clima Tool: -> A free and open-source web application for climate analysis tailored to sustainable building design. -> Build. Simul. (2023). [https://doi.org/10.1007/s12273-023-1090-5](https://doi.org/10.1007/s12273-023-1090-5). +> If you use this tool please consider citing us. Betti, G., Tartarini, F., Nguyen, C, Schiavon, S. CBE Clima Tool: A free and open-source web application for climate analysis tailored to sustainable building design. Build. Simul. (2023). [https://doi.org/10.1007/s12273-023-1090-5](https://doi.org/10.1007/s12273-023-1090-5). The CBE Clima Tool is open source. We have released the source code on a [public repository](https://github.com/CenterForTheBuiltEnvironment/clima). We welcome contributions from the community ([more info here](contributing/contributing.md)). +### Video Tutorial + +Learn more about the CBE Clima Tool by watching the following video. + +{% embed url="https://www.youtube.com/watch?v=VJ_wOHadVdw" %} +CBE Clima tool tutorial and overview +{% endembed %} + ## Contributions This ongoing project results from the collaboration and contributions of the people listed below. diff --git a/docs/documentation/tabs-explained/data-explorer.md b/docs/documentation/tabs-explained/data-explorer.md index 56dfd797..f2ca916e 100644 --- a/docs/documentation/tabs-explained/data-explorer.md +++ b/docs/documentation/tabs-explained/data-explorer.md @@ -8,10 +8,15 @@ The tab is divided into three sections: * Single-variable + filter analysis * Triple-variable + filter analysis -The single-variable analysis allows data to be displayed in 4 outputs: a yearly chart, monthly daily charts, an annual heatmap chart, and a descriptive statistics table. +The single-variable analysis allows data to be displayed in 4 outputs: a yearly chart, monthly daily charts, an annual heatmap chart, and a descriptive statistics table. The single-variable + filter analysis allows data to be displayed in a customizable heatmap. The chart can be created starting from one variable inside the Clima dataframe and, eventually, filtered by another one. The triple-variable + filter analysis allows data to be displayed in two composite charts, a scatterplot, and a heat map. The baselines of the two graphs are driven by the choice of one variable to be placed on the x-axis and one on the y-axis. Then, data can be colored according to a third variable and filtered according to a fourth. -__ +### Video Tutorial + +Learn more about the Data Explorer tab by watching the following video. + +{% embed url="https://youtu.be/VJ_wOHadVdw?si=1iXC2GvWPdPBGzMc&t=825" %} + diff --git a/docs/documentation/tabs-explained/natural-ventilation.md b/docs/documentation/tabs-explained/natural-ventilation.md index 0724145e..f863de59 100644 --- a/docs/documentation/tabs-explained/natural-ventilation.md +++ b/docs/documentation/tabs-explained/natural-ventilation.md @@ -26,4 +26,12 @@ The minimum temperature threshold is typically dictated by local discomfort near Natural ventilation can be used in combination with and in aid of radiant cooling systems. The most common risk is that condensation will form on cold surfaces, creating slippery floors or potential mold. **Clima** allows this control to be performed with the "surface temperature" filter, which is a function of the dew temperature. -_\[video as for psychometric chart]_ +### Video Tutorial + +Learn more about the Natural Ventilation tab by watching the following video. + +{% embed url="https://youtu.be/VJ_wOHadVdw?si=_cUoFQGyxJD7V85a&t=703" %} + + + + diff --git a/docs/documentation/tabs-explained/outdoor-comfort/README.md b/docs/documentation/tabs-explained/outdoor-comfort/README.md index d958554a..f5f3b996 100644 --- a/docs/documentation/tabs-explained/outdoor-comfort/README.md +++ b/docs/documentation/tabs-explained/outdoor-comfort/README.md @@ -2,18 +2,18 @@ The **Outdoor Comfort** tab shows an overview of the perceived environmental condition based on the UTCI model. -The [Universal Thermal Climate Index](http://www.utci.org/index.php) (UTCI), introduced in 1994, aims to be the measure of human physiological reaction to the atmospheric environment. +The [Universal Thermal Climate Index](http://www.utci.org/index.php) (UTCI), introduced in 1994, aims to be the measure of human physiological reaction to the atmospheric environment. -

Graphical scheme of the UTCI calculation methodology

+

Graphical scheme of the UTCI calculation methodology

It considers: -* dry bulb temperature +* dry bulb temperature * mean radiant temperature * wind speed * relative humidity - to calculate a reference environmental temperature causing strain when compared to an individual's response to the real environment. It is based on [Fiala et al](https://link.springer.com/article/10.1007/s00484-011-0424-7).'s multi-node model of thermo-regulation. +to calculate a reference environmental temperature causing strain when compared to an individual's response to the real environment. It is based on [Fiala et al](https://link.springer.com/article/10.1007/s00484-011-0424-7).'s multi-node model of thermo-regulation. The [UTCI equivalent temperature](https://doi.org/10.1016/j.wace.2018.01.004) is a function of the above parameters, which are combined in a multinode thermo-physiological model that takes into account clothing insulation and metabolic rate. From this a[Universal Thermal Climate Index](http://www.utci.org/index.php) (UTCI) of perceived thermal stress is derived. @@ -24,3 +24,10 @@ The [UTCI equivalent temperature](https://doi.org/10.1016/j.wace.2018.01.004) is The UTCI temperature can be converted in a scale assessing thermal stress, displayed by **Clima** in a heatmap graph.

Example: UTCI heat stress index heatmap in 'Sun and Wind' conditions for Rome, ITA

+ +### Video Tutorial + +Learn more about the Outdoor Comfort tab by watching the following video. + +{% embed url="https://youtu.be/VJ_wOHadVdw?si=hIa-95u3wpeP6eq0&t=781" %} + diff --git a/docs/documentation/tabs-explained/psychrometric-chart/README.md b/docs/documentation/tabs-explained/psychrometric-chart/README.md index 6070829b..1ef00ac0 100644 --- a/docs/documentation/tabs-explained/psychrometric-chart/README.md +++ b/docs/documentation/tabs-explained/psychrometric-chart/README.md @@ -1,6 +1,6 @@ # Psychrometric Chart -**Clima** allows the user to visualize all annual weather conditions on a [psychometric diagram.](psychrometric-chart-explained.md) +**Clima** allows the user to visualize all annual weather conditions on a [psychometric diagram.](psychrometric-chart-explained.md) The default diagram allows the users to overlay the frequency with which weather conditions recur throughout the year. @@ -17,3 +17,10 @@ Then, users can overlay another variable on the graphs, choosing from [Clima dat Moreover, data can be filtered by date, time, or one of the [Clima dataframe](../tab-summary/clima-dataframe.md) variables.

Psychrometric charts filters

+ +### Video Tutorial + +Learn more about the Psychrometric tab by watching the following video. + +{% embed url="https://youtu.be/VJ_wOHadVdw?si=iAcBQpq3IgCNY-H6&t=582" %} + diff --git a/docs/documentation/tabs-explained/sun-and-cloud/README.md b/docs/documentation/tabs-explained/sun-and-cloud/README.md index d0dd117a..ff1f17f2 100644 --- a/docs/documentation/tabs-explained/sun-and-cloud/README.md +++ b/docs/documentation/tabs-explained/sun-and-cloud/README.md @@ -1,8 +1,8 @@ # Sun and Clouds -The **Sun and Clouds** tab presents an overview of various climatic factors that relate to sun, solar position, intensity, and cloud cover, in particular: +The **Sun and Clouds** tab presents an overview of various climatic factors that relate to sun, solar position, intensity, and cloud cover, in particular: -* [Apparent sunpath for the location (spherical and cartesian projection)](broken-reference) +* [Apparent sunpath for the location (spherical and cartesian projection)](broken-reference/) * [Global and Diffuse Horizontal Solar Radiation](global-and-diffuse-horizontal-solar-radiation/) * [Cloud coverage](cloud-coverage.md) * [Customizable daily and hourly maps](customizable-daily-and-hourly-maps.md) @@ -11,12 +11,19 @@ The **Sun and Clouds** tab presents an overview of various climatic factors that **Clima** allows the user to visualize the sun path for the chosen location in spherical and cartesian projection -![Example: spherical sun path for Berlin, DEU ](../../../.gitbook/assets/cbeclima\_berlin\_deu\_spherical\_sun\_path\_sun\_tab.svg) +![Example: spherical sun path for Berlin, DEU](../../../.gitbook/assets/cbeclima\_berlin\_deu\_spherical\_sun\_path\_sun\_tab.svg) -![Example: cartesian sun path for Berlin, DEU ](../../../.gitbook/assets/cbeclima\_berlin\_deu\_cartesian\_sun\_path\_sun\_tab.svg) +![Example: cartesian sun path for Berlin, DEU](../../../.gitbook/assets/cbeclima\_berlin\_deu\_cartesian\_sun\_path\_sun\_tab.svg) Clima optionally allows a variety of variables to be overlayed on either sun path type. This allows the user to identify climatic patterns in relation to the apparent solar position. Data are plotted on the analemma. ![Spherical and carthesian sun paths for Berlin, DEU with various data overlays](../../../.gitbook/assets/sunpath+variables.png) + +### Video Tutorial + +Learn more about the Sun and Cloud tab by watching the following video. + +{% embed url="https://youtu.be/VJ_wOHadVdw?si=mB2xNH57MWW_4CRR&t=447" %} + diff --git a/docs/documentation/tabs-explained/tab-home.md b/docs/documentation/tabs-explained/tab-home.md index e4af9123..c0e73350 100644 --- a/docs/documentation/tabs-explained/tab-home.md +++ b/docs/documentation/tabs-explained/tab-home.md @@ -1,6 +1,16 @@ +--- +description: This page explains how a user can load an EPW file in the Clima tool +--- + # Select Weather File -Users can either choose to analyse the climate of the locations displayed on the map or upload a custom EPW file. After loading an EPW file the user can then access the other tabs to generate dynamic visualisations of the data. +Users can either choose to analyse the climate of the locations displayed on the map or upload a custom EPW file. After loading an EPW file the user can then access the other tabs to generate dynamic visualisations of the data. + +### Video Tutorial + +Learn more about how to analyse the climate of a specific location and uploading your custom EPW file by watching the following video. -![](../../.gitbook/assets/clima-home.png) +{% embed url="https://youtu.be/VJ_wOHadVdw?si=SxvUzaI9rCNIFFs0&t=136" %} +How to select or upload a EPW file +{% endembed %} diff --git a/docs/documentation/tabs-explained/tab-summary/README.md b/docs/documentation/tabs-explained/tab-summary/README.md index 7281cc2e..0f251189 100644 --- a/docs/documentation/tabs-explained/tab-summary/README.md +++ b/docs/documentation/tabs-explained/tab-summary/README.md @@ -7,3 +7,10 @@ The top section of the page provides information about the selected location suc The bottom section of the page comprises the heating and cooling degree day chart and four violin plots showing the distribution of the dry-bulb air temperature (Tdb), relative humidity (RH), Global Horizontal Irradiance (GHI), and wind speed (U). ![Tab summary top](../../../.gitbook/assets/clima-summary-bottom.png) + +### Video Tutorial + +Learn more about the Climate Summary tab by watching the following video. + +{% embed url="https://youtu.be/VJ_wOHadVdw?si=H-93XRhh5Neuby_b&t=220" %} + diff --git a/docs/documentation/tabs-explained/temperature-and-humidity/README.md b/docs/documentation/tabs-explained/temperature-and-humidity/README.md index 65527769..48fad2bf 100644 --- a/docs/documentation/tabs-explained/temperature-and-humidity/README.md +++ b/docs/documentation/tabs-explained/temperature-and-humidity/README.md @@ -2,7 +2,7 @@ The **Temperature and Humidity** tab presents an overview of [air dry bulb temperature](temperatures-explained.md) and [relative humidity](relative-humidity-explained.md) trends. -**Clima** allows the user to visualize the annual data trend through a customizable chart. +**Clima** allows the user to visualize the annual data trend through a customizable chart.

Example: annual dry bulb temperatures trend for Paris, FRA

@@ -19,3 +19,10 @@ Daily [scatter plot](https://en.wikipedia.org/wiki/Scatter\_plot) shows all hour

Example: annual dry bulb temperatures heatmap for Paris, FRA

Example: annual relative humidity heatmap for Paris, FRA

+ +### Video Tutorial + +Learn more about the Temperature and Relative Humidity tab by watching the following video. + +{% embed url="https://youtu.be/VJ_wOHadVdw?si=a1lgX6Lpt8fUXiCr&t=433" %} + diff --git a/docs/documentation/tabs-explained/wind/README.md b/docs/documentation/tabs-explained/wind/README.md index b9118714..4d2abbe3 100644 --- a/docs/documentation/tabs-explained/wind/README.md +++ b/docs/documentation/tabs-explained/wind/README.md @@ -11,3 +11,10 @@ Moreover, **Clima** shows wind intensity and direction using [heat maps](https:/

Example: Heat map of the hourly wind intensity on all days of the year for Rome, ITA

Example: Heat map of the hourly wind direction on all days of the year for Rome, ITA

+ +### Video Tutorial + +Learn more about the Wind tab by watching the following video. + +{% embed url="https://youtu.be/VJ_wOHadVdw?si=W90QpOb4VoqUPcbA&t=524" %} + diff --git a/docs/documentation/weather-file-repositories.md b/docs/documentation/weather-file-repositories.md index 5725c61e..de63edaa 100644 --- a/docs/documentation/weather-file-repositories.md +++ b/docs/documentation/weather-file-repositories.md @@ -1,6 +1,6 @@ # Weather file repositories -In addition to the data from [Energy Plus](https://energyplus.net/weather) and [Climate.One.Building.org](http://climate.onebuilding.org/) CBE Clima Tool allows users to visualize any valid EPW file. Below we list some free sources from which climate files can be obtained. +In addition to the data from [Energy Plus](https://energyplus.net/weather) and [Climate.One.Building.org](http://climate.onebuilding.org/) CBE Clima Tool allows users to visualize any valid EPW file. Below we list some free sources from which climate files can be obtained. The following sources will be divided according to: @@ -9,16 +9,13 @@ The following sources will be divided according to: **TMY repositories:** -* ****[Climate.OneBuilding](https://climate.onebuilding.org/); +* [Climate.OneBuilding](https://climate.onebuilding.org/); * [EnergyPlus](https://energyplus.net/weather); * European commission [Photovoltaic Geographical Information System](https://re.jrc.ec.europa.eu/pvg\_tools/it/#TMY); * [CSIRO](https://agdatashop.csiro.au/future-climate-predictive-weather) projected weather files for future climate scenarios for Australian locations; * University of Exeter [Prometheus](https://engineering.exeter.ac.uk/research/cee/research/prometheus/termsandconditions/futureweatherfiles/), current and future weather files for British cities - - **AMY repositories:** * NASA [Power tool](https://power.larc.nasa.gov/data-access-viewer/); * Pacific Northwest National Laboratory [diyepw tool](https://github.com/IMMM-SFA/diyepw) (given set of WMOs station) - From a25dc26f3fb36dac260228d29de2d152fcb226b3 Mon Sep 17 00:00:00 2001 From: t-kramer Date: Tue, 20 May 2025 11:46:42 -0700 Subject: [PATCH 28/91] feat: add Siteimprove analytics --- app.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app.py b/app.py index 2a2830b1..6eff14d5 100644 --- a/app.py +++ b/app.py @@ -22,6 +22,7 @@ gtag('config', 'G-JDQTBEPS4B'); + + + + + From 98bd75ad2980a13f1a7d95a643351f2fb6610c4f Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 31 Jul 2025 13:04:01 +1000 Subject: [PATCH 29/91] build: reverted to pipenv in cypress.yml --- .github/workflows/cypress.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index 475691b0..f1810e28 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -14,11 +14,12 @@ jobs: - name: Build Clima run: |- - pip install -r requirements.txt + pip install pipenv + pipenv install - name: Start Clima run: |- - python main.py & + pipenv run python main.py & - name: Setup Node uses: actions/setup-node@v4 From d09dccbdbf79afac60b12af6b0d40178ccb36475 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 31 Jul 2025 13:06:06 +1000 Subject: [PATCH 30/91] build: using pipenv in python.yml --- .github/workflows/python.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 1f76d6b6..919693a6 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -12,13 +12,14 @@ jobs: with: python-version: '3.11' - - name: Install + - name: Build Clima run: |- - pip install -r requirements.txt + pip install pipenv + pipenv install --dev - name: Test Clima run: |- - python -m pytest + pipenv run python -m pytest - name: Run Black # TODO: Add to dev dependencies: Adding it right now From 51b8f1a068c8976fec4c942d41e50c93957ed63a Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 31 Jul 2025 13:09:27 +1000 Subject: [PATCH 31/91] build: created Pipfile --- Pipfile | 1 + Pipfile.lock | 1395 +++++++++++++++++++++++++++----------------------- 2 files changed, 764 insertions(+), 632 deletions(-) diff --git a/Pipfile b/Pipfile index e0fd4b77..c16551ca 100644 --- a/Pipfile +++ b/Pipfile @@ -14,6 +14,7 @@ requests = "*" plotly = "*" pandas = "*" numpy = "*" +dash-iconify = "*" [dev-packages] cleanpy = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 35bee841..2a33deb2 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "869990943a6e4779782ac8e9242dd5d8b8e9a43905f47f2c0c11d7369ad19073" + "sha256": "d3b477f30d912b7e556bfc1a5892f75a59c464ea0ee66413d3e950573fe90b96" }, "pipfile-spec": 6, "requires": { @@ -18,19 +18,19 @@ "default": { "ansi2html": { "hashes": [ - "sha256:29ccdb1e83520d648ebdc9c9544059ea4d424ecc33d3ef723657f7f5a9ae5225", - "sha256:5c6837a13ecc1903aab7a545353312049dfedfe5105362ad3a8d9d207871ec71" + "sha256:3453bf87535d37b827b05245faaa756dbab4ec3d69925e352b6319c3c955c0a5", + "sha256:dccb75aa95fb018e5d299be2b45f802952377abfdce0504c17a6ee6ef0a420c5" ], "markers": "python_version >= '3.7'", - "version": "==1.9.1" + "version": "==1.9.2" }, "blinker": { "hashes": [ - "sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9", - "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182" + "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", + "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc" ], - "markers": "python_version >= '3.8'", - "version": "==1.7.0" + "markers": "python_version >= '3.9'", + "version": "==1.9.0" }, "cachelib": { "hashes": [ @@ -42,115 +42,117 @@ }, "certifi": { "hashes": [ - "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1", - "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474" + "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", + "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995" ], - "markers": "python_version >= '3.6'", - "version": "==2023.11.17" + "markers": "python_version >= '3.7'", + "version": "==2025.7.14" }, "charset-normalizer": { "hashes": [ - "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", - "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", - "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", - "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", - "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", - "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", - "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", - "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", - "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", - "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", - "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", - "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", - "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", - "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", - "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", - "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", - "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", - "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", - "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", - "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", - "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", - "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", - "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", - "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", - "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", - "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", - "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", - "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", - "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", - "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", - "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", - "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", - "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", - "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", - "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", - "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", - "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", - "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", - "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", - "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", - "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", - "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", - "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", - "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", - "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", - "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", - "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", - "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", - "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", - "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", - "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", - "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", - "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", - "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", - "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", - "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", - "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", - "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", - "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", - "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", - "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", - "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", - "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", - "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", - "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", - "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", - "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", - "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", - "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", - "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", - "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", - "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", - "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", - "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", - "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", - "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", - "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", - "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", - "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", - "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", - "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", - "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", - "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", - "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", - "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", - "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", - "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", - "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", - "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", - "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" - ], - "markers": "python_full_version >= '3.7.0'", - "version": "==3.3.2" + "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4", + "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45", + "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", + "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", + "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", + "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", + "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d", + "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", + "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184", + "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", + "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b", + "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64", + "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", + "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", + "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", + "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344", + "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58", + "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", + "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471", + "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", + "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", + "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836", + "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", + "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", + "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", + "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1", + "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01", + "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", + "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58", + "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", + "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", + "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2", + "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a", + "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597", + "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", + "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5", + "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb", + "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f", + "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", + "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", + "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", + "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", + "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7", + "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7", + "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455", + "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", + "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4", + "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", + "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3", + "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", + "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", + "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", + "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", + "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", + "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", + "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", + "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12", + "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa", + "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", + "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", + "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f", + "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", + "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", + "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5", + "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02", + "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", + "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", + "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e", + "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", + "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", + "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", + "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", + "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681", + "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba", + "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", + "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a", + "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", + "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", + "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", + "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", + "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027", + "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7", + "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518", + "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", + "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", + "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", + "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", + "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da", + "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", + "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f", + "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", + "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f" + ], + "markers": "python_version >= '3.7'", + "version": "==3.4.2" }, "click": { "hashes": [ - "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", - "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" + "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", + "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b" ], - "markers": "python_version >= '3.7'", - "version": "==8.1.7" + "markers": "python_version >= '3.10'", + "version": "==8.2.1" }, "dash": { "hashes": [ @@ -158,6 +160,7 @@ "sha256:8e1a280f1c7be0714825f04786beab41d40752095e116204e64b13ac978b654d" ], "index": "pypi", + "markers": "python_version >= '3.6'", "version": "==2.14.2" }, "dash-bootstrap-components": { @@ -166,6 +169,7 @@ "sha256:d7fd69cb2b1e86f9cc4bcee4036302e5d534d0bf102d331b29392a3c355d776a" ], "index": "pypi", + "markers": "python_version >= '3.6' and python_version < '4'", "version": "==1.2.0" }, "dash-core-components": { @@ -181,6 +185,7 @@ "sha256:a582f79debd6d1332b4382b575d77a9cfca220860ef9b17c24541e7e6d77d8eb" ], "index": "pypi", + "markers": "python_version >= '3.8' and python_version < '4'", "version": "==1.0.7" }, "dash-html-components": { @@ -190,13 +195,21 @@ ], "version": "==2.0.0" }, + "dash-iconify": { + "hashes": [ + "sha256:564774be6b11b0ac3a8999b7137c3d17a1d351d69b673aa313c7228eacc9d143", + "sha256:9ab0eda19bb4514e177bf1f8f36947bb9e5b44aba6ce947f22a1cf0f1a45fc14" + ], + "index": "pypi", + "version": "==0.1.2" + }, "dash-mantine-components": { "hashes": [ - "sha256:2630bca31cb96d96fb2c4f986e639b9f92d6319aba8cba02f76da6c0d8f5ca48", - "sha256:c3dcbfd89813a1539654b8d016eb953dc5f67aafe1a77d45b5ec9faa6f25d3e7" + "sha256:3a0a18c26e7373f2c465631a31688d4d25f2584da9770b650bb75da11898934a", + "sha256:dc75fe485c9b5e2ae9c939f8c09611778b3aad50318e63e1bd1eb3abe2a33711" ], "index": "pypi", - "version": "==0.12.1" + "version": "==2.1.0" }, "dash-table": { "hashes": [ @@ -207,17 +220,18 @@ }, "dataclass-wizard": { "hashes": [ - "sha256:211f842e5e9a8ace50ba891ef428cd78c82579fb98024f80f3e630ca8d1946f6", - "sha256:49be36ecc64bc5a1e9a35a6bad1d71d33b6b9b06877404931a17c6a3a6dfbb10" + "sha256:4c46591782265058f1148cfd1f54a3a91221e63986fdd04c9d59f4ced61f4424", + "sha256:63751203e54b9b9349212cc185331da73c1adc99c51312575eb73bb5c00c1962" ], - "version": "==0.22.2" + "version": "==0.22.3" }, "editorconfig": { "hashes": [ - "sha256:57f8ce78afcba15c8b18d46b5170848c88d56fd38f05c2ec60dbbfcb8996e89e", - "sha256:6b0851425aa875b08b16789ee0eeadbd4ab59666e9ebe728e526314c4a2e52c1" + "sha256:1eda9c2c0db8c16dbd50111b710572a5e6de934e39772de1959d41f64fc17c82", + "sha256:23c08b00e8e08cc3adcddb825251c497478df1dada6aefeb01e626ad37303745" ], - "version": "==0.12.3" + "markers": "python_version >= '3.9'", + "version": "==0.17.1" }, "flask": { "hashes": [ @@ -237,168 +251,168 @@ }, "h5py": { "hashes": [ - "sha256:012ab448590e3c4f5a8dd0f3533255bc57f80629bf7c5054cf4c87b30085063c", - "sha256:212bb997a91e6a895ce5e2f365ba764debeaef5d2dca5c6fb7098d66607adf99", - "sha256:2381e98af081b6df7f6db300cd88f88e740649d77736e4b53db522d8874bf2dc", - "sha256:2c8e4fda19eb769e9a678592e67eaec3a2f069f7570c82d2da909c077aa94339", - "sha256:3074ec45d3dc6e178c6f96834cf8108bf4a60ccb5ab044e16909580352010a97", - "sha256:3c97d03f87f215e7759a354460fb4b0d0f27001450b18b23e556e7856a0b21c3", - "sha256:43a61b2c2ad65b1fabc28802d133eed34debcc2c8b420cb213d3d4ef4d3e2229", - "sha256:492305a074327e8d2513011fa9fffeb54ecb28a04ca4c4227d7e1e9616d35641", - "sha256:5dfc65ac21fa2f630323c92453cadbe8d4f504726ec42f6a56cf80c2f90d6c52", - "sha256:667fe23ab33d5a8a6b77970b229e14ae3bb84e4ea3382cc08567a02e1499eedd", - "sha256:6c013d2e79c00f28ffd0cc24e68665ea03ae9069e167087b2adb5727d2736a52", - "sha256:781a24263c1270a62cd67be59f293e62b76acfcc207afa6384961762bb88ea03", - "sha256:86df4c2de68257b8539a18646ceccdcf2c1ce6b1768ada16c8dcfb489eafae20", - "sha256:90286b79abd085e4e65e07c1bd7ee65a0f15818ea107f44b175d2dfe1a4674b7", - "sha256:92273ce69ae4983dadb898fd4d3bea5eb90820df953b401282ee69ad648df684", - "sha256:93dd840bd675787fc0b016f7a05fc6efe37312a08849d9dd4053fd0377b1357f", - "sha256:9450464b458cca2c86252b624279115dcaa7260a40d3cb1594bf2b410a2bd1a3", - "sha256:ae2f0201c950059676455daf92700eeb57dcf5caaf71b9e1328e6e6593601770", - "sha256:aece0e2e1ed2aab076c41802e50a0c3e5ef8816d60ece39107d68717d4559824", - "sha256:b963fb772964fc1d1563c57e4e2e874022ce11f75ddc6df1a626f42bd49ab99f", - "sha256:ba9ab36be991119a3ff32d0c7cbe5faf9b8d2375b5278b2aea64effbeba66039", - "sha256:d4682b94fd36ab217352be438abd44c8f357c5449b8995e63886b431d260f3d3", - "sha256:d93adc48ceeb33347eb24a634fb787efc7ae4644e6ea4ba733d099605045c049", - "sha256:f42e6c30698b520f0295d70157c4e202a9e402406f50dc08f5a7bc416b24e52d", - "sha256:fd6f6d1384a9f491732cee233b99cd4bfd6e838a8815cc86722f9d2ee64032af" + "sha256:016e89d3be4c44f8d5e115fab60548e518ecd9efe9fa5c5324505a90773e6f03", + "sha256:0cbd41f4e3761f150aa5b662df991868ca533872c95467216f2bec5fcad84882", + "sha256:1223b902ef0b5d90bcc8a4778218d6d6cd0f5561861611eda59fa6c52b922f4d", + "sha256:2372116b2e0d5d3e5e705b7f663f7c8d96fa79a4052d250484ef91d24d6a08f4", + "sha256:24df6b2622f426857bda88683b16630014588a0e4155cba44e872eb011c4eaed", + "sha256:4f025cf30ae738c4c4e38c7439a761a71ccfcce04c2b87b2a2ac64e8c5171d43", + "sha256:543877d7f3d8f8a9828ed5df6a0b78ca3d8846244b9702e99ed0d53610b583a8", + "sha256:554ef0ced3571366d4d383427c00c966c360e178b5fb5ee5bb31a435c424db0c", + "sha256:573c33ad056ac7c1ab6d567b6db9df3ffc401045e3f605736218f96c1e0490c6", + "sha256:5e59d2136a8b302afd25acdf7a89b634e0eb7c66b1a211ef2d0457853768a2ef", + "sha256:6da62509b7e1d71a7d110478aa25d245dd32c8d9a1daee9d2a42dba8717b047a", + "sha256:6ff2389961ee5872de697054dd5a033b04284afc3fb52dc51d94561ece2c10c6", + "sha256:723a40ee6505bd354bfd26385f2dae7bbfa87655f4e61bab175a49d72ebfc06b", + "sha256:852b81f71df4bb9e27d407b43071d1da330d6a7094a588efa50ef02553fa7ce4", + "sha256:8c497600c0496548810047257e36360ff551df8b59156d3a4181072eed47d8ad", + "sha256:aa4b7bbce683379b7bf80aaba68e17e23396100336a8d500206520052be2f812", + "sha256:ae18e3de237a7a830adb76aaa68ad438d85fe6e19e0d99944a3ce46b772c69b3", + "sha256:bf4897d67e613ecf5bdfbdab39a1158a64df105827da70ea1d90243d796d367f", + "sha256:ccbe17dc187c0c64178f1a10aa274ed3a57d055117588942b8a08793cc448216", + "sha256:d2744b520440a996f2dae97f901caa8a953afc055db4673a993f2d87d7f38713", + "sha256:d90e6445ab7c146d7f7981b11895d70bc1dd91278a4f9f9028bc0c95e4a53f13", + "sha256:e0045115d83272090b0717c555a31398c2c089b87d212ceba800d3dc5d952e23", + "sha256:e8cbaf6910fa3983c46172666b0b8da7b7bd90d764399ca983236f2400436eeb", + "sha256:ef9603a501a04fcd0ba28dd8f0995303d26a77a980a1f9474b3417543d4c6174", + "sha256:f30dbc58f2a0efeec6c8836c97f6c94afd769023f44e2bb0ed7b17a16ec46088", + "sha256:f5cc1601e78027cedfec6dd50efb4802f018551754191aeb58d948bd3ec3bd7a" ], - "markers": "python_version >= '3.8'", - "version": "==3.10.0" + "markers": "python_version >= '3.9'", + "version": "==3.14.0" }, "idna": { "hashes": [ - "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", - "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" + "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", + "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" ], - "markers": "python_version >= '3.5'", - "version": "==3.6" + "markers": "python_version >= '3.6'", + "version": "==3.10" }, "importlib-metadata": { "hashes": [ - "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e", - "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc" + "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", + "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd" ], "markers": "python_version >= '3.7'", - "version": "==7.0.1" + "version": "==8.7.0" }, "itsdangerous": { "hashes": [ - "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", - "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a" + "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", + "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173" ], - "markers": "python_version >= '3.7'", - "version": "==2.1.2" + "markers": "python_version >= '3.8'", + "version": "==2.2.0" }, "jinja2": { "hashes": [ - "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa", - "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90" + "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", + "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67" ], "markers": "python_version >= '3.7'", - "version": "==3.1.3" + "version": "==3.1.6" }, "jsbeautifier": { "hashes": [ - "sha256:6b632581ea60dd1c133cd25a48ad187b4b91f526623c4b0fb5443ef805250505" + "sha256:5bb18d9efb9331d825735fbc5360ee8f1aac5e52780042803943aa7f854f7592", + "sha256:72f65de312a3f10900d7685557f84cb61a9733c50dcc27271a39f5b0051bf528" ], - "version": "==1.14.11" + "version": "==1.15.4" }, "llvmlite": { "hashes": [ - "sha256:04725975e5b2af416d685ea0769f4ecc33f97be541e301054c9f741003085802", - "sha256:0dd0338da625346538f1173a17cabf21d1e315cf387ca21b294ff209d176e244", - "sha256:150d0bc275a8ac664a705135e639178883293cf08c1a38de3bbaa2f693a0a867", - "sha256:1eee5cf17ec2b4198b509272cf300ee6577229d237c98cc6e63861b08463ddc6", - "sha256:210e458723436b2469d61b54b453474e09e12a94453c97ea3fbb0742ba5a83d8", - "sha256:2181bb63ef3c607e6403813421b46982c3ac6bfc1f11fa16a13eaafb46f578e6", - "sha256:24091a6b31242bcdd56ae2dbea40007f462260bc9bdf947953acc39dffd54f8f", - "sha256:2b76acee82ea0e9304be6be9d4b3840208d050ea0dcad75b1635fa06e949a0ae", - "sha256:2d92c51e6e9394d503033ffe3292f5bef1566ab73029ec853861f60ad5c925d0", - "sha256:5940bc901fb0325970415dbede82c0b7f3e35c2d5fd1d5e0047134c2c46b3281", - "sha256:8454c1133ef701e8c050a59edd85d238ee18bb9a0eb95faf2fca8b909ee3c89a", - "sha256:855f280e781d49e0640aef4c4af586831ade8f1a6c4df483fb901cbe1a48d127", - "sha256:880cb57ca49e862e1cd077104375b9d1dfdc0622596dfa22105f470d7bacb309", - "sha256:8b0a9a47c28f67a269bb62f6256e63cef28d3c5f13cbae4fab587c3ad506778b", - "sha256:92c32356f669e036eb01016e883b22add883c60739bc1ebee3a1cc0249a50828", - "sha256:92f093986ab92e71c9ffe334c002f96defc7986efda18397d0f08534f3ebdc4d", - "sha256:9564c19b31a0434f01d2025b06b44c7ed422f51e719ab5d24ff03b7560066c9a", - "sha256:b67340c62c93a11fae482910dc29163a50dff3dfa88bc874872d28ee604a83be", - "sha256:bf14aa0eb22b58c231243dccf7e7f42f7beec48970f2549b3a6acc737d1a4ba4", - "sha256:c1e1029d47ee66d3a0c4d6088641882f75b93db82bd0e6178f7bd744ebce42b9", - "sha256:df75594e5a4702b032684d5481db3af990b69c249ccb1d32687b8501f0689432", - "sha256:f19f767a018e6ec89608e1f6b13348fa2fcde657151137cb64e56d48598a92db", - "sha256:f8afdfa6da33f0b4226af8e64cfc2b28986e005528fbf944d0a24a72acfc9432", - "sha256:fa1469901a2e100c17eb8fe2678e34bd4255a3576d1a543421356e9c14d6e2ae" - ], - "markers": "python_version >= '3.8'", - "version": "==0.41.1" + "sha256:07667d66a5d150abed9157ab6c0b9393c9356f229784a4385c02f99e94fc94d4", + "sha256:1d671a56acf725bf1b531d5ef76b86660a5ab8ef19bb6a46064a705c6ca80aad", + "sha256:2fb7c4f2fb86cbae6dca3db9ab203eeea0e22d73b99bc2341cdf9de93612e930", + "sha256:319bddd44e5f71ae2689859b7203080716448a3cd1128fb144fe5c055219d516", + "sha256:40526fb5e313d7b96bda4cbb2c85cd5374e04d80732dd36a282d72a560bb6408", + "sha256:41e3839150db4330e1b2716c0be3b5c4672525b4c9005e17c7597f835f351ce2", + "sha256:46224058b13c96af1365290bdfebe9a6264ae62fb79b2b55693deed11657a8bf", + "sha256:5f79a728e0435493611c9f405168682bb75ffd1fbe6fc360733b850c80a026db", + "sha256:7202b678cdf904823c764ee0fe2dfe38a76981f4c1e51715b4cb5abb6cf1d9e8", + "sha256:9c58867118bad04a0bb22a2e0068c693719658105e40009ffe95c7000fcde88e", + "sha256:9fbadbfba8422123bab5535b293da1cf72f9f478a65645ecd73e781f962ca614", + "sha256:aa0097052c32bf721a4efc03bd109d335dfa57d9bffb3d4c24cc680711b8b4fc", + "sha256:ace564d9fa44bb91eb6e6d8e7754977783c68e90a471ea7ce913bff30bd62427", + "sha256:c0143a5ef336da14deaa8ec26c5449ad5b6a2b564df82fcef4be040b9cacfea9", + "sha256:c5d22c3bfc842668168a786af4205ec8e3ad29fb1bc03fd11fd48460d0df64c1", + "sha256:cccf8eb28f24840f2689fb1a45f9c0f7e582dd24e088dcf96e424834af11f791", + "sha256:d752f89e31b66db6f8da06df8b39f9b91e78c5feea1bf9e8c1fba1d1c24c065d", + "sha256:d8489634d43c20cd0ad71330dde1d5bc7b9966937a263ff1ec1cebb90dc50955", + "sha256:eae7e2d4ca8f88f89d315b48c6b741dcb925d6a1042da694aa16ab3dd4cbd3a1", + "sha256:eed7d5f29136bda63b6d7804c279e2b72e08c952b7c5df61f45db408e0ee52f3", + "sha256:f01a394e9c9b7b1d4e63c327b096d10f6f0ed149ef53d38a09b3749dcf8c9610" + ], + "markers": "python_version >= '3.10'", + "version": "==0.44.0" }, "markupsafe": { "hashes": [ - "sha256:0042d6a9880b38e1dd9ff83146cc3c9c18a059b9360ceae207805567aacccc69", - "sha256:0c26f67b3fe27302d3a412b85ef696792c4a2386293c53ba683a89562f9399b0", - "sha256:0fbad3d346df8f9d72622ac71b69565e621ada2ce6572f37c2eae8dacd60385d", - "sha256:15866d7f2dc60cfdde12ebb4e75e41be862348b4728300c36cdf405e258415ec", - "sha256:1c98c33ffe20e9a489145d97070a435ea0679fddaabcafe19982fe9c971987d5", - "sha256:21e7af8091007bf4bebf4521184f4880a6acab8df0df52ef9e513d8e5db23411", - "sha256:23984d1bdae01bee794267424af55eef4dfc038dc5d1272860669b2aa025c9e3", - "sha256:31f57d64c336b8ccb1966d156932f3daa4fee74176b0fdc48ef580be774aae74", - "sha256:3583a3a3ab7958e354dc1d25be74aee6228938312ee875a22330c4dc2e41beb0", - "sha256:36d7626a8cca4d34216875aee5a1d3d654bb3dac201c1c003d182283e3205949", - "sha256:396549cea79e8ca4ba65525470d534e8a41070e6b3500ce2414921099cb73e8d", - "sha256:3a66c36a3864df95e4f62f9167c734b3b1192cb0851b43d7cc08040c074c6279", - "sha256:3aae9af4cac263007fd6309c64c6ab4506dd2b79382d9d19a1994f9240b8db4f", - "sha256:3ab3a886a237f6e9c9f4f7d272067e712cdb4efa774bef494dccad08f39d8ae6", - "sha256:47bb5f0142b8b64ed1399b6b60f700a580335c8e1c57f2f15587bd072012decc", - "sha256:49a3b78a5af63ec10d8604180380c13dcd870aba7928c1fe04e881d5c792dc4e", - "sha256:4df98d4a9cd6a88d6a585852f56f2155c9cdb6aec78361a19f938810aa020954", - "sha256:5045e892cfdaecc5b4c01822f353cf2c8feb88a6ec1c0adef2a2e705eef0f656", - "sha256:5244324676254697fe5c181fc762284e2c5fceeb1c4e3e7f6aca2b6f107e60dc", - "sha256:54635102ba3cf5da26eb6f96c4b8c53af8a9c0d97b64bdcb592596a6255d8518", - "sha256:54a7e1380dfece8847c71bf7e33da5d084e9b889c75eca19100ef98027bd9f56", - "sha256:55d03fea4c4e9fd0ad75dc2e7e2b6757b80c152c032ea1d1de487461d8140efc", - "sha256:698e84142f3f884114ea8cf83e7a67ca8f4ace8454e78fe960646c6c91c63bfa", - "sha256:6aa5e2e7fc9bc042ae82d8b79d795b9a62bd8f15ba1e7594e3db243f158b5565", - "sha256:7653fa39578957bc42e5ebc15cf4361d9e0ee4b702d7d5ec96cdac860953c5b4", - "sha256:765f036a3d00395a326df2835d8f86b637dbaf9832f90f5d196c3b8a7a5080cb", - "sha256:78bc995e004681246e85e28e068111a4c3f35f34e6c62da1471e844ee1446250", - "sha256:7a07f40ef8f0fbc5ef1000d0c78771f4d5ca03b4953fc162749772916b298fc4", - "sha256:8b570a1537367b52396e53325769608f2a687ec9a4363647af1cded8928af959", - "sha256:987d13fe1d23e12a66ca2073b8d2e2a75cec2ecb8eab43ff5624ba0ad42764bc", - "sha256:9896fca4a8eb246defc8b2a7ac77ef7553b638e04fbf170bff78a40fa8a91474", - "sha256:9e9e3c4020aa2dc62d5dd6743a69e399ce3de58320522948af6140ac959ab863", - "sha256:a0b838c37ba596fcbfca71651a104a611543077156cb0a26fe0c475e1f152ee8", - "sha256:a4d176cfdfde84f732c4a53109b293d05883e952bbba68b857ae446fa3119b4f", - "sha256:a76055d5cb1c23485d7ddae533229039b850db711c554a12ea64a0fd8a0129e2", - "sha256:a76cd37d229fc385738bd1ce4cba2a121cf26b53864c1772694ad0ad348e509e", - "sha256:a7cc49ef48a3c7a0005a949f3c04f8baa5409d3f663a1b36f0eba9bfe2a0396e", - "sha256:abf5ebbec056817057bfafc0445916bb688a255a5146f900445d081db08cbabb", - "sha256:b0fe73bac2fed83839dbdbe6da84ae2a31c11cfc1c777a40dbd8ac8a6ed1560f", - "sha256:b6f14a9cd50c3cb100eb94b3273131c80d102e19bb20253ac7bd7336118a673a", - "sha256:b83041cda633871572f0d3c41dddd5582ad7d22f65a72eacd8d3d6d00291df26", - "sha256:b835aba863195269ea358cecc21b400276747cc977492319fd7682b8cd2c253d", - "sha256:bf1196dcc239e608605b716e7b166eb5faf4bc192f8a44b81e85251e62584bd2", - "sha256:c669391319973e49a7c6230c218a1e3044710bc1ce4c8e6eb71f7e6d43a2c131", - "sha256:c7556bafeaa0a50e2fe7dc86e0382dea349ebcad8f010d5a7dc6ba568eaaa789", - "sha256:c8f253a84dbd2c63c19590fa86a032ef3d8cc18923b8049d91bcdeeb2581fbf6", - "sha256:d18b66fe626ac412d96c2ab536306c736c66cf2a31c243a45025156cc190dc8a", - "sha256:d5291d98cd3ad9a562883468c690a2a238c4a6388ab3bd155b0c75dd55ece858", - "sha256:d5c31fe855c77cad679b302aabc42d724ed87c043b1432d457f4976add1c2c3e", - "sha256:d6e427c7378c7f1b2bef6a344c925b8b63623d3321c09a237b7cc0e77dd98ceb", - "sha256:dac1ebf6983148b45b5fa48593950f90ed6d1d26300604f321c74a9ca1609f8e", - "sha256:de8153a7aae3835484ac168a9a9bdaa0c5eee4e0bc595503c95d53b942879c84", - "sha256:e1a0d1924a5013d4f294087e00024ad25668234569289650929ab871231668e7", - "sha256:e7902211afd0af05fbadcc9a312e4cf10f27b779cf1323e78d52377ae4b72bea", - "sha256:e888ff76ceb39601c59e219f281466c6d7e66bd375b4ec1ce83bcdc68306796b", - "sha256:f06e5a9e99b7df44640767842f414ed5d7bedaaa78cd817ce04bbd6fd86e2dd6", - "sha256:f6be2d708a9d0e9b0054856f07ac7070fbe1754be40ca8525d5adccdbda8f475", - "sha256:f9917691f410a2e0897d1ef99619fd3f7dd503647c8ff2475bf90c3cf222ad74", - "sha256:fc1a75aa8f11b87910ffd98de62b29d6520b6d6e8a3de69a70ca34dea85d2a8a", - "sha256:fe8512ed897d5daf089e5bd010c3dc03bb1bdae00b35588c49b98268d4a01e00" + "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", + "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", + "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", + "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", + "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", + "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", + "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", + "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", + "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", + "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", + "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", + "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", + "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", + "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", + "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", + "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", + "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", + "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", + "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", + "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", + "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", + "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", + "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", + "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", + "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", + "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", + "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", + "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", + "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", + "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", + "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", + "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", + "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", + "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", + "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", + "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", + "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", + "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", + "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", + "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", + "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", + "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", + "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", + "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", + "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", + "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", + "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", + "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", + "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", + "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", + "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", + "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", + "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", + "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", + "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", + "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", + "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", + "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", + "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", + "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", + "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50" ], - "markers": "python_version >= '3.7'", - "version": "==2.1.4" + "markers": "python_version >= '3.9'", + "version": "==3.0.2" }, "more-itertools": { "hashes": [ @@ -408,6 +422,14 @@ "markers": "python_version >= '3.7'", "version": "==9.1.0" }, + "narwhals": { + "hashes": [ + "sha256:235e61ca807bc21110ca36a4d53888ecc22c42dcdf50a7c886e10dde3fd7f38c", + "sha256:837457e36a2ba1710c881fb69e1f79ce44fb81728c92ac378f70892a53af8ddb" + ], + "markers": "python_version >= '3.9'", + "version": "==2.0.1" + }, "nest-asyncio": { "hashes": [ "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", @@ -418,123 +440,158 @@ }, "numba": { "hashes": [ - "sha256:07f2fa7e7144aa6f275f27260e73ce0d808d3c62b30cff8906ad1dec12d87bbe", - "sha256:240e7a1ae80eb6b14061dc91263b99dc8d6af9ea45d310751b780888097c1aaa", - "sha256:45698b995914003f890ad839cfc909eeb9c74921849c712a05405d1a79c50f68", - "sha256:487ded0633efccd9ca3a46364b40006dbdaca0f95e99b8b83e778d1195ebcbaa", - "sha256:4e79b6cc0d2bf064a955934a2e02bf676bc7995ab2db929dbbc62e4c16551be6", - "sha256:55a01e1881120e86d54efdff1be08381886fe9f04fc3006af309c602a72bc44d", - "sha256:5c765aef472a9406a97ea9782116335ad4f9ef5c9f93fc05fd44aab0db486954", - "sha256:6fe7a9d8e3bd996fbe5eac0683227ccef26cba98dae6e5cee2c1894d4b9f16c1", - "sha256:7bf1ddd4f7b9c2306de0384bf3854cac3edd7b4d8dffae2ec1b925e4c436233f", - "sha256:811305d5dc40ae43c3ace5b192c670c358a89a4d2ae4f86d1665003798ea7a1a", - "sha256:81fe5b51532478149b5081311b0fd4206959174e660c372b94ed5364cfb37c82", - "sha256:898af055b03f09d33a587e9425500e5be84fc90cd2f80b3fb71c6a4a17a7e354", - "sha256:9e9356e943617f5e35a74bf56ff6e7cc83e6b1865d5e13cee535d79bf2cae954", - "sha256:a1eaa744f518bbd60e1f7ccddfb8002b3d06bd865b94a5d7eac25028efe0e0ff", - "sha256:bc2d904d0319d7a5857bd65062340bed627f5bfe9ae4a495aef342f072880d50", - "sha256:bcecd3fb9df36554b342140a4d77d938a549be635d64caf8bd9ef6c47a47f8aa", - "sha256:bd3dda77955be03ff366eebbfdb39919ce7c2620d86c906203bed92124989032", - "sha256:bf68df9c307fb0aa81cacd33faccd6e419496fdc621e83f1efce35cdc5e79cac", - "sha256:d3e2fe81fe9a59fcd99cc572002101119059d64d31eb6324995ee8b0f144a306", - "sha256:e63d6aacaae1ba4ef3695f1c2122b30fa3d8ba039c8f517784668075856d79e2", - "sha256:ea5bfcf7d641d351c6a80e8e1826eb4a145d619870016eeaf20bbd71ef5caa22" - ], - "markers": "python_version >= '3.8'", - "version": "==0.58.1" + "sha256:34fba9406078bac7ab052efbf0d13939426c753ad72946baaa5bf9ae0ebb8dd2", + "sha256:3945615cd73c2c7eba2a85ccc9c1730c21cd3958bfcf5a44302abae0fb07bb60", + "sha256:3a10a8fc9afac40b1eac55717cece1b8b1ac0b946f5065c89e00bde646b5b154", + "sha256:48a53a3de8f8793526cbe330f2a39fe9a6638efcbf11bd63f3d2f9757ae345cd", + "sha256:49c980e4171948ffebf6b9a2520ea81feed113c1f4890747ba7f59e74be84b1b", + "sha256:4ddce10009bc097b080fc96876d14c051cc0c7679e99de3e0af59014dab7dfe8", + "sha256:59321215e2e0ac5fa928a8020ab00b8e57cda8a97384963ac0dfa4d4e6aa54e7", + "sha256:5b1bb509d01f23d70325d3a5a0e237cbc9544dd50e50588bc581ba860c213546", + "sha256:5f154aaea625fb32cfbe3b80c5456d514d416fcdf79733dd69c0df3a11348e9e", + "sha256:76bcec9f46259cedf888041b9886e257ae101c6268261b19fda8cfbc52bec9d1", + "sha256:7d3bcada3c9afba3bed413fba45845f2fb9cd0d2b27dd58a1be90257e293d140", + "sha256:8750ee147940a6637b80ecf7f95062185ad8726c8c28a2295b8ec1160a196f7d", + "sha256:97cf4f12c728cf77c9c1d7c23707e4d8fb4632b46275f8f3397de33e5877af18", + "sha256:ae45830b129c6137294093b269ef0a22998ccc27bf7cf096ab8dcf7bca8946f9", + "sha256:ae8c7a522c26215d5f62ebec436e3d341f7f590079245a2f1008dfd498cc1642", + "sha256:bbfdf4eca202cebade0b7d43896978e146f39398909a42941c9303f82f403a18", + "sha256:bd1e74609855aa43661edffca37346e4e8462f6903889917e9f41db40907daa2", + "sha256:bdbca73ad81fa196bd53dc12e3aaf1564ae036e0c125f237c7644fe64a4928ab", + "sha256:cf9f9fc00d6eca0c23fc840817ce9f439b9f03c8f03d6246c0e7f0cb15b7162a", + "sha256:ea0247617edcb5dd61f6106a56255baab031acc4257bddaeddb3a1003b4ca3fd", + "sha256:efd3db391df53aaa5cfbee189b6c910a5b471488749fd6606c3f33fc984c2ae2" + ], + "markers": "python_version >= '3.10'", + "version": "==0.61.2" }, "numpy": { "hashes": [ - "sha256:02f98011ba4ab17f46f80f7f8f1c291ee7d855fcef0a5a98db80767a468c85cd", - "sha256:0b7e807d6888da0db6e7e75838444d62495e2b588b99e90dd80c3459594e857b", - "sha256:12c70ac274b32bc00c7f61b515126c9205323703abb99cd41836e8125ea0043e", - "sha256:1666f634cb3c80ccbd77ec97bc17337718f56d6658acf5d3b906ca03e90ce87f", - "sha256:18c3319a7d39b2c6a9e3bb75aab2304ab79a811ac0168a671a62e6346c29b03f", - "sha256:211ddd1e94817ed2d175b60b6374120244a4dd2287f4ece45d49228b4d529178", - "sha256:21a9484e75ad018974a2fdaa216524d64ed4212e418e0a551a2d83403b0531d3", - "sha256:39763aee6dfdd4878032361b30b2b12593fb445ddb66bbac802e2113eb8a6ac4", - "sha256:3c67423b3703f8fbd90f5adaa37f85b5794d3366948efe9a5190a5f3a83fc34e", - "sha256:46f47ee566d98849323f01b349d58f2557f02167ee301e5e28809a8c0e27a2d0", - "sha256:51c7f1b344f302067b02e0f5b5d2daa9ed4a721cf49f070280ac202738ea7f00", - "sha256:5f24750ef94d56ce6e33e4019a8a4d68cfdb1ef661a52cdaee628a56d2437419", - "sha256:697df43e2b6310ecc9d95f05d5ef20eacc09c7c4ecc9da3f235d39e71b7da1e4", - "sha256:6d45b3ec2faed4baca41c76617fcdcfa4f684ff7a151ce6fc78ad3b6e85af0a6", - "sha256:77810ef29e0fb1d289d225cabb9ee6cf4d11978a00bb99f7f8ec2132a84e0166", - "sha256:7ca4f24341df071877849eb2034948459ce3a07915c2734f1abb4018d9c49d7b", - "sha256:7f784e13e598e9594750b2ef6729bcd5a47f6cfe4a12cca13def35e06d8163e3", - "sha256:806dd64230dbbfaca8a27faa64e2f414bf1c6622ab78cc4264f7f5f028fee3bf", - "sha256:867e3644e208c8922a3be26fc6bbf112a035f50f0a86497f98f228c50c607bb2", - "sha256:8c66d6fec467e8c0f975818c1796d25c53521124b7cfb760114be0abad53a0a2", - "sha256:8ed07a90f5450d99dad60d3799f9c03c6566709bd53b497eb9ccad9a55867f36", - "sha256:9bc6d1a7f8cedd519c4b7b1156d98e051b726bf160715b769106661d567b3f03", - "sha256:9e1591f6ae98bcfac2a4bbf9221c0b92ab49762228f38287f6eeb5f3f55905ce", - "sha256:9e87562b91f68dd8b1c39149d0323b42e0082db7ddb8e934ab4c292094d575d6", - "sha256:a7081fd19a6d573e1a05e600c82a1c421011db7935ed0d5c483e9dd96b99cf13", - "sha256:a8474703bffc65ca15853d5fd4d06b18138ae90c17c8d12169968e998e448bb5", - "sha256:af36e0aa45e25c9f57bf684b1175e59ea05d9a7d3e8e87b7ae1a1da246f2767e", - "sha256:b1240f767f69d7c4c8a29adde2310b871153df9b26b5cb2b54a561ac85146485", - "sha256:b4d362e17bcb0011738c2d83e0a65ea8ce627057b2fdda37678f4374a382a137", - "sha256:b831295e5472954104ecb46cd98c08b98b49c69fdb7040483aff799a755a7374", - "sha256:b8c275f0ae90069496068c714387b4a0eba5d531aace269559ff2b43655edd58", - "sha256:bdd2b45bf079d9ad90377048e2747a0c82351989a2165821f0c96831b4a2a54b", - "sha256:cc0743f0302b94f397a4a65a660d4cd24267439eb16493fb3caad2e4389bccbb", - "sha256:da4b0c6c699a0ad73c810736303f7fbae483bcb012e38d7eb06a5e3b432c981b", - "sha256:f25e2811a9c932e43943a2615e65fc487a0b6b49218899e62e426e7f0a57eeda", - "sha256:f73497e8c38295aaa4741bdfa4fda1a5aedda5473074369eca10626835445511" + "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", + "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", + "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", + "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", + "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", + "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", + "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", + "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", + "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", + "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", + "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", + "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", + "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", + "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", + "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", + "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", + "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", + "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", + "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", + "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", + "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", + "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", + "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", + "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", + "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", + "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", + "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", + "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", + "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", + "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", + "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", + "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", + "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", + "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", + "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", + "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", + "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", + "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", + "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", + "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", + "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", + "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", + "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", + "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", + "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", + "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", + "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", + "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", + "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", + "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", + "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", + "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", + "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", + "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", + "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8" ], "index": "pypi", - "version": "==1.26.3" + "markers": "python_version >= '3.10'", + "version": "==2.2.6" }, "packaging": { "hashes": [ - "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", - "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" ], - "markers": "python_version >= '3.7'", - "version": "==23.2" + "markers": "python_version >= '3.8'", + "version": "==25.0" }, "pandas": { "hashes": [ - "sha256:159205c99d7a5ce89ecfc37cb08ed179de7783737cea403b295b5eda8e9c56d1", - "sha256:20404d2adefe92aed3b38da41d0847a143a09be982a31b85bc7dd565bdba0f4e", - "sha256:2707514a7bec41a4ab81f2ccce8b382961a29fbe9492eab1305bb075b2b1ff4f", - "sha256:30b83f7c3eb217fb4d1b494a57a2fda5444f17834f5df2de6b2ffff68dc3c8e2", - "sha256:38e0b4fc3ddceb56ec8a287313bc22abe17ab0eb184069f08fc6a9352a769b18", - "sha256:3de918a754bbf2da2381e8a3dcc45eede8cd7775b047b923f9006d5f876802ae", - "sha256:52826b5f4ed658fa2b729264d63f6732b8b29949c7fd234510d57c61dbeadfcd", - "sha256:57abcaeda83fb80d447f28ab0cc7b32b13978f6f733875ebd1ed14f8fbc0f4ab", - "sha256:5a946f210383c7e6d16312d30b238fd508d80d927014f3b33fb5b15c2f895430", - "sha256:736da9ad4033aeab51d067fc3bd69a0ba36f5a60f66a527b3d72e2030e63280a", - "sha256:761cb99b42a69005dec2b08854fb1d4888fdf7b05db23a8c5a099e4b886a2106", - "sha256:7ea3ee3f125032bfcade3a4cf85131ed064b4f8dd23e5ce6fa16473e48ebcaf5", - "sha256:8108ee1712bb4fa2c16981fba7e68b3f6ea330277f5ca34fa8d557e986a11670", - "sha256:85793cbdc2d5bc32620dc8ffa715423f0c680dacacf55056ba13454a5be5de88", - "sha256:8ce2fbc8d9bf303ce54a476116165220a1fedf15985b09656b4b4275300e920b", - "sha256:9f66419d4a41132eb7e9a73dcec9486cf5019f52d90dd35547af11bc58f8637d", - "sha256:a146b9dcacc3123aa2b399df1a284de5f46287a4ab4fbfc237eac98a92ebcb71", - "sha256:a1b438fa26b208005c997e78672f1aa8138f67002e833312e6230f3e57fa87d5", - "sha256:a20628faaf444da122b2a64b1e5360cde100ee6283ae8effa0d8745153809a2e", - "sha256:a41d06f308a024981dcaa6c41f2f2be46a6b186b902c94c2674e8cb5c42985bc", - "sha256:a626795722d893ed6aacb64d2401d017ddc8a2341b49e0384ab9bf7112bdec30", - "sha256:bde2bc699dbd80d7bc7f9cab1e23a95c4375de615860ca089f34e7c64f4a8de7", - "sha256:cfd6c2491dc821b10c716ad6776e7ab311f7df5d16038d0b7458bc0b67dc10f3", - "sha256:e60f1f7dba3c2d5ca159e18c46a34e7ca7247a73b5dd1a22b6d59707ed6b899a", - "sha256:eb1e1f3861ea9132b32f2133788f3b14911b68102d562715d71bd0013bc45440", - "sha256:eb61dc8567b798b969bcc1fc964788f5a68214d333cade8319c7ab33e2b5d88a", - "sha256:f5be5d03ea2073627e7111f61b9f1f0d9625dc3c4d8dda72cc827b0c58a1d042", - "sha256:f9670b3ac00a387620489dfc1bca66db47a787f4e55911f1293063a78b108df1", - "sha256:fbc1b53c0e1fdf16388c33c3cca160f798d38aea2978004dd3f4d3dec56454c9" + "sha256:025e92411c16cbe5bb2a4abc99732a6b132f439b8aab23a59fa593eb00704232", + "sha256:09e3b1587f0f3b0913e21e8b32c3119174551deb4a4eba4a89bc7377947977e7", + "sha256:0a95b9ac964fe83ce317827f80304d37388ea77616b1425f0ae41c9d2d0d7bb2", + "sha256:0f951fbb702dacd390561e0ea45cdd8ecfa7fb56935eb3dd78e306c19104b9b0", + "sha256:1b916a627919a247d865aed068eb65eb91a344b13f5b57ab9f610b7716c92de1", + "sha256:1c78cf43c8fde236342a1cb2c34bcff89564a7bfed7e474ed2fffa6aed03a956", + "sha256:1d12f618d80379fde6af007f65f0c25bd3e40251dbd1636480dfffce2cf1e6da", + "sha256:22c2e866f7209ebc3a8f08d75766566aae02bcc91d196935a1d9e59c7b990ac9", + "sha256:2323294c73ed50f612f67e2bf3ae45aea04dce5690778e08a09391897f35ff88", + "sha256:2b0540963d83431f5ce8870ea02a7430adca100cec8a050f0811f8e31035541b", + "sha256:2ba6aff74075311fc88504b1db890187a3cd0f887a5b10f5525f8e2ef55bfdb9", + "sha256:2eb789ae0274672acbd3c575b0598d213345660120a257b47b5dafdc618aec83", + "sha256:2f4d6feeba91744872a600e6edbbd5b033005b431d5ae8379abee5bcfa479fab", + "sha256:342e59589cc454aaff7484d75b816a433350b3d7964d7847327edda4d532a2e3", + "sha256:3462c3735fe19f2638f2c3a40bd94ec2dc5ba13abbb032dd2fa1f540a075509d", + "sha256:3583d348546201aff730c8c47e49bc159833f971c2899d6097bce68b9112a4f1", + "sha256:4645f770f98d656f11c69e81aeb21c6fca076a44bed3dcbb9396a4311bc7f6d8", + "sha256:4d544806b485ddf29e52d75b1f559142514e60ef58a832f74fb38e48d757b299", + "sha256:56a342b231e8862c96bdb6ab97170e203ce511f4d0429589c8ede1ee8ece48b8", + "sha256:5db9637dbc24b631ff3707269ae4559bce4b7fd75c1c4d7e13f40edc42df4444", + "sha256:689968e841136f9e542020698ee1c4fbe9caa2ed2213ae2388dc7b81721510d3", + "sha256:6de8547d4fdb12421e2d047a2c446c623ff4c11f47fddb6b9169eb98ffba485a", + "sha256:6f3bf5ec947526106399a9e1d26d40ee2b259c66422efdf4de63c848492d91bb", + "sha256:782647ddc63c83133b2506912cc6b108140a38a37292102aaa19c81c83db2928", + "sha256:7dcb79bf373a47d2a40cf7232928eb7540155abbc460925c2c96d2d30b006eb4", + "sha256:8dfc17328e8da77be3cf9f47509e5637ba8f137148ed0e9b5241e1baf526e20a", + "sha256:9026bd4a80108fac2239294a15ef9003c4ee191a0f64b90f170b40cfb7cf2d22", + "sha256:911580460fc4884d9b05254b38a6bfadddfcc6aaef856fb5859e7ca202e45275", + "sha256:98bcc8b5bf7afed22cc753a28bc4d9e26e078e777066bc53fac7904ddef9a678", + "sha256:9b7ff55f31c4fcb3e316e8f7fa194566b286d6ac430afec0d461163312c5841e", + "sha256:ac942bfd0aca577bef61f2bc8da8147c4ef6879965ef883d8e8d5d2dc3e744b8", + "sha256:b3cd4273d3cb3707b6fffd217204c52ed92859533e31dc03b7c5008aa933aaab", + "sha256:b4b0de34dc8499c2db34000ef8baad684cfa4cbd836ecee05f323ebfba348c7d", + "sha256:ca7ed14832bce68baef331f4d7f294411bed8efd032f8109d690df45e00c4679", + "sha256:cd05b72ec02ebfb993569b4931b2e16fbb4d6ad6ce80224a3ee838387d83a191", + "sha256:dd71c47a911da120d72ef173aeac0bf5241423f9bfea57320110a978457e069e", + "sha256:e5635178b387bd2ba4ac040f82bc2ef6e6b500483975c4ebacd34bec945fda12", + "sha256:e6723a27ad7b244c0c79d8e7007092d7c8f0f11305770e2f4cd778b3ad5f9f85", + "sha256:ec6c851509364c59a5344458ab935e6451b31b818be467eb24b0fe89bd05b6b9", + "sha256:fe37e757f462d31a9cd7580236a82f353f5713a80e059a29753cf938c6775d96", + "sha256:fe67dc676818c186d5a3d5425250e40f179c2a89145df477dd82945eaea89e97", + "sha256:fe7317f578c6a153912bd2292f02e40c1d8f253e93c599e82620c7f69755c74f" ], "index": "pypi", - "version": "==2.2.0" + "markers": "python_version >= '3.9'", + "version": "==2.3.1" }, "plotly": { "hashes": [ - "sha256:23aa8ea2f4fb364a20d34ad38235524bd9d691bf5299e800bca608c31e8db8de", - "sha256:360a31e6fbb49d12b007036eb6929521343d6bee2236f8459915821baefa2cbb" + "sha256:32c444d4c940887219cb80738317040363deefdfee4f354498cc0b6dab8978bd", + "sha256:9dfa23c328000f16c928beb68927444c1ab9eae837d1fe648dbcda5360c7953d" ], "index": "pypi", - "version": "==5.18.0" + "markers": "python_version >= '3.8'", + "version": "==6.2.0" }, "pvlib": { "hashes": [ @@ -542,140 +599,166 @@ "sha256:f03b63e2d2fca13d5ca4c69010929dca5046e935da943d2dd1806e265aa44e93" ], "index": "pypi", + "markers": "python_version >= '3.6'", "version": "==0.9.1" }, "pythermalcomfort": { "hashes": [ - "sha256:607995f6920a03911c7b9fddd06d819db1fa6e658d742b4ec8395a2c90707da5", - "sha256:9b221ba003e8ea17267d496dc082ce89c0b00ed8de0fc349babea0e76fc53358" + "sha256:5f319fe0e699daea752c789efad3cf2c865a8b24b26fb25ef315462bc1a26dab", + "sha256:78c35c2791ddc01da8355cb3048dee702ee7f7bd645b4fef7a37185d5778900d" ], "index": "pypi", - "version": "==2.9.1" + "markers": "python_full_version >= '3.10.0'", + "version": "==3.4.4" }, "python-dateutil": { "hashes": [ - "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", - "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" + "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", + "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.8.2" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "version": "==2.9.0.post0" }, "pytz": { "hashes": [ - "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b", - "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7" + "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", + "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00" ], - "version": "==2023.3.post1" + "version": "==2025.2" }, "requests": { "hashes": [ - "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", - "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" + "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", + "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422" ], "index": "pypi", - "version": "==2.31.0" + "markers": "python_version >= '3.8'", + "version": "==2.32.4" }, "retrying": { "hashes": [ - "sha256:345da8c5765bd982b1d1915deb9102fd3d1f7ad16bd84a9700b85f64d24e8f3e", - "sha256:8cc4d43cb8e1125e0ff3344e9de678fefd85db3b750b81b2240dc0183af37b35" + "sha256:4d206e0ed2aff5ef2f3cd867abb9511e9e8f31127c5aca20f1d5246e476903b0", + "sha256:d736050c1adfc0a71fa022d9198ee130b0e66be318678a3fdd8b1b8872dc0997" ], - "version": "==1.3.4" + "markers": "python_version >= '3.6'", + "version": "==1.4.1" }, "scipy": { "hashes": [ - "sha256:196ebad3a4882081f62a5bf4aeb7326aa34b110e533aab23e4374fcccb0890dc", - "sha256:408c68423f9de16cb9e602528be4ce0d6312b05001f3de61fe9ec8b1263cad08", - "sha256:4bf5abab8a36d20193c698b0f1fc282c1d083c94723902c447e5d2f1780936a3", - "sha256:4c1020cad92772bf44b8e4cdabc1df5d87376cb219742549ef69fc9fd86282dd", - "sha256:5adfad5dbf0163397beb4aca679187d24aec085343755fcdbdeb32b3679f254c", - "sha256:5e32847e08da8d895ce09d108a494d9eb78974cf6de23063f93306a3e419960c", - "sha256:6546dc2c11a9df6926afcbdd8a3edec28566e4e785b915e849348c6dd9f3f490", - "sha256:730badef9b827b368f351eacae2e82da414e13cf8bd5051b4bdfd720271a5371", - "sha256:75ea2a144096b5e39402e2ff53a36fecfd3b960d786b7efd3c180e29c39e53f2", - "sha256:78e4402e140879387187f7f25d91cc592b3501a2e51dfb320f48dfb73565f10b", - "sha256:8b8066bce124ee5531d12a74b617d9ac0ea59245246410e19bca549656d9a40a", - "sha256:8bee4993817e204d761dba10dbab0774ba5a8612e57e81319ea04d84945375ba", - "sha256:913d6e7956c3a671de3b05ccb66b11bc293f56bfdef040583a7221d9e22a2e35", - "sha256:95e5c750d55cf518c398a8240571b0e0782c2d5a703250872f36eaf737751338", - "sha256:9c39f92041f490422924dfdb782527a4abddf4707616e07b021de33467f917bc", - "sha256:a24024d45ce9a675c1fb8494e8e5244efea1c7a09c60beb1eeb80373d0fecc70", - "sha256:a7ebda398f86e56178c2fa94cad15bf457a218a54a35c2a7b4490b9f9cb2676c", - "sha256:b360f1b6b2f742781299514e99ff560d1fe9bd1bff2712894b52abe528d1fd1e", - "sha256:bba1b0c7256ad75401c73e4b3cf09d1f176e9bd4248f0d3112170fb2ec4db067", - "sha256:c3003652496f6e7c387b1cf63f4bb720951cfa18907e998ea551e6de51a04467", - "sha256:e53958531a7c695ff66c2e7bb7b79560ffdc562e2051644c5576c39ff8efb563", - "sha256:e646d8571804a304e1da01040d21577685ce8e2db08ac58e543eaca063453e1c", - "sha256:e7e76cc48638228212c747ada851ef355c2bb5e7f939e10952bc504c11f4e372", - "sha256:f5f00ebaf8de24d14b8449981a2842d404152774c1a1d880c901bf454cb8e2a1", - "sha256:f7ce148dffcd64ade37b2df9315541f9adad6efcaa86866ee7dd5db0c8f041c3" - ], - "markers": "python_version >= '3.9'", - "version": "==1.12.0" + "sha256:0851f6a1e537fe9399f35986897e395a1aa61c574b178c0d456be5b1a0f5ca1f", + "sha256:0a55ffe0ba0f59666e90951971a884d1ff6f4ec3275a48f472cfb64175570f77", + "sha256:15240c3aac087a522b4eaedb09f0ad061753c5eebf1ea430859e5bf8640d5919", + "sha256:18aca1646a29ee9a0625a1be5637fa798d4d81fdf426481f06d69af828f16958", + "sha256:21a611ced9275cb861bacadbada0b8c0623bc00b05b09eb97f23b370fc2ae56d", + "sha256:226652fca853008119c03a8ce71ffe1b3f6d2844cc1686e8f9806edafae68596", + "sha256:2ef500e72f9623a6735769e4b93e9dcb158d40752cdbb077f305487e3e2d1f45", + "sha256:30cc4bb81c41831ecfd6dc450baf48ffd80ef5aed0f5cf3ea775740e80f16ecc", + "sha256:367d567ee9fc1e9e2047d31f39d9d6a7a04e0710c86e701e053f237d14a9b4f6", + "sha256:3d0b80fb26d3e13a794c71d4b837e2a589d839fd574a6bbb4ee1288c213ad4a3", + "sha256:3ddfb1e8d0b540cb4ee9c53fc3dea3186f97711248fb94b4142a1b27178d8b4b", + "sha256:3ea0733a2ff73fd6fdc5fecca54ee9b459f4d74f00b99aced7d9a3adb43fb1cc", + "sha256:44c76f9e8b6e8e488a586190ab38016e4ed2f8a038af7cd3defa903c0a2238b3", + "sha256:4cf5785e44e19dcd32a0e4807555e1e9a9b8d475c6afff3d21c3c543a6aa84f4", + "sha256:4dc0e7be79e95d8ba3435d193e0d8ce372f47f774cffd882f88ea4e1e1ddc731", + "sha256:5451606823a5e73dfa621a89948096c6528e2896e40b39248295d3a0138d594f", + "sha256:57d75524cb1c5a374958a2eae3d84e1929bb971204cc9d52213fb8589183fc19", + "sha256:5aa2687b9935da3ed89c5dbed5234576589dd28d0bf7cd237501ccfbdf1ad608", + "sha256:5e1a106f8c023d57a2a903e771228bf5c5b27b5d692088f457acacd3b54511e4", + "sha256:65f81a25805f3659b48126b5053d9e823d3215e4a63730b5e1671852a1705921", + "sha256:6c62eea7f607f122069b9bad3f99489ddca1a5173bef8a0c75555d7488b6f725", + "sha256:6e5c2f74e5df33479b5cd4e97a9104c511518fbd979aa9b8f6aec18b2e9ecae7", + "sha256:709559a1db68a9abc3b2c8672c4badf1614f3b440b3ab326d86a5c0491eafae3", + "sha256:744d977daa4becb9fc59135e75c069f8d301a87d64f88f1e602a9ecf51e77b27", + "sha256:796a5a9ad36fa3a782375db8f4241ab02a091308eb079746bc0f874c9b998318", + "sha256:81929ed0fa7a5713fcdd8b2e6f73697d3b4c4816d090dd34ff937c20fa90e8ab", + "sha256:81b433bbeaf35728dad619afc002db9b189e45eebe2cd676effe1fb93fef2b9c", + "sha256:8503517c44c18d1030d666cb70aaac1cc8913608816e06742498833b128488b7", + "sha256:85764fb15a2ad994e708258bb4ed8290d1305c62a4e1ef07c414356a24fcfbf8", + "sha256:886cc81fdb4c6903a3bb0464047c25a6d1016fef77bb97949817d0c0d79f9e04", + "sha256:89728678c5ca5abd610aee148c199ac1afb16e19844401ca97d43dc548a354eb", + "sha256:8dfbb25dffc4c3dd9371d8ab456ca81beeaf6f9e1c2119f179392f0dc1ab7695", + "sha256:978d8311674b05a8f7ff2ea6c6bce5d8b45a0cb09d4c5793e0318f448613ea65", + "sha256:adccd93a2fa937a27aae826d33e3bfa5edf9aa672376a4852d23a7cd67a2e5b7", + "sha256:bcc12db731858abda693cecdb3bdc9e6d4bd200213f49d224fe22df82687bdd6", + "sha256:c033fa32bab91dc98ca59d0cf23bb876454e2bb02cbe592d5023138778f70030", + "sha256:c0c804d60492a0aad7f5b2bb1862f4548b990049e27e828391ff2bf6f7199998", + "sha256:c24fa02f7ed23ae514460a22c57eca8f530dbfa50b1cfdbf4f37c05b5309cc39", + "sha256:ca66d980469cb623b1759bdd6e9fd97d4e33a9fad5b33771ced24d0cb24df67e", + "sha256:cb18899127278058bcc09e7b9966d41a5a43740b5bb8dcba401bd983f82e885b", + "sha256:cc1d2f2fd48ba1e0620554fe5bc44d3e8f5d4185c8c109c7fbdf5af2792cfad2", + "sha256:d85495cef541729a70cdddbbf3e6b903421bc1af3e8e3a9a72a06751f33b7c39", + "sha256:d8da7c3dd67bcd93f15618938f43ed0995982eb38973023d46d4646c4283ad65", + "sha256:dc54f76ac18073bcecffb98d93f03ed6b81a92ef91b5d3b135dcc81d55a724c7", + "sha256:e756d688cb03fd07de0fffad475649b03cb89bee696c98ce508b17c11a03f95c", + "sha256:e7cc1ffcc230f568549fc56670bcf3df1884c30bd652c5da8138199c8c76dae0", + "sha256:e8fd15fc5085ab4cca74cb91fe0a4263b1f32e4420761ddae531ad60934c2119", + "sha256:f006e323874ffd0b0b816d8c6a8e7f9a73d55ab3b8c3f72b752b226d0e3ac83d", + "sha256:f0ebb7204f063fad87fc0a0e4ff4a2ff40b2a226e4ba1b7e34bf4b79bf97cd86", + "sha256:f1b9e5962656f2734c2b285a8745358ecb4e4efbadd00208c80a389227ec61ff", + "sha256:f23634f9e5adb51b2a77766dac217063e764337fbc816aa8ad9aaebcd4397fd3", + "sha256:f7b8013c6c066609577d910d1a2a077021727af07b6fab0ee22c2f901f22352a", + "sha256:f8a5d6cd147acecc2603fbd382fed6c46f474cccfcf69ea32582e033fb54dcfe", + "sha256:f965bbf3235b01c776115ab18f092a95aa74c271a52577bcb0563e85738fd618", + "sha256:fedc2cbd1baed37474b1924c331b97bdff611d762c196fac1a9b71e67b813b1b" + ], + "markers": "python_version >= '3.11'", + "version": "==1.16.1" }, "setuptools": { "hashes": [ - "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05", - "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78" + "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", + "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c" ], - "markers": "python_version >= '3.8'", - "version": "==69.0.3" + "markers": "python_version >= '3.9'", + "version": "==80.9.0" }, "six": { "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.16.0" - }, - "tenacity": { - "hashes": [ - "sha256:5398ef0d78e63f40007c1fb4c0bff96e1911394d2fa8d194f77619c05ff6cc8a", - "sha256:ce510e327a630c9e1beaf17d42e6ffacc88185044ad85cf74c0a8887c6a0f88c" + "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", + "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" ], - "markers": "python_version >= '3.7'", - "version": "==8.2.3" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "version": "==1.17.0" }, "typing-extensions": { "hashes": [ - "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783", - "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd" + "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", + "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76" ], - "markers": "python_version >= '3.8'", - "version": "==4.9.0" + "markers": "python_version >= '3.9'", + "version": "==4.14.1" }, "tzdata": { "hashes": [ - "sha256:aa3ace4329eeacda5b7beb7ea08ece826c28d761cda36e747cfbf97996d39bf3", - "sha256:dd54c94f294765522c77399649b4fefd95522479a664a0cec87f41bebc6148c9" + "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", + "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9" ], "markers": "python_version >= '2'", - "version": "==2023.4" + "version": "==2025.2" }, "urllib3": { "hashes": [ - "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3", - "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54" + "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", + "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc" ], - "markers": "python_version >= '3.8'", - "version": "==2.1.0" + "markers": "python_version >= '3.9'", + "version": "==2.5.0" }, "werkzeug": { "hashes": [ - "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc", - "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10" + "sha256:1bc0c2310d2fbb07b1dd1105eba2f7af72f322e1e455f2f93c993bee8c8a5f17", + "sha256:a8dd59d4de28ca70471a34cba79bed5f7ef2e036a76b3ab0835474246eb41f8d" ], "markers": "python_version >= '3.8'", - "version": "==3.0.1" + "version": "==3.0.6" }, "zipp": { "hashes": [ - "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31", - "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0" + "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", + "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166" ], - "markers": "python_version >= '3.8'", - "version": "==3.17.0" + "markers": "python_version >= '3.9'", + "version": "==3.23.0" } }, "develop": { @@ -685,283 +768,331 @@ "sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6" ], "index": "pypi", + "markers": "python_version >= '3.5'", "version": "==1.0.1" }, "certifi": { "hashes": [ - "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1", - "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474" + "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", + "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995" ], - "markers": "python_version >= '3.6'", - "version": "==2023.11.17" + "markers": "python_version >= '3.7'", + "version": "==2025.7.14" }, "charset-normalizer": { "hashes": [ - "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", - "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", - "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", - "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", - "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", - "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", - "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", - "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", - "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", - "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", - "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", - "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", - "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", - "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", - "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", - "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", - "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", - "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", - "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", - "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", - "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", - "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", - "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", - "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", - "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", - "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", - "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", - "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", - "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", - "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", - "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", - "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", - "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", - "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", - "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", - "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", - "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", - "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", - "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", - "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", - "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", - "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", - "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", - "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", - "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", - "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", - "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", - "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", - "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", - "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", - "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", - "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", - "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", - "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", - "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", - "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", - "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", - "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", - "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", - "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", - "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", - "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", - "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", - "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", - "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", - "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", - "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", - "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", - "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", - "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", - "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", - "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", - "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", - "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", - "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", - "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", - "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", - "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", - "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", - "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", - "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", - "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", - "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", - "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", - "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", - "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", - "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", - "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", - "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", - "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" - ], - "markers": "python_full_version >= '3.7.0'", - "version": "==3.3.2" + "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4", + "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45", + "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", + "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", + "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", + "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", + "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d", + "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", + "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184", + "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", + "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b", + "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64", + "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", + "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", + "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", + "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344", + "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58", + "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", + "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471", + "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", + "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", + "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836", + "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", + "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", + "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", + "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1", + "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01", + "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", + "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58", + "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", + "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", + "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2", + "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a", + "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597", + "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", + "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5", + "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb", + "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f", + "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", + "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", + "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", + "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", + "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7", + "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7", + "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455", + "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", + "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4", + "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", + "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3", + "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", + "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", + "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", + "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", + "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", + "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", + "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", + "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12", + "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa", + "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", + "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", + "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f", + "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", + "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", + "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5", + "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02", + "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", + "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", + "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e", + "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", + "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", + "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", + "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", + "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681", + "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba", + "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", + "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a", + "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", + "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", + "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", + "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", + "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027", + "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7", + "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518", + "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", + "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", + "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", + "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", + "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da", + "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", + "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f", + "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", + "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f" + ], + "markers": "python_version >= '3.7'", + "version": "==3.4.2" }, "cleanpy": { "hashes": [ - "sha256:291dd6b2643b2f434a95c0a4bbefac8b152e7b63cad1d369b0b605b156dbfa90", - "sha256:9d047ed8c6d3e1439f99b02633a94b1cbae65bbd4d928b5484367a674d66b5d6" + "sha256:9ddfa7ce80dd888b597a8b0bfeea3b69567839b6f41b775a4f76f46914d5170e", + "sha256:c60589d5da68527ca0c9151e28ed56fffae69df4ab6c9bfd8c1cf1d9e76a09b8" ], "index": "pypi", - "version": "==0.4.0" + "markers": "python_version >= '3.9'", + "version": "==0.5.1" }, "idna": { "hashes": [ - "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", - "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" + "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", + "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" ], - "markers": "python_version >= '3.5'", - "version": "==3.6" + "markers": "python_version >= '3.6'", + "version": "==3.10" }, "iniconfig": { "hashes": [ - "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", - "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" + "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", + "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760" ], - "markers": "python_version >= '3.7'", - "version": "==2.0.0" + "markers": "python_version >= '3.8'", + "version": "==2.1.0" }, "numpy": { "hashes": [ - "sha256:02f98011ba4ab17f46f80f7f8f1c291ee7d855fcef0a5a98db80767a468c85cd", - "sha256:0b7e807d6888da0db6e7e75838444d62495e2b588b99e90dd80c3459594e857b", - "sha256:12c70ac274b32bc00c7f61b515126c9205323703abb99cd41836e8125ea0043e", - "sha256:1666f634cb3c80ccbd77ec97bc17337718f56d6658acf5d3b906ca03e90ce87f", - "sha256:18c3319a7d39b2c6a9e3bb75aab2304ab79a811ac0168a671a62e6346c29b03f", - "sha256:211ddd1e94817ed2d175b60b6374120244a4dd2287f4ece45d49228b4d529178", - "sha256:21a9484e75ad018974a2fdaa216524d64ed4212e418e0a551a2d83403b0531d3", - "sha256:39763aee6dfdd4878032361b30b2b12593fb445ddb66bbac802e2113eb8a6ac4", - "sha256:3c67423b3703f8fbd90f5adaa37f85b5794d3366948efe9a5190a5f3a83fc34e", - "sha256:46f47ee566d98849323f01b349d58f2557f02167ee301e5e28809a8c0e27a2d0", - "sha256:51c7f1b344f302067b02e0f5b5d2daa9ed4a721cf49f070280ac202738ea7f00", - "sha256:5f24750ef94d56ce6e33e4019a8a4d68cfdb1ef661a52cdaee628a56d2437419", - "sha256:697df43e2b6310ecc9d95f05d5ef20eacc09c7c4ecc9da3f235d39e71b7da1e4", - "sha256:6d45b3ec2faed4baca41c76617fcdcfa4f684ff7a151ce6fc78ad3b6e85af0a6", - "sha256:77810ef29e0fb1d289d225cabb9ee6cf4d11978a00bb99f7f8ec2132a84e0166", - "sha256:7ca4f24341df071877849eb2034948459ce3a07915c2734f1abb4018d9c49d7b", - "sha256:7f784e13e598e9594750b2ef6729bcd5a47f6cfe4a12cca13def35e06d8163e3", - "sha256:806dd64230dbbfaca8a27faa64e2f414bf1c6622ab78cc4264f7f5f028fee3bf", - "sha256:867e3644e208c8922a3be26fc6bbf112a035f50f0a86497f98f228c50c607bb2", - "sha256:8c66d6fec467e8c0f975818c1796d25c53521124b7cfb760114be0abad53a0a2", - "sha256:8ed07a90f5450d99dad60d3799f9c03c6566709bd53b497eb9ccad9a55867f36", - "sha256:9bc6d1a7f8cedd519c4b7b1156d98e051b726bf160715b769106661d567b3f03", - "sha256:9e1591f6ae98bcfac2a4bbf9221c0b92ab49762228f38287f6eeb5f3f55905ce", - "sha256:9e87562b91f68dd8b1c39149d0323b42e0082db7ddb8e934ab4c292094d575d6", - "sha256:a7081fd19a6d573e1a05e600c82a1c421011db7935ed0d5c483e9dd96b99cf13", - "sha256:a8474703bffc65ca15853d5fd4d06b18138ae90c17c8d12169968e998e448bb5", - "sha256:af36e0aa45e25c9f57bf684b1175e59ea05d9a7d3e8e87b7ae1a1da246f2767e", - "sha256:b1240f767f69d7c4c8a29adde2310b871153df9b26b5cb2b54a561ac85146485", - "sha256:b4d362e17bcb0011738c2d83e0a65ea8ce627057b2fdda37678f4374a382a137", - "sha256:b831295e5472954104ecb46cd98c08b98b49c69fdb7040483aff799a755a7374", - "sha256:b8c275f0ae90069496068c714387b4a0eba5d531aace269559ff2b43655edd58", - "sha256:bdd2b45bf079d9ad90377048e2747a0c82351989a2165821f0c96831b4a2a54b", - "sha256:cc0743f0302b94f397a4a65a660d4cd24267439eb16493fb3caad2e4389bccbb", - "sha256:da4b0c6c699a0ad73c810736303f7fbae483bcb012e38d7eb06a5e3b432c981b", - "sha256:f25e2811a9c932e43943a2615e65fc487a0b6b49218899e62e426e7f0a57eeda", - "sha256:f73497e8c38295aaa4741bdfa4fda1a5aedda5473074369eca10626835445511" + "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", + "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", + "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", + "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", + "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", + "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", + "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", + "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", + "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", + "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", + "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", + "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", + "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", + "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", + "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", + "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", + "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", + "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", + "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", + "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", + "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", + "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", + "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", + "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", + "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", + "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", + "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", + "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", + "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", + "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", + "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", + "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", + "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", + "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", + "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", + "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", + "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", + "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", + "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", + "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", + "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", + "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", + "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", + "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", + "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", + "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", + "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", + "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", + "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", + "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", + "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", + "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", + "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", + "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", + "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8" ], "index": "pypi", - "version": "==1.26.3" + "markers": "python_version >= '3.10'", + "version": "==2.2.6" }, "packaging": { "hashes": [ - "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", - "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" ], - "markers": "python_version >= '3.7'", - "version": "==23.2" + "markers": "python_version >= '3.8'", + "version": "==25.0" }, "pandas": { "hashes": [ - "sha256:159205c99d7a5ce89ecfc37cb08ed179de7783737cea403b295b5eda8e9c56d1", - "sha256:20404d2adefe92aed3b38da41d0847a143a09be982a31b85bc7dd565bdba0f4e", - "sha256:2707514a7bec41a4ab81f2ccce8b382961a29fbe9492eab1305bb075b2b1ff4f", - "sha256:30b83f7c3eb217fb4d1b494a57a2fda5444f17834f5df2de6b2ffff68dc3c8e2", - "sha256:38e0b4fc3ddceb56ec8a287313bc22abe17ab0eb184069f08fc6a9352a769b18", - "sha256:3de918a754bbf2da2381e8a3dcc45eede8cd7775b047b923f9006d5f876802ae", - "sha256:52826b5f4ed658fa2b729264d63f6732b8b29949c7fd234510d57c61dbeadfcd", - "sha256:57abcaeda83fb80d447f28ab0cc7b32b13978f6f733875ebd1ed14f8fbc0f4ab", - "sha256:5a946f210383c7e6d16312d30b238fd508d80d927014f3b33fb5b15c2f895430", - "sha256:736da9ad4033aeab51d067fc3bd69a0ba36f5a60f66a527b3d72e2030e63280a", - "sha256:761cb99b42a69005dec2b08854fb1d4888fdf7b05db23a8c5a099e4b886a2106", - "sha256:7ea3ee3f125032bfcade3a4cf85131ed064b4f8dd23e5ce6fa16473e48ebcaf5", - "sha256:8108ee1712bb4fa2c16981fba7e68b3f6ea330277f5ca34fa8d557e986a11670", - "sha256:85793cbdc2d5bc32620dc8ffa715423f0c680dacacf55056ba13454a5be5de88", - "sha256:8ce2fbc8d9bf303ce54a476116165220a1fedf15985b09656b4b4275300e920b", - "sha256:9f66419d4a41132eb7e9a73dcec9486cf5019f52d90dd35547af11bc58f8637d", - "sha256:a146b9dcacc3123aa2b399df1a284de5f46287a4ab4fbfc237eac98a92ebcb71", - "sha256:a1b438fa26b208005c997e78672f1aa8138f67002e833312e6230f3e57fa87d5", - "sha256:a20628faaf444da122b2a64b1e5360cde100ee6283ae8effa0d8745153809a2e", - "sha256:a41d06f308a024981dcaa6c41f2f2be46a6b186b902c94c2674e8cb5c42985bc", - "sha256:a626795722d893ed6aacb64d2401d017ddc8a2341b49e0384ab9bf7112bdec30", - "sha256:bde2bc699dbd80d7bc7f9cab1e23a95c4375de615860ca089f34e7c64f4a8de7", - "sha256:cfd6c2491dc821b10c716ad6776e7ab311f7df5d16038d0b7458bc0b67dc10f3", - "sha256:e60f1f7dba3c2d5ca159e18c46a34e7ca7247a73b5dd1a22b6d59707ed6b899a", - "sha256:eb1e1f3861ea9132b32f2133788f3b14911b68102d562715d71bd0013bc45440", - "sha256:eb61dc8567b798b969bcc1fc964788f5a68214d333cade8319c7ab33e2b5d88a", - "sha256:f5be5d03ea2073627e7111f61b9f1f0d9625dc3c4d8dda72cc827b0c58a1d042", - "sha256:f9670b3ac00a387620489dfc1bca66db47a787f4e55911f1293063a78b108df1", - "sha256:fbc1b53c0e1fdf16388c33c3cca160f798d38aea2978004dd3f4d3dec56454c9" + "sha256:025e92411c16cbe5bb2a4abc99732a6b132f439b8aab23a59fa593eb00704232", + "sha256:09e3b1587f0f3b0913e21e8b32c3119174551deb4a4eba4a89bc7377947977e7", + "sha256:0a95b9ac964fe83ce317827f80304d37388ea77616b1425f0ae41c9d2d0d7bb2", + "sha256:0f951fbb702dacd390561e0ea45cdd8ecfa7fb56935eb3dd78e306c19104b9b0", + "sha256:1b916a627919a247d865aed068eb65eb91a344b13f5b57ab9f610b7716c92de1", + "sha256:1c78cf43c8fde236342a1cb2c34bcff89564a7bfed7e474ed2fffa6aed03a956", + "sha256:1d12f618d80379fde6af007f65f0c25bd3e40251dbd1636480dfffce2cf1e6da", + "sha256:22c2e866f7209ebc3a8f08d75766566aae02bcc91d196935a1d9e59c7b990ac9", + "sha256:2323294c73ed50f612f67e2bf3ae45aea04dce5690778e08a09391897f35ff88", + "sha256:2b0540963d83431f5ce8870ea02a7430adca100cec8a050f0811f8e31035541b", + "sha256:2ba6aff74075311fc88504b1db890187a3cd0f887a5b10f5525f8e2ef55bfdb9", + "sha256:2eb789ae0274672acbd3c575b0598d213345660120a257b47b5dafdc618aec83", + "sha256:2f4d6feeba91744872a600e6edbbd5b033005b431d5ae8379abee5bcfa479fab", + "sha256:342e59589cc454aaff7484d75b816a433350b3d7964d7847327edda4d532a2e3", + "sha256:3462c3735fe19f2638f2c3a40bd94ec2dc5ba13abbb032dd2fa1f540a075509d", + "sha256:3583d348546201aff730c8c47e49bc159833f971c2899d6097bce68b9112a4f1", + "sha256:4645f770f98d656f11c69e81aeb21c6fca076a44bed3dcbb9396a4311bc7f6d8", + "sha256:4d544806b485ddf29e52d75b1f559142514e60ef58a832f74fb38e48d757b299", + "sha256:56a342b231e8862c96bdb6ab97170e203ce511f4d0429589c8ede1ee8ece48b8", + "sha256:5db9637dbc24b631ff3707269ae4559bce4b7fd75c1c4d7e13f40edc42df4444", + "sha256:689968e841136f9e542020698ee1c4fbe9caa2ed2213ae2388dc7b81721510d3", + "sha256:6de8547d4fdb12421e2d047a2c446c623ff4c11f47fddb6b9169eb98ffba485a", + "sha256:6f3bf5ec947526106399a9e1d26d40ee2b259c66422efdf4de63c848492d91bb", + "sha256:782647ddc63c83133b2506912cc6b108140a38a37292102aaa19c81c83db2928", + "sha256:7dcb79bf373a47d2a40cf7232928eb7540155abbc460925c2c96d2d30b006eb4", + "sha256:8dfc17328e8da77be3cf9f47509e5637ba8f137148ed0e9b5241e1baf526e20a", + "sha256:9026bd4a80108fac2239294a15ef9003c4ee191a0f64b90f170b40cfb7cf2d22", + "sha256:911580460fc4884d9b05254b38a6bfadddfcc6aaef856fb5859e7ca202e45275", + "sha256:98bcc8b5bf7afed22cc753a28bc4d9e26e078e777066bc53fac7904ddef9a678", + "sha256:9b7ff55f31c4fcb3e316e8f7fa194566b286d6ac430afec0d461163312c5841e", + "sha256:ac942bfd0aca577bef61f2bc8da8147c4ef6879965ef883d8e8d5d2dc3e744b8", + "sha256:b3cd4273d3cb3707b6fffd217204c52ed92859533e31dc03b7c5008aa933aaab", + "sha256:b4b0de34dc8499c2db34000ef8baad684cfa4cbd836ecee05f323ebfba348c7d", + "sha256:ca7ed14832bce68baef331f4d7f294411bed8efd032f8109d690df45e00c4679", + "sha256:cd05b72ec02ebfb993569b4931b2e16fbb4d6ad6ce80224a3ee838387d83a191", + "sha256:dd71c47a911da120d72ef173aeac0bf5241423f9bfea57320110a978457e069e", + "sha256:e5635178b387bd2ba4ac040f82bc2ef6e6b500483975c4ebacd34bec945fda12", + "sha256:e6723a27ad7b244c0c79d8e7007092d7c8f0f11305770e2f4cd778b3ad5f9f85", + "sha256:ec6c851509364c59a5344458ab935e6451b31b818be467eb24b0fe89bd05b6b9", + "sha256:fe37e757f462d31a9cd7580236a82f353f5713a80e059a29753cf938c6775d96", + "sha256:fe67dc676818c186d5a3d5425250e40f179c2a89145df477dd82945eaea89e97", + "sha256:fe7317f578c6a153912bd2292f02e40c1d8f253e93c599e82620c7f69755c74f" ], "index": "pypi", - "version": "==2.2.0" + "markers": "python_version >= '3.9'", + "version": "==2.3.1" }, "pluggy": { "hashes": [ - "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12", - "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7" + "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", + "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" + ], + "markers": "python_version >= '3.9'", + "version": "==1.6.0" + }, + "pygments": { + "hashes": [ + "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", + "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" ], "markers": "python_version >= '3.8'", - "version": "==1.3.0" + "version": "==2.19.2" }, "pytest": { "hashes": [ - "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280", - "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8" + "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", + "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c" ], "index": "pypi", - "version": "==7.4.4" + "markers": "python_version >= '3.9'", + "version": "==8.4.1" }, "python-dateutil": { "hashes": [ - "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", - "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" + "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", + "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.8.2" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "version": "==2.9.0.post0" }, "pytz": { "hashes": [ - "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b", - "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7" + "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", + "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00" ], - "version": "==2023.3.post1" + "version": "==2025.2" }, "requests": { "hashes": [ - "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", - "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" + "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", + "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422" ], "index": "pypi", - "version": "==2.31.0" + "markers": "python_version >= '3.8'", + "version": "==2.32.4" }, "six": { "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", + "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.16.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "version": "==1.17.0" }, "tzdata": { "hashes": [ - "sha256:aa3ace4329eeacda5b7beb7ea08ece826c28d761cda36e747cfbf97996d39bf3", - "sha256:dd54c94f294765522c77399649b4fefd95522479a664a0cec87f41bebc6148c9" + "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", + "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9" ], "markers": "python_version >= '2'", - "version": "==2023.4" + "version": "==2025.2" }, "urllib3": { "hashes": [ - "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3", - "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54" + "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", + "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc" ], - "markers": "python_version >= '3.8'", - "version": "==2.1.0" + "markers": "python_version >= '3.9'", + "version": "==2.5.0" } } } From cdde3ee9efff2b882615ee0df39096e037c3e797 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 31 Jul 2025 16:07:31 +1000 Subject: [PATCH 32/91] build: updated dependencies to run js tests --- tests/node/package-lock.json | 480 +++++++++++++++++++++++------------ 1 file changed, 314 insertions(+), 166 deletions(-) diff --git a/tests/node/package-lock.json b/tests/node/package-lock.json index c137b1f4..37f5fdc1 100644 --- a/tests/node/package-lock.json +++ b/tests/node/package-lock.json @@ -22,10 +22,11 @@ } }, "node_modules/@cypress/request": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz", - "integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.9.tgz", + "integrity": "sha512-I3l7FdGRXluAS44/0NguwWlO83J18p0vlr2FYHrJkWdNYhgVoiYo61IXPqaOsL+vNxU1ZqMACzItGK3/KKDsdw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -33,16 +34,16 @@ "combined-stream": "~1.0.6", "extend": "~3.0.2", "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "http-signature": "~1.3.6", + "form-data": "~4.0.4", + "http-signature": "~1.4.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", - "qs": "6.10.4", + "qs": "6.14.0", "safe-buffer": "^5.1.2", - "tough-cookie": "^4.1.3", + "tough-cookie": "^5.0.0", "tunnel-agent": "^0.6.0", "uuid": "^8.3.2" }, @@ -187,6 +188,7 @@ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", "dev": true, + "license": "MIT", "dependencies": { "safer-buffer": "~2.1.0" } @@ -196,6 +198,7 @@ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8" } @@ -219,7 +222,8 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/at-least-node": { "version": "1.0.0", @@ -276,6 +280,7 @@ "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "tweetnacl": "^0.14.3" } @@ -293,10 +298,11 @@ "dev": true }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -344,15 +350,32 @@ "node": ">=6" } }, - "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, + "license": "MIT", "dependencies": { - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -497,6 +520,7 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, + "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -532,13 +556,15 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -610,6 +636,7 @@ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", "dev": true, + "license": "MIT", "dependencies": { "assert-plus": "^1.0.0" }, @@ -646,34 +673,37 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.4.0" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", "dev": true, + "license": "MIT", "dependencies": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -707,6 +737,55 @@ "node": ">=8.6" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -790,7 +869,8 @@ "dev": true, "engines": [ "node >=0.6.0" - ] + ], + "license": "MIT" }, "node_modules/fd-slicer": { "version": "1.1.0", @@ -826,17 +906,20 @@ } }, "node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "dev": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { - "node": ">= 0.12" + "node": ">= 6" } }, "node_modules/fs-extra": { @@ -874,25 +957,50 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, + "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -922,6 +1030,7 @@ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", "dev": true, + "license": "MIT", "dependencies": { "assert-plus": "^1.0.0" } @@ -962,12 +1071,13 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -988,23 +1098,12 @@ "node": ">=8" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -1012,11 +1111,15 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, "engines": { "node": ">= 0.4" }, @@ -1025,10 +1128,11 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -1037,14 +1141,15 @@ } }, "node_modules/http-signature": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", - "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", + "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", "dev": true, + "license": "MIT", "dependencies": { "assert-plus": "^1.0.0", "jsprim": "^2.0.2", - "sshpk": "^1.14.1" + "sshpk": "^1.18.0" }, "engines": { "node": ">=0.10" @@ -1205,13 +1310,15 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-schema": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true + "dev": true, + "license": "(AFL-2.1 OR BSD-3-Clause)" }, "node_modules/json-stringify-safe": { "version": "5.0.1", @@ -1248,6 +1355,7 @@ "engines": [ "node >=0.6.0" ], + "license": "MIT", "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -1380,6 +1488,16 @@ "node": ">=10" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -1391,6 +1509,7 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -1400,6 +1519,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -1456,10 +1576,14 @@ } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -1575,12 +1699,6 @@ "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", "dev": true }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true - }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -1591,22 +1709,14 @@ "once": "^1.3.1" } }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/qs": { - "version": "6.10.4", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz", - "integrity": "sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -1615,12 +1725,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, "node_modules/request-progress": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", @@ -1630,12 +1734,6 @@ "throttleit": "^1.0.0" } }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, "node_modules/restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -1703,7 +1801,8 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/semver": { "version": "7.5.4", @@ -1720,21 +1819,6 @@ "node": ">=10" } }, - "node_modules/set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", - "dev": true, - "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -1757,14 +1841,76 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1795,6 +1941,7 @@ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", "dev": true, + "license": "MIT", "dependencies": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -1880,6 +2027,26 @@ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, "node_modules/tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", @@ -1893,18 +2060,16 @@ } }, "node_modules/tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" + "tldts": "^6.1.32" }, "engines": { - "node": ">=6" + "node": ">=16" } }, "node_modules/tslib": { @@ -1929,7 +2094,8 @@ "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true + "dev": true, + "license": "Unlicense" }, "node_modules/type-fest": { "version": "0.21.3", @@ -1950,15 +2116,6 @@ "dev": true, "optional": true }, - "node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/untildify": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", @@ -1968,16 +2125,6 @@ "node": ">=8" } }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -1995,6 +2142,7 @@ "engines": [ "node >=0.6.0" ], + "license": "MIT", "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", From 0dac00bdd3df5af2daa614576ce4c9de03aebad8 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 31 Jul 2025 16:07:56 +1000 Subject: [PATCH 33/91] build: updated python dependencies --- Pipfile | 17 +- Pipfile.lock | 584 +++++------------------ docs/contributing/run-project-locally.md | 2 +- 3 files changed, 130 insertions(+), 473 deletions(-) diff --git a/Pipfile b/Pipfile index c16551ca..7dee142f 100644 --- a/Pipfile +++ b/Pipfile @@ -4,24 +4,23 @@ verify_ssl = true name = "pypi" [packages] -dash = "==2.14.2" +dash = "==2.15" pvlib = "==0.9.1" -pythermalcomfort = "*" +pythermalcomfort = "==2.9.1" dash-bootstrap-components = "==1.2.0" dash-extensions = "==1.0.7" -dash-mantine-components = "*" -requests = "*" -plotly = "*" -pandas = "*" -numpy = "*" +dash-mantine-components = "==0.12.1" +requests = "==2.32.4" +plotly = "==5.18.0" +pandas = "==2.2.0" +numpy = "==1.26.3" dash-iconify = "*" +scipy = "==1.12.0" [dev-packages] cleanpy = "*" pytest = "*" bump2version = "*" -pandas = "*" -requests = "*" [requires] python_version = "3.11" diff --git a/Pipfile.lock b/Pipfile.lock index 2a33deb2..aeebe902 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d3b477f30d912b7e556bfc1a5892f75a59c464ea0ee66413d3e950573fe90b96" + "sha256": "2aad826e7b3b0432aabffe731eab3c1884727f637e6a82056478828dace471c1" }, "pipfile-spec": 6, "requires": { @@ -16,14 +16,6 @@ ] }, "default": { - "ansi2html": { - "hashes": [ - "sha256:3453bf87535d37b827b05245faaa756dbab4ec3d69925e352b6319c3c955c0a5", - "sha256:dccb75aa95fb018e5d299be2b45f802952377abfdce0504c17a6ee6ef0a420c5" - ], - "markers": "python_version >= '3.7'", - "version": "==1.9.2" - }, "blinker": { "hashes": [ "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", @@ -156,12 +148,12 @@ }, "dash": { "hashes": [ - "sha256:602e7b0047cc9c48a81244377b812e79198678ef759a0039dadfe81023e20e96", - "sha256:8e1a280f1c7be0714825f04786beab41d40752095e116204e64b13ac978b654d" + "sha256:d38891337fc855d5673f75e5346354daa063c4ff45a8a6a21f25e858fcae41c2", + "sha256:df1882bbf613e4ca4372281c8facbeb68e97d76720336b051bf84c75d2de8588" ], "index": "pypi", "markers": "python_version >= '3.6'", - "version": "==2.14.2" + "version": "==2.15.0" }, "dash-bootstrap-components": { "hashes": [ @@ -205,11 +197,11 @@ }, "dash-mantine-components": { "hashes": [ - "sha256:3a0a18c26e7373f2c465631a31688d4d25f2584da9770b650bb75da11898934a", - "sha256:dc75fe485c9b5e2ae9c939f8c09611778b3aad50318e63e1bd1eb3abe2a33711" + "sha256:2630bca31cb96d96fb2c4f986e639b9f92d6319aba8cba02f76da6c0d8f5ca48", + "sha256:c3dcbfd89813a1539654b8d016eb953dc5f67aafe1a77d45b5ec9faa6f25d3e7" ], "index": "pypi", - "version": "==2.1.0" + "version": "==0.12.1" }, "dash-table": { "hashes": [ @@ -422,14 +414,6 @@ "markers": "python_version >= '3.7'", "version": "==9.1.0" }, - "narwhals": { - "hashes": [ - "sha256:235e61ca807bc21110ca36a4d53888ecc22c42dcdf50a7c886e10dde3fd7f38c", - "sha256:837457e36a2ba1710c881fb69e1f79ce44fb81728c92ac378f70892a53af8ddb" - ], - "markers": "python_version >= '3.9'", - "version": "==2.0.1" - }, "nest-asyncio": { "hashes": [ "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", @@ -467,65 +451,46 @@ }, "numpy": { "hashes": [ - "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", - "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", - "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", - "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", - "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", - "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", - "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", - "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", - "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", - "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", - "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", - "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", - "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", - "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", - "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", - "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", - "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", - "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", - "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", - "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", - "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", - "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", - "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", - "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", - "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", - "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", - "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", - "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", - "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", - "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", - "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", - "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", - "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", - "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", - "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", - "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", - "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", - "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", - "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", - "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", - "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", - "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", - "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", - "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", - "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", - "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", - "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", - "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", - "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", - "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", - "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", - "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", - "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", - "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", - "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8" + "sha256:02f98011ba4ab17f46f80f7f8f1c291ee7d855fcef0a5a98db80767a468c85cd", + "sha256:0b7e807d6888da0db6e7e75838444d62495e2b588b99e90dd80c3459594e857b", + "sha256:12c70ac274b32bc00c7f61b515126c9205323703abb99cd41836e8125ea0043e", + "sha256:1666f634cb3c80ccbd77ec97bc17337718f56d6658acf5d3b906ca03e90ce87f", + "sha256:18c3319a7d39b2c6a9e3bb75aab2304ab79a811ac0168a671a62e6346c29b03f", + "sha256:211ddd1e94817ed2d175b60b6374120244a4dd2287f4ece45d49228b4d529178", + "sha256:21a9484e75ad018974a2fdaa216524d64ed4212e418e0a551a2d83403b0531d3", + "sha256:39763aee6dfdd4878032361b30b2b12593fb445ddb66bbac802e2113eb8a6ac4", + "sha256:3c67423b3703f8fbd90f5adaa37f85b5794d3366948efe9a5190a5f3a83fc34e", + "sha256:46f47ee566d98849323f01b349d58f2557f02167ee301e5e28809a8c0e27a2d0", + "sha256:51c7f1b344f302067b02e0f5b5d2daa9ed4a721cf49f070280ac202738ea7f00", + "sha256:5f24750ef94d56ce6e33e4019a8a4d68cfdb1ef661a52cdaee628a56d2437419", + "sha256:697df43e2b6310ecc9d95f05d5ef20eacc09c7c4ecc9da3f235d39e71b7da1e4", + "sha256:6d45b3ec2faed4baca41c76617fcdcfa4f684ff7a151ce6fc78ad3b6e85af0a6", + "sha256:77810ef29e0fb1d289d225cabb9ee6cf4d11978a00bb99f7f8ec2132a84e0166", + "sha256:7ca4f24341df071877849eb2034948459ce3a07915c2734f1abb4018d9c49d7b", + "sha256:7f784e13e598e9594750b2ef6729bcd5a47f6cfe4a12cca13def35e06d8163e3", + "sha256:806dd64230dbbfaca8a27faa64e2f414bf1c6622ab78cc4264f7f5f028fee3bf", + "sha256:867e3644e208c8922a3be26fc6bbf112a035f50f0a86497f98f228c50c607bb2", + "sha256:8c66d6fec467e8c0f975818c1796d25c53521124b7cfb760114be0abad53a0a2", + "sha256:8ed07a90f5450d99dad60d3799f9c03c6566709bd53b497eb9ccad9a55867f36", + "sha256:9bc6d1a7f8cedd519c4b7b1156d98e051b726bf160715b769106661d567b3f03", + "sha256:9e1591f6ae98bcfac2a4bbf9221c0b92ab49762228f38287f6eeb5f3f55905ce", + "sha256:9e87562b91f68dd8b1c39149d0323b42e0082db7ddb8e934ab4c292094d575d6", + "sha256:a7081fd19a6d573e1a05e600c82a1c421011db7935ed0d5c483e9dd96b99cf13", + "sha256:a8474703bffc65ca15853d5fd4d06b18138ae90c17c8d12169968e998e448bb5", + "sha256:af36e0aa45e25c9f57bf684b1175e59ea05d9a7d3e8e87b7ae1a1da246f2767e", + "sha256:b1240f767f69d7c4c8a29adde2310b871153df9b26b5cb2b54a561ac85146485", + "sha256:b4d362e17bcb0011738c2d83e0a65ea8ce627057b2fdda37678f4374a382a137", + "sha256:b831295e5472954104ecb46cd98c08b98b49c69fdb7040483aff799a755a7374", + "sha256:b8c275f0ae90069496068c714387b4a0eba5d531aace269559ff2b43655edd58", + "sha256:bdd2b45bf079d9ad90377048e2747a0c82351989a2165821f0c96831b4a2a54b", + "sha256:cc0743f0302b94f397a4a65a660d4cd24267439eb16493fb3caad2e4389bccbb", + "sha256:da4b0c6c699a0ad73c810736303f7fbae483bcb012e38d7eb06a5e3b432c981b", + "sha256:f25e2811a9c932e43943a2615e65fc487a0b6b49218899e62e426e7f0a57eeda", + "sha256:f73497e8c38295aaa4741bdfa4fda1a5aedda5473074369eca10626835445511" ], "index": "pypi", - "markers": "python_version >= '3.10'", - "version": "==2.2.6" + "markers": "python_version >= '3.9'", + "version": "==1.26.3" }, "packaging": { "hashes": [ @@ -537,61 +502,48 @@ }, "pandas": { "hashes": [ - "sha256:025e92411c16cbe5bb2a4abc99732a6b132f439b8aab23a59fa593eb00704232", - "sha256:09e3b1587f0f3b0913e21e8b32c3119174551deb4a4eba4a89bc7377947977e7", - "sha256:0a95b9ac964fe83ce317827f80304d37388ea77616b1425f0ae41c9d2d0d7bb2", - "sha256:0f951fbb702dacd390561e0ea45cdd8ecfa7fb56935eb3dd78e306c19104b9b0", - "sha256:1b916a627919a247d865aed068eb65eb91a344b13f5b57ab9f610b7716c92de1", - "sha256:1c78cf43c8fde236342a1cb2c34bcff89564a7bfed7e474ed2fffa6aed03a956", - "sha256:1d12f618d80379fde6af007f65f0c25bd3e40251dbd1636480dfffce2cf1e6da", - "sha256:22c2e866f7209ebc3a8f08d75766566aae02bcc91d196935a1d9e59c7b990ac9", - "sha256:2323294c73ed50f612f67e2bf3ae45aea04dce5690778e08a09391897f35ff88", - "sha256:2b0540963d83431f5ce8870ea02a7430adca100cec8a050f0811f8e31035541b", - "sha256:2ba6aff74075311fc88504b1db890187a3cd0f887a5b10f5525f8e2ef55bfdb9", - "sha256:2eb789ae0274672acbd3c575b0598d213345660120a257b47b5dafdc618aec83", - "sha256:2f4d6feeba91744872a600e6edbbd5b033005b431d5ae8379abee5bcfa479fab", - "sha256:342e59589cc454aaff7484d75b816a433350b3d7964d7847327edda4d532a2e3", - "sha256:3462c3735fe19f2638f2c3a40bd94ec2dc5ba13abbb032dd2fa1f540a075509d", - "sha256:3583d348546201aff730c8c47e49bc159833f971c2899d6097bce68b9112a4f1", - "sha256:4645f770f98d656f11c69e81aeb21c6fca076a44bed3dcbb9396a4311bc7f6d8", - "sha256:4d544806b485ddf29e52d75b1f559142514e60ef58a832f74fb38e48d757b299", - "sha256:56a342b231e8862c96bdb6ab97170e203ce511f4d0429589c8ede1ee8ece48b8", - "sha256:5db9637dbc24b631ff3707269ae4559bce4b7fd75c1c4d7e13f40edc42df4444", - "sha256:689968e841136f9e542020698ee1c4fbe9caa2ed2213ae2388dc7b81721510d3", - "sha256:6de8547d4fdb12421e2d047a2c446c623ff4c11f47fddb6b9169eb98ffba485a", - "sha256:6f3bf5ec947526106399a9e1d26d40ee2b259c66422efdf4de63c848492d91bb", - "sha256:782647ddc63c83133b2506912cc6b108140a38a37292102aaa19c81c83db2928", - "sha256:7dcb79bf373a47d2a40cf7232928eb7540155abbc460925c2c96d2d30b006eb4", - "sha256:8dfc17328e8da77be3cf9f47509e5637ba8f137148ed0e9b5241e1baf526e20a", - "sha256:9026bd4a80108fac2239294a15ef9003c4ee191a0f64b90f170b40cfb7cf2d22", - "sha256:911580460fc4884d9b05254b38a6bfadddfcc6aaef856fb5859e7ca202e45275", - "sha256:98bcc8b5bf7afed22cc753a28bc4d9e26e078e777066bc53fac7904ddef9a678", - "sha256:9b7ff55f31c4fcb3e316e8f7fa194566b286d6ac430afec0d461163312c5841e", - "sha256:ac942bfd0aca577bef61f2bc8da8147c4ef6879965ef883d8e8d5d2dc3e744b8", - "sha256:b3cd4273d3cb3707b6fffd217204c52ed92859533e31dc03b7c5008aa933aaab", - "sha256:b4b0de34dc8499c2db34000ef8baad684cfa4cbd836ecee05f323ebfba348c7d", - "sha256:ca7ed14832bce68baef331f4d7f294411bed8efd032f8109d690df45e00c4679", - "sha256:cd05b72ec02ebfb993569b4931b2e16fbb4d6ad6ce80224a3ee838387d83a191", - "sha256:dd71c47a911da120d72ef173aeac0bf5241423f9bfea57320110a978457e069e", - "sha256:e5635178b387bd2ba4ac040f82bc2ef6e6b500483975c4ebacd34bec945fda12", - "sha256:e6723a27ad7b244c0c79d8e7007092d7c8f0f11305770e2f4cd778b3ad5f9f85", - "sha256:ec6c851509364c59a5344458ab935e6451b31b818be467eb24b0fe89bd05b6b9", - "sha256:fe37e757f462d31a9cd7580236a82f353f5713a80e059a29753cf938c6775d96", - "sha256:fe67dc676818c186d5a3d5425250e40f179c2a89145df477dd82945eaea89e97", - "sha256:fe7317f578c6a153912bd2292f02e40c1d8f253e93c599e82620c7f69755c74f" + "sha256:159205c99d7a5ce89ecfc37cb08ed179de7783737cea403b295b5eda8e9c56d1", + "sha256:20404d2adefe92aed3b38da41d0847a143a09be982a31b85bc7dd565bdba0f4e", + "sha256:2707514a7bec41a4ab81f2ccce8b382961a29fbe9492eab1305bb075b2b1ff4f", + "sha256:30b83f7c3eb217fb4d1b494a57a2fda5444f17834f5df2de6b2ffff68dc3c8e2", + "sha256:38e0b4fc3ddceb56ec8a287313bc22abe17ab0eb184069f08fc6a9352a769b18", + "sha256:3de918a754bbf2da2381e8a3dcc45eede8cd7775b047b923f9006d5f876802ae", + "sha256:52826b5f4ed658fa2b729264d63f6732b8b29949c7fd234510d57c61dbeadfcd", + "sha256:57abcaeda83fb80d447f28ab0cc7b32b13978f6f733875ebd1ed14f8fbc0f4ab", + "sha256:5a946f210383c7e6d16312d30b238fd508d80d927014f3b33fb5b15c2f895430", + "sha256:736da9ad4033aeab51d067fc3bd69a0ba36f5a60f66a527b3d72e2030e63280a", + "sha256:761cb99b42a69005dec2b08854fb1d4888fdf7b05db23a8c5a099e4b886a2106", + "sha256:7ea3ee3f125032bfcade3a4cf85131ed064b4f8dd23e5ce6fa16473e48ebcaf5", + "sha256:8108ee1712bb4fa2c16981fba7e68b3f6ea330277f5ca34fa8d557e986a11670", + "sha256:85793cbdc2d5bc32620dc8ffa715423f0c680dacacf55056ba13454a5be5de88", + "sha256:8ce2fbc8d9bf303ce54a476116165220a1fedf15985b09656b4b4275300e920b", + "sha256:9f66419d4a41132eb7e9a73dcec9486cf5019f52d90dd35547af11bc58f8637d", + "sha256:a146b9dcacc3123aa2b399df1a284de5f46287a4ab4fbfc237eac98a92ebcb71", + "sha256:a1b438fa26b208005c997e78672f1aa8138f67002e833312e6230f3e57fa87d5", + "sha256:a20628faaf444da122b2a64b1e5360cde100ee6283ae8effa0d8745153809a2e", + "sha256:a41d06f308a024981dcaa6c41f2f2be46a6b186b902c94c2674e8cb5c42985bc", + "sha256:a626795722d893ed6aacb64d2401d017ddc8a2341b49e0384ab9bf7112bdec30", + "sha256:bde2bc699dbd80d7bc7f9cab1e23a95c4375de615860ca089f34e7c64f4a8de7", + "sha256:cfd6c2491dc821b10c716ad6776e7ab311f7df5d16038d0b7458bc0b67dc10f3", + "sha256:e60f1f7dba3c2d5ca159e18c46a34e7ca7247a73b5dd1a22b6d59707ed6b899a", + "sha256:eb1e1f3861ea9132b32f2133788f3b14911b68102d562715d71bd0013bc45440", + "sha256:eb61dc8567b798b969bcc1fc964788f5a68214d333cade8319c7ab33e2b5d88a", + "sha256:f5be5d03ea2073627e7111f61b9f1f0d9625dc3c4d8dda72cc827b0c58a1d042", + "sha256:f9670b3ac00a387620489dfc1bca66db47a787f4e55911f1293063a78b108df1", + "sha256:fbc1b53c0e1fdf16388c33c3cca160f798d38aea2978004dd3f4d3dec56454c9" ], "index": "pypi", "markers": "python_version >= '3.9'", - "version": "==2.3.1" + "version": "==2.2.0" }, "plotly": { "hashes": [ - "sha256:32c444d4c940887219cb80738317040363deefdfee4f354498cc0b6dab8978bd", - "sha256:9dfa23c328000f16c928beb68927444c1ab9eae837d1fe648dbcda5360c7953d" + "sha256:23aa8ea2f4fb364a20d34ad38235524bd9d691bf5299e800bca608c31e8db8de", + "sha256:360a31e6fbb49d12b007036eb6929521343d6bee2236f8459915821baefa2cbb" ], "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==6.2.0" + "markers": "python_version >= '3.6'", + "version": "==5.18.0" }, "pvlib": { "hashes": [ @@ -604,19 +556,19 @@ }, "pythermalcomfort": { "hashes": [ - "sha256:5f319fe0e699daea752c789efad3cf2c865a8b24b26fb25ef315462bc1a26dab", - "sha256:78c35c2791ddc01da8355cb3048dee702ee7f7bd645b4fef7a37185d5778900d" + "sha256:607995f6920a03911c7b9fddd06d819db1fa6e658d742b4ec8395a2c90707da5", + "sha256:9b221ba003e8ea17267d496dc082ce89c0b00ed8de0fc349babea0e76fc53358" ], "index": "pypi", - "markers": "python_full_version >= '3.10.0'", - "version": "==3.4.4" + "markers": "python_full_version >= '3.9.0'", + "version": "==2.9.1" }, "python-dateutil": { "hashes": [ "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.9.0.post0" }, "pytz": { @@ -645,64 +597,35 @@ }, "scipy": { "hashes": [ - "sha256:0851f6a1e537fe9399f35986897e395a1aa61c574b178c0d456be5b1a0f5ca1f", - "sha256:0a55ffe0ba0f59666e90951971a884d1ff6f4ec3275a48f472cfb64175570f77", - "sha256:15240c3aac087a522b4eaedb09f0ad061753c5eebf1ea430859e5bf8640d5919", - "sha256:18aca1646a29ee9a0625a1be5637fa798d4d81fdf426481f06d69af828f16958", - "sha256:21a611ced9275cb861bacadbada0b8c0623bc00b05b09eb97f23b370fc2ae56d", - "sha256:226652fca853008119c03a8ce71ffe1b3f6d2844cc1686e8f9806edafae68596", - "sha256:2ef500e72f9623a6735769e4b93e9dcb158d40752cdbb077f305487e3e2d1f45", - "sha256:30cc4bb81c41831ecfd6dc450baf48ffd80ef5aed0f5cf3ea775740e80f16ecc", - "sha256:367d567ee9fc1e9e2047d31f39d9d6a7a04e0710c86e701e053f237d14a9b4f6", - "sha256:3d0b80fb26d3e13a794c71d4b837e2a589d839fd574a6bbb4ee1288c213ad4a3", - "sha256:3ddfb1e8d0b540cb4ee9c53fc3dea3186f97711248fb94b4142a1b27178d8b4b", - "sha256:3ea0733a2ff73fd6fdc5fecca54ee9b459f4d74f00b99aced7d9a3adb43fb1cc", - "sha256:44c76f9e8b6e8e488a586190ab38016e4ed2f8a038af7cd3defa903c0a2238b3", - "sha256:4cf5785e44e19dcd32a0e4807555e1e9a9b8d475c6afff3d21c3c543a6aa84f4", - "sha256:4dc0e7be79e95d8ba3435d193e0d8ce372f47f774cffd882f88ea4e1e1ddc731", - "sha256:5451606823a5e73dfa621a89948096c6528e2896e40b39248295d3a0138d594f", - "sha256:57d75524cb1c5a374958a2eae3d84e1929bb971204cc9d52213fb8589183fc19", - "sha256:5aa2687b9935da3ed89c5dbed5234576589dd28d0bf7cd237501ccfbdf1ad608", - "sha256:5e1a106f8c023d57a2a903e771228bf5c5b27b5d692088f457acacd3b54511e4", - "sha256:65f81a25805f3659b48126b5053d9e823d3215e4a63730b5e1671852a1705921", - "sha256:6c62eea7f607f122069b9bad3f99489ddca1a5173bef8a0c75555d7488b6f725", - "sha256:6e5c2f74e5df33479b5cd4e97a9104c511518fbd979aa9b8f6aec18b2e9ecae7", - "sha256:709559a1db68a9abc3b2c8672c4badf1614f3b440b3ab326d86a5c0491eafae3", - "sha256:744d977daa4becb9fc59135e75c069f8d301a87d64f88f1e602a9ecf51e77b27", - "sha256:796a5a9ad36fa3a782375db8f4241ab02a091308eb079746bc0f874c9b998318", - "sha256:81929ed0fa7a5713fcdd8b2e6f73697d3b4c4816d090dd34ff937c20fa90e8ab", - "sha256:81b433bbeaf35728dad619afc002db9b189e45eebe2cd676effe1fb93fef2b9c", - "sha256:8503517c44c18d1030d666cb70aaac1cc8913608816e06742498833b128488b7", - "sha256:85764fb15a2ad994e708258bb4ed8290d1305c62a4e1ef07c414356a24fcfbf8", - "sha256:886cc81fdb4c6903a3bb0464047c25a6d1016fef77bb97949817d0c0d79f9e04", - "sha256:89728678c5ca5abd610aee148c199ac1afb16e19844401ca97d43dc548a354eb", - "sha256:8dfbb25dffc4c3dd9371d8ab456ca81beeaf6f9e1c2119f179392f0dc1ab7695", - "sha256:978d8311674b05a8f7ff2ea6c6bce5d8b45a0cb09d4c5793e0318f448613ea65", - "sha256:adccd93a2fa937a27aae826d33e3bfa5edf9aa672376a4852d23a7cd67a2e5b7", - "sha256:bcc12db731858abda693cecdb3bdc9e6d4bd200213f49d224fe22df82687bdd6", - "sha256:c033fa32bab91dc98ca59d0cf23bb876454e2bb02cbe592d5023138778f70030", - "sha256:c0c804d60492a0aad7f5b2bb1862f4548b990049e27e828391ff2bf6f7199998", - "sha256:c24fa02f7ed23ae514460a22c57eca8f530dbfa50b1cfdbf4f37c05b5309cc39", - "sha256:ca66d980469cb623b1759bdd6e9fd97d4e33a9fad5b33771ced24d0cb24df67e", - "sha256:cb18899127278058bcc09e7b9966d41a5a43740b5bb8dcba401bd983f82e885b", - "sha256:cc1d2f2fd48ba1e0620554fe5bc44d3e8f5d4185c8c109c7fbdf5af2792cfad2", - "sha256:d85495cef541729a70cdddbbf3e6b903421bc1af3e8e3a9a72a06751f33b7c39", - "sha256:d8da7c3dd67bcd93f15618938f43ed0995982eb38973023d46d4646c4283ad65", - "sha256:dc54f76ac18073bcecffb98d93f03ed6b81a92ef91b5d3b135dcc81d55a724c7", - "sha256:e756d688cb03fd07de0fffad475649b03cb89bee696c98ce508b17c11a03f95c", - "sha256:e7cc1ffcc230f568549fc56670bcf3df1884c30bd652c5da8138199c8c76dae0", - "sha256:e8fd15fc5085ab4cca74cb91fe0a4263b1f32e4420761ddae531ad60934c2119", - "sha256:f006e323874ffd0b0b816d8c6a8e7f9a73d55ab3b8c3f72b752b226d0e3ac83d", - "sha256:f0ebb7204f063fad87fc0a0e4ff4a2ff40b2a226e4ba1b7e34bf4b79bf97cd86", - "sha256:f1b9e5962656f2734c2b285a8745358ecb4e4efbadd00208c80a389227ec61ff", - "sha256:f23634f9e5adb51b2a77766dac217063e764337fbc816aa8ad9aaebcd4397fd3", - "sha256:f7b8013c6c066609577d910d1a2a077021727af07b6fab0ee22c2f901f22352a", - "sha256:f8a5d6cd147acecc2603fbd382fed6c46f474cccfcf69ea32582e033fb54dcfe", - "sha256:f965bbf3235b01c776115ab18f092a95aa74c271a52577bcb0563e85738fd618", - "sha256:fedc2cbd1baed37474b1924c331b97bdff611d762c196fac1a9b71e67b813b1b" + "sha256:196ebad3a4882081f62a5bf4aeb7326aa34b110e533aab23e4374fcccb0890dc", + "sha256:408c68423f9de16cb9e602528be4ce0d6312b05001f3de61fe9ec8b1263cad08", + "sha256:4bf5abab8a36d20193c698b0f1fc282c1d083c94723902c447e5d2f1780936a3", + "sha256:4c1020cad92772bf44b8e4cdabc1df5d87376cb219742549ef69fc9fd86282dd", + "sha256:5adfad5dbf0163397beb4aca679187d24aec085343755fcdbdeb32b3679f254c", + "sha256:5e32847e08da8d895ce09d108a494d9eb78974cf6de23063f93306a3e419960c", + "sha256:6546dc2c11a9df6926afcbdd8a3edec28566e4e785b915e849348c6dd9f3f490", + "sha256:730badef9b827b368f351eacae2e82da414e13cf8bd5051b4bdfd720271a5371", + "sha256:75ea2a144096b5e39402e2ff53a36fecfd3b960d786b7efd3c180e29c39e53f2", + "sha256:78e4402e140879387187f7f25d91cc592b3501a2e51dfb320f48dfb73565f10b", + "sha256:8b8066bce124ee5531d12a74b617d9ac0ea59245246410e19bca549656d9a40a", + "sha256:8bee4993817e204d761dba10dbab0774ba5a8612e57e81319ea04d84945375ba", + "sha256:913d6e7956c3a671de3b05ccb66b11bc293f56bfdef040583a7221d9e22a2e35", + "sha256:95e5c750d55cf518c398a8240571b0e0782c2d5a703250872f36eaf737751338", + "sha256:9c39f92041f490422924dfdb782527a4abddf4707616e07b021de33467f917bc", + "sha256:a24024d45ce9a675c1fb8494e8e5244efea1c7a09c60beb1eeb80373d0fecc70", + "sha256:a7ebda398f86e56178c2fa94cad15bf457a218a54a35c2a7b4490b9f9cb2676c", + "sha256:b360f1b6b2f742781299514e99ff560d1fe9bd1bff2712894b52abe528d1fd1e", + "sha256:bba1b0c7256ad75401c73e4b3cf09d1f176e9bd4248f0d3112170fb2ec4db067", + "sha256:c3003652496f6e7c387b1cf63f4bb720951cfa18907e998ea551e6de51a04467", + "sha256:e53958531a7c695ff66c2e7bb7b79560ffdc562e2051644c5576c39ff8efb563", + "sha256:e646d8571804a304e1da01040d21577685ce8e2db08ac58e543eaca063453e1c", + "sha256:e7e76cc48638228212c747ada851ef355c2bb5e7f939e10952bc504c11f4e372", + "sha256:f5f00ebaf8de24d14b8449981a2842d404152774c1a1d880c901bf454cb8e2a1", + "sha256:f7ce148dffcd64ade37b2df9315541f9adad6efcaa86866ee7dd5db0c8f041c3" ], - "markers": "python_version >= '3.11'", - "version": "==1.16.1" + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==1.12.0" }, "setuptools": { "hashes": [ @@ -717,9 +640,17 @@ "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.17.0" }, + "tenacity": { + "hashes": [ + "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", + "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138" + ], + "markers": "python_version >= '3.9'", + "version": "==9.1.2" + }, "typing-extensions": { "hashes": [ "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", @@ -771,112 +702,6 @@ "markers": "python_version >= '3.5'", "version": "==1.0.1" }, - "certifi": { - "hashes": [ - "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", - "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995" - ], - "markers": "python_version >= '3.7'", - "version": "==2025.7.14" - }, - "charset-normalizer": { - "hashes": [ - "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4", - "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45", - "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", - "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", - "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", - "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", - "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d", - "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", - "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184", - "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", - "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b", - "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64", - "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", - "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", - "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", - "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344", - "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58", - "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", - "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471", - "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", - "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", - "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836", - "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", - "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", - "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", - "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1", - "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01", - "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", - "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58", - "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", - "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", - "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2", - "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a", - "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597", - "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", - "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5", - "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb", - "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f", - "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", - "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", - "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", - "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", - "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7", - "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7", - "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455", - "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", - "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4", - "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", - "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3", - "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", - "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", - "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", - "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", - "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", - "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", - "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", - "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12", - "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa", - "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", - "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", - "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f", - "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", - "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", - "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5", - "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02", - "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", - "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", - "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e", - "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", - "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", - "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", - "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", - "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681", - "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba", - "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", - "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a", - "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", - "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", - "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", - "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", - "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027", - "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7", - "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518", - "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", - "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", - "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", - "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", - "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da", - "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", - "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f", - "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", - "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f" - ], - "markers": "python_version >= '3.7'", - "version": "==3.4.2" - }, "cleanpy": { "hashes": [ "sha256:9ddfa7ce80dd888b597a8b0bfeea3b69567839b6f41b775a4f76f46914d5170e", @@ -886,14 +711,6 @@ "markers": "python_version >= '3.9'", "version": "==0.5.1" }, - "idna": { - "hashes": [ - "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", - "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" - ], - "markers": "python_version >= '3.6'", - "version": "==3.10" - }, "iniconfig": { "hashes": [ "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", @@ -902,68 +719,6 @@ "markers": "python_version >= '3.8'", "version": "==2.1.0" }, - "numpy": { - "hashes": [ - "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", - "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", - "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", - "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", - "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", - "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", - "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", - "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", - "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", - "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", - "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", - "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", - "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", - "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", - "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", - "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", - "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", - "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", - "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", - "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", - "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", - "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", - "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", - "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", - "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", - "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", - "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", - "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", - "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", - "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", - "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", - "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", - "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", - "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", - "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", - "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", - "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", - "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", - "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", - "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", - "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", - "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", - "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", - "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", - "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", - "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", - "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", - "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", - "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", - "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", - "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", - "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", - "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", - "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", - "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8" - ], - "index": "pypi", - "markers": "python_version >= '3.10'", - "version": "==2.2.6" - }, "packaging": { "hashes": [ "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", @@ -972,55 +727,6 @@ "markers": "python_version >= '3.8'", "version": "==25.0" }, - "pandas": { - "hashes": [ - "sha256:025e92411c16cbe5bb2a4abc99732a6b132f439b8aab23a59fa593eb00704232", - "sha256:09e3b1587f0f3b0913e21e8b32c3119174551deb4a4eba4a89bc7377947977e7", - "sha256:0a95b9ac964fe83ce317827f80304d37388ea77616b1425f0ae41c9d2d0d7bb2", - "sha256:0f951fbb702dacd390561e0ea45cdd8ecfa7fb56935eb3dd78e306c19104b9b0", - "sha256:1b916a627919a247d865aed068eb65eb91a344b13f5b57ab9f610b7716c92de1", - "sha256:1c78cf43c8fde236342a1cb2c34bcff89564a7bfed7e474ed2fffa6aed03a956", - "sha256:1d12f618d80379fde6af007f65f0c25bd3e40251dbd1636480dfffce2cf1e6da", - "sha256:22c2e866f7209ebc3a8f08d75766566aae02bcc91d196935a1d9e59c7b990ac9", - "sha256:2323294c73ed50f612f67e2bf3ae45aea04dce5690778e08a09391897f35ff88", - "sha256:2b0540963d83431f5ce8870ea02a7430adca100cec8a050f0811f8e31035541b", - "sha256:2ba6aff74075311fc88504b1db890187a3cd0f887a5b10f5525f8e2ef55bfdb9", - "sha256:2eb789ae0274672acbd3c575b0598d213345660120a257b47b5dafdc618aec83", - "sha256:2f4d6feeba91744872a600e6edbbd5b033005b431d5ae8379abee5bcfa479fab", - "sha256:342e59589cc454aaff7484d75b816a433350b3d7964d7847327edda4d532a2e3", - "sha256:3462c3735fe19f2638f2c3a40bd94ec2dc5ba13abbb032dd2fa1f540a075509d", - "sha256:3583d348546201aff730c8c47e49bc159833f971c2899d6097bce68b9112a4f1", - "sha256:4645f770f98d656f11c69e81aeb21c6fca076a44bed3dcbb9396a4311bc7f6d8", - "sha256:4d544806b485ddf29e52d75b1f559142514e60ef58a832f74fb38e48d757b299", - "sha256:56a342b231e8862c96bdb6ab97170e203ce511f4d0429589c8ede1ee8ece48b8", - "sha256:5db9637dbc24b631ff3707269ae4559bce4b7fd75c1c4d7e13f40edc42df4444", - "sha256:689968e841136f9e542020698ee1c4fbe9caa2ed2213ae2388dc7b81721510d3", - "sha256:6de8547d4fdb12421e2d047a2c446c623ff4c11f47fddb6b9169eb98ffba485a", - "sha256:6f3bf5ec947526106399a9e1d26d40ee2b259c66422efdf4de63c848492d91bb", - "sha256:782647ddc63c83133b2506912cc6b108140a38a37292102aaa19c81c83db2928", - "sha256:7dcb79bf373a47d2a40cf7232928eb7540155abbc460925c2c96d2d30b006eb4", - "sha256:8dfc17328e8da77be3cf9f47509e5637ba8f137148ed0e9b5241e1baf526e20a", - "sha256:9026bd4a80108fac2239294a15ef9003c4ee191a0f64b90f170b40cfb7cf2d22", - "sha256:911580460fc4884d9b05254b38a6bfadddfcc6aaef856fb5859e7ca202e45275", - "sha256:98bcc8b5bf7afed22cc753a28bc4d9e26e078e777066bc53fac7904ddef9a678", - "sha256:9b7ff55f31c4fcb3e316e8f7fa194566b286d6ac430afec0d461163312c5841e", - "sha256:ac942bfd0aca577bef61f2bc8da8147c4ef6879965ef883d8e8d5d2dc3e744b8", - "sha256:b3cd4273d3cb3707b6fffd217204c52ed92859533e31dc03b7c5008aa933aaab", - "sha256:b4b0de34dc8499c2db34000ef8baad684cfa4cbd836ecee05f323ebfba348c7d", - "sha256:ca7ed14832bce68baef331f4d7f294411bed8efd032f8109d690df45e00c4679", - "sha256:cd05b72ec02ebfb993569b4931b2e16fbb4d6ad6ce80224a3ee838387d83a191", - "sha256:dd71c47a911da120d72ef173aeac0bf5241423f9bfea57320110a978457e069e", - "sha256:e5635178b387bd2ba4ac040f82bc2ef6e6b500483975c4ebacd34bec945fda12", - "sha256:e6723a27ad7b244c0c79d8e7007092d7c8f0f11305770e2f4cd778b3ad5f9f85", - "sha256:ec6c851509364c59a5344458ab935e6451b31b818be467eb24b0fe89bd05b6b9", - "sha256:fe37e757f462d31a9cd7580236a82f353f5713a80e059a29753cf938c6775d96", - "sha256:fe67dc676818c186d5a3d5425250e40f179c2a89145df477dd82945eaea89e97", - "sha256:fe7317f578c6a153912bd2292f02e40c1d8f253e93c599e82620c7f69755c74f" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==2.3.1" - }, "pluggy": { "hashes": [ "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", @@ -1045,54 +751,6 @@ "index": "pypi", "markers": "python_version >= '3.9'", "version": "==8.4.1" - }, - "python-dateutil": { - "hashes": [ - "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", - "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", - "version": "==2.9.0.post0" - }, - "pytz": { - "hashes": [ - "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", - "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00" - ], - "version": "==2025.2" - }, - "requests": { - "hashes": [ - "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", - "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==2.32.4" - }, - "six": { - "hashes": [ - "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", - "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", - "version": "==1.17.0" - }, - "tzdata": { - "hashes": [ - "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", - "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9" - ], - "markers": "python_version >= '2'", - "version": "==2025.2" - }, - "urllib3": { - "hashes": [ - "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", - "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc" - ], - "markers": "python_version >= '3.9'", - "version": "==2.5.0" } } } diff --git a/docs/contributing/run-project-locally.md b/docs/contributing/run-project-locally.md index b6222b7b..73ccae08 100644 --- a/docs/contributing/run-project-locally.md +++ b/docs/contributing/run-project-locally.md @@ -27,7 +27,7 @@ This guide is for Mac OSX, Linux, or Windows. 2. **Create a virtual environment using pipenv and install dependencies:** ```text - pipenv install --three + pipenv install ``` 3. **Run tool locally** From e9b8ec6ef369b05d8fa38e35f373f49eff518525 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 31 Jul 2025 16:17:40 +1000 Subject: [PATCH 34/91] build: removed Procfile --- Procfile | 1 - Procfile.windows | 1 - 2 files changed, 2 deletions(-) delete mode 100644 Procfile delete mode 100644 Procfile.windows diff --git a/Procfile b/Procfile deleted file mode 100644 index 4808b15c..00000000 --- a/Procfile +++ /dev/null @@ -1 +0,0 @@ -web: gunicorn main:server \ No newline at end of file diff --git a/Procfile.windows b/Procfile.windows deleted file mode 100644 index 19b83ee6..00000000 --- a/Procfile.windows +++ /dev/null @@ -1 +0,0 @@ -web: python main.py runserver 0.0.0.0:5000 \ No newline at end of file From 2ced355639c5e908944a53170e00f16884db67b1 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 31 Jul 2025 16:17:50 +1000 Subject: [PATCH 35/91] build: updated requirements.txt --- requirements.txt | 61 ++++++++++++++++++++++++------------------------ 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/requirements.txt b/requirements.txt index f2a5c358..5bf11246 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,52 +1,53 @@ -ansi2html==1.9.1 -blinker==1.7.0 +ansi2html==1.9.2 +blinker==1.9.0 bump2version==1.0.1 cachelib==0.9.0 -certifi==2023.11.17 -charset-normalizer==3.3.2 +certifi==2025.7.14 +charset-normalizer==3.4.2 cleanpy==0.4.0 -click==8.1.7 -dash==2.14.2 +click==8.2.1 +dash==2.15.0 dash-bootstrap-components==1.2.0 dash-core-components==2.0.0 dash-extensions==1.0.7 dash-html-components==2.0.0 +dash-iconify==0.1.2 dash-mantine-components==0.12.1 dash-table==5.0.0 -dataclass-wizard==0.22.2 -EditorConfig==0.12.3 +dataclass-wizard==0.22.3 +EditorConfig==0.17.1 Flask==2.3.3 Flask-Caching==2.0.2 -gunicorn==20.1.0 -h5py==3.10.0 -idna==3.6 -importlib-metadata==7.0.1 +h5py==3.14.0 +idna==3.10 +importlib_metadata==8.7.0 iniconfig==2.0.0 -itsdangerous==2.1.2 -Jinja2==3.1.3 -jsbeautifier==1.14.11 -llvmlite==0.41.1 -MarkupSafe==2.1.4 +itsdangerous==2.2.0 +Jinja2==3.1.6 +jsbeautifier==1.15.4 +llvmlite==0.44.0 +MarkupSafe==3.0.2 more-itertools==9.1.0 +narwhals==2.0.1 nest-asyncio==1.6.0 -numba==0.58.1 +numba==0.61.2 numpy==1.26.3 -packaging==23.2 +packaging==25.0 pandas==2.2.0 plotly==5.18.0 pluggy==1.3.0 pvlib==0.9.1 pytest==7.4.4 pythermalcomfort==2.9.1 -python-dateutil==2.8.2 -pytz==2023.3.post1 -requests==2.31.0 -retrying==1.3.4 +python-dateutil==2.9.0.post0 +pytz==2025.2 +requests==2.32.4 +retrying==1.4.1 scipy==1.12.0 -six==1.16.0 -tenacity==8.2.3 -typing_extensions==4.9.0 -tzdata==2023.4 -urllib3==2.1.0 -Werkzeug==3.0.1 -zipp==3.17.0 +six==1.17.0 +tenacity==9.1.2 +typing_extensions==4.14.1 +tzdata==2025.2 +urllib3==2.5.0 +Werkzeug==3.0.6 +zipp==3.23.0 From 34f6fba9e364bb6dd3922fff95fa3855e5cc0b27 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 31 Jul 2025 16:18:08 +1000 Subject: [PATCH 36/91] build: updated .gcloudignore --- .gcloudignore | 10 ++++++---- docs/contributing/run-project-locally.md | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.gcloudignore b/.gcloudignore index f7c97aad..e5bde90c 100644 --- a/.gcloudignore +++ b/.gcloudignore @@ -7,13 +7,15 @@ README.md LICENSE -Procfile -Procfile.windows *.pyo *.pyd __pycache__ .pytest_cache +.gitbook +docs +file_system_backend +tests +.gitbook.yaml assets/data/Region*.kml -file_system_store -test \ No newline at end of file +file_system_store \ No newline at end of file diff --git a/docs/contributing/run-project-locally.md b/docs/contributing/run-project-locally.md index 73ccae08..c1310463 100644 --- a/docs/contributing/run-project-locally.md +++ b/docs/contributing/run-project-locally.md @@ -91,7 +91,7 @@ First make sure you that: * you have updated the Pipfile.lock. ```text -gcloud components update +gcloud components update --quiet gcloud auth list pipenv lock pipenv run pip3 freeze > requirements.txt From cb74a345466bae29612c17659182c6aafb889467 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 31 Jul 2025 16:20:00 +1000 Subject: [PATCH 37/91] lint: reformatted code with Black --- .github/workflows/cypress.yml | 2 +- main.py | 14 +- pages/changelog.py | 5 +- pages/explorer.py | 10 +- pages/lib/global_scheme.py | 4 +- pages/lib/layout.py | 42 ++---- pages/lib/page_urls.py | 21 +-- pages/lib/template_graphs.py | 2 +- pages/natural_ventilation.py | 30 ++-- pages/not_found_404.py | 2 +- pages/outdoor.py | 10 +- pages/psy-chart.py | 8 +- pages/select.py | 26 ++-- pages/summary.py | 262 ++++++++++++++++------------------ pages/sun.py | 19 +-- pages/t_rh.py | 14 +- pages/wind.py | 8 +- tests/python/test_utils.py | 2 +- 18 files changed, 203 insertions(+), 278 deletions(-) diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index f1810e28..3ff8ea76 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -10,7 +10,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: '3.10' + python-version: '3.11' - name: Build Clima run: |- diff --git a/main.py b/main.py index 764c023a..511e4593 100644 --- a/main.py +++ b/main.py @@ -13,22 +13,16 @@ fluid=True, style={"padding": "0"}, children=[ - dcc.Location(id="url", refresh=False), # connected to callback below + dcc.Location(id="url", refresh=False), # connected to callback below banner(), - html.Div( - id="page-content", - children=build_tabs() - ), + html.Div(id="page-content", children=build_tabs()), footer(), ], ) # callback for survey alert (dbc.Toast) -@callback( - Output('alert-auto', 'is_open'), - Input('interval-component', 'n_intervals') -) +@callback(Output("alert-auto", "is_open"), Input("interval-component", "n_intervals")) def display_alert(n): return n == 1 @@ -40,4 +34,4 @@ def display_alert(n): port=8080, processes=1, threaded=True, - ) \ No newline at end of file + ) diff --git a/pages/changelog.py b/pages/changelog.py index 39590099..f1e8c04a 100644 --- a/pages/changelog.py +++ b/pages/changelog.py @@ -5,10 +5,7 @@ from pages.lib.page_urls import PageUrls -dash.register_page(__name__, - name='changelog', - path=PageUrls.CHANGELOG.value - ) +dash.register_page(__name__, name="changelog", path=PageUrls.CHANGELOG.value) def layout(): diff --git a/pages/explorer.py b/pages/explorer.py index 41d15855..a8608593 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -44,11 +44,9 @@ ) -dash.register_page(__name__, - name= 'Data Explorer', - path=PageUrls.EXPLORER.value, - order=8 - ) +dash.register_page( + __name__, name="Data Explorer", path=PageUrls.EXPLORER.value, order=8 +) explore_dropdown_names = {} @@ -947,4 +945,4 @@ def update_table( ] return summary_table_tmp_rh_tab( filtered_df[["month", "hour", dd_value, "month_names"]], dd_value, si_ip - ) \ No newline at end of file + ) diff --git a/pages/lib/global_scheme.py b/pages/lib/global_scheme.py index ddc4e884..c2c08468 100644 --- a/pages/lib/global_scheme.py +++ b/pages/lib/global_scheme.py @@ -104,8 +104,8 @@ tight_margins = dict(l=20, r=20, t=33, b=20) # Units Dictionary -degrees_unit = "\u00B0deg" -temperature_unit = "\u00B0C" +degrees_unit = "\u00b0deg" +temperature_unit = "\u00b0C" thermal_stress_label = "Thermal stress" mapping_dictionary = { diff --git a/pages/lib/layout.py b/pages/lib/layout.py index 3921c794..1a6a6d41 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -8,7 +8,6 @@ def alert(): return html.Div( id="alert-container", children=[ - dbc.Toast( [ "If you have a moment, help us improving Clima and take a ", @@ -16,9 +15,9 @@ def alert(): "quick user survey", href="https://forms.gle/k289zP3R92jdu14M7", className="alert-link", - target="_blank" + target="_blank", ), - "! ☀️" + "! ☀️", ], id="alert-auto", header="CBE Clima User Survey", @@ -26,19 +25,9 @@ def alert(): is_open=False, dismissable=True, className="survey-alert", - style={ - "position": "fixed", - "top": 25, - "right": 10, - "width": 400 - }, + style={"position": "fixed", "top": 25, "right": 10, "width": 400}, ), - - dcc.Interval( - id='interval-component', - interval=12*1000, - n_intervals=0 - ) + dcc.Interval(id="interval-component", interval=12 * 1000, n_intervals=0), ], ) @@ -176,7 +165,6 @@ def banner(): ) - def store(): return html.Div( id="store", @@ -215,18 +203,18 @@ def build_tabs(): className="nav-link", disabled=True, ), - className="custom-tab" + className="custom-tab", ) - for page in dash.page_registry.values() if page["name"] not in ["404", "changelog"] + for page in dash.page_registry.values() + if page["name"] not in ["404", "changelog"] ], id="tabs", class_name="tab-container", pills=True, - justified=True + justified=True, ) - ] + ], ), - html.Div( id="store-container", children=[ @@ -234,11 +222,11 @@ def build_tabs(): html.Div( id="tabs-content", children=[ - alert(), # alert can be removed after survey is done - dash.page_container + alert(), # alert can be removed after survey is done + dash.page_container, ], - ) - ] + ), + ], ), - ] - ) \ No newline at end of file + ], + ) diff --git a/pages/lib/page_urls.py b/pages/lib/page_urls.py index 42d593b7..ea0b027f 100644 --- a/pages/lib/page_urls.py +++ b/pages/lib/page_urls.py @@ -1,14 +1,15 @@ from enum import Enum + class PageUrls(Enum): - SELECT: str = '/' - SUMMARY: str = '/summary' - T_RH: str = '/t-rh' - SUN: str = '/sun' - WIND: str = '/wind' - PSY_CHART: str = '/psy-chart' - NATURAL_VENTILATION: str = '/natural-ventilation' - OUTDOOR: str = '/outdoor' - EXPLORER: str = '/explorer' - CHANGELOG: str = '/changelog' + SELECT: str = "/" + SUMMARY: str = "/summary" + T_RH: str = "/t-rh" + SUN: str = "/sun" + WIND: str = "/wind" + PSY_CHART: str = "/psy-chart" + NATURAL_VENTILATION: str = "/natural-ventilation" + OUTDOOR: str = "/outdoor" + EXPLORER: str = "/explorer" + CHANGELOG: str = "/changelog" NOT_FOUND: str = '"../assets/animations/page_not_found.json"' diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index 9a02b024..7c2be5b9 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -527,7 +527,7 @@ def wind_rose(df, title, month, hour, labels, si_ip): hovertemplate="frequency: %{r:.2f}%" + "
" + "direction: %{theta:.2f}" - + "\u00B0 deg" + + "\u00b0 deg" + "
", ) ) diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 66c701b0..5e83583f 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -29,27 +29,25 @@ ) -dash.register_page(__name__, - name= 'Natural Ventilation', - path=PageUrls.NATURAL_VENTILATION.value, - order=6 - ) +dash.register_page( + __name__, + name="Natural Ventilation", + path=PageUrls.NATURAL_VENTILATION.value, + order=6, +) def layout(): return html.Div( className="container-col", - id='main-nv-section', + id="main-nv-section", children=[ - # - ] + # + ], ) -@callback( - Output('main-nv-section', 'children'), - [Input('si-ip-radio-input', 'value')] -) +@callback(Output("main-nv-section", "children"), [Input("si-ip-radio-input", "value")]) def update_layout(si_ip): if si_ip == "ip": tdb_set_min = 50 @@ -59,7 +57,7 @@ def update_layout(si_ip): tdb_set_min = 10 tdb_set_max = 24 dpt_set = 16 - + return [ inputs_tab(tdb_set_min, tdb_set_max, dpt_set), dcc.Loading( @@ -106,8 +104,6 @@ def update_layout(si_ip): ), ] - - def inputs_tab(t_min, t_max, d_set): return html.Div( @@ -606,9 +602,7 @@ def nv_bar_chart( ) -@callback( - Output("nv-dpt-filter", "disabled"), Input("enable-condensation", "value") -) +@callback(Output("nv-dpt-filter", "disabled"), Input("enable-condensation", "value")) def enable_disable_button_data_filter(state_checklist): if len(state_checklist) == 1: return False diff --git a/pages/not_found_404.py b/pages/not_found_404.py index 5da94903..be98d555 100644 --- a/pages/not_found_404.py +++ b/pages/not_found_404.py @@ -6,7 +6,7 @@ from pages.lib.page_urls import PageUrls -dash.register_page(__name__, name= '404') +dash.register_page(__name__, name="404") layout = [ diff --git a/pages/outdoor.py b/pages/outdoor.py index 7684432f..1f95b8fd 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -27,11 +27,9 @@ ) -dash.register_page(__name__, - name= 'Outdoor Comfort', - path=PageUrls.OUTDOOR.value, - order=7 - ) +dash.register_page( + __name__, name="Outdoor Comfort", path=PageUrls.OUTDOOR.value, order=7 +) def inputs_outdoor_comfort(): @@ -419,4 +417,4 @@ def update_tab_utci_summary_chart( return dcc.Graph( config=generate_chart_name("summary", meta, custom_inputs, units), figure=utci_summary_chart, - ) \ No newline at end of file + ) diff --git a/pages/psy-chart.py b/pages/psy-chart.py index cf3bc5f9..de62d567 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -35,11 +35,9 @@ ) -dash.register_page(__name__, - name= 'Psychrometric Chart', - path=PageUrls.PSY_CHART.value, - order=5 - ) +dash.register_page( + __name__, name="Psychrometric Chart", path=PageUrls.PSY_CHART.value, order=5 +) psy_dropdown_names = { diff --git a/pages/select.py b/pages/select.py index c92d3b09..46f543a1 100644 --- a/pages/select.py +++ b/pages/select.py @@ -9,23 +9,14 @@ from pages.lib.page_urls import PageUrls from pages.lib.extract_df import convert_data -from pages.lib.extract_df import ( - create_df, - get_data, - get_location_info -) +from pages.lib.extract_df import create_df, get_data, get_location_info from pages.lib.global_scheme import mapping_dictionary -from pages.lib.utils import ( - plot_location_epw_files, - generate_chart_name -) +from pages.lib.utils import plot_location_epw_files, generate_chart_name -dash.register_page(__name__, - name='Select Weather File', - path=PageUrls.SELECT.value, - order=0 - ) +dash.register_page( + __name__, name="Select Weather File", path=PageUrls.SELECT.value, order=0 +) messages_alert = { @@ -36,6 +27,7 @@ "wrong_extension": "The file you have uploaded is not an EPW file", } + def layout(): """Contents in the first tab 'Select Weather File'""" return html.Div( @@ -96,6 +88,7 @@ def layout(): ], ) + def alert(): """Alert layout for the submit button.""" return html.Div( @@ -231,7 +224,7 @@ def switch_si_ip(ts, si_ip_input, url_store, lines): None, None, ) - + @callback( [ @@ -282,7 +275,6 @@ def enable_tabs_when_data_is_loaded(meta, data): ) - @callback( [ Output("modal", "is_open"), @@ -319,4 +311,4 @@ def display_modal_when_data_clicked(click_map): """change the text of the modal header""" if click_map: return [f"Analyse data from {click_map['points'][0]['hovertext']}?"] - return ["Analyse data from this location?"] \ No newline at end of file + return ["Analyse data from this location?"] diff --git a/pages/summary.py b/pages/summary.py index c2a5ffd4..2e9e6d1c 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -9,11 +9,7 @@ from pages.lib.page_urls import PageUrls from pages.lib.charts_summary import world_map from pages.lib.extract_df import get_data -from pages.lib.global_scheme import ( - template, - tight_margins, - mapping_dictionary -) +from pages.lib.global_scheme import template, tight_margins, mapping_dictionary from pages.lib.template_graphs import violin from pages.lib.utils import ( generate_chart_name, @@ -24,11 +20,9 @@ ) -dash.register_page(__name__, - name='Climate Summary', - path=PageUrls.SUMMARY.value, - order=1 - ) +dash.register_page( + __name__, name="Climate Summary", path=PageUrls.SUMMARY.value, order=1 +) def layout(): @@ -39,14 +33,12 @@ def layout(): id="tab-two-container", children=[ # - ] + ], ) - @callback( - Output('tab-two-container', 'children'), - [Input('si-ip-radio-input', 'value')] + Output("tab-two-container", "children"), [Input("si-ip-radio-input", "value")] ) def update_layout(si_ip): @@ -58,139 +50,137 @@ def update_layout(si_ip): cooling_setpoint = 64 return html.Div( - className="container-col", - id="tab2-sec1-container", - children=[ - dcc.Loading( - type="circle", - children=html.Div( - className="container-col", - id="location-info", - style={"padding": "12px"}, + className="container-col", + id="tab2-sec1-container", + children=[ + dcc.Loading( + type="circle", + children=html.Div( + className="container-col", + id="location-info", + style={"padding": "12px"}, + ), + ), + dcc.Loading( + type="circle", + children=html.Div(className="tab-two-section", id="world-map"), + ), + html.Div( + children=title_with_tooltip( + text="Download", + id_button="download-button-label", + tooltip_text="Use the following buttons to download either the Clima sourcefile or the EPW file", + ), + ), + dcc.Loading( + type="circle", + children=dbc.Row( + [ + dbc.Col( + dbc.Button( + "Download EPW", + color="primary", + id="download-epw-button", + ), + width="auto", ), - ), - dcc.Loading( - type="circle", - children=html.Div(className="tab-two-section", id="world-map"), - ), - html.Div( - children=title_with_tooltip( - text="Download", - id_button="download-button-label", - tooltip_text="Use the following buttons to download either the Clima sourcefile or the EPW file", + dbc.Col( + dbc.Button( + "Download Clima dataframe", + color="primary", + id="download-button", + ), + width="auto", ), - ), - dcc.Loading( - type="circle", - children=dbc.Row( + dbc.Col( [ - dbc.Col( - dbc.Button( - "Download EPW", - color="primary", - id="download-epw-button", - ), - width="auto", - ), - dbc.Col( - dbc.Button( - "Download Clima dataframe", - color="primary", - id="download-button", - ), - width="auto", - ), - dbc.Col( - [ - dcc.Download(id="download-dataframe-csv"), - dcc.Download(id="download-epw"), - ], - width=1, - ), + dcc.Download(id="download-dataframe-csv"), + dcc.Download(id="download-epw"), ], + width=1, ), - ), - html.Div( - children=title_with_link( - text="Heating and Cooling Degree Days", - id_button="hdd-cdd-chart", - doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/tab-summary/degree-days-explained", + ], + ), + ), + html.Div( + children=title_with_link( + text="Heating and Cooling Degree Days", + id_button="hdd-cdd-chart", + doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/tab-summary/degree-days-explained", + ), + ), + dbc.Alert( + "WARNING: Invalid Results! The CDD setpoint should be higher than the HDD setpoint!", + color="warning", + is_open=False, + id="warning-cdd-higher-hdd", + ), + dbc.Row( + [ + dbc.Col( + html.Label( + "Heating degree day (HDD) setpoint", ), + width="auto", ), - dbc.Alert( - "WARNING: Invalid Results! The CDD setpoint should be higher than the HDD setpoint!", - color="warning", - is_open=False, - id="warning-cdd-higher-hdd", - ), - dbc.Row( - [ - dbc.Col( - html.Label( - "Heating degree day (HDD) setpoint", - ), - width="auto", - ), - dbc.Col( - dbc.Input( - id="input-hdd-set-point", - type="number", - value=heating_setpoint, - style={"width": "4rem"}, - ), - width="auto", - ), - dbc.Col( - html.Label( - "Cooling degree day (CDD) setpoint", - ), - width="auto", - ), - dbc.Col( - dbc.Input( - id="input-cdd-set-point", - type="number", - value=cooling_setpoint, - style={"width": "4rem"}, - ), - width="auto", - ), - dbc.Col( - dbc.Button( - id="submit-set-points", - children="Submit", - color="primary", - ), - width="auto", - ), - ], - align="center", - justify="center", + dbc.Col( + dbc.Input( + id="input-hdd-set-point", + type="number", + value=heating_setpoint, + style={"width": "4rem"}, + ), + width="auto", ), - dcc.Loading( - type="circle", - children=html.Div(id="degree-days-chart-wrapper"), + dbc.Col( + html.Label( + "Cooling degree day (CDD) setpoint", + ), + width="auto", ), - html.Div( - children=title_with_link( - text="Climate Profiles", - id_button="climate-profiles-chart", - doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/tab-summary/climate-profiles-explained", + dbc.Col( + dbc.Input( + id="input-cdd-set-point", + type="number", + value=cooling_setpoint, + style={"width": "4rem"}, ), + width="auto", ), - dbc.Row( - id="graph-container", - children=[ - dbc.Col(id="temp-profile-graph", width=12, md=6, lg=3), - dbc.Col(id="humidity-profile-graph", width=12, md=6, lg=3), - dbc.Col(id="solar-radiation-graph", width=12, md=6, lg=3), - dbc.Col(id="wind-speed-graph", width=12, md=6, lg=3), - ], + dbc.Col( + dbc.Button( + id="submit-set-points", + children="Submit", + color="primary", + ), + width="auto", ), ], - ) - - + align="center", + justify="center", + ), + dcc.Loading( + type="circle", + children=html.Div(id="degree-days-chart-wrapper"), + ), + html.Div( + children=title_with_link( + text="Climate Profiles", + id_button="climate-profiles-chart", + doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/tab-summary/climate-profiles-explained", + ), + ), + dbc.Row( + id="graph-container", + children=[ + dbc.Col(id="temp-profile-graph", width=12, md=6, lg=3), + dbc.Col(id="humidity-profile-graph", width=12, md=6, lg=3), + dbc.Col(id="solar-radiation-graph", width=12, md=6, lg=3), + dbc.Col(id="wind-speed-graph", width=12, md=6, lg=3), + ], + ), + ], + ) # @callback( @@ -204,7 +194,6 @@ def update_layout(si_ip): # return 50, 64 - @callback( Output("world-map", "children"), Input("meta-store", "data"), @@ -309,9 +298,6 @@ def update_location_info(ts, df, meta, si_ip): return location_info - - - @callback( [ Output("degree-days-chart-wrapper", "children"), diff --git a/pages/sun.py b/pages/sun.py index d5a80976..ca3e109e 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -33,11 +33,7 @@ ) -dash.register_page(__name__, - name= 'Sun and Clouds', - path=PageUrls.SUN.value, - order=3 - ) +dash.register_page(__name__, name="Sun and Clouds", path=PageUrls.SUN.value, order=3) sc_dropdown_names = { @@ -56,7 +52,6 @@ sc_dropdown_names.pop("UTCI: no Sun & no Wind : categories", None) - def sun_path(): """Return the layout for the custom sun path and its dropdowns.""" return html.Div( @@ -153,10 +148,9 @@ def explore_daily_heatmap(): ) - def static_section(): return html.Div( - id='static-section', + id="static-section", className="container-col full-width", children=[ # ... @@ -173,10 +167,7 @@ def layout(): ) -@callback( - Output('static-section', 'children'), - [Input('si-ip-radio-input', 'value')] -) +@callback(Output("static-section", "children"), [Input("si-ip-radio-input", "value")]) def update_static_section(si_ip): if si_ip == "si": hor_unit = "Wh/m²" @@ -185,9 +176,7 @@ def update_static_section(si_ip): return [ html.Div( children=title_with_link( - text="Global and Diffuse Horizontal Solar Radiation (" - + hor_unit - + ")", + text="Global and Diffuse Horizontal Solar Radiation (" + hor_unit + ")", id_button="monthly-chart-label", doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/sun-and-cloud/global-and-diffuse-horizontal-solar-radiation", ), diff --git a/pages/t_rh.py b/pages/t_rh.py index b8db5f78..b5014a29 100644 --- a/pages/t_rh.py +++ b/pages/t_rh.py @@ -3,11 +3,7 @@ from pages.lib.page_urls import PageUrls from pages.lib.global_scheme import dropdown_names -from pages.lib.template_graphs import ( - heatmap, - yearly_profile, - daily_profile -) +from pages.lib.template_graphs import heatmap, yearly_profile, daily_profile from pages.lib.utils import ( generate_chart_name, generate_units, @@ -19,11 +15,9 @@ ) -dash.register_page(__name__, - name= 'Temperature and Humidity', - path=PageUrls.T_RH.value, - order=2 - ) +dash.register_page( + __name__, name="Temperature and Humidity", path=PageUrls.T_RH.value, order=2 +) var_to_plot = ["Dry bulb temperature", "Relative humidity"] diff --git a/pages/wind.py b/pages/wind.py index 0820576c..0e814e12 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -16,11 +16,7 @@ ) -dash.register_page(__name__, - name= 'Wind', - path=PageUrls.WIND.value, - order=4 - ) +dash.register_page(__name__, name="Wind", path=PageUrls.WIND.value, order=4) def sliders(): @@ -675,4 +671,4 @@ def daily_chart_caption(hour_start, hour_end, count, calm_count): morning_text, noon_text, night_text, - ) \ No newline at end of file + ) diff --git a/tests/python/test_utils.py b/tests/python/test_utils.py index 92672cc4..f7fc08b9 100644 --- a/tests/python/test_utils.py +++ b/tests/python/test_utils.py @@ -1,7 +1,7 @@ import sys import os -root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) +root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) if root_dir not in sys.path: sys.path.append(root_dir) From 97f1c9d7dcb0bb6d77128265380598058b52ad69 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 31 Jul 2025 16:24:07 +1000 Subject: [PATCH 38/91] build: updated artifact version in cypress.yml --- .github/workflows/cypress.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index 3ff8ea76..0a8d7a21 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -37,14 +37,14 @@ jobs: npm run cy:run - name: Archive screenshots - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() with: name: cypress-screenshots path: tests/node/cypress/screenshots - name: Archive videos - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() with: name: cypress-videos From e43b461a41c7708f24b60b345f48589d301695f9 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 31 Jul 2025 17:06:40 +1000 Subject: [PATCH 39/91] build: installed black --- Pipfile | 1 + Pipfile.lock | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 7dee142f..1a26aa56 100644 --- a/Pipfile +++ b/Pipfile @@ -21,6 +21,7 @@ scipy = "==1.12.0" cleanpy = "*" pytest = "*" bump2version = "*" +black = "*" [requires] python_version = "3.11" diff --git a/Pipfile.lock b/Pipfile.lock index aeebe902..297c5816 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "2aad826e7b3b0432aabffe731eab3c1884727f637e6a82056478828dace471c1" + "sha256": "3016238cc740a16affb252e933956b3fd8377e899cb9c8563a69eceaafc31ccd" }, "pipfile-spec": 6, "requires": { @@ -693,6 +693,35 @@ } }, "develop": { + "black": { + "hashes": [ + "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", + "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7", + "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da", + "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", + "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", + "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", + "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", + "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", + "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32", + "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", + "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", + "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", + "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0", + "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", + "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", + "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", + "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355", + "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", + "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e", + "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9", + "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", + "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==25.1.0" + }, "bump2version": { "hashes": [ "sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410", @@ -711,6 +740,14 @@ "markers": "python_version >= '3.9'", "version": "==0.5.1" }, + "click": { + "hashes": [ + "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", + "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b" + ], + "markers": "python_version >= '3.10'", + "version": "==8.2.1" + }, "iniconfig": { "hashes": [ "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", @@ -719,6 +756,14 @@ "markers": "python_version >= '3.8'", "version": "==2.1.0" }, + "mypy-extensions": { + "hashes": [ + "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", + "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558" + ], + "markers": "python_version >= '3.8'", + "version": "==1.1.0" + }, "packaging": { "hashes": [ "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", @@ -727,6 +772,22 @@ "markers": "python_version >= '3.8'", "version": "==25.0" }, + "pathspec": { + "hashes": [ + "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", + "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712" + ], + "markers": "python_version >= '3.8'", + "version": "==0.12.1" + }, + "platformdirs": { + "hashes": [ + "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", + "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4" + ], + "markers": "python_version >= '3.9'", + "version": "==4.3.8" + }, "pluggy": { "hashes": [ "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", From 15ebc962f35b66cb0271abf05851acca08847acb Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 31 Jul 2025 17:06:45 +1000 Subject: [PATCH 40/91] build: installed black --- requirements.txt | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 5bf11246..23c46a7f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,11 @@ ansi2html==1.9.2 +black==25.1.0 blinker==1.9.0 bump2version==1.0.1 cachelib==0.9.0 certifi==2025.7.14 charset-normalizer==3.4.2 -cleanpy==0.4.0 +cleanpy==0.5.1 click==8.2.1 dash==2.15.0 dash-bootstrap-components==1.2.0 @@ -21,23 +22,27 @@ Flask-Caching==2.0.2 h5py==3.14.0 idna==3.10 importlib_metadata==8.7.0 -iniconfig==2.0.0 +iniconfig==2.1.0 itsdangerous==2.2.0 Jinja2==3.1.6 jsbeautifier==1.15.4 llvmlite==0.44.0 MarkupSafe==3.0.2 more-itertools==9.1.0 +mypy_extensions==1.1.0 narwhals==2.0.1 nest-asyncio==1.6.0 numba==0.61.2 numpy==1.26.3 packaging==25.0 pandas==2.2.0 +pathspec==0.12.1 +platformdirs==4.3.8 plotly==5.18.0 -pluggy==1.3.0 +pluggy==1.6.0 pvlib==0.9.1 -pytest==7.4.4 +Pygments==2.19.2 +pytest==8.4.1 pythermalcomfort==2.9.1 python-dateutil==2.9.0.post0 pytz==2025.2 From 4a6b1c8344192d54c8459e5b255583cd67e4008e Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 31 Jul 2025 17:07:09 +1000 Subject: [PATCH 41/91] feat: removed loading around store --- pages/lib/layout.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/pages/lib/layout.py b/pages/lib/layout.py index 1a6a6d41..2ee80b19 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -169,17 +169,11 @@ def store(): return html.Div( id="store", children=[ - dcc.Loading( - [ - dcc.Store(id="df-store", storage_type="session"), - dcc.Store(id="meta-store", storage_type="session"), - dcc.Store(id="url-store", storage_type="session"), - dcc.Store(id="si-ip-unit-store", storage_type="session"), - dcc.Store(id="lines-store", storage_type="session"), - ], - fullscreen=True, - type="dot", - ) + dcc.Store(id="df-store", storage_type="session"), + dcc.Store(id="meta-store", storage_type="session"), + dcc.Store(id="url-store", storage_type="session"), + dcc.Store(id="si-ip-unit-store", storage_type="session"), + dcc.Store(id="lines-store", storage_type="session"), ], ) From 8d4ad8b2a1e1649ae6d6e22e82d5edc34a6b8b7c Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 31 Jul 2025 17:09:49 +1000 Subject: [PATCH 42/91] feat: added a skeleton world map --- main.py | 5 ++- pages/lib/utils.py | 65 +++----------------------------------- pages/select.py | 79 +++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 78 insertions(+), 71 deletions(-) diff --git a/main.py b/main.py index 511e4593..5420b9f8 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,6 @@ -import dash import dash_bootstrap_components as dbc -from dash import html, dcc, no_update -from dash_extensions.enrich import Output, Input, State, callback +from dash import html, dcc +from dash_extensions.enrich import Output, Input, callback from app import app from pages.lib.layout import banner, footer, build_tabs diff --git a/pages/lib/utils.py b/pages/lib/utils.py index 3547cc7e..fd2884b3 100644 --- a/pages/lib/utils.py +++ b/pages/lib/utils.py @@ -1,14 +1,12 @@ +import copy import functools import time -from pages.lib.global_scheme import fig_config, mapping_dictionary + +import dash_bootstrap_components as dbc import pandas as pd -import json -from pandas import json_normalize from dash import html, dash_table, dcc -import dash_bootstrap_components as dbc -import plotly.express as px -import copy -import requests + +from pages.lib.global_scheme import fig_config, mapping_dictionary def code_timer(func): @@ -193,59 +191,6 @@ def generate_custom_inputs_psy( return custom_inputs -def plot_location_epw_files(): - with open("./assets/data/epw_location.json", encoding="utf8") as data_file: - data = json.load(data_file) - - df = json_normalize(data["features"]) - df[["lon", "lat"]] = pd.DataFrame(df["geometry.coordinates"].tolist()) - df["lat"] += 0.005 - df["lat"] += 0.005 - df = df.rename(columns={"properties.epw": "Source"}) - - fig = px.scatter_mapbox( - df.head(2585), - lat="lat", - lon="lon", - hover_name="properties.title", - color_discrete_sequence=["#3a0ca3"], - hover_data=["Source"], - zoom=2, - height=500, - ) - try: - print(requests.get("https://climate.onebuilding.org", timeout=2)) - - df_one_building = pd.read_csv( - "./assets/data/one_building.csv", compression="gzip" - ) - - fig2 = px.scatter_mapbox( - df_one_building, - lat="lat", - lon="lon", - hover_name=df_one_building["name"], - color_discrete_sequence=["#4895ef"], - hover_data=[ - "period", - "elevation (m)", - "time zone (GMT)", - "99% Heating DB", - "1% Cooling DB ", - "Source", - ], - zoom=2, - height=500, - ) - fig.add_trace(fig2.data[0]) - except requests.exceptions.ConnectTimeout: - pass - fig.update_layout(mapbox_style="carto-positron") - fig.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0}) - - return fig - - def title_with_tooltip(text, tooltip_text, id_button): display_tooltip = "none" if tooltip_text: diff --git a/pages/select.py b/pages/select.py index 46f543a1..6f142a8f 100644 --- a/pages/select.py +++ b/pages/select.py @@ -4,15 +4,18 @@ import dash import dash_bootstrap_components as dbc +import dash_mantine_components as dmc +import pandas as pd +import plotly.express as px from dash.exceptions import PreventUpdate from dash_extensions.enrich import Serverside, Output, Input, State, html, dcc, callback +from pandas import json_normalize -from pages.lib.page_urls import PageUrls from pages.lib.extract_df import convert_data from pages.lib.extract_df import create_df, get_data, get_location_info from pages.lib.global_scheme import mapping_dictionary -from pages.lib.utils import plot_location_epw_files, generate_chart_name - +from pages.lib.page_urls import PageUrls +from pages.lib.utils import generate_chart_name dash.register_page( __name__, name="Select Weather File", path=PageUrls.SELECT.value, order=0 @@ -56,14 +59,14 @@ def layout(): multiple=True, className="d-grid", ), - dcc.Graph( - id="tab-one-map", - figure=plot_location_epw_files(), - config=generate_chart_name("epw_location_select"), + dmc.Skeleton( + visible=False, + id="skeleton-graph-container", + height=500, + children=html.Div(id="tab-one-map"), ), dbc.Modal( [ - # dbc.ModalHeader("Header"), dbc.ModalHeader(id="modal-header"), dbc.ModalFooter( children=[ @@ -312,3 +315,63 @@ def display_modal_when_data_clicked(click_map): if click_map: return [f"Analyse data from {click_map['points'][0]['hovertext']}?"] return ["Analyse data from this location?"] + + +@callback( + Output("skeleton-graph-container", "children"), + Input("url", "pathname"), +) +def plot_location_epw_files(pathname): + # print(pathname) + if pathname != "/": + raise PreventUpdate + + with open("./assets/data/epw_location.json", encoding="utf8") as data_file: + data = json.load(data_file) + + df = json_normalize(data["features"]) + df[["lon", "lat"]] = pd.DataFrame(df["geometry.coordinates"].tolist()) + df["lat"] += 0.005 + df["lat"] += 0.005 + df = df.rename(columns={"properties.epw": "Source"}) + + fig = px.scatter_mapbox( + df.head(2585), + lat="lat", + lon="lon", + hover_name="properties.title", + color_discrete_sequence=["#3a0ca3"], + hover_data=["Source"], + zoom=2, + height=500, + ) + df_one_building = pd.read_csv("./assets/data/one_building.csv", compression="gzip") + + fig2 = px.scatter_mapbox( + df_one_building, + lat="lat", + lon="lon", + hover_name=df_one_building["name"], + color_discrete_sequence=["#4895ef"], + hover_data=[ + "period", + "elevation (m)", + "time zone (GMT)", + "99% Heating DB", + "1% Cooling DB ", + "Source", + ], + zoom=2, + height=500, + ) + fig.add_trace(fig2.data[0]) + fig.update_layout(mapbox_style="carto-positron") + fig.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0}) + + return ( + dcc.Graph( + id="tab-one-map", + figure=fig, + config=generate_chart_name("epw_location_select"), + ), + ) From 62bd4de9b3db5d9f41fb94f227a7062dc3ce57f3 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 31 Jul 2025 17:19:26 +1000 Subject: [PATCH 43/91] fix: depreciation warning wind rose --- pages/lib/template_graphs.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index 7c2be5b9..e34ca7e0 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -496,26 +496,30 @@ def wind_rose(df, title, month, hour, labels, si_ip): dir_labels = (dir_bins[:-1] + dir_bins[1:]) / 2 total_count = df.shape[0] calm_count = df.query("wind_speed == 0").shape[0] + + # Create a temporary DataFrame with binned data + df_binned = df.assign( + WindSpd_bins=lambda d: pd.cut( + d["wind_speed"], bins=spd_bins, labels=spd_labels, right=True + ), + WindDir_bins=lambda d: pd.cut( + d["wind_dir"], bins=dir_bins, labels=dir_labels, right=False + ), + ) + + # Rename the category in the 'WindDir_bins' column + df_binned["WindDir_bins"] = df_binned["WindDir_bins"].rename({360.0: 0.0}) + rose = ( - df.assign( - WindSpd_bins=lambda df: pd.cut( - df["wind_speed"], bins=spd_bins, labels=spd_labels, right=True - ) - ) - .assign( - WindDir_bins=lambda df: pd.cut( - df["wind_dir"], bins=dir_bins, labels=dir_labels, right=False - ) - ) - .replace({"WindDir_bins": {360: 0}}) - .groupby(by=["WindSpd_bins", "WindDir_bins"], observed=False) + df_binned.groupby(by=["WindSpd_bins", "WindDir_bins"], observed=False) .size() .unstack(level="WindSpd_bins") .fillna(0) - .assign(calm=lambda df: calm_count / df.shape[0]) + .assign(calm=lambda d: calm_count / d.shape[0]) .sort_index(axis=1) .map(lambda x: x / total_count * 100) ) + fig = go.Figure() for i, col in enumerate(rose.columns): fig.add_trace( From 5b1125896c273081660c95254aff2618adc7a98d Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 31 Jul 2025 17:25:13 +1000 Subject: [PATCH 44/91] lint: added ruff --- Pipfile | 1 + Pipfile.lock | 27 +++++++++++++++++++++++++- pages/explorer.py | 2 -- pages/lib/charts_data_explorer.py | 1 - pages/lib/charts_sun.py | 1 - pages/lib/extract_df.py | 2 +- pages/lib/import_one_building_files.py | 6 +++--- pages/lib/template_graphs.py | 2 +- pages/lib/utils.py | 6 +++--- pages/natural_ventilation.py | 1 - pages/outdoor.py | 4 ---- pages/psy-chart.py | 1 - pages/select.py | 2 +- pages/summary.py | 3 +-- pages/sun.py | 15 +++++--------- pages/wind.py | 1 - 16 files changed, 42 insertions(+), 33 deletions(-) diff --git a/Pipfile b/Pipfile index 1a26aa56..b1667a53 100644 --- a/Pipfile +++ b/Pipfile @@ -22,6 +22,7 @@ cleanpy = "*" pytest = "*" bump2version = "*" black = "*" +ruff = "*" [requires] python_version = "3.11" diff --git a/Pipfile.lock b/Pipfile.lock index 297c5816..beb16723 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "3016238cc740a16affb252e933956b3fd8377e899cb9c8563a69eceaafc31ccd" + "sha256": "f96acae67c176ca7533141c154725a6a5563f14bf40da515952c2ee9b02a74c8" }, "pipfile-spec": 6, "requires": { @@ -812,6 +812,31 @@ "index": "pypi", "markers": "python_version >= '3.9'", "version": "==8.4.1" + }, + "ruff": { + "hashes": [ + "sha256:06bfb01e1623bf7f59ea749a841da56f8f653d641bfd046edee32ede7ff6c606", + "sha256:1fc3193f238bc2d7968772c82831a4ff69252f673be371fb49663f0068b7ec71", + "sha256:2e1c2a3b8626339bb6369116e7030a4cf194ea48f49b64bb505732a7fce4f4e3", + "sha256:32dec41817623d388e645612ec70d5757a6d9c035f3744a52c7b195a57e03860", + "sha256:4000623300563c709458d0ce170c3d0d788c23a058912f28bbadc6f905d67afa", + "sha256:47ef751f722053a5df5fa48d412dbb54d41ab9b17875c6840a58ec63ff0c247c", + "sha256:5726f59b171111fa6a69d82aef48f00b56598b03a22f0f4170664ff4d8298efb", + "sha256:5d0bfe4e77fba61bf2ccadf8cf005d6133e3ce08793bbe870dd1c734f2699a3e", + "sha256:69ffe0e5f9b2cf2b8e289a3f8945b402a1b19eff24ec389f45f23c42a3dd6fb5", + "sha256:74e6f5c04c4dd4aba223f4fe6e7104f79e0eebf7d307e4f9b18c18362124bccd", + "sha256:76e4f31529899b8c434c3c1dede98c4483b89590e15fb49f2d46183801565303", + "sha256:789b7a03e72507c54fb3ba6209e4bb36517b90f1a3569ea17084e3fd295500fb", + "sha256:9c18f3d707ee9edf89da76131956aba1270c6348bfee8f6c647de841eac7194f", + "sha256:a07a5c8ffa2611a52732bdc67bf88e243abd84fe2d7f6daef3826b59abbfeda4", + "sha256:a828a5fc25a3efd3e1ff7b241fd392686c9386f20e5ac90aa9234a5faa12c423", + "sha256:c928f1b2ec59fb77dfdf70e0419408898b63998789cc98197e15f560b9e77f77", + "sha256:dfce05101dbd11833a0776716d5d1578641b7fddb537fe7fa956ab85d1769b69", + "sha256:e41df94a957d50083fd09b916d6e89e497246698c3f3d5c681c8b3e7b9bb4ac8" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==0.12.7" } } } diff --git a/pages/explorer.py b/pages/explorer.py index a8608593..f7304b1f 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -20,7 +20,6 @@ sun_cloud_tab_explore_dropdown_names, container_row_center_full, container_col_center_one_of_three, - mapping_dictionary, ) from pages.lib.template_graphs import ( heatmap, @@ -37,7 +36,6 @@ generate_units, title_with_tooltip, summary_table_tmp_rh_tab, - code_timer, title_with_link, determine_month_and_hour_filter, dropdown, diff --git a/pages/lib/charts_data_explorer.py b/pages/lib/charts_data_explorer.py index 0b162943..2a085bd3 100644 --- a/pages/lib/charts_data_explorer.py +++ b/pages/lib/charts_data_explorer.py @@ -1,6 +1,5 @@ from math import ceil, floor -import json import numpy as np import math import plotly.express as px diff --git a/pages/lib/charts_sun.py b/pages/lib/charts_sun.py index b08d970f..3169e271 100644 --- a/pages/lib/charts_sun.py +++ b/pages/lib/charts_sun.py @@ -1,7 +1,6 @@ from datetime import timedelta from math import ceil, cos, floor, radians -import json import numpy as np import pandas as pd import plotly.graph_objects as go diff --git a/pages/lib/extract_df.py b/pages/lib/extract_df.py index d3f96ec4..ac370ee5 100644 --- a/pages/lib/extract_df.py +++ b/pages/lib/extract_df.py @@ -151,7 +151,7 @@ def create_df(lst, file_name): years = epw_df["year"].astype("int").unique() if len(years) == 1: year_rounded_up = int(math.ceil(years[0] / 10.0)) * 10 - location_info["period"] = f"{year_rounded_up-10}-{year_rounded_up}" + location_info["period"] = f"{year_rounded_up - 10}-{year_rounded_up}" else: min_year = int(math.floor(min(years) / 10.0)) * 10 max_year = int(math.ceil(max(years) / 10.0)) * 10 diff --git a/pages/lib/import_one_building_files.py b/pages/lib/import_one_building_files.py index 32a57c5e..90cdae8d 100644 --- a/pages/lib/import_one_building_files.py +++ b/pages/lib/import_one_building_files.py @@ -77,13 +77,13 @@ def import_kml_files(file_name): ) try: - df_old = pd.read_csv(f"./assets/data/one_building.csv", compression="gzip") + df_old = pd.read_csv("./assets/data/one_building.csv", compression="gzip") df_old = pd.concat([df_old, df]).drop_duplicates() df_old.to_csv( - f"./assets/data/one_building.csv", index=False, compression="gzip" + "./assets/data/one_building.csv", index=False, compression="gzip" ) except FileNotFoundError: - df.to_csv(f"./assets/data/one_building.csv", index=False, compression="gzip") + df.to_csv("./assets/data/one_building.csv", index=False, compression="gzip") if __name__ == "__main__": diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index e34ca7e0..8be6c077 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -848,5 +848,5 @@ def catch(func, handle=lambda e: e, *args, **kwargs): # Handle category not in dictionary try: return func(*args, **kwargs) - except Exception as e: + except Exception: return 0 diff --git a/pages/lib/utils.py b/pages/lib/utils.py index fd2884b3..2ee5c2f9 100644 --- a/pages/lib/utils.py +++ b/pages/lib/utils.py @@ -32,9 +32,9 @@ def generate_chart_name(tab_name, meta=None, custom_inputs=None, units=None): if units: custom_str += f"_{units}" if meta: - figure_config["toImageButtonOptions"][ - "filename" - ] = f"{meta['city']}_{meta['country']}_{tab_name}{custom_str}" + figure_config["toImageButtonOptions"]["filename"] = ( + f"{meta['city']}_{meta['country']}_{tab_name}{custom_str}" + ) else: figure_config["toImageButtonOptions"]["filename"] = f"{tab_name}{custom_str}" return figure_config diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 5e83583f..ef65dd30 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -1,5 +1,4 @@ import math -import json import dash from dash import dcc, html diff --git a/pages/outdoor.py b/pages/outdoor.py index 1f95b8fd..1ebe0c82 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -8,10 +8,6 @@ from pages.lib.page_urls import PageUrls from pages.lib.global_scheme import ( outdoor_dropdown_names, - tight_margins, - month_lst, - container_row_center_full, - container_col_center_one_of_three, ) from pages.lib.template_graphs import ( heatmap_with_filter, diff --git a/pages/psy-chart.py b/pages/psy-chart.py index de62d567..a6287080 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -1,4 +1,3 @@ -import json from math import ceil, floor import dash diff --git a/pages/select.py b/pages/select.py index 6f142a8f..b0858439 100644 --- a/pages/select.py +++ b/pages/select.py @@ -191,7 +191,7 @@ def submitted_data( messages_alert["invalid_format"], "warning", ) - except Exception as e: + except Exception: # print(e) return ( None, diff --git a/pages/summary.py b/pages/summary.py index 2e9e6d1c..b1a9037d 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -41,7 +41,6 @@ def layout(): Output("tab-two-container", "children"), [Input("si-ip-radio-input", "value")] ) def update_layout(si_ip): - if si_ip == "si": heating_setpoint = 10 cooling_setpoint = 18 @@ -259,7 +258,7 @@ def update_location_info(ts, df, meta, si_ip): total_solar_rad_unit = "k" + mapping_dictionary["glob_hor_rad"][si_ip]["unit"] total_solar_rad = f"Annual cumulative horizontal solar radiation: {total_solar_rad_value} {total_solar_rad_unit}" - total_diffuse_rad = f"Percentage of diffuse horizontal solar radiation: {round(df['dif_hor_rad'].sum()/df['glob_hor_rad'].sum()*100, 1)} %" + total_diffuse_rad = f"Percentage of diffuse horizontal solar radiation: {round(df['dif_hor_rad'].sum() / df['glob_hor_rad'].sum() * 100, 1)} %" tmp_unit = mapping_dictionary["DBT"][si_ip]["unit"] average_yearly_tmp = ( f"Average yearly temperature: {df['DBT'].mean().round(1)} " + tmp_unit diff --git a/pages/sun.py b/pages/sun.py index ca3e109e..dcf93d69 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -1,12 +1,11 @@ +from copy import deepcopy + import dash -from dash import html, dcc -from dash_extensions.enrich import Output, Input, State, callback import dash_bootstrap_components as dbc - import numpy as np -from copy import deepcopy +from dash import html, dcc +from dash_extensions.enrich import Output, Input, State, callback -from pages.lib.page_urls import PageUrls from pages.lib.charts_sun import ( monthly_solar, polar_graph, @@ -16,23 +15,19 @@ sun_cloud_tab_dropdown_names, sun_cloud_tab_explore_dropdown_names, dropdown_names, - more_variables_dropdown, tight_margins, month_lst, - mapping_dictionary, ) +from pages.lib.page_urls import PageUrls from pages.lib.template_graphs import heatmap, barchart, daily_profile from pages.lib.utils import ( - code_timer, dropdown, - title_with_tooltip, generate_chart_name, generate_units, generate_custom_inputs, title_with_link, ) - dash.register_page(__name__, name="Sun and Clouds", path=PageUrls.SUN.value, order=3) diff --git a/pages/wind.py b/pages/wind.py index 0e814e12..abff7cf6 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -6,7 +6,6 @@ from pages.lib.global_scheme import month_lst, container_row_center_full from pages.lib.template_graphs import heatmap, wind_rose from pages.lib.utils import ( - code_timer, title_with_tooltip, generate_chart_name, generate_units, From 06c1b7389bc823e083d80ff182dd2795a45f1bfe Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 31 Jul 2025 17:29:50 +1000 Subject: [PATCH 45/91] lint: added ruff --- pages/lib/extract_df.py | 2 +- pages/lib/global_scheme.py | 4 ++-- pages/select.py | 2 +- pages/summary.py | 2 +- tests/python/test_utils.py | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pages/lib/extract_df.py b/pages/lib/extract_df.py index ac370ee5..a0def908 100644 --- a/pages/lib/extract_df.py +++ b/pages/lib/extract_df.py @@ -379,7 +379,7 @@ def convert_data(df, mapping_json): for key in json.loads(mapping_json): if "conversion_function" in mapping_dict[key]: conversion_function_name = mapping_dict[key]["conversion_function"] - if conversion_function_name != None: + if conversion_function_name is not None: conversion_function = globals()[conversion_function_name] conversion_function(df, key) return json.dumps(mapping_dict) diff --git a/pages/lib/global_scheme.py b/pages/lib/global_scheme.py index c2c08468..dc308105 100644 --- a/pages/lib/global_scheme.py +++ b/pages/lib/global_scheme.py @@ -1,3 +1,5 @@ +import plotly.io as pio + # Colors Dictionary blue_red_yellow = ["#00b3ff", "#000082", "#ff0000", "#ffff00"] dry_humid = ["#ffe600", "#00c8ff", "#0000ff"] @@ -76,8 +78,6 @@ "Dec", ] -import plotly.io as pio - clima_template = "plotly_white" pio.templates.default = clima_template diff --git a/pages/select.py b/pages/select.py index b0858439..68d7451d 100644 --- a/pages/select.py +++ b/pages/select.py @@ -310,7 +310,7 @@ def display_modal_when_data_clicked(clicks_use_epw, click_map, close_clicks, is_ ], prevent_initial_call=True, ) -def display_modal_when_data_clicked(click_map): +def change_text_modal(click_map): """change the text of the modal header""" if click_map: return [f"Analyse data from {click_map['points'][0]['hovertext']}?"] diff --git a/pages/summary.py b/pages/summary.py index b1a9037d..fea5ba47 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -531,7 +531,7 @@ def download_clima_dataframe(n_clicks, df, meta, si_ip): [State("meta-store", "data")], prevent_initial_call=True, ) -def download_clima_dataframe(n_clicks, meta): +def download_epw(n_clicks, meta): if n_clicks is None: raise PreventUpdate elif meta is not None: diff --git a/tests/python/test_utils.py b/tests/python/test_utils.py index f7fc08b9..e22777d1 100644 --- a/tests/python/test_utils.py +++ b/tests/python/test_utils.py @@ -1,6 +1,8 @@ import sys import os +import pandas as pd + root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) if root_dir not in sys.path: sys.path.append(root_dir) @@ -10,8 +12,6 @@ from pages.lib.utils import summary_table_tmp_rh_tab from pages.lib.extract_df import get_data, create_df -import pandas as pd - def save_epw_test(path_file): test_url = "http://climate.onebuilding.org/WMO_Region_6_Europe/ITA_Italy/ER_Emilia-Romagna/ITA_ER_Bologna-Marconi.AP.161400_TMYx.2004-2018.zip" From f7f314f0060fc28df4f712e86014aa145a3501e5 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 31 Jul 2025 17:47:30 +1000 Subject: [PATCH 46/91] feat: changed the color of low wind speed --- pages/lib/global_scheme.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/lib/global_scheme.py b/pages/lib/global_scheme.py index dc308105..c1554207 100644 --- a/pages/lib/global_scheme.py +++ b/pages/lib/global_scheme.py @@ -365,7 +365,7 @@ "wind_speed": { "name": "Wind speed", "color": [ - "#ffffff", + "#D3D3D3", "#b2f2ff", "#33ddff", "#00aaff", From b9832caf9619c800b4040e3cc1ec720465327634 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 31 Jul 2025 17:47:58 +1000 Subject: [PATCH 47/91] feat: shared y-axis plots --- pages/lib/charts_sun.py | 10 ++++++---- pages/lib/template_graphs.py | 5 +++-- pages/sun.py | 4 ++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/pages/lib/charts_sun.py b/pages/lib/charts_sun.py index 3169e271..5f94b2c4 100644 --- a/pages/lib/charts_sun.py +++ b/pages/lib/charts_sun.py @@ -26,6 +26,7 @@ def monthly_solar(epw_df, si_ip): rows=1, cols=12, subplot_titles=month_lst, + shared_yaxes=True, ) for i in range(12): @@ -91,10 +92,11 @@ def monthly_solar(epw_df, si_ip): ) fig.update_xaxes(range=[0, 25], row=1, col=i + 1) - if si_ip == "si": - fig.update_yaxes(range=[0, 1000], row=1, col=i + 1) - if si_ip == "ip": - fig.update_yaxes(range=[0, 400], row=1, col=i + 1) + + if si_ip == "si": + fig.update_yaxes(range=[0, 1000]) + if si_ip == "ip": + fig.update_yaxes(range=[0, 400]) fig.update_layout( template=template, diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index 8be6c077..da806d2e 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -256,6 +256,7 @@ def daily_profile(df, var, global_local, si_ip): rows=1, cols=12, subplot_titles=month_lst, + shared_yaxes=True, ) for i in range(12): @@ -461,9 +462,9 @@ def speed_labels(bins, units): if left == bins[0]: labels.append("calm") elif np.isinf(right): - labels.append(">{} {}".format(left, units)) + labels.append(f">{left} {units}") else: - labels.append("{} - {} {}".format(left, right, units)) + labels.append(f"{left} - {right} {units}") return labels diff --git a/pages/sun.py b/pages/sun.py index dcf93d69..057bb5e9 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -208,7 +208,7 @@ def update_static_section(si_ip): State("si-ip-unit-store", "data"), ], ) -def monthly_and_cloud_chart(ts, df, meta, si_ip): +def monthly_and_cloud_chart(_, df, meta, si_ip): """Update the contents of tab four. Passing in the polar selection and the general info (df, meta).""" # Sun Radiation @@ -303,7 +303,7 @@ def daily(ts, var, global_local, df, meta, si_ip): State("si-ip-unit-store", "data"), ], ) -def update_heatmap(ts, var, global_local, df, meta, si_ip): +def update_heatmap(_, var, global_local, df, meta, si_ip): custom_inputs = generate_custom_inputs(var) units = generate_units(si_ip) return dcc.Graph( From 641b855177d35bb0499714bd9693939b0ca18efc Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 31 Jul 2025 17:57:26 +1000 Subject: [PATCH 48/91] feat: added units to wind roses legend --- pages/lib/template_graphs.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index da806d2e..ab40e08d 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -8,7 +8,6 @@ import dash_bootstrap_components as dbc from .global_scheme import month_lst, template, tight_margins - from .utils import code_timer, determine_month_and_hour_filter @@ -454,7 +453,6 @@ def heatmap(df, var, global_local, si_ip): return fig -### WIND ROSE TEMPLATE def speed_labels(bins, units): """Return nice labels for a wind speed range.""" labels = [] @@ -566,6 +564,7 @@ def wind_rose(df, title, month, hour, labels, si_ip): showlegend=labels, dragmode=False, margin=tight_margins, + legend_title_text=f"Wind Speed ({spd_unit})", ) fig.update_xaxes(showline=True, linewidth=1, linecolor="black", mirror=True) fig.update_yaxes(showline=True, linewidth=1, linecolor="black", mirror=True) From f221f45d1b4fa6e87f4325317b987e847214dd55 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 31 Jul 2025 18:07:59 +1000 Subject: [PATCH 49/91] feat: added link to documentation natural ventilation tab --- pages/natural_ventilation.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index ef65dd30..813a1f6e 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -24,8 +24,8 @@ generate_units_degree, generate_units, generate_custom_inputs_nv, - determine_month_and_hour_filter, -) + determine_month_and_hour_filter, title_with_link, + ) dash.register_page( @@ -58,6 +58,13 @@ def update_layout(si_ip): dpt_set = 16 return [ + html.Div( + children=title_with_link( + text="Natural ventilation potential", + id_button="natural-ventilation-potential", + doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/natural-ventilation", + ), + ), inputs_tab(tdb_set_min, tdb_set_max, dpt_set), dcc.Loading( html.Div( From b8ada5c6a306a8bdde826b3db99c1d0c2c57b255 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 31 Jul 2025 18:11:57 +1000 Subject: [PATCH 50/91] build: updated requirements.txt --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 23c46a7f..d2d7baaf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -48,6 +48,7 @@ python-dateutil==2.9.0.post0 pytz==2025.2 requests==2.32.4 retrying==1.4.1 +ruff==0.12.7 scipy==1.12.0 six==1.17.0 tenacity==9.1.2 From 59e0c0b56f3bc6048a0269c892c69349e0528503 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 31 Jul 2025 18:12:20 +1000 Subject: [PATCH 51/91] lint: formatted code --- pages/lib/import_one_building_files.py | 4 +--- pages/lib/utils.py | 6 +++--- pages/natural_ventilation.py | 19 ++++++++++--------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/pages/lib/import_one_building_files.py b/pages/lib/import_one_building_files.py index 90cdae8d..c9760733 100644 --- a/pages/lib/import_one_building_files.py +++ b/pages/lib/import_one_building_files.py @@ -79,9 +79,7 @@ def import_kml_files(file_name): try: df_old = pd.read_csv("./assets/data/one_building.csv", compression="gzip") df_old = pd.concat([df_old, df]).drop_duplicates() - df_old.to_csv( - "./assets/data/one_building.csv", index=False, compression="gzip" - ) + df_old.to_csv("./assets/data/one_building.csv", index=False, compression="gzip") except FileNotFoundError: df.to_csv("./assets/data/one_building.csv", index=False, compression="gzip") diff --git a/pages/lib/utils.py b/pages/lib/utils.py index 2ee5c2f9..fd2884b3 100644 --- a/pages/lib/utils.py +++ b/pages/lib/utils.py @@ -32,9 +32,9 @@ def generate_chart_name(tab_name, meta=None, custom_inputs=None, units=None): if units: custom_str += f"_{units}" if meta: - figure_config["toImageButtonOptions"]["filename"] = ( - f"{meta['city']}_{meta['country']}_{tab_name}{custom_str}" - ) + figure_config["toImageButtonOptions"][ + "filename" + ] = f"{meta['city']}_{meta['country']}_{tab_name}{custom_str}" else: figure_config["toImageButtonOptions"]["filename"] = f"{tab_name}{custom_str}" return figure_config diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 813a1f6e..9c45249c 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -24,8 +24,9 @@ generate_units_degree, generate_units, generate_custom_inputs_nv, - determine_month_and_hour_filter, title_with_link, - ) + determine_month_and_hour_filter, + title_with_link, +) dash.register_page( @@ -58,13 +59,13 @@ def update_layout(si_ip): dpt_set = 16 return [ - html.Div( - children=title_with_link( - text="Natural ventilation potential", - id_button="natural-ventilation-potential", - doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/natural-ventilation", - ), - ), + html.Div( + children=title_with_link( + text="Natural ventilation potential", + id_button="natural-ventilation-potential", + doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/natural-ventilation", + ), + ), inputs_tab(tdb_set_min, tdb_set_max, dpt_set), dcc.Loading( html.Div( From 8e8089f86f516d0c2f6b38ef2d59430bdd06bbd9 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 1 Aug 2025 08:34:58 +1000 Subject: [PATCH 52/91] docs: updated README.md --- README.md | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index e31ffe1c..ab1cf7e2 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,32 @@ # CBE Clima Tool -The CBE Clima Tool is a web-based application built to support climate analysis specifically designed to support the need of architects and engineers interested in climate-adapted design. -It allows users to analyze the climate data of more than 27,500 locations worldwide from both [Energy Plus](https://energyplus.net/weather) and [Climate.One.Building.org](http://climate.onebuilding.org/). -You can, however, also choose to upload your own EPW weather file. Our tool can be used to analyze and visualize data contained in EnergyPlus Weather \(EPW\) files. -It furthermore calculates a number of climate-related values \(i.e. -solar azimuth and altitude, Universal Thermal Climate Index \(UTCI\), comfort indices, etc.\) that are not contained in the EPW files but can be derived from the information therein contained. -It can be freely accessed at [clima.cbe.berkeley.edu](http://clima.cbe.berkeley.edu) +The CBE Clima Tool is a web-based application built to support climate analysis specifically designed to support the need of architects and engineers interested in climate-adapted design. It can be freely accessed at [clima.cbe.berkeley.edu](http://clima.cbe.berkeley.edu). -For any questions and feedback related to this tool, please use the [Discussions](https://github.com/CenterForTheBuiltEnvironment/clima/discussions) section. +It allows users to analyze the climate data of more than 27,500 locations worldwide from both [Energy Plus](https://energyplus.net/weather) and [Climate.One.Building.org](http://climate.onebuilding.org/). You can, however, also choose to upload your own EPW weather file. -If you use this tool please consider citing us. +Our tool can be used to analyze and visualize data contained in EnergyPlus Weather (EPW) files. It furthermore calculates a number of climate-related values (i.e. solar azimuth and altitude, Universal Thermal Climate Index (UTCI), comfort indices, etc.) that are not contained in the EPW files but can be derived from the information therein contained. -## Official documentation +## Key Features -[Official documentation](https://center-for-the-built-environment.gitbook.io/clima/). +* **Interactive Climate Analysis:** Visualize EPW weather data through a variety of interactive charts. +* **Extensive Weather Data:** Access weather files for over 27,500 locations from EnergyPlus and Climate.One.Building.org. +* **Custom Data Upload:** Analyze your own custom `.epw` weather files. +* **Advanced Calculations:** Computes derived metrics like solar positions, UTCI, and various thermal comfort indices. +* **Data Export:** Download charts, data, and psychrometric chart overlays. + +## Documentation + +The official documentation for the tool can be found [here](https://center-for-the-built-environment.gitbook.io/clima/). + +## Citation + +If you use this tool in your research or work, please cite our paper: + +Betti, G., Tartarini, F. & Schiavon, S. CBE Clima Tool: a free and open-source web application for climate analysis for architects and building engineers. *Build. Simul.* (2023). https://doi.org/10.1007/s12273-023-1090-5 + +## Getting Support + +For any questions, feedback, or bug reports, please use the [GitHub Discussions](https://github.com/CenterForTheBuiltEnvironment/clima/discussions) section. ## Authors * [Giovanni Betti](https://www.linkedin.com/in/gbetti/) @@ -22,6 +35,6 @@ If you use this tool please consider citing us. ## Built with -* [Dash](https://plotly.com/dash/) - Framework for building the web app -* [Plotly Python](https://plotly.com/python/) - Used to create the interactive plots - +* [Dash](https://plotly.com/dash/) - Main web framework +* [Plotly Python](https://plotly.com/python/) - Interactive scientific graphing +* [Pandas](https://pandas.pydata.org/) - Data analysis and manipulation From 69fe4360bc0f77da28c3da2dac9c39ae2cb8ecef Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 1 Aug 2025 09:16:59 +1000 Subject: [PATCH 53/91] fix: #230 heatmap visualisation and filtering --- pages/lib/template_graphs.py | 2 +- pages/natural_ventilation.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index ab40e08d..c03a5e4a 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -835,7 +835,7 @@ def filter_df_by_month_and_hour( df.loc[mask, var] = None if start_hour <= end_hour: - mask = (df["hour"] < start_hour) | (df["hour"] > end_hour) + mask = (df["hour"] <= start_hour) | (df["hour"] > end_hour) df.loc[mask, var] = None else: mask = (df["hour"] >= end_hour) & (df["hour"] <= start_hour) diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 9c45249c..64d90bc8 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -386,7 +386,7 @@ def nv_heatmap( fig = go.Figure( data=go.Heatmap( - y=df["hour"], + y=df["hour"] - 0.5, x=df["UTC_time"].dt.date, z=df[var], colorscale=var_color, @@ -414,7 +414,7 @@ def nv_heatmap( template=template, title=title, yaxis_nticks=13, - yaxis=dict(range=(1, 24)), + yaxis=dict(range=(0, 24)), margin=tight_margins.copy().update({"t": 55}), ) From 6b2d37c56f3e525af2e76ee93205df575953755f Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 1 Aug 2025 09:29:38 +1000 Subject: [PATCH 54/91] fix: #234 wind rose filtering time --- pages/lib/template_graphs.py | 4 ++-- pages/wind.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index c03a5e4a..ca748cd1 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -480,7 +480,7 @@ def wind_rose(df, title, month, hour, labels, si_ip): else: df = df.loc[(df["month"] <= end_month) | (df["month"] >= start_month)] if start_hour <= end_hour: - df = df.loc[(df["hour"] >= start_hour) & (df["hour"] <= end_hour)] + df = df.loc[(df["hour"] > start_hour) & (df["hour"] <= end_hour)] else: df = df.loc[(df["hour"] <= end_hour) | (df["hour"] >= start_hour)] @@ -838,7 +838,7 @@ def filter_df_by_month_and_hour( mask = (df["hour"] <= start_hour) | (df["hour"] > end_hour) df.loc[mask, var] = None else: - mask = (df["hour"] >= end_hour) & (df["hour"] <= start_hour) + mask = (df["hour"] > end_hour) & (df["hour"] <= start_hour) df.loc[mask, var] = None return df diff --git a/pages/wind.py b/pages/wind.py index abff7cf6..90d57d43 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -257,9 +257,9 @@ def custom_wind_rose(): dropdown( id="tab5-custom-start-hour", options={ - str(i) + ":00": i for i in range(1, 25) + str(i) + ":00": i for i in range(0, 24) }, - value=1, + value=0, style={"width": "6rem"}, ), ], From ddc37effecdd9c4992b475a614fa8d2faf26028f Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 1 Aug 2025 09:42:41 +1000 Subject: [PATCH 55/91] fix: #234 fixed heatmaps across whole tool --- pages/lib/template_graphs.py | 2 +- pages/natural_ventilation.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index ca748cd1..34c5eba2 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -362,7 +362,7 @@ def heatmap_with_filter( range_z = [data_min, data_max] fig = go.Figure( data=go.Heatmap( - y=df["hour"], + y=df["hour"] - 0.5, # Offset by 0.5 to center the hour labels x=df["UTC_time"].dt.date, z=df[var], colorscale=var_color, diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 64d90bc8..70b392b8 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -386,7 +386,7 @@ def nv_heatmap( fig = go.Figure( data=go.Heatmap( - y=df["hour"] - 0.5, + y=df["hour"] - 0.5, # Offset by 0.5 to center the hour labels x=df["UTC_time"].dt.date, z=df[var], colorscale=var_color, From 3fc7418644e87a984aed79d4d795d87c0deeb39f Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 1 Aug 2025 09:54:08 +1000 Subject: [PATCH 56/91] build: excluded more files from .dockerignore and .gcloudignore --- .dockerignore | 298 ++++++++++++++++++++++++++++++++++++++++++++++++++ .gcloudignore | 1 + 2 files changed, 299 insertions(+) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..56153a8a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,298 @@ +README.md +LICENSE +Procfile +Procfile.windows +Pipfile.lock +Pipfile +*.pyo +*.pyd +__pycache__ +.pytest_cache +file_system_store +.git +.gitbook +.gitbook.yaml +docs +tests +node_modules +.my_cache + +assets/data/Region*.kml +assets/data/OneBuilding*.zip +file_system_backend + +venv +.idea +cache-directory +cloud-run-key-file.json + +# Created by .ignore support plugin (hsz.mobi) +### VisualStudioCode template +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests +### Node template +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless +### Python template +# Byte-compiled / optimized / DLL files +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +develop-eggs/ +downloads/ +eggs/ +.eggs/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +### SublimeText template +# Cache files for Sublime Text +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache + +# Workspace files are user-specific +*.sublime-workspace + +# Project files should be checked into the repository, unless a significant +# proportion of contributors will probably not be using Sublime Text +# *.sublime-project + +# SFTP configuration file +sftp-config.json + +# Package control specific files +Package Control.last-run +Package Control.ca-list +Package Control.ca-bundle +Package Control.system-ca-bundle +Package Control.cache/ +Package Control.ca-certs/ +Package Control.merged-ca-bundle +Package Control.user-ca-bundle +oscrypto-ca-bundle.crt +bh_unicode_properties.cache + +# Sublime-github package stores a github token in this file +# https://packagecontrol.io/packages/sublime-github +GitHub.sublime-settings \ No newline at end of file diff --git a/.gcloudignore b/.gcloudignore index e5bde90c..1a8d45f8 100644 --- a/.gcloudignore +++ b/.gcloudignore @@ -16,6 +16,7 @@ docs file_system_backend tests .gitbook.yaml +assets/data/OneBuilding*.zip assets/data/Region*.kml file_system_store \ No newline at end of file From a1e52c1466bd5c456623f0f2bb158d3c94ce75c2 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 1 Aug 2025 10:25:31 +1000 Subject: [PATCH 57/91] feat: storing config variables in the config.py file --- config.py | 48 ++++++++++++++++++++++++++++++++++++ main.py | 13 +++++----- pages/changelog.py | 7 +++--- pages/explorer.py | 2 +- pages/lib/page_urls.py | 15 ----------- pages/natural_ventilation.py | 2 +- pages/not_found_404.py | 24 +++++++++--------- pages/outdoor.py | 2 +- pages/psy-chart.py | 2 +- pages/select.py | 2 +- pages/summary.py | 2 +- pages/sun.py | 2 +- pages/t_rh.py | 2 +- pages/wind.py | 2 +- 14 files changed, 80 insertions(+), 45 deletions(-) create mode 100644 config.py delete mode 100644 pages/lib/page_urls.py diff --git a/config.py b/config.py new file mode 100644 index 00000000..f4c5d067 --- /dev/null +++ b/config.py @@ -0,0 +1,48 @@ +from enum import Enum +import platform + + +class AppConfig: + """Stores application configuration.""" + + TITLE = "CBE Clima Tool" + DEBUG: bool = "macOS" in platform.platform() + HOST = "0.0.0.0" + PORT = 8080 + PROCESSES = 1 + THREADED = True + + +class PageUrls(str, Enum): + """ + Stores application routes. + Inheriting from `str` allows the enum members to be used as strings directly. + """ + + SELECT: str = "/" + SUMMARY: str = "/summary" + T_RH: str = "/t-rh" + SUN: str = "/sun" + WIND: str = "/wind" + PSY_CHART: str = "/psy-chart" + NATURAL_VENTILATION: str = "/natural-ventilation" + OUTDOOR: str = "/outdoor" + EXPLORER: str = "/explorer" + CHANGELOG: str = "/changelog" + NOT_FOUND: str = "/404" + + +class FilePaths: + """Stores file paths used in the application.""" + + CHANGELOG = "CHANGELOG.md" + + +class Assets: + """Stores paths to assets.""" + + NOT_FOUND_ANIMATION = "/assets/animations/page_not_found.json" + + +# You can also store other constants or settings here +DEFAULT_UNITS = "si" diff --git a/main.py b/main.py index 5420b9f8..d6fccbfa 100644 --- a/main.py +++ b/main.py @@ -4,10 +4,11 @@ from app import app from pages.lib.layout import banner, footer, build_tabs +from config import AppConfig server = app.server -app.title = "CBE Clima Tool" +app.title = AppConfig.TITLE app.layout = dbc.Container( fluid=True, style={"padding": "0"}, @@ -28,9 +29,9 @@ def display_alert(n): if __name__ == "__main__": app.run_server( - debug=False, - host="0.0.0.0", - port=8080, - processes=1, - threaded=True, + debug=AppConfig.DEBUG, + host=AppConfig.HOST, + port=AppConfig.PORT, + processes=AppConfig.PROCESSES, + threaded=AppConfig.THREADED, ) diff --git a/pages/changelog.py b/pages/changelog.py index f1e8c04a..07b78aba 100644 --- a/pages/changelog.py +++ b/pages/changelog.py @@ -2,7 +2,7 @@ import dash_bootstrap_components as dbc from dash import dcc -from pages.lib.page_urls import PageUrls +from config import PageUrls, FilePaths dash.register_page(__name__, name="changelog", path=PageUrls.CHANGELOG.value) @@ -10,9 +10,10 @@ def layout(): """changelog page""" - f = open("CHANGELOG.md", "r") + with open(FilePaths.CHANGELOG, "r") as f: + changelog_content = f.read() return dbc.Container( className="container p-4", - children=[dcc.Markdown(f.read())], + children=[dcc.Markdown(changelog_content)], ) diff --git a/pages/explorer.py b/pages/explorer.py index f7304b1f..ab8d0648 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -6,7 +6,7 @@ from copy import deepcopy -from pages.lib.page_urls import PageUrls +from config import PageUrls from pages.lib.charts_data_explorer import ( custom_heatmap, two_var_graph, diff --git a/pages/lib/page_urls.py b/pages/lib/page_urls.py deleted file mode 100644 index ea0b027f..00000000 --- a/pages/lib/page_urls.py +++ /dev/null @@ -1,15 +0,0 @@ -from enum import Enum - - -class PageUrls(Enum): - SELECT: str = "/" - SUMMARY: str = "/summary" - T_RH: str = "/t-rh" - SUN: str = "/sun" - WIND: str = "/wind" - PSY_CHART: str = "/psy-chart" - NATURAL_VENTILATION: str = "/natural-ventilation" - OUTDOOR: str = "/outdoor" - EXPLORER: str = "/explorer" - CHANGELOG: str = "/changelog" - NOT_FOUND: str = '"../assets/animations/page_not_found.json"' diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 70b392b8..6028c704 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -8,7 +8,7 @@ import numpy as np import plotly.graph_objects as go -from pages.lib.page_urls import PageUrls +from config import PageUrls from pages.lib.global_scheme import ( template, mapping_dictionary, diff --git a/pages/not_found_404.py b/pages/not_found_404.py index be98d555..0e0b6501 100644 --- a/pages/not_found_404.py +++ b/pages/not_found_404.py @@ -3,10 +3,10 @@ from dash_iconify import DashIconify from dash_extensions import Lottie -from pages.lib.page_urls import PageUrls +from config import PageUrls, Assets -dash.register_page(__name__, name="404") +dash.register_page(__name__, name="404", path=PageUrls.NOT_FOUND.value) layout = [ @@ -14,21 +14,21 @@ dmc.Text( "Please navigate the the home page by using the button below", className="mb-2" ), - dmc.Anchor( - dmc.Button( - "Home page", - fullWidth=True, - leftIcon=DashIconify(icon="material-symbols:home-outline-rounded"), - ), - href="/", - ), Lottie( options=dict( loop=True, autoplay=True, rendererSettings=dict(preserveAspectRatio="xMidYMid slice"), ), - width="100%", - url=PageUrls.NOT_FOUND.value, + width="20%", + url=Assets.NOT_FOUND_ANIMATION, + ), + dmc.Anchor( + dmc.Button( + "Home page", + fullWidth=True, + leftIcon=DashIconify(icon="material-symbols:home-outline-rounded"), + ), + href=PageUrls.SELECT.value, ), ] diff --git a/pages/outdoor.py b/pages/outdoor.py index 1ebe0c82..752cb011 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -5,7 +5,7 @@ import numpy as np -from pages.lib.page_urls import PageUrls +from config import PageUrls from pages.lib.global_scheme import ( outdoor_dropdown_names, ) diff --git a/pages/psy-chart.py b/pages/psy-chart.py index a6287080..e43115c2 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -11,7 +11,7 @@ import plotly.graph_objects as go from pythermalcomfort import psychrometrics as psy -from pages.lib.page_urls import PageUrls +from config import PageUrls from pages.lib.global_scheme import ( container_row_center_full, container_col_center_one_of_three, diff --git a/pages/select.py b/pages/select.py index 68d7451d..36dbe5fb 100644 --- a/pages/select.py +++ b/pages/select.py @@ -14,7 +14,7 @@ from pages.lib.extract_df import convert_data from pages.lib.extract_df import create_df, get_data, get_location_info from pages.lib.global_scheme import mapping_dictionary -from pages.lib.page_urls import PageUrls +from config import PageUrls from pages.lib.utils import generate_chart_name dash.register_page( diff --git a/pages/summary.py b/pages/summary.py index fea5ba47..a218b0c1 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -6,7 +6,7 @@ import plotly.graph_objects as go import requests -from pages.lib.page_urls import PageUrls +from config import PageUrls from pages.lib.charts_summary import world_map from pages.lib.extract_df import get_data from pages.lib.global_scheme import template, tight_margins, mapping_dictionary diff --git a/pages/sun.py b/pages/sun.py index 057bb5e9..8d206be0 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -18,7 +18,7 @@ tight_margins, month_lst, ) -from pages.lib.page_urls import PageUrls +from config import PageUrls from pages.lib.template_graphs import heatmap, barchart, daily_profile from pages.lib.utils import ( dropdown, diff --git a/pages/t_rh.py b/pages/t_rh.py index b5014a29..2fd610ec 100644 --- a/pages/t_rh.py +++ b/pages/t_rh.py @@ -1,7 +1,7 @@ import dash from dash_extensions.enrich import Output, Input, State, dcc, html, callback -from pages.lib.page_urls import PageUrls +from config import PageUrls from pages.lib.global_scheme import dropdown_names from pages.lib.template_graphs import heatmap, yearly_profile, daily_profile from pages.lib.utils import ( diff --git a/pages/wind.py b/pages/wind.py index 90d57d43..3246e827 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -2,7 +2,7 @@ from dash import dcc, html from dash_extensions.enrich import Output, Input, State, callback -from pages.lib.page_urls import PageUrls +from config import PageUrls from pages.lib.global_scheme import month_lst, container_row_center_full from pages.lib.template_graphs import heatmap, wind_rose from pages.lib.utils import ( From 6335e5b6b3af301da16d7d2bf6e7165c6ab5f88c Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 1 Aug 2025 10:55:30 +1000 Subject: [PATCH 58/91] feat: using class for doclinks --- config.py | 24 ++++++++++++++++++++++++ pages/explorer.py | 13 ++++++++----- pages/lib/utils.py | 2 +- pages/natural_ventilation.py | 8 ++++---- pages/outdoor.py | 6 +++--- pages/psy-chart.py | 4 ++-- pages/summary.py | 6 +++--- pages/sun.py | 14 ++++++-------- pages/t_rh.py | 8 ++++---- pages/wind.py | 12 ++++++------ 10 files changed, 61 insertions(+), 36 deletions(-) diff --git a/config.py b/config.py index f4c5d067..d2802855 100644 --- a/config.py +++ b/config.py @@ -44,5 +44,29 @@ class Assets: NOT_FOUND_ANIMATION = "/assets/animations/page_not_found.json" +class PageInfo: + """Stores page names and orders for registration.""" + + EXPLORER_NAME = "Data Explorer" + EXPLORER_ORDER = 8 + + +class DocLinks(str, Enum): + """Stores documentation links.""" + + CLIMA_DOCS = "https://cbe-berkeley.gitbook.io/clima/documentation" + TEMP_HUMIDITY_EXPLAINED = "https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/temperature-and-humidity/temperatures-explained" + SUN_PATH_DIAGRAM = "https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/sun-and-cloud/how-to-read-a-sun-path-diagram" + WIND_ROSE = "https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/wind/how-to-read-a-wind-rose" + NATURAL_VENTILATION = "https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/natural-ventilation" + UTCI_CHART = "https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/outdoor-comfort/utci-explained" + PSYCHROMETRIC_CHART = "https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/psychrometric-chart" + CLOUD_COVER = "https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/sun-and-cloud/cloud-coverage" + CUSTOM_HEATMAP = "https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/sun-and-cloud/customizable-daily-and-hourly-maps" + DEGREE_DAYS = "https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/tab-summary/degree-days-explained" + CLIMATE_PROFILES = "https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/tab-summary/climate-profiles-explained" + SOLAR_RADIATION = "https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/sun-and-cloud/global-and-diffuse-horizontal-solar-radiation" + + # You can also store other constants or settings here DEFAULT_UNITS = "si" diff --git a/pages/explorer.py b/pages/explorer.py index ab8d0648..0fe872f2 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -6,7 +6,7 @@ from copy import deepcopy -from config import PageUrls +from config import PageUrls, PageInfo, DocLinks from pages.lib.charts_data_explorer import ( custom_heatmap, two_var_graph, @@ -43,7 +43,10 @@ dash.register_page( - __name__, name="Data Explorer", path=PageUrls.EXPLORER.value, order=8 + __name__, + name=PageInfo.EXPLORER_NAME, + path=PageUrls.EXPLORER.value, + order=PageInfo.EXPLORER_ORDER, ) @@ -80,7 +83,7 @@ def section_one(): children=title_with_link( text="Yearly chart", id_button="explore-yearly-chart-label", - doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/temperature-and-humidity/temperatures-explained", + doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, ), ), dcc.Loading( @@ -91,7 +94,7 @@ def section_one(): children=title_with_link( text="Daily chart", id_button="explore-daily-chart-label", - doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/temperature-and-humidity/temperatures-explained", + doc_link=DocLinks.CLIMA_DOCS, ), ), dcc.Loading( @@ -102,7 +105,7 @@ def section_one(): children=title_with_link( text="Heatmap chart", id_button="explore-heatmap-chart-label", - doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/temperature-and-humidity/temperatures-explained", + doc_link=DocLinks.CLIMA_DOCS, ), ), dcc.Loading( diff --git a/pages/lib/utils.py b/pages/lib/utils.py index fd2884b3..e20651e9 100644 --- a/pages/lib/utils.py +++ b/pages/lib/utils.py @@ -230,7 +230,7 @@ def title_with_link( text, tooltip_text="Click to access the official documentation", id_button=None, - doc_link=None, + doc_link: str = "", ): return html.Div( className="container-row", diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 6028c704..91bee755 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -8,7 +8,7 @@ import numpy as np import plotly.graph_objects as go -from config import PageUrls +from config import PageUrls, DocLinks from pages.lib.global_scheme import ( template, mapping_dictionary, @@ -61,9 +61,9 @@ def update_layout(si_ip): return [ html.Div( children=title_with_link( - text="Natural ventilation potential", - id_button="natural-ventilation-potential", - doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/natural-ventilation", + text="Natural Ventilation Potential", + id_button="natural-ventilation-label", + doc_link=DocLinks.NATURAL_VENTILATION, ), ), inputs_tab(tdb_set_min, tdb_set_max, dpt_set), diff --git a/pages/outdoor.py b/pages/outdoor.py index 752cb011..bcba584c 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -5,7 +5,7 @@ import numpy as np -from config import PageUrls +from config import PageUrls, DocLinks from pages.lib.global_scheme import ( outdoor_dropdown_names, ) @@ -142,7 +142,7 @@ def outdoor_comfort_chart(): children=title_with_link( text="UTCI heatmap chart", id_button="utci-charts-label", - doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/outdoor-comfort/utci-explained", + doc_link=DocLinks.UTCI_CHART, ) ), dcc.Loading( @@ -153,7 +153,7 @@ def outdoor_comfort_chart(): children=title_with_link( text="UTCI thermal stress chart", id_button="utci-charts-label", - doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/outdoor-comfort/utci-explained", + doc_link=DocLinks.UTCI_CHART, ) ), dcc.Loading( diff --git a/pages/psy-chart.py b/pages/psy-chart.py index e43115c2..edb1abfe 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -11,7 +11,7 @@ import plotly.graph_objects as go from pythermalcomfort import psychrometrics as psy -from config import PageUrls +from config import PageUrls, DocLinks from pages.lib.global_scheme import ( container_row_center_full, container_col_center_one_of_three, @@ -215,7 +215,7 @@ def layout(): children=title_with_link( text="Psychrometric Chart", id_button="Psychrometric-Chart-chart", - doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/psychrometric-chart", + doc_link=DocLinks.PSYCHROMETRIC_CHART, ), ), dcc.Loading( diff --git a/pages/summary.py b/pages/summary.py index a218b0c1..175dfec5 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -6,7 +6,7 @@ import plotly.graph_objects as go import requests -from config import PageUrls +from config import PageUrls, DocLinks from pages.lib.charts_summary import world_map from pages.lib.extract_df import get_data from pages.lib.global_scheme import template, tight_margins, mapping_dictionary @@ -105,7 +105,7 @@ def update_layout(si_ip): children=title_with_link( text="Heating and Cooling Degree Days", id_button="hdd-cdd-chart", - doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/tab-summary/degree-days-explained", + doc_link=DocLinks.DEGREE_DAYS, ), ), dbc.Alert( @@ -166,7 +166,7 @@ def update_layout(si_ip): children=title_with_link( text="Climate Profiles", id_button="climate-profiles-chart", - doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/tab-summary/climate-profiles-explained", + doc_link=DocLinks.CLIMATE_PROFILES, ), ), dbc.Row( diff --git a/pages/sun.py b/pages/sun.py index 8d206be0..bb52736b 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -6,6 +6,7 @@ from dash import html, dcc from dash_extensions.enrich import Output, Input, State, callback +from config import PageUrls, DocLinks from pages.lib.charts_sun import ( monthly_solar, polar_graph, @@ -17,8 +18,7 @@ dropdown_names, tight_margins, month_lst, -) -from config import PageUrls + ) from pages.lib.template_graphs import heatmap, barchart, daily_profile from pages.lib.utils import ( dropdown, @@ -53,12 +53,10 @@ def sun_path(): className="container-col justify-center", children=[ html.Div( - children=title_with_link( text="Sun path chart", id_button="sun-path-chart-label", - doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/sun-and-cloud/how-to-read-a-sun-path-diagram", + doc_link=DocLinks.SUN_PATH_DIAGRAM, ), - ), dbc.Row( align="center", justify="center", @@ -115,7 +113,7 @@ def explore_daily_heatmap(): children=title_with_link( text="Daily charts", id_button="daily-chart-label", - doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/sun-and-cloud/customizable-daily-and-hourly-maps", + doc_link=DocLinks.CUSTOM_HEATMAP, ), ), html.Div( @@ -173,7 +171,7 @@ def update_static_section(si_ip): children=title_with_link( text="Global and Diffuse Horizontal Solar Radiation (" + hor_unit + ")", id_button="monthly-chart-label", - doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/sun-and-cloud/global-and-diffuse-horizontal-solar-radiation", + doc_link=DocLinks.SOLAR_RADIATION, ), ), dcc.Loading( @@ -184,7 +182,7 @@ def update_static_section(si_ip): children=title_with_link( text="Cloud coverage", id_button="cloud-chart-label", - doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/sun-and-cloud/cloud-coverage", + doc_link=DocLinks.CLOUD_COVER, ), ), dcc.Loading( diff --git a/pages/t_rh.py b/pages/t_rh.py index 2fd610ec..820fd3d7 100644 --- a/pages/t_rh.py +++ b/pages/t_rh.py @@ -1,7 +1,7 @@ import dash from dash_extensions.enrich import Output, Input, State, dcc, html, callback -from config import PageUrls +from config import PageUrls, DocLinks from pages.lib.global_scheme import dropdown_names from pages.lib.template_graphs import heatmap, yearly_profile, daily_profile from pages.lib.utils import ( @@ -48,7 +48,7 @@ def layout(): children=title_with_link( text="Yearly chart", id_button="yearly-chart-label", - doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/temperature-and-humidity/temperatures-explained", + doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, ), ), dcc.Loading( @@ -59,7 +59,7 @@ def layout(): children=title_with_link( text="Daily chart", id_button="daily-chart-label", - doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/temperature-and-humidity/temperatures-explained", + doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, ), ), dcc.Loading( @@ -70,7 +70,7 @@ def layout(): children=title_with_link( text="Heatmap chart", id_button="heatmap-chart-label", - doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/temperature-and-humidity/temperatures-explained", + doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, ), ), dcc.Loading( diff --git a/pages/wind.py b/pages/wind.py index 3246e827..73092148 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -2,7 +2,7 @@ from dash import dcc, html from dash_extensions.enrich import Output, Input, State, callback -from config import PageUrls +from config import PageUrls, DocLinks from pages.lib.global_scheme import month_lst, container_row_center_full from pages.lib.template_graphs import heatmap, wind_rose from pages.lib.utils import ( @@ -69,7 +69,7 @@ def seasonal_wind_rose(): children=title_with_link( text="Seasonal Wind Rose", id_button="seasonal-rose-chart", - doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/wind/how-to-read-a-wind-rose", + doc_link=DocLinks.WIND_ROSE, ), ), html.Div( @@ -154,7 +154,7 @@ def daily_wind_rose(): children=title_with_link( text="Daily Wind Rose", id_button="daily-rose-chart", - doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/wind/how-to-read-a-wind-rose", + doc_link=DocLinks.WIND_ROSE, ), ), html.Div( @@ -322,9 +322,9 @@ def layout(): children=[ html.Div( children=title_with_link( - text="Annual Wind Rose", - id_button="seasonal-rose-chart", - doc_link="https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/wind/how-to-read-a-wind-rose", + text="Wind Rose", + id_button="wind-rose-label", + doc_link=DocLinks.WIND_ROSE, ), ), dcc.Loading( From 85e13d6948343c3aed5a53f0f2959d2edbe1e4a3 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 1 Aug 2025 11:08:12 +1000 Subject: [PATCH 59/91] feat: using class to register pages --- config.py | 21 +++++++++++++++++++++ pages/changelog.py | 8 ++++++-- pages/natural_ventilation.py | 6 +++--- pages/not_found_404.py | 6 ++++-- pages/outdoor.py | 7 +++++-- pages/psy-chart.py | 7 +++++-- pages/select.py | 7 +++++-- pages/summary.py | 7 +++++-- pages/sun.py | 6 ++++-- pages/t_rh.py | 7 +++++-- pages/wind.py | 6 ++++-- 11 files changed, 67 insertions(+), 21 deletions(-) diff --git a/config.py b/config.py index d2802855..62280c92 100644 --- a/config.py +++ b/config.py @@ -47,8 +47,29 @@ class Assets: class PageInfo: """Stores page names and orders for registration.""" + SELECT_NAME = "Select Weather File" + SELECT_ORDER = 0 + SUMMARY_NAME = "Climate Summary" + SUMMARY_ORDER = 1 + TEMP_RH_NAME = "Temperature and Humidity" + TEMP_RH_ORDER = 2 + SOLAR_RADIATION_NAME = "Solar Radiation" + SOLAR_RADIATION_ORDER = 2 + SUN_NAME = "Sun and Clouds" + SUN_ORDER = 3 + WIND_NAME = "Wind" + WIND_ORDER = 4 + PSYCHROMETRIC_NAME = "Psychrometric Chart" + PSYCHROMETRIC_ORDER = 5 + NATURAL_VENTILATION_NAME = "Natural Ventilation" + NATURAL_VENTILATION_ORDER = 6 + UTCI_NAME = "Outdoor Comfort" + UTCI_ORDER = 7 EXPLORER_NAME = "Data Explorer" EXPLORER_ORDER = 8 + CHANGELOG_NAME = "Changelog" + CHANGELOG_ORDER = 9 + NOT_FOUND_NAME = "404" class DocLinks(str, Enum): diff --git a/pages/changelog.py b/pages/changelog.py index 07b78aba..be4578f6 100644 --- a/pages/changelog.py +++ b/pages/changelog.py @@ -2,10 +2,14 @@ import dash_bootstrap_components as dbc from dash import dcc -from config import PageUrls, FilePaths +from config import PageUrls, FilePaths, PageInfo -dash.register_page(__name__, name="changelog", path=PageUrls.CHANGELOG.value) +dash.register_page( + __name__, + name=PageInfo.CHANGELOG_NAME, + path=PageUrls.CHANGELOG.value, +) def layout(): diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 91bee755..4698f491 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -8,7 +8,7 @@ import numpy as np import plotly.graph_objects as go -from config import PageUrls, DocLinks +from config import PageUrls, DocLinks, PageInfo from pages.lib.global_scheme import ( template, mapping_dictionary, @@ -31,9 +31,9 @@ dash.register_page( __name__, - name="Natural Ventilation", + name=PageInfo.NATURAL_VENTILATION_NAME, path=PageUrls.NATURAL_VENTILATION.value, - order=6, + order=PageInfo.NATURAL_VENTILATION_ORDER, ) diff --git a/pages/not_found_404.py b/pages/not_found_404.py index 0e0b6501..d28a630e 100644 --- a/pages/not_found_404.py +++ b/pages/not_found_404.py @@ -3,10 +3,12 @@ from dash_iconify import DashIconify from dash_extensions import Lottie -from config import PageUrls, Assets +from config import PageUrls, Assets, PageInfo -dash.register_page(__name__, name="404", path=PageUrls.NOT_FOUND.value) +dash.register_page( + __name__, name=PageInfo.NOT_FOUND_NAME, path=PageUrls.NOT_FOUND.value +) layout = [ diff --git a/pages/outdoor.py b/pages/outdoor.py index bcba584c..ce044743 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -5,7 +5,7 @@ import numpy as np -from config import PageUrls, DocLinks +from config import PageUrls, DocLinks, PageInfo from pages.lib.global_scheme import ( outdoor_dropdown_names, ) @@ -24,7 +24,10 @@ dash.register_page( - __name__, name="Outdoor Comfort", path=PageUrls.OUTDOOR.value, order=7 + __name__, + name=PageInfo.UTCI_NAME, + path=PageUrls.OUTDOOR.value, + order=PageInfo.UTCI_ORDER, ) diff --git a/pages/psy-chart.py b/pages/psy-chart.py index edb1abfe..4730d8ff 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -11,7 +11,7 @@ import plotly.graph_objects as go from pythermalcomfort import psychrometrics as psy -from config import PageUrls, DocLinks +from config import PageUrls, DocLinks, PageInfo from pages.lib.global_scheme import ( container_row_center_full, container_col_center_one_of_three, @@ -35,7 +35,10 @@ dash.register_page( - __name__, name="Psychrometric Chart", path=PageUrls.PSY_CHART.value, order=5 + __name__, + name=PageInfo.PSYCHROMETRIC_NAME, + path=PageUrls.PSY_CHART.value, + order=PageInfo.PSYCHROMETRIC_ORDER, ) diff --git a/pages/select.py b/pages/select.py index 36dbe5fb..9adedd1a 100644 --- a/pages/select.py +++ b/pages/select.py @@ -14,11 +14,14 @@ from pages.lib.extract_df import convert_data from pages.lib.extract_df import create_df, get_data, get_location_info from pages.lib.global_scheme import mapping_dictionary -from config import PageUrls +from config import PageUrls, PageInfo from pages.lib.utils import generate_chart_name dash.register_page( - __name__, name="Select Weather File", path=PageUrls.SELECT.value, order=0 + __name__, + name=PageInfo.SELECT_NAME, + path=PageUrls.SELECT.value, + order=PageInfo.SELECT_ORDER, ) diff --git a/pages/summary.py b/pages/summary.py index 175dfec5..956a22eb 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -6,7 +6,7 @@ import plotly.graph_objects as go import requests -from config import PageUrls, DocLinks +from config import PageUrls, DocLinks, PageInfo from pages.lib.charts_summary import world_map from pages.lib.extract_df import get_data from pages.lib.global_scheme import template, tight_margins, mapping_dictionary @@ -21,7 +21,10 @@ dash.register_page( - __name__, name="Climate Summary", path=PageUrls.SUMMARY.value, order=1 + __name__, + name=PageInfo.SUMMARY_NAME, + path=PageUrls.SUMMARY.value, + order=PageInfo.SUMMARY_ORDER, ) diff --git a/pages/sun.py b/pages/sun.py index bb52736b..d290f5f8 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -6,7 +6,7 @@ from dash import html, dcc from dash_extensions.enrich import Output, Input, State, callback -from config import PageUrls, DocLinks +from config import PageUrls, DocLinks, PageInfo from pages.lib.charts_sun import ( monthly_solar, polar_graph, @@ -28,7 +28,9 @@ title_with_link, ) -dash.register_page(__name__, name="Sun and Clouds", path=PageUrls.SUN.value, order=3) +dash.register_page( + __name__, name=PageInfo.SUN_NAME, path=PageUrls.SUN.value, order=PageInfo.SUN_ORDER +) sc_dropdown_names = { diff --git a/pages/t_rh.py b/pages/t_rh.py index 820fd3d7..af8dbbf3 100644 --- a/pages/t_rh.py +++ b/pages/t_rh.py @@ -1,7 +1,7 @@ import dash from dash_extensions.enrich import Output, Input, State, dcc, html, callback -from config import PageUrls, DocLinks +from config import PageUrls, DocLinks, PageInfo from pages.lib.global_scheme import dropdown_names from pages.lib.template_graphs import heatmap, yearly_profile, daily_profile from pages.lib.utils import ( @@ -16,7 +16,10 @@ dash.register_page( - __name__, name="Temperature and Humidity", path=PageUrls.T_RH.value, order=2 + __name__, + name=PageInfo.TEMP_RH_NAME, + path=PageUrls.T_RH.value, + order=PageInfo.TEMP_RH_ORDER, ) diff --git a/pages/wind.py b/pages/wind.py index 73092148..7be8b333 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -2,7 +2,7 @@ from dash import dcc, html from dash_extensions.enrich import Output, Input, State, callback -from config import PageUrls, DocLinks +from config import PageUrls, DocLinks, PageInfo from pages.lib.global_scheme import month_lst, container_row_center_full from pages.lib.template_graphs import heatmap, wind_rose from pages.lib.utils import ( @@ -15,7 +15,9 @@ ) -dash.register_page(__name__, name="Wind", path=PageUrls.WIND.value, order=4) +dash.register_page( + __name__, name=PageInfo.WIND_NAME, path=PageUrls.WIND.value, order=PageInfo.WIND_ORDER +) def sliders(): From bca62dcc802fa1c3dffc4a53165ae08f7cc32cb0 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 1 Aug 2025 16:23:54 +1000 Subject: [PATCH 60/91] build: created a cloudbuild.yaml file --- cloudbuild.yaml | 15 +++++++++++++ docs/contributing/run-project-locally.md | 27 ++++++------------------ 2 files changed, 22 insertions(+), 20 deletions(-) create mode 100644 cloudbuild.yaml diff --git a/cloudbuild.yaml b/cloudbuild.yaml new file mode 100644 index 00000000..a60de353 --- /dev/null +++ b/cloudbuild.yaml @@ -0,0 +1,15 @@ +steps: +# Build the container image +- name: 'gcr.io/cloud-builders/docker' + args: ['build', '-t', '$_GCR/$_PROJ_NAME/$_REPO_NAME/$_IMG_NAME', '.'] +# Push the container image to Container Registry +- name: 'gcr.io/cloud-builders/docker' + args: ['push', '$_GCR/$_PROJ_NAME/$_REPO_NAME/$_IMG_NAME'] +# Deploy container image to Cloud Run +- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk' + entrypoint: 'gcloud' + args: ['run', 'deploy', '$_REPO_NAME', + '--image', '$_GCR/$_PROJ_NAME/$_REPO_NAME/$_IMG_NAME', + '--region', '$_REGION', + '--memory', '$_MEMORY', + '--cpu', '$_CPU',] \ No newline at end of file diff --git a/docs/contributing/run-project-locally.md b/docs/contributing/run-project-locally.md index 88c1e9fb..fef988bd 100644 --- a/docs/contributing/run-project-locally.md +++ b/docs/contributing/run-project-locally.md @@ -92,30 +92,17 @@ First make sure you that: ```text gcloud components update --quiet -gcloud auth list -pipenv lock -pipenv run pip3 freeze > requirements.txt +pipenv requirements > requirements.txt ``` -```bash -gcloud builds submit --tag us-docker.pkg.dev/clima-316917/gcr.io/clima --project=clima-316917 +### Deploy test version of the project -gcloud run deploy clima --image us-docker.pkg.dev/clima-316917/gcr.io/clima --platform managed --project=clima-316917 --allow-unauthenticated --region=us-central1 --memory=4Gi --concurrency=80 --cpu=2 -``` - -### Test project - -```bash -gcloud builds submit --tag us-docker.pkg.dev/clima-316917/gcr.io/clima-test --project=clima-316917 - -gcloud run deploy clima-test --image us-docker.pkg.dev/clima-316917/gcr.io/clima-test --platform managed --project=clima-316917 --allow-unauthenticated --region=us-central1 --memory=4Gi --concurrency=80 --cpu=2 +```text +gcloud builds submit --project=clima-316917 --substitutions=_REPO_NAME="clima-test",_PROJ_NAME="clima-316917",_IMG_NAME="test",_GCR="us.gcr.io",_REGION="us-central1",_MEMORY="4Gi",_CPU="2" ``` -### Test project +### Deploy main version of the project ```text -gcloud components update -gcloud config set run/region us-central1 -gcloud config set project clima-316917 -gcloud run deploy clima-test --source . -``` +gcloud builds submit --project=clima-316917 --substitutions=_REPO_NAME="clima",_PROJ_NAME="clima-316917",_IMG_NAME="main",_GCR="us.gcr.io",_REGION="us-central1",_MEMORY="4Gi",_CPU="2" +``` \ No newline at end of file From 87ec5cdab089d6a659e06f69b2040b3f385c06c0 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 1 Aug 2025 16:36:02 +1000 Subject: [PATCH 61/91] docs: redirected contact us to GitHub discussions --- pages/lib/layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/lib/layout.py b/pages/lib/layout.py index 2ee80b19..15ce434e 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -72,7 +72,7 @@ def footer(): """ [Contributors](https://cbe-berkeley.gitbook.io/clima/#contributions), [Report issues on GitHub](https://github.com/CenterForTheBuiltEnvironment/clima/issues), - [Contact us page](https://forms.gle/LRUq3vsFnE1QCLiA6), + [Contact us page](https://github.com/CenterForTheBuiltEnvironment/clima/discussions), [Documentation page](https://center-for-the-built-environment.gitbook.io/clima/), [License](https://center-for-the-built-environment.gitbook.io/clima/#license) """, From 5e24c7699cfb73cd3c4d70948bad99b42f311cf4 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 1 Aug 2025 16:51:06 +1000 Subject: [PATCH 62/91] lint: ruff formatting --- pages/lib/utils.py | 6 +++--- pages/sun.py | 10 +++++----- pages/wind.py | 5 ++++- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/pages/lib/utils.py b/pages/lib/utils.py index e20651e9..49152444 100644 --- a/pages/lib/utils.py +++ b/pages/lib/utils.py @@ -32,9 +32,9 @@ def generate_chart_name(tab_name, meta=None, custom_inputs=None, units=None): if units: custom_str += f"_{units}" if meta: - figure_config["toImageButtonOptions"][ - "filename" - ] = f"{meta['city']}_{meta['country']}_{tab_name}{custom_str}" + figure_config["toImageButtonOptions"]["filename"] = ( + f"{meta['city']}_{meta['country']}_{tab_name}{custom_str}" + ) else: figure_config["toImageButtonOptions"]["filename"] = f"{tab_name}{custom_str}" return figure_config diff --git a/pages/sun.py b/pages/sun.py index d290f5f8..e7844fd7 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -18,7 +18,7 @@ dropdown_names, tight_margins, month_lst, - ) +) from pages.lib.template_graphs import heatmap, barchart, daily_profile from pages.lib.utils import ( dropdown, @@ -55,10 +55,10 @@ def sun_path(): className="container-col justify-center", children=[ html.Div( - text="Sun path chart", - id_button="sun-path-chart-label", - doc_link=DocLinks.SUN_PATH_DIAGRAM, - ), + text="Sun path chart", + id_button="sun-path-chart-label", + doc_link=DocLinks.SUN_PATH_DIAGRAM, + ), dbc.Row( align="center", justify="center", diff --git a/pages/wind.py b/pages/wind.py index 7be8b333..f431d9ea 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -16,7 +16,10 @@ dash.register_page( - __name__, name=PageInfo.WIND_NAME, path=PageUrls.WIND.value, order=PageInfo.WIND_ORDER + __name__, + name=PageInfo.WIND_NAME, + path=PageUrls.WIND.value, + order=PageInfo.WIND_ORDER, ) From 1b8b089b4d934d56cf15fa708247d7c1718d0842 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 1 Aug 2025 17:15:24 +1000 Subject: [PATCH 63/91] feat: improved the layout of the header --- config.py | 2 +- pages/lib/layout.py | 114 ++++++++++++++++++++++++-------------------- pages/select.py | 18 +++---- 3 files changed, 69 insertions(+), 65 deletions(-) diff --git a/config.py b/config.py index 62280c92..97ace6aa 100644 --- a/config.py +++ b/config.py @@ -75,7 +75,7 @@ class PageInfo: class DocLinks(str, Enum): """Stores documentation links.""" - CLIMA_DOCS = "https://cbe-berkeley.gitbook.io/clima/documentation" + MAIN = "https://cbe-berkeley.gitbook.io/clima" TEMP_HUMIDITY_EXPLAINED = "https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/temperature-and-humidity/temperatures-explained" SUN_PATH_DIAGRAM = "https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/sun-and-cloud/how-to-read-a-sun-path-diagram" WIND_ROSE = "https://cbe-berkeley.gitbook.io/clima/documentation/tabs-explained/wind/how-to-read-a-wind-rose" diff --git a/pages/lib/layout.py b/pages/lib/layout.py index 15ce434e..350122c8 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -1,6 +1,10 @@ import dash_bootstrap_components as dbc import dash from dash import dcc, html +import dash_mantine_components as dmc +from dash_iconify import DashIconify + +from config import DocLinks def alert(): @@ -94,73 +98,77 @@ def banner(): return html.Div( id="banner", children=[ - dbc.Row( + dmc.Group( + position="apart", align="center", children=[ - dbc.Col( - html.A( - href="/", - children=[ - html.Img( - src="assets/img/cbe-logo-small.png", - ), - ], - ), - width="auto", - ), - dbc.Col( + dmc.Group( + align="center", children=[ - html.H1(id="banner-title", children=["CBE Clima Tool"]), - html.H5( - id="banner-subtitle", - children=["Current Location:"], + html.A( + href="/", + children=[ + dmc.Image( + src="assets/img/cbe-logo-small.png", + height=40, + width="auto", + ) + ], + ), + dmc.Stack( + spacing=0, + children=[ + dmc.Title( + "CBE Clima Tool", + order=1, + id="banner-title", + style={"fontSize": "2rem"}, + ), + dmc.Text( + "Current Location:", + id="banner-subtitle", + size="sm", + ), + ], ), ], ), - dbc.Col( - style={"fontWeight": "400", "padding": "1rem"}, - align="end", + dmc.Group( + align="center", children=[ - dbc.Row( - style={"text-align": "right"}, - children=[ - dbc.RadioItems( - options=[ - { - "label": "Global Value Ranges", - "value": "global", - }, - { - "label": "Local Value Ranges", - "value": "local", - }, - ], - value="local", - id="global-local-radio-input", - inline=True, - ), + html.A( + dmc.Button( + "Documentation", + leftIcon=DashIconify(icon="bi:book-half", width=20), + variant="filled", + color="#5c7cfa", + ), + href=DocLinks.MAIN.value, + target="_blank", + style={"textDecoration": "none"}, + ), + dmc.SegmentedControl( + id="global-local-radio-input", + value="local", + radius="md", + data=[ + {"label": "Global Value Ranges", "value": "global"}, + {"label": "Local Value Ranges", "value": "local"}, ], ), - dbc.Row( - align="end", - style={"text-align": "right"}, - children=[ - dbc.RadioItems( - options=[ - {"label": "SI", "value": "si"}, - {"label": "IP", "value": "ip"}, - ], - value="si", - id="si-ip-radio-input", - inline=True, - ), + dmc.SegmentedControl( + id="si-ip-radio-input", + value="si", + radius="md", + data=[ + {"label": "SI", "value": "si"}, + {"label": "IP", "value": "ip"}, ], ), ], - width="auto", ), ], - ), + ) ], ) diff --git a/pages/select.py b/pages/select.py index 9adedd1a..78266be1 100644 --- a/pages/select.py +++ b/pages/select.py @@ -97,17 +97,13 @@ def layout(): def alert(): """Alert layout for the submit button.""" - return html.Div( - [ - dbc.Alert( - messages_alert["start"], - color="primary", - id="alert", - dismissable=False, - is_open=True, - style={"maxHeight": "66px"}, - ) - ] + return dbc.Alert( + messages_alert["start"], + color="primary", + id="alert", + dismissable=False, + is_open=True, + style={"maxHeight": "66px"}, ) From c5379031bc0a111b2932b121dd5994adfb103736 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 1 Aug 2025 17:40:32 +1000 Subject: [PATCH 64/91] feat: created class for unit system --- config.py | 31 ++++--- pages/explorer.py | 10 ++- pages/lib/charts_sun.py | 6 +- pages/lib/global_scheme.py | 154 ++++++++++++++++++----------------- pages/lib/layout.py | 14 +++- pages/lib/template_graphs.py | 4 +- pages/lib/utils.py | 75 +++-------------- pages/natural_ventilation.py | 4 +- pages/psy-chart.py | 4 +- pages/select.py | 4 +- pages/summary.py | 8 +- pages/sun.py | 6 +- tests/python/test_utils.py | 4 +- 13 files changed, 150 insertions(+), 174 deletions(-) diff --git a/config.py b/config.py index 97ace6aa..bc7c61e8 100644 --- a/config.py +++ b/config.py @@ -13,23 +13,30 @@ class AppConfig: THREADED = True +class UnitSystem(str, Enum): + """Unit systems.""" + + SI = "si" + IP = "ip" + + class PageUrls(str, Enum): """ Stores application routes. Inheriting from `str` allows the enum members to be used as strings directly. """ - SELECT: str = "/" - SUMMARY: str = "/summary" - T_RH: str = "/t-rh" - SUN: str = "/sun" - WIND: str = "/wind" - PSY_CHART: str = "/psy-chart" - NATURAL_VENTILATION: str = "/natural-ventilation" - OUTDOOR: str = "/outdoor" - EXPLORER: str = "/explorer" - CHANGELOG: str = "/changelog" - NOT_FOUND: str = "/404" + SELECT = "/" + SUMMARY = "/summary" + T_RH = "/t-rh" + SUN = "/sun" + WIND = "/wind" + PSY_CHART = "/psy-chart" + NATURAL_VENTILATION = "/natural-ventilation" + OUTDOOR = "/outdoor" + EXPLORER = "/explorer" + CHANGELOG = "/changelog" + NOT_FOUND = "/404" class FilePaths: @@ -90,4 +97,4 @@ class DocLinks(str, Enum): # You can also store other constants or settings here -DEFAULT_UNITS = "si" +DEFAULT_UNITS = UnitSystem.SI diff --git a/pages/explorer.py b/pages/explorer.py index 0fe872f2..69961ade 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -6,7 +6,7 @@ from copy import deepcopy -from config import PageUrls, PageInfo, DocLinks +from config import PageUrls, PageInfo, DocLinks, UnitSystem from pages.lib.charts_data_explorer import ( custom_heatmap, two_var_graph, @@ -803,7 +803,13 @@ def update_heatmap( {}, ) custom_inputs = f"{var}" - units = "SI" if si_ip == "si" else "IP" if si_ip == "ip" else None + units = ( + UnitSystem.SI.upper() + if si_ip == UnitSystem.SI + else UnitSystem.IP.upper() + if si_ip == UnitSystem.IP + else None + ) return ( dcc.Graph( config=generate_chart_name("heatmap", meta, custom_inputs, units), diff --git a/pages/lib/charts_sun.py b/pages/lib/charts_sun.py index 5f94b2c4..73d43a24 100644 --- a/pages/lib/charts_sun.py +++ b/pages/lib/charts_sun.py @@ -4,6 +4,8 @@ import numpy as np import pandas as pd import plotly.graph_objects as go + +from config import UnitSystem from pages.lib.global_scheme import ( template, mapping_dictionary, @@ -93,9 +95,9 @@ def monthly_solar(epw_df, si_ip): fig.update_xaxes(range=[0, 25], row=1, col=i + 1) - if si_ip == "si": + if si_ip == UnitSystem.SI: fig.update_yaxes(range=[0, 1000]) - if si_ip == "ip": + if si_ip == UnitSystem.IP: fig.update_yaxes(range=[0, 400]) fig.update_layout( diff --git a/pages/lib/global_scheme.py b/pages/lib/global_scheme.py index c1554207..b71d008c 100644 --- a/pages/lib/global_scheme.py +++ b/pages/lib/global_scheme.py @@ -1,5 +1,7 @@ import plotly.io as pio +from config import UnitSystem + # Colors Dictionary blue_red_yellow = ["#00b3ff", "#000082", "#ff0000", "#ffff00"] dry_humid = ["#ffe600", "#00c8ff", "#0000ff"] @@ -132,11 +134,11 @@ "DBT": { "name": "Dry bulb temperature", "color": ["#00b3ff", "#000082", "#ff0000", "#ffff00"], - "si": { + UnitSystem.SI: { "unit": "°C", "range": [-40, 50], }, - "ip": { + UnitSystem.IP: { "unit": "°F", "range": [-40, 122], }, @@ -145,11 +147,11 @@ "DPT": { "name": "Dew point temperature", "color": ["#00b3ff", "#000082", "#ff0000", "#ffff00"], - "si": { + UnitSystem.SI: { "unit": "°C", "range": [-50, 35], }, - "ip": { + UnitSystem.IP: { "unit": "°F", "range": [-58, 95], }, @@ -158,11 +160,11 @@ "RH": { "name": "Relative humidity", "color": ["#ffe600", "#00c8ff", "#0000ff"], - "si": { + UnitSystem.SI: { "unit": "%", "range": [0, 100], }, - "ip": { + UnitSystem.IP: { "unit": "%", "range": [0, 100], }, @@ -182,11 +184,11 @@ "#cc0000", "#ffaa00", ], - "si": { + UnitSystem.SI: { "unit": "Pa", "range": [95000, 105000], }, - "ip": { + UnitSystem.IP: { "unit": "Psi", "range": [95000 * 0.000145038, 1050000.000145038], }, @@ -203,11 +205,11 @@ "#ffff7b", "#ffffff", ], - "si": { + UnitSystem.SI: { "unit": "Wh/m2", "range": [0, 1200], }, - "ip": { + UnitSystem.IP: { "unit": "Btu/ft2", "range": [0, 1200 * 0.3169983306], }, @@ -224,11 +226,11 @@ "#ffff7b", "#ffffff", ], - "si": { + UnitSystem.SI: { "unit": "Wh/m2", "range": [0, 500], }, - "ip": { + UnitSystem.IP: { "unit": "Btu/ft2", "range": [0, 500 * 0.3169983306], }, @@ -245,11 +247,11 @@ "#ffff7b", "#ffffff", ], - "si": { + UnitSystem.SI: { "unit": "Wh/m2", "range": [0, 1200], }, - "ip": { + UnitSystem.IP: { "unit": "Btu/ft2", "range": [0, 1200 * 0.3169983306], }, @@ -266,11 +268,11 @@ "#ffff7b", "#ffffff", ], - "si": { + UnitSystem.SI: { "unit": "Wh/m2", "range": [0, 1200], }, - "ip": { + UnitSystem.IP: { "unit": "Btu/ft2", "range": [0, 1200 * 0.3169983306], }, @@ -287,11 +289,11 @@ "#ffff7b", "#ffffff", ], - "si": { + UnitSystem.SI: { "unit": "Wh/m2", "range": [0, 1200], }, - "ip": { + UnitSystem.IP: { "unit": "Btu/ft2", "range": [0, 1200 * 0.3169983306], }, @@ -300,11 +302,11 @@ "glob_hor_ill": { "name": "Global horizontal illuminance", "color": ["#4d6daa", "#a0beed", "#f1e969", "#eb7d05", "#d81600"], - "si": { + UnitSystem.SI: { "unit": "lux", "range": [0, 120000], }, - "ip": { + UnitSystem.IP: { "unit": "fc", "range": [0, 120000 * 0.0929], }, @@ -313,11 +315,11 @@ "dir_nor_ill": { "name": "Direct normal illuminance", "color": ["#4d6daa", "#a0beed", "#f1e969", "#eb7d05", "#d81600"], - "si": { + UnitSystem.SI: { "unit": "lux", "range": [0, 120000], }, - "ip": { + UnitSystem.IP: { "unit": "fc", "range": [0, 120000 * 0.0929], }, @@ -326,11 +328,11 @@ "dif_hor_ill": { "name": "Diffuse horizontal illuminance", "color": ["#4d6daa", "#a0beed", "#f1e969", "#eb7d05", "#d81600"], - "si": { + UnitSystem.SI: { "unit": "lux", "range": [0, 120000], }, - "ip": { + UnitSystem.IP: { "unit": "fc", "range": [0, 120000 * 0.0929], }, @@ -339,11 +341,11 @@ "Zlumi": { "name": "Zenith luminance", "color": ["#730a8c", "#0d0db3", "#0f85be", "#0f85be", "#b11421", "#fdf130"], - "si": { + UnitSystem.SI: { "unit": "cd/m2", "range": [0, 60000], }, - "ip": { + UnitSystem.IP: { "unit": "cd/ft2", "range": [0, 60000 * 0.0929], }, @@ -352,11 +354,11 @@ "wind_dir": { "name": "Wind direction", "color": ["#0072dd", "#00c420", "#eded00", "#be00d5", "#0072dd"], - "si": { + UnitSystem.SI: { "unit": "°deg", "range": [0, 360], }, - "ip": { + UnitSystem.IP: { "unit": "°deg", "range": [0, 360], }, @@ -376,11 +378,11 @@ "#cc0000", "#ffaa00", ], - "si": { + UnitSystem.SI: { "unit": "m/s", "range": [0, 20], }, - "ip": { + UnitSystem.IP: { "unit": "fpm", "range": [0, 20 * 196.85039370078738], }, @@ -389,11 +391,11 @@ "tot_sky_cover": { "name": "Total sky cover", "color": cloud_colors, - "si": { + UnitSystem.SI: { "unit": "tenths", "range": [0, 10], }, - "ip": { + UnitSystem.IP: { "unit": "tenths", "range": [0, 10], }, @@ -402,11 +404,11 @@ "Oskycover": { "name": "Opaque sky cover", "color": cloud_colors, - "si": { + UnitSystem.SI: { "unit": "tenths", "range": [0, 10], }, - "ip": { + UnitSystem.IP: { "unit": "tenths", "range": [0, 10], }, @@ -415,11 +417,11 @@ "Vis": { "name": "Visibility", "color": cloud_colors, - "si": { + UnitSystem.SI: { "unit": "km", "range": [0, 100], }, - "ip": { + UnitSystem.IP: { "unit": "miles", "range": [0, 100 * 0.6215], }, @@ -436,11 +438,11 @@ "#ffff7b", "#ffffff", ], - "si": { + UnitSystem.SI: { "unit": "°deg", "range": [0, 180], }, - "ip": { + UnitSystem.IP: { "unit": "°deg", "range": [0, 180], }, @@ -457,11 +459,11 @@ "#ffff7b", "#ffffff", ], - "si": { + UnitSystem.SI: { "unit": "°deg", "range": [0, 180], }, - "ip": { + UnitSystem.IP: { "unit": "°deg", "range": [0, 180], }, @@ -478,11 +480,11 @@ "#ffff7b", "#ffffff", ], - "si": { + UnitSystem.SI: { "unit": "°deg", "range": [-90, 90], }, - "ip": { + UnitSystem.IP: { "unit": "°deg", "range": [-90, 90], }, @@ -499,11 +501,11 @@ "#ffff7b", "#ffffff", ], - "si": { + UnitSystem.SI: { "unit": "°deg", "range": [-90, 90], }, - "ip": { + UnitSystem.IP: { "unit": "°deg", "range": [-90, 90], }, @@ -520,11 +522,11 @@ "#ffff7b", "#ffffff", ], - "si": { + UnitSystem.SI: { "unit": "°deg", "range": [0, 360], }, - "ip": { + UnitSystem.IP: { "unit": "°deg", "range": [0, 360], }, @@ -541,11 +543,11 @@ "#ffff7b", "#ffffff", ], - "si": { + UnitSystem.SI: { "unit": "°deg", "range": [-20, 20], }, - "ip": { + UnitSystem.IP: { "unit": "°deg", "range": [-20, 20], }, @@ -554,11 +556,11 @@ "utci_Sun_Wind": { "name": "UTCI: Sun & Wind", "color": ["#00b3ff", "#000082", "#ff0000", "#ffff00"], - "si": { + UnitSystem.SI: { "unit": "°C", "range": [-70, 70], }, - "ip": { + UnitSystem.IP: { "unit": "°F", "range": [-94, 158], }, @@ -567,11 +569,11 @@ "utci_noSun_Wind": { "name": "UTCI: no Sun & Wind", "color": ["#00b3ff", "#000082", "#ff0000", "#ffff00"], - "si": { + UnitSystem.SI: { "unit": "°C", "range": [-70, 70], }, - "ip": { + UnitSystem.IP: { "unit": "°F", "range": [-94, 158], }, @@ -580,11 +582,11 @@ "utci_Sun_noWind": { "name": "UTCI: Sun & no Wind", "color": ["#00b3ff", "#000082", "#ff0000", "#ffff00"], - "si": { + UnitSystem.SI: { "unit": "°C", "range": [-70, 70], }, - "ip": { + UnitSystem.IP: { "unit": "°F", "range": [-94, 158], }, @@ -593,11 +595,11 @@ "utci_noSun_noWind": { "name": "UTCI: no Sun & no Wind", "color": ["#00b3ff", "#000082", "#ff0000", "#ffff00"], - "si": { + UnitSystem.SI: { "unit": "°C", "range": [-70, 70], }, - "ip": { + UnitSystem.IP: { "unit": "°F", "range": [-94, 158], }, @@ -627,11 +629,11 @@ [0.9435, "#751613"], [1.0, "#751613"], ], - "si": { + UnitSystem.SI: { "unit": thermal_stress_label, "range": [-5, 4], }, - "ip": { + UnitSystem.IP: { "unit": thermal_stress_label, "range": [-5, 4], }, @@ -661,11 +663,11 @@ [0.9435, "#751613"], [1.0, "#751613"], ], - "si": { + UnitSystem.SI: { "unit": thermal_stress_label, "range": [-5, 4], }, - "ip": { + UnitSystem.IP: { "unit": thermal_stress_label, "range": [-5, 4], }, @@ -695,11 +697,11 @@ [0.9435, "#751613"], [1.0, "#751613"], ], - "si": { + UnitSystem.SI: { "unit": thermal_stress_label, "range": [-5, 4], }, - "ip": { + UnitSystem.IP: { "unit": thermal_stress_label, "range": [-5, 4], }, @@ -729,11 +731,11 @@ [0.9435, "#751613"], [1.0, "#751613"], ], - "si": { + UnitSystem.SI: { "unit": thermal_stress_label, "range": [-5, 4], }, - "ip": { + UnitSystem.IP: { "unit": thermal_stress_label, "range": [-5, 4], }, @@ -742,11 +744,11 @@ "p_vap": { "name": "Vapor partial pressure", "color": ["#ffe600", "#00c8ff", "#0000ff"], - "si": { + UnitSystem.SI: { "unit": "Pa", "range": [0, 5000], }, - "ip": { + UnitSystem.IP: { "unit": "Psi", "range": [0, 5000 * 0.000145038], }, @@ -754,11 +756,11 @@ }, "p_sat": { "name": "Saturation pressure", - "si": { + UnitSystem.SI: { "unit": "Pa", "range": [0, 5000], }, - "ip": { + UnitSystem.IP: { "unit": "Psi", "range": [0, 5000 * 0.000145038], }, @@ -767,11 +769,11 @@ "hr": { "name": "Absolute humidity", "color": ["#ffe600", "#00c8ff", "#0000ff"], - "si": { + UnitSystem.SI: { "unit": "g water/kg dry air", "range": [0, 0.03 * 1000], }, - "ip": { + UnitSystem.IP: { "unit": "lb water/klb dry air", "range": [0, 0.03 * 1000], }, @@ -780,11 +782,11 @@ "t_wb": { "name": "Wet bulb temperature", "color": ["#00b3ff", "#000082", "#ff0000", "#ffff00"], - "si": { + UnitSystem.SI: { "unit": "°C", "range": [-40, 50], }, - "ip": { + UnitSystem.IP: { "unit": "°F", "range": [-40, 122], }, @@ -793,11 +795,11 @@ "t_dp": { "name": "Dew point temperature", "color": ["#00b3ff", "#000082", "#ff0000", "#ffff00"], - "si": { + UnitSystem.SI: { "unit": "°C", "range": [-40, 50], }, - "ip": { + UnitSystem.IP: { "unit": "°F", "range": [-40, 122], }, @@ -806,11 +808,11 @@ "h": { "name": "Enthalpy", "color": ["#00b3ff", "#000082", "#ff0000", "#ffff00"], - "si": { + UnitSystem.SI: { "unit": "J/kg dry air", "range": [0, 110000], }, - "ip": { + UnitSystem.IP: { "unit": "Btu/lb dry air", "range": [0, 110000 * 0.000429923], }, diff --git a/pages/lib/layout.py b/pages/lib/layout.py index 350122c8..bf1b1232 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -4,7 +4,7 @@ import dash_mantine_components as dmc from dash_iconify import DashIconify -from config import DocLinks +from config import DocLinks, UnitSystem def alert(): @@ -158,11 +158,17 @@ def banner(): ), dmc.SegmentedControl( id="si-ip-radio-input", - value="si", + value=UnitSystem.SI, radius="md", data=[ - {"label": "SI", "value": "si"}, - {"label": "IP", "value": "ip"}, + { + "label": UnitSystem.SI.upper(), + "value": UnitSystem.SI, + }, + { + "label": UnitSystem.IP.upper(), + "value": UnitSystem.IP, + }, ], ), ], diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index 34c5eba2..5f06e73c 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -4,6 +4,8 @@ import pandas as pd import plotly.graph_objects as go from plotly.subplots import make_subplots + +from config import UnitSystem from pages.lib.global_scheme import mapping_dictionary import dash_bootstrap_components as dbc from .global_scheme import month_lst, template, tight_margins @@ -487,7 +489,7 @@ def wind_rose(df, title, month, hour, labels, si_ip): spd_colors = mapping_dictionary["wind_speed"]["color"] spd_unit = mapping_dictionary["wind_speed"][si_ip]["unit"] spd_bins = [-1, 0.5, 1.5, 3.3, 5.5, 7.9, 10.7, 13.8, 17.1, 20.7, np.inf] - if si_ip == "ip": + if si_ip == UnitSystem.IP: spd_bins = convert_bins(spd_bins) spd_labels = speed_labels(spd_bins, spd_unit) diff --git a/pages/lib/utils.py b/pages/lib/utils.py index 49152444..ef3e4427 100644 --- a/pages/lib/utils.py +++ b/pages/lib/utils.py @@ -6,7 +6,8 @@ import pandas as pd from dash import html, dash_table, dcc -from pages.lib.global_scheme import fig_config, mapping_dictionary +from config import UnitSystem +from pages.lib.global_scheme import fig_config, mapping_dictionary, month_lst def code_timer(func): @@ -41,11 +42,15 @@ def generate_chart_name(tab_name, meta=None, custom_inputs=None, units=None): def generate_units(si_ip): - return "SI" if si_ip == "si" else "IP" if si_ip == "ip" else None + """Generate units for the chart titles.""" + if si_ip == UnitSystem.SI: + return UnitSystem.SI + else: + return UnitSystem.IP def generate_units_degree(si_ip): - return "C" if si_ip == "si" else "F" if si_ip == "ip" else None + return "C" if si_ip == UnitSystem.SI else "F" if si_ip == UnitSystem.IP else None def generate_custom_inputs(var): @@ -58,21 +63,7 @@ def generate_custom_inputs(var): def generate_custom_inputs_time(start_month, end_month, start_hour, end_hour): - month_names = [ - "", - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - ] + month_names = [""] + month_lst start_month_abbr = month_names[int(start_month)] end_month_abbr = month_names[int(end_month)] custom_inputs = ( @@ -84,21 +75,7 @@ def generate_custom_inputs_time(start_month, end_month, start_hour, end_hour): def generate_custom_inputs_nv( start_month, end_month, start_hour, end_hour, min_dbt_val, max_dbt_val ): - month_names = [ - "", - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - ] + month_names = [""] + month_lst start_month_abbr = month_names[int(start_month)] end_month_abbr = month_names[int(end_month)] custom_inputs = f"{min_dbt_val:02d}-{max_dbt_val:02d}_{start_month_abbr}-{end_month_abbr}_{start_hour:02d}-{end_hour:02d}" @@ -108,21 +85,7 @@ def generate_custom_inputs_nv( def generate_custom_inputs_explorer( var, start_month, end_month, start_hour, end_hour, filter_var, min_val, max_val ): - month_names = [ - "", - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - ] + month_names = [""] + month_lst start_month_abbr = month_names[int(start_month)] end_month_abbr = month_names[int(end_month)] if var in mapping_dictionary: @@ -152,21 +115,7 @@ def generate_custom_inputs_psy( min_val, max_val, ): - month_names = [ - "", - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - ] + month_names = [""] + month_lst start_month_abbr = month_names[int(start_month)] end_month_abbr = month_names[int(end_month)] if colorby_var in mapping_dictionary: diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 4698f491..32c62cdb 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -8,7 +8,7 @@ import numpy as np import plotly.graph_objects as go -from config import PageUrls, DocLinks, PageInfo +from config import PageUrls, DocLinks, PageInfo, UnitSystem from pages.lib.global_scheme import ( template, mapping_dictionary, @@ -49,7 +49,7 @@ def layout(): @callback(Output("main-nv-section", "children"), [Input("si-ip-radio-input", "value")]) def update_layout(si_ip): - if si_ip == "ip": + if si_ip == UnitSystem.IP: tdb_set_min = 50 tdb_set_max = 75 dpt_set = 61 diff --git a/pages/psy-chart.py b/pages/psy-chart.py index 4730d8ff..2c46b885 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -11,7 +11,7 @@ import plotly.graph_objects as go from pythermalcomfort import psychrometrics as psy -from config import PageUrls, DocLinks, PageInfo +from config import PageUrls, DocLinks, PageInfo, UnitSystem from pages.lib.global_scheme import ( container_row_center_full, container_col_center_one_of_three, @@ -352,7 +352,7 @@ def update_psych_chart( for k in range(len(rh_df[name])): rh_multiply[k] = rh_multiply[k] * 1000 - if si_ip == "ip": + if si_ip == UnitSystem.IP: for j in range(len(dbt_list)): dbt_list_convert[j] = dbt_list_convert[j] * 1.8 + 32 diff --git a/pages/select.py b/pages/select.py index 78266be1..710fb9c0 100644 --- a/pages/select.py +++ b/pages/select.py @@ -14,7 +14,7 @@ from pages.lib.extract_df import convert_data from pages.lib.extract_df import create_df, get_data, get_location_info from pages.lib.global_scheme import mapping_dictionary -from config import PageUrls, PageInfo +from config import PageUrls, PageInfo, UnitSystem from pages.lib.utils import generate_chart_name dash.register_page( @@ -218,7 +218,7 @@ def switch_si_ip(ts, si_ip_input, url_store, lines): if lines is not None: df, _ = create_df(lines, url_store) map_json = json.dumps(mapping_dictionary) - if si_ip_input == "ip": + if si_ip_input == UnitSystem.IP: map_json = convert_data(df, map_json) return Serverside(df), si_ip_input else: diff --git a/pages/summary.py b/pages/summary.py index 956a22eb..c6611c11 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -6,7 +6,7 @@ import plotly.graph_objects as go import requests -from config import PageUrls, DocLinks, PageInfo +from config import PageUrls, DocLinks, PageInfo, UnitSystem from pages.lib.charts_summary import world_map from pages.lib.extract_df import get_data from pages.lib.global_scheme import template, tight_margins, mapping_dictionary @@ -44,7 +44,7 @@ def layout(): Output("tab-two-container", "children"), [Input("si-ip-radio-input", "value")] ) def update_layout(si_ip): - if si_ip == "si": + if si_ip == UnitSystem.SI: heating_setpoint = 10 cooling_setpoint = 18 else: @@ -228,7 +228,7 @@ def update_location_info(ts, df, meta, si_ip): site_elevation = float(meta["site_elevation"]) site_elevation = round(site_elevation, 2) - if si_ip != "si": + if si_ip != UnitSystem.SI: site_elevation = site_elevation * 3.281 site_elevation = round(site_elevation, 2) elevation = f"Elevation above sea level: {str(site_elevation)} ft" @@ -516,7 +516,7 @@ def download_clima_dataframe(n_clicks, df, meta, si_ip): if n_clicks is None: raise PreventUpdate elif df is not None: - if si_ip == "si": + if si_ip == UnitSystem.SI: return dcc.send_data_frame( df.to_csv, f"df_{meta['city']}_{meta['country']}_Clima_SIunit.csv" ) diff --git a/pages/sun.py b/pages/sun.py index e7844fd7..2c3db2ef 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -6,7 +6,7 @@ from dash import html, dcc from dash_extensions.enrich import Output, Input, State, callback -from config import PageUrls, DocLinks, PageInfo +from config import PageUrls, DocLinks, PageInfo, UnitSystem from pages.lib.charts_sun import ( monthly_solar, polar_graph, @@ -164,9 +164,9 @@ def layout(): @callback(Output("static-section", "children"), [Input("si-ip-radio-input", "value")]) def update_static_section(si_ip): - if si_ip == "si": + if si_ip == UnitSystem.SI: hor_unit = "Wh/m²" - if si_ip == "ip": + if si_ip == UnitSystem.IP: hor_unit = "Btu/ft²" return [ html.Div( diff --git a/tests/python/test_utils.py b/tests/python/test_utils.py index e22777d1..eb0c59bd 100644 --- a/tests/python/test_utils.py +++ b/tests/python/test_utils.py @@ -3,6 +3,8 @@ import pandas as pd +from config import UnitSystem + root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) if root_dir not in sys.path: sys.path.append(root_dir) @@ -36,7 +38,7 @@ def test_summary_table_tmp_rh_tab(): # check tha the climate.onebuilding website is on print(requests.get("https://climate.onebuilding.org", timeout=2)) df = import_epw_test() - data_table = summary_table_tmp_rh_tab(df, "RH", "si") + data_table = summary_table_tmp_rh_tab(df, "RH", UnitSystem.SI) assert data_table.data[0]["month"] == "Jan" except requests.exceptions.ConnectionError: From 1d91d9e4b3cd78ae8203c18d257dd13c501b0e80 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 1 Aug 2025 17:49:20 +1000 Subject: [PATCH 65/91] lint: code --- pages/explorer.py | 22 +++++++++++----------- pages/lib/utils.py | 4 +++- pages/outdoor.py | 24 ++++++++++++------------ pages/select.py | 8 ++++---- pages/sun.py | 7 +++---- pages/t_rh.py | 8 ++++---- pages/wind.py | 12 +++++------- 7 files changed, 42 insertions(+), 43 deletions(-) diff --git a/pages/explorer.py b/pages/explorer.py index 69961ade..43384601 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -94,7 +94,7 @@ def section_one(): children=title_with_link( text="Daily chart", id_button="explore-daily-chart-label", - doc_link=DocLinks.CLIMA_DOCS, + doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, ), ), dcc.Loading( @@ -105,7 +105,7 @@ def section_one(): children=title_with_link( text="Heatmap chart", id_button="explore-heatmap-chart-label", - doc_link=DocLinks.CLIMA_DOCS, + doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, ), ), dcc.Loading( @@ -612,9 +612,9 @@ def section_three(): def layout(): - """Return the contents of tab six." """ + """Return the contents of tab six.""" return html.Div( - className="continer-col justify-center", + className="justify-center", children=[section_one(), section_two(), section_three()], ) @@ -633,7 +633,7 @@ def layout(): State("si-ip-unit-store", "data"), ], ) -def update_tab_yearly(ts, var, global_local, df, meta, si_ip): +def update_tab_yearly(_, var, global_local, df, meta, si_ip): """Update the contents of tab size. Passing in the info from the dropdown and the general info.""" if df[var].mean() == 99990.0: @@ -665,7 +665,7 @@ def update_tab_yearly(ts, var, global_local, df, meta, si_ip): State("si-ip-unit-store", "data"), ], ) -def update_tab_daily(ts, var, global_local, df, meta, si_ip): +def update_tab_daily(_, var, global_local, df, meta, si_ip): """Update the contents of tab size. Passing in the info from the dropdown and the general info.""" custom_inputs = generate_custom_inputs(var) units = generate_units(si_ip) @@ -690,7 +690,7 @@ def update_tab_daily(ts, var, global_local, df, meta, si_ip): State("si-ip-unit-store", "data"), ], ) -def update_tab_heatmap(ts, var, global_local, df, meta, si_ip): +def update_tab_heatmap(_, var, global_local, df, meta, si_ip): """Update the contents of tab size. Passing in the info from the dropdown and the general info.""" custom_inputs = generate_custom_inputs(var) units = generate_units(si_ip) @@ -732,7 +732,7 @@ def update_tab_heatmap(ts, var, global_local, df, meta, si_ip): ], ) def update_heatmap( - ts, + _, var, time_filter, data_filter, @@ -846,7 +846,7 @@ def update_heatmap( ], ) def update_more_charts( - ts, + _, var_x, var_y, color_by, @@ -930,9 +930,9 @@ def update_more_charts( ], ) def update_table( - ts, + _, dd_value, - n_clicks, + __, df, si_ip, month_range, diff --git a/pages/lib/utils.py b/pages/lib/utils.py index ef3e4427..0b6ccc24 100644 --- a/pages/lib/utils.py +++ b/pages/lib/utils.py @@ -269,12 +269,14 @@ def determine_month_and_hour_filter(month, hour, invert_month, invert_hour): return start_month, end_month, start_hour, end_hour -def dropdown(options={}, **kwargs): +def dropdown(options=None, **kwargs): """ Wrapper for dcc.Dropdown which - makes "clearable=False" the default, so we don't need to handle None - accepts dicts, and preserves order. """ + if options is None: + options = {} return dcc.Dropdown( options=[{"label": k, "value": v} for k, v in options.items()], clearable=False, diff --git a/pages/outdoor.py b/pages/outdoor.py index ce044743..59153930 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -220,27 +220,27 @@ def layout(): State("df-store", "data"), ], ) -def update_outdoor_comfort_output(ts, df): +def update_outdoor_comfort_output(_, df): cols = [ "utci_noSun_Wind_categories", "utci_noSun_noWind_categories", "utci_Sun_Wind_categories", "utci_Sun_noWind_categories", ] - colsWithTheHighestNumberOfZero = [] - highestCount = 0 + cols_with_the_highest_number_of_zero = [] + highest_count = 0 for col in cols: try: count = df[col].value_counts()[0] # this can cause error if there is no 0 - if count > highestCount: - highestCount = count - colsWithTheHighestNumberOfZero.clear() - colsWithTheHighestNumberOfZero.append(col) - elif count == highestCount: - colsWithTheHighestNumberOfZero.append(col) + if count > highest_count: + highest_count = count + cols_with_the_highest_number_of_zero.clear() + cols_with_the_highest_number_of_zero.append(col) + elif count == highest_count: + cols_with_the_highest_number_of_zero.append(col) except: continue - return f"The Best Weather Condition is: {', '.join(colsWithTheHighestNumberOfZero)}" + return f"The Best Weather Condition is: {', '.join(cols_with_the_highest_number_of_zero)}" @callback( @@ -262,7 +262,7 @@ def update_outdoor_comfort_output(ts, df): ], ) def update_tab_utci_value( - ts, + _, var, global_local, time_filter, @@ -329,7 +329,7 @@ def change_image_based_on_selection(value): ], ) def update_tab_utci_category( - ts, + _, var, global_local, time_filter, diff --git a/pages/select.py b/pages/select.py index 710fb9c0..cfc770d7 100644 --- a/pages/select.py +++ b/pages/select.py @@ -129,8 +129,8 @@ def alert(): ) # @code_timer def submitted_data( - use_epw_click, - upload_click, + _, + __, list_of_contents, list_of_names, url_store, @@ -214,7 +214,7 @@ def submitted_data( ], [State("url-store", "data"), State("lines-store", "data")], ) -def switch_si_ip(ts, si_ip_input, url_store, lines): +def switch_si_ip(_, si_ip_input, url_store, lines): if lines is not None: df, _ = create_df(lines, url_store) map_json = json.dumps(mapping_dictionary) @@ -290,7 +290,7 @@ def enable_tabs_when_data_is_loaded(meta, data): [State("modal", "is_open")], prevent_initial_call=True, ) -def display_modal_when_data_clicked(clicks_use_epw, click_map, close_clicks, is_open): +def display_modal_when_data_clicked(_, click_map, __, is_open): """display the modal to the user and check if he wants to use that file""" if click_map: url = re.search( diff --git a/pages/sun.py b/pages/sun.py index 2c3db2ef..27de58fd 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -164,8 +164,7 @@ def layout(): @callback(Output("static-section", "children"), [Input("si-ip-radio-input", "value")]) def update_static_section(si_ip): - if si_ip == UnitSystem.SI: - hor_unit = "Wh/m²" + hor_unit = "Wh/m²" if si_ip == UnitSystem.IP: hor_unit = "Btu/ft²" return [ @@ -251,7 +250,7 @@ def monthly_and_cloud_chart(_, df, meta, si_ip): State("si-ip-unit-store", "data"), ], ) -def sun_path_chart(ts, view, var, global_local, df, meta, si_ip): +def sun_path_chart(_, view, var, global_local, df, meta, si_ip): """Update the contents of tab four. Passing in the polar selection and the general info (df, meta).""" custom_inputs = "" if var == "None" else f"{var}" units = "" if var == "None" else generate_units(si_ip) @@ -280,7 +279,7 @@ def sun_path_chart(ts, view, var, global_local, df, meta, si_ip): State("si-ip-unit-store", "data"), ], ) -def daily(ts, var, global_local, df, meta, si_ip): +def daily(_, var, global_local, df, meta, si_ip): """Update the contents of tab four section two. Passing in the general info (df, meta).""" custom_inputs = generate_custom_inputs(var) units = generate_units(si_ip) diff --git a/pages/t_rh.py b/pages/t_rh.py index af8dbbf3..c3d90f82 100644 --- a/pages/t_rh.py +++ b/pages/t_rh.py @@ -109,7 +109,7 @@ def layout(): State("si-ip-unit-store", "data"), ], ) -def update_yearly_chart(ts, global_local, dd_value, df, meta, si_ip): +def update_yearly_chart(_, global_local, dd_value, df, meta, si_ip): if dd_value == dropdown_names[var_to_plot[0]]: dbt_yearly = yearly_profile(df, "DBT", global_local, si_ip) dbt_yearly.update_layout(xaxis=dict(rangeslider=dict(visible=True))) @@ -141,7 +141,7 @@ def update_yearly_chart(ts, global_local, dd_value, df, meta, si_ip): State("si-ip-unit-store", "data"), ], ) -def update_daily(ts, global_local, dd_value, df, meta, si_ip): +def update_daily(_, global_local, dd_value, df, meta, si_ip): if dd_value == dropdown_names[var_to_plot[0]]: units = generate_units_degree(si_ip) return dcc.Graph( @@ -179,7 +179,7 @@ def update_daily(ts, global_local, dd_value, df, meta, si_ip): State("si-ip-unit-store", "data"), ], ) -def update_heatmap(ts, global_local, dd_value, df, meta, si_ip): +def update_heatmap(_, global_local, dd_value, df, meta, si_ip): """Update the contents of tab three. Passing in general info (df, meta).""" if dd_value == dropdown_names[var_to_plot[0]]: units = generate_units_degree(si_ip) @@ -213,7 +213,7 @@ def update_heatmap(ts, global_local, dd_value, df, meta, si_ip): ], [State("df-store", "data"), State("si-ip-unit-store", "data")], ) -def update_table(ts, dd_value, df, si_ip): +def update_table(_, dd_value, df, si_ip): """Update the contents of tab three. Passing in general info (df, meta).""" return summary_table_tmp_rh_tab( df[["month", "hour", dd_value, "month_names"]], dd_value, si_ip diff --git a/pages/wind.py b/pages/wind.py index f431d9ea..ff90ce5a 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -363,7 +363,7 @@ def layout(): State("si-ip-unit-store", "data"), ], ) -def update_annual_wind_rose(ts, df, meta, si_ip): +def update_annual_wind_rose(_, df, meta, si_ip): """Update the contents of tab five. Passing in the info from the sliders and the general info (df, meta).""" annual = wind_rose(df, "", [1, 12], [1, 24], True, si_ip) @@ -388,7 +388,7 @@ def update_annual_wind_rose(ts, df, meta, si_ip): State("si-ip-unit-store", "data"), ], ) -def update_tab_wind_speed(ts, global_local, df, meta, si_ip): +def update_tab_wind_speed(_, global_local, df, meta, si_ip): """Update the contents of tab five. Passing in the info from the sliders and the general info (df, meta).""" speed = heatmap(df, "wind_speed", global_local, si_ip) @@ -441,7 +441,7 @@ def update_tab_wind_direction(global_local, df, meta, si_ip): ], ) def update_custom_wind_rose( - ts, start_month, start_hour, end_month, end_hour, df, meta, si_ip + _, start_month, start_hour, end_month, end_hour, df, meta, si_ip ): """Update the contents of tab five. Passing in the info from the sliders and the general info (df, meta).""" @@ -472,7 +472,6 @@ def update_custom_wind_rose( ) -### Seasonal Graphs ### @callback( [ Output("winter-wind-rose", "children"), @@ -493,7 +492,7 @@ def update_custom_wind_rose( State("si-ip-unit-store", "data"), ], ) -def update_seasonal_graphs(ts, df, meta, si_ip): +def update_seasonal_graphs(_, df, meta, si_ip): hours = [1, 24] winter_months = [12, 2] spring_months = [3, 5] @@ -588,7 +587,6 @@ def seasonal_chart_caption(month_start, month_end, count, n_calm): ) -### Daily Graphs ### @callback( # Daily Graphs [ @@ -607,7 +605,7 @@ def seasonal_chart_caption(month_start, month_end, count, n_calm): State("si-ip-unit-store", "data"), ], ) -def update_daily_graphs(ts, df, meta, si_ip): +def update_daily_graphs(_, df, meta, si_ip): """Update the contents of tab five. Passing in the info from the sliders and the general info (df, meta).""" months = [1, 12] From f1146ee1ac428479b7d9eb9bbb8d4dde94b77509 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 1 Aug 2025 17:59:24 +1000 Subject: [PATCH 66/91] feat: updated footer --- pages/lib/layout.py | 49 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/pages/lib/layout.py b/pages/lib/layout.py index bf1b1232..f42ac28e 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -37,6 +37,7 @@ def alert(): def footer(): + """Build the footer at the bottom of the page.""" return dbc.Row( align="center", justify="between", @@ -69,17 +70,47 @@ def footer(): Betti, G., Tartarini, F., Nguyen, C, Schiavon, S. CBE Clima Tool: A free and open-source web application for climate analysis tailored to sustainable building design. Build. Simul. (2023). [https://doi.org/10.1007/s12273-023-1090-5](https://doi.org/10.1007/s12273-023-1090-5). - [Version: 0.8.17](https://center-for-the-built-environment.gitbook.io/clima/version/changelog) """ ), - dcc.Markdown( - """ - [Contributors](https://cbe-berkeley.gitbook.io/clima/#contributions), - [Report issues on GitHub](https://github.com/CenterForTheBuiltEnvironment/clima/issues), - [Contact us page](https://github.com/CenterForTheBuiltEnvironment/clima/discussions), - [Documentation page](https://center-for-the-built-environment.gitbook.io/clima/), - [License](https://center-for-the-built-environment.gitbook.io/clima/#license) - """, + dmc.Group( + [ + dmc.Anchor( + "Contributors", + href="https://cbe-berkeley.gitbook.io/clima/#contributions", + underline=True, + c="white", + target="_blank", + ), + dmc.Anchor( + "Report issues on GitHub", + href="https://github.com/CenterForTheBuiltEnvironment/clima/issues", + underline=True, + c="white", + target="_blank", + ), + dmc.Anchor( + "Contact us", + href="https://github.com/CenterForTheBuiltEnvironment/clima/discussions", + underline=True, + c="white", + target="_blank", + ), + dmc.Anchor( + "Documentation", + href="https://center-for-the-built-environment.gitbook.io/clima/", + underline=True, + c="white", + target="_blank", + ), + dmc.Anchor( + "License", + href="https://center-for-the-built-environment.gitbook.io/clima/#license", + underline=True, + c="white", + target="_blank", + ), + ], + spacing="sm", style={"marginTop": "1rem"}, ), ], From 0823643ca3888971328ca7fca4612750c885de32 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 1 Aug 2025 18:01:08 +1000 Subject: [PATCH 67/91] feat: added back version to footer --- pages/lib/layout.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pages/lib/layout.py b/pages/lib/layout.py index f42ac28e..fc016a41 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -74,6 +74,13 @@ def footer(): ), dmc.Group( [ + dmc.Anchor( + "Version: 0.8.17", + href="https://center-for-the-built-environment.gitbook.io/clima/version/changelog", + underline=True, + c="white", + target="_blank", + ), dmc.Anchor( "Contributors", href="https://cbe-berkeley.gitbook.io/clima/#contributions", From 09060f45ab61d4a805d7b5b9bf9f7851bc5c11d3 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 1 Aug 2025 18:01:52 +1000 Subject: [PATCH 68/91] build: updated .bumpversion.cfg --- .bumpversion.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 6e613412..aac3bc4c 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -3,7 +3,7 @@ current_version = 0.8.17 commit = True tag = True -[bumpversion:file:my_project/layout.py] +[bumpversion:file:pages/lib/layout.py] search = Version: {current_version} replace = Version: {new_version} From 56afe934d8f80ce030792e846e96a0005bcc7dd6 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 1 Aug 2025 18:01:55 +1000 Subject: [PATCH 69/91] =?UTF-8?q?Bump=20version:=200.8.17=20=E2=86=92=200.?= =?UTF-8?q?8.18?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- assets/manifest.json | 2 +- pages/lib/layout.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index aac3bc4c..a862796a 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.8.17 +current_version = 0.8.18 commit = True tag = True diff --git a/assets/manifest.json b/assets/manifest.json index 0fae249d..40b8618d 100644 --- a/assets/manifest.json +++ b/assets/manifest.json @@ -467,7 +467,7 @@ "orientation": "portrait", "background_color": "#ffffff", "display": "standalone", - "id": "0.8.17", + "id": "0.8.18", "description": "CBE Clima Tool: a free and open-source web application for climate analysis tailored to sustainable building design", "start_url": "/", "scope": "/", diff --git a/pages/lib/layout.py b/pages/lib/layout.py index fc016a41..1edea71d 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -75,7 +75,7 @@ def footer(): dmc.Group( [ dmc.Anchor( - "Version: 0.8.17", + "Version: 0.8.18", href="https://center-for-the-built-environment.gitbook.io/clima/version/changelog", underline=True, c="white", From b10dc7c2214209cff5af6080a7f01521f7c9e007 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 1 Aug 2025 18:09:46 +1000 Subject: [PATCH 70/91] fix: error in sun.py --- pages/sun.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pages/sun.py b/pages/sun.py index 27de58fd..a77c4c7f 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -55,9 +55,11 @@ def sun_path(): className="container-col justify-center", children=[ html.Div( - text="Sun path chart", - id_button="sun-path-chart-label", - doc_link=DocLinks.SUN_PATH_DIAGRAM, + children=title_with_link( + text="Sun path chart", + id_button="sun-path-chart-label", + doc_link=DocLinks.SUN_PATH_DIAGRAM, + ), ), dbc.Row( align="center", From 1568c59e63025a6a6982be0b545eba5b817c6be7 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 1 Aug 2025 18:16:29 +1000 Subject: [PATCH 71/91] lint: formatting with black and ruff match --- pages/explorer.py | 12 +++++------- pages/lib/utils.py | 5 ++--- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/pages/explorer.py b/pages/explorer.py index 43384601..f2c899bd 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -803,13 +803,11 @@ def update_heatmap( {}, ) custom_inputs = f"{var}" - units = ( - UnitSystem.SI.upper() - if si_ip == UnitSystem.SI - else UnitSystem.IP.upper() - if si_ip == UnitSystem.IP - else None - ) + + units = UnitSystem.SI.upper() + if si_ip == UnitSystem.IP: + units = UnitSystem.IP.upper() + return ( dcc.Graph( config=generate_chart_name("heatmap", meta, custom_inputs, units), diff --git a/pages/lib/utils.py b/pages/lib/utils.py index 0b6ccc24..860a0409 100644 --- a/pages/lib/utils.py +++ b/pages/lib/utils.py @@ -33,9 +33,8 @@ def generate_chart_name(tab_name, meta=None, custom_inputs=None, units=None): if units: custom_str += f"_{units}" if meta: - figure_config["toImageButtonOptions"]["filename"] = ( - f"{meta['city']}_{meta['country']}_{tab_name}{custom_str}" - ) + file_name = f"{meta['city']}_{meta['country']}_{tab_name}{custom_str}" + figure_config["toImageButtonOptions"]["filename"] = file_name else: figure_config["toImageButtonOptions"]["filename"] = f"{tab_name}{custom_str}" return figure_config From c6948f879501d2b540e1942e99354953dabc58ba Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 21 Aug 2025 10:00:01 +1000 Subject: [PATCH 72/91] feat(viz): Update wind rose label to annual description Change the label from "Wind Rose" to "Annual Wind Rose" for better clarity and context in the visualization. --- pages/wind.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/wind.py b/pages/wind.py index ff90ce5a..0e48d6d3 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -327,7 +327,7 @@ def layout(): children=[ html.Div( children=title_with_link( - text="Wind Rose", + text="Annual Wind Rose", id_button="wind-rose-label", doc_link=DocLinks.WIND_ROSE, ), From dc2574a5878151ccfe9f0059c22350a956d3cb0f Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 21 Aug 2025 10:12:12 +1000 Subject: [PATCH 73/91] fix(outdoor): Handle errors in zero value count Improve robustness of the update_outdoor_comfort_output function by catching KeyError and TypeError exceptions when counting zero values in specified columns. Added docstring for better understanding of function parameters and return value. --- pages/outdoor.py | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/pages/outdoor.py b/pages/outdoor.py index 59153930..ab8d53e1 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -221,6 +221,18 @@ def layout(): ], ) def update_outdoor_comfort_output(_, df): + """ + Find the column(s) with the highest number of zero values. + + Args: + _: Unused callback input. + df: DataFrame-like object containing UTCI category columns. + + Returns + ------- + str + Description of the best weather condition(s). + """ cols = [ "utci_noSun_Wind_categories", "utci_noSun_noWind_categories", @@ -231,15 +243,16 @@ def update_outdoor_comfort_output(_, df): highest_count = 0 for col in cols: try: - count = df[col].value_counts()[0] # this can cause error if there is no 0 - if count > highest_count: - highest_count = count - cols_with_the_highest_number_of_zero.clear() - cols_with_the_highest_number_of_zero.append(col) - elif count == highest_count: - cols_with_the_highest_number_of_zero.append(col) - except: + count = df[col].value_counts()[0] + except (KeyError, TypeError): + # KeyError: 0 not in value_counts; TypeError: df[col] is not valid continue + if count > highest_count: + highest_count = count + cols_with_the_highest_number_of_zero.clear() + cols_with_the_highest_number_of_zero.append(col) + elif count == highest_count: + cols_with_the_highest_number_of_zero.append(col) return f"The Best Weather Condition is: {', '.join(cols_with_the_highest_number_of_zero)}" From 8d0e5b92cf823ebaca223474ec31f936c6bd5158 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 21 Aug 2025 10:12:20 +1000 Subject: [PATCH 74/91] fix(extract): Log error when fetching EPW data Add logging to capture exceptions during the EPW data retrieval process, improving error handling and debugging capabilities. --- pages/lib/extract_df.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pages/lib/extract_df.py b/pages/lib/extract_df.py index a0def908..b030a3eb 100644 --- a/pages/lib/extract_df.py +++ b/pages/lib/extract_df.py @@ -6,6 +6,7 @@ from datetime import timedelta from urllib.request import Request, urlopen +import logging import numpy as np import pandas as pd import requests @@ -42,7 +43,8 @@ def get_data(source_url): req = Request(source_url, headers=headers) epw = urlopen(req).read().decode() return epw.split("\n") - except: + except Exception as e: + logging.error(f"Failed to fetch EPW data: {e}") return None From 8dcb13b1e6a851323d51b1f1a772e60721ea8eb1 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 21 Aug 2025 10:12:36 +1000 Subject: [PATCH 75/91] fix(precommit): use pre-commit for ruff check and format --- .pre-commit-config.yaml | 10 + Pipfile | 1 + Pipfile.lock | 341 +++++++++++++++++++----------- docs/contributing/contributing.md | 11 +- 4 files changed, 243 insertions(+), 120 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..8a2b92b7 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,10 @@ +repos: +- repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.12.9 + hooks: + # Run the linter. + - id: ruff-check + args: [ --fix ] + # Run the formatter. + - id: ruff-format \ No newline at end of file diff --git a/Pipfile b/Pipfile index b1667a53..91288833 100644 --- a/Pipfile +++ b/Pipfile @@ -23,6 +23,7 @@ pytest = "*" bump2version = "*" black = "*" ruff = "*" +pre-commit = "*" [requires] python_version = "3.11" diff --git a/Pipfile.lock b/Pipfile.lock index beb16723..26a3f806 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "f96acae67c176ca7533141c154725a6a5563f14bf40da515952c2ee9b02a74c8" + "sha256": "7214a1158f64483648ecff36a57b61b67020408e0f16770b77d7165a9d6f47e0" }, "pipfile-spec": 6, "requires": { @@ -34,109 +34,96 @@ }, "certifi": { "hashes": [ - "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", - "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995" + "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", + "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5" ], "markers": "python_version >= '3.7'", - "version": "==2025.7.14" + "version": "==2025.8.3" }, "charset-normalizer": { "hashes": [ - "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4", - "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45", - "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", - "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", - "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", - "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", - "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d", - "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", - "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184", - "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", - "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b", - "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64", - "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", - "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", - "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", - "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344", - "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58", - "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", - "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471", - "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", - "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", - "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836", - "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", - "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", - "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", - "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1", - "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01", - "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", - "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58", - "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", - "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", - "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2", - "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a", - "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597", - "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", - "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5", - "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb", - "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f", - "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", - "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", - "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", - "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", - "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7", - "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7", - "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455", - "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", - "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4", - "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", - "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3", - "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", - "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", - "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", - "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", - "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", - "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", - "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", - "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12", - "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa", - "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", - "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", - "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f", - "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", - "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", - "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5", - "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02", - "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", - "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", - "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e", - "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", - "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", - "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", - "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", - "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681", - "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba", - "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", - "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a", - "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", - "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", - "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", - "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", - "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027", - "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7", - "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518", - "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", - "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", - "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", - "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", - "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da", - "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", - "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f", - "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", - "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f" + "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91", + "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0", + "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", + "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601", + "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", + "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07", + "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c", + "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64", + "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", + "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", + "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432", + "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", + "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", + "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", + "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae", + "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19", + "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", + "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e", + "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4", + "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7", + "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312", + "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", + "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", + "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c", + "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", + "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99", + "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b", + "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", + "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", + "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", + "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", + "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", + "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0", + "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc", + "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", + "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f", + "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a", + "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40", + "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", + "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849", + "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", + "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", + "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05", + "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", + "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c", + "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a", + "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", + "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34", + "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9", + "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", + "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14", + "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30", + "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b", + "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b", + "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942", + "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", + "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", + "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b", + "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", + "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669", + "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0", + "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", + "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", + "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe", + "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", + "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", + "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", + "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2", + "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca", + "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", + "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f", + "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb", + "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", + "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557", + "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", + "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7", + "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72", + "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c", + "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9" ], "markers": "python_version >= '3.7'", - "version": "==3.4.2" + "version": "==3.4.3" }, "click": { "hashes": [ @@ -589,11 +576,11 @@ }, "retrying": { "hashes": [ - "sha256:4d206e0ed2aff5ef2f3cd867abb9511e9e8f31127c5aca20f1d5246e476903b0", - "sha256:d736050c1adfc0a71fa022d9198ee130b0e66be318678a3fdd8b1b8872dc0997" + "sha256:bbc004aeb542a74f3569aeddf42a2516efefcdaff90df0eb38fbfbf19f179f59", + "sha256:d102e75d53d8d30b88562d45361d6c6c934da06fab31bd81c0420acb97a8ba39" ], "markers": "python_version >= '3.6'", - "version": "==1.4.1" + "version": "==1.4.2" }, "scipy": { "hashes": [ @@ -731,6 +718,14 @@ "markers": "python_version >= '3.5'", "version": "==1.0.1" }, + "cfgv": { + "hashes": [ + "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", + "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560" + ], + "markers": "python_version >= '3.8'", + "version": "==3.4.0" + }, "cleanpy": { "hashes": [ "sha256:9ddfa7ce80dd888b597a8b0bfeea3b69567839b6f41b775a4f76f46914d5170e", @@ -748,6 +743,29 @@ "markers": "python_version >= '3.10'", "version": "==8.2.1" }, + "distlib": { + "hashes": [ + "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", + "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d" + ], + "version": "==0.4.0" + }, + "filelock": { + "hashes": [ + "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", + "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d" + ], + "markers": "python_version >= '3.9'", + "version": "==3.19.1" + }, + "identify": { + "hashes": [ + "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b", + "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32" + ], + "markers": "python_version >= '3.9'", + "version": "==2.6.13" + }, "iniconfig": { "hashes": [ "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", @@ -764,6 +782,14 @@ "markers": "python_version >= '3.8'", "version": "==1.1.0" }, + "nodeenv": { + "hashes": [ + "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", + "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", + "version": "==1.9.1" + }, "packaging": { "hashes": [ "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", @@ -796,6 +822,15 @@ "markers": "python_version >= '3.9'", "version": "==1.6.0" }, + "pre-commit": { + "hashes": [ + "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", + "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==4.3.0" + }, "pygments": { "hashes": [ "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", @@ -813,30 +848,98 @@ "markers": "python_version >= '3.9'", "version": "==8.4.1" }, + "pyyaml": { + "hashes": [ + "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", + "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", + "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", + "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", + "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", + "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", + "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", + "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", + "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", + "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", + "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", + "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", + "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", + "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", + "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", + "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", + "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", + "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", + "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", + "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", + "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", + "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", + "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", + "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", + "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", + "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", + "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", + "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", + "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", + "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", + "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", + "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", + "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", + "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", + "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", + "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", + "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", + "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", + "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", + "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", + "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", + "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", + "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", + "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", + "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", + "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", + "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", + "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", + "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", + "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", + "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", + "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", + "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4" + ], + "markers": "python_version >= '3.8'", + "version": "==6.0.2" + }, "ruff": { "hashes": [ - "sha256:06bfb01e1623bf7f59ea749a841da56f8f653d641bfd046edee32ede7ff6c606", - "sha256:1fc3193f238bc2d7968772c82831a4ff69252f673be371fb49663f0068b7ec71", - "sha256:2e1c2a3b8626339bb6369116e7030a4cf194ea48f49b64bb505732a7fce4f4e3", - "sha256:32dec41817623d388e645612ec70d5757a6d9c035f3744a52c7b195a57e03860", - "sha256:4000623300563c709458d0ce170c3d0d788c23a058912f28bbadc6f905d67afa", - "sha256:47ef751f722053a5df5fa48d412dbb54d41ab9b17875c6840a58ec63ff0c247c", - "sha256:5726f59b171111fa6a69d82aef48f00b56598b03a22f0f4170664ff4d8298efb", - "sha256:5d0bfe4e77fba61bf2ccadf8cf005d6133e3ce08793bbe870dd1c734f2699a3e", - "sha256:69ffe0e5f9b2cf2b8e289a3f8945b402a1b19eff24ec389f45f23c42a3dd6fb5", - "sha256:74e6f5c04c4dd4aba223f4fe6e7104f79e0eebf7d307e4f9b18c18362124bccd", - "sha256:76e4f31529899b8c434c3c1dede98c4483b89590e15fb49f2d46183801565303", - "sha256:789b7a03e72507c54fb3ba6209e4bb36517b90f1a3569ea17084e3fd295500fb", - "sha256:9c18f3d707ee9edf89da76131956aba1270c6348bfee8f6c647de841eac7194f", - "sha256:a07a5c8ffa2611a52732bdc67bf88e243abd84fe2d7f6daef3826b59abbfeda4", - "sha256:a828a5fc25a3efd3e1ff7b241fd392686c9386f20e5ac90aa9234a5faa12c423", - "sha256:c928f1b2ec59fb77dfdf70e0419408898b63998789cc98197e15f560b9e77f77", - "sha256:dfce05101dbd11833a0776716d5d1578641b7fddb537fe7fa956ab85d1769b69", - "sha256:e41df94a957d50083fd09b916d6e89e497246698c3f3d5c681c8b3e7b9bb4ac8" + "sha256:07adb221c54b6bba24387911e5734357f042e5669fa5718920ee728aba3cbadc", + "sha256:17d5b6b0b3a25259b69ebcba87908496e6830e03acfb929ef9fd4c58675fa2ea", + "sha256:1b15599931a1a7a03c388b9c5df1bfa62be7ede6eb7ef753b272381f39c3d0ff", + "sha256:3d02faa2977fb6f3f32ddb7828e212b7dd499c59eb896ae6c03ea5c303575756", + "sha256:43f07a3ccfc62cdb4d3a3348bf0588358a66da756aa113e071b8ca8c3b9826af", + "sha256:5b15ea354c6ff0d7423814ba6d44be2807644d0c05e9ed60caca87e963e93f70", + "sha256:63c8c819739d86b96d500cce885956a1a48ab056bbcbc61b747ad494b2485089", + "sha256:6fb15b1977309741d7d098c8a3cb7a30bc112760a00fb6efb7abc85f00ba5908", + "sha256:72db7521860e246adbb43f6ef464dd2a532ef2ef1f5dd0d470455b8d9f1773e0", + "sha256:881465ed56ba4dd26a691954650de6ad389a2d1fdb130fe51ff18a25639fe4bb", + "sha256:9fc83e4e9751e6c13b5046d7162f205d0a7bac5840183c5beebf824b08a27340", + "sha256:a03242c1522b4e0885af63320ad754d53983c9599157ee33e77d748363c561ce", + "sha256:aed9d15f8c5755c0e74467731a007fcad41f19bcce41cd75f768bbd687f8535f", + "sha256:cc7a37bd2509974379d0115cc5608a1a4a6c4bff1b452ea69db83c8855d53f93", + "sha256:d596c2d0393c2502eaabfef723bd74ca35348a8dac4267d18a94910087807c53", + "sha256:f5cd34fabfdea3933ab85d72359f118035882a01bff15bd1d2b15261d85d5f66", + "sha256:f6be1d2ca0686c54564da8e7ee9e25f93bdd6868263805f8c0b8fc6a449db6d7", + "sha256:fbd94b2e3c623f659962934e52c2bea6fc6da11f667a427a368adaf3af2c866a", + "sha256:fcebc6c79fcae3f220d05585229463621f5dbf24d79fdc4936d9302e177cfa3e" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==0.12.7" + "version": "==0.12.9" + }, + "virtualenv": { + "hashes": [ + "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026", + "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a" + ], + "markers": "python_version >= '3.8'", + "version": "==20.34.0" } } } diff --git a/docs/contributing/contributing.md b/docs/contributing/contributing.md index fa8fb538..bc625143 100644 --- a/docs/contributing/contributing.md +++ b/docs/contributing/contributing.md @@ -16,7 +16,16 @@ Available [here](code_of_conduct.md) ## Code style -We use Black.exe to format the code. +We use ruff to enforce the code style and code formatting. You can run it with: + +```bash +pipenv run ruff check . +pipenv run ruff format . +``` + +To ensure that your code is formatted correctly, we have a pre-commit hook that will run ruff before every commit. +Hence, you will need to make that the code is formatted correctly before committing your changes otherwise the commit will fail. +More information about pre-commit hooks can be found [here](https://pre-commit.com/). ## Submitting changes From 0f1ce276b5d5e28510127e5e3844b743ad032fe3 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 21 Aug 2025 10:13:41 +1000 Subject: [PATCH 76/91] fix(tests): Ensure root directory is added to sys.path Refactor the code to maintain the addition of the root directory to sys.path, ensuring that the necessary modules can be imported correctly during test execution. --- tests/python/test_utils.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/python/test_utils.py b/tests/python/test_utils.py index eb0c59bd..0b6cd488 100644 --- a/tests/python/test_utils.py +++ b/tests/python/test_utils.py @@ -5,16 +5,17 @@ from config import UnitSystem -root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) -if root_dir not in sys.path: - sys.path.append(root_dir) - import requests from pages.lib.utils import summary_table_tmp_rh_tab from pages.lib.extract_df import get_data, create_df +root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) +if root_dir not in sys.path: + sys.path.append(root_dir) + + def save_epw_test(path_file): test_url = "http://climate.onebuilding.org/WMO_Region_6_Europe/ITA_Italy/ER_Emilia-Romagna/ITA_ER_Bologna-Marconi.AP.161400_TMYx.2004-2018.zip" From f5fed9c33b65406eb37f7b9e8cf06209987f3a0e Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 21 Aug 2025 10:27:32 +1000 Subject: [PATCH 77/91] ci(cypress): Update Cypress configuration for development branch Specify the development branch for push and pull request events to ensure consistent testing during development. --- .github/workflows/cypress.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index 0a8d7a21..36aa2465 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -1,5 +1,11 @@ -name: Cypress 🌲 -on: [push, pull_request] +name: Cypress Testing 🌲 +on: + push: + branches: + - development + pull_request: + branches: + - development jobs: cypress: runs-on: ubuntu-latest From c6197230d277b506be5d6da29d8ad800465df3e2 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 21 Aug 2025 10:27:41 +1000 Subject: [PATCH 78/91] ci(python): Update Python CI configuration for development branch Enhance the CI workflow by specifying the development branch for push and pull request events. Add Ruff checks and formatting steps to ensure code quality before tests are run. --- .github/workflows/python.yml | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 919693a6..7a8ced32 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -1,5 +1,11 @@ -name: Python 🐍 -on: [push, pull_request] +name: Python Testing 🐍 +on: + push: + branches: + - development + pull_request: + branches: + - development jobs: pytest: runs-on: ubuntu-latest @@ -17,13 +23,14 @@ jobs: pip install pipenv pipenv install --dev - - name: Test Clima + - name: Ruff Check run: |- - pipenv run python -m pytest + pipenv run ruff check . + + - name: Ruff Format + run: |- + pipenv run ruff format . - - name: Run Black - # TODO: Add to dev dependencies: Adding it right now - # bumps other dependencies and the application won't run. + - name: Test Clima run: |- - pip install black - black . --check + pipenv run python -m pytest From ed0239ebdc22b26d6897f0a2dfc1e904caae666a Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 21 Aug 2025 10:32:03 +1000 Subject: [PATCH 79/91] ci(deploy): Update deployment workflow for Google Cloud Run Upgrade actions to latest versions and streamline deployment steps. Change project ID and image naming conventions for better clarity. --- .github/workflows/deploy.yml | 41 ++++++++++++------------------------ 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e550f2d6..29843937 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,46 +1,31 @@ # .github/workflows/deploy.yml name: Deploy 🚀 Clima to Google Cloud Run (☁🏃) + on: push: branches: - main + jobs: deploy: name: Deploying 🚀 Clima runs-on: ubuntu-latest - if: "contains(github.event.head_commit.message, 'bump version')" steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - - name: Setup python - uses: actions/setup-python@v4 + - id: auth + uses: google-github-actions/auth@v1 with: - python-version: '3.11' - - - name: Export gcloud related env variable - run: export CLOUDSDK_PYTHON="/usr/bin/python3" + credentials_json: ${{ secrets.GCP_SA_KEY_JSON }} - # Build and push image to Google Container Registry - - name: Setting up - uses: google-github-actions/setup-gcloud@v0 + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@v1 with: - version: '318.0.0' - service_account_key: ${{ secrets.GCP_SA_KEY_JSON }} - service_account_email: "federico.tartarini@bears-berkeley.sg" - project_id: clima-316917 + project_id: heat-stress-scale - - name: Building (🏗️) - run: |- + - name: Building (🏗️) and Deploying (🚀) + run: | gcloud builds submit \ - --tag us-docker.pkg.dev/clima-316917/gcr.io/clima - - # Setup gcloud CLI - - name: Deploy (🚀) - uses: google-github-actions/deploy-cloudrun@v1 - with: - service: clima - image: us-docker.pkg.dev/clima-316917/gcr.io/clima - region: us-central1 - credentials: ${{ secrets.GCP_SA_KEY_JSON }} - project_id: clima-316917 \ No newline at end of file + --project=clima-316917 \ + --substitutions=_REPO_NAME="clima",_PROJ_NAME="clima-316917",_IMG_NAME="main",_GCR="us.gcr.io",_REGION="us-central1",_MEMORY="4Gi",_CPU="2" From 250077f5103b8719c76a8242ba5b58a0fbf1d2ee Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 21 Aug 2025 10:32:36 +1000 Subject: [PATCH 80/91] =?UTF-8?q?Bump=20version:=200.8.18=20=E2=86=92=200.?= =?UTF-8?q?8.19?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- assets/manifest.json | 2 +- pages/lib/layout.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index a862796a..c887b56e 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.8.18 +current_version = 0.8.19 commit = True tag = True diff --git a/assets/manifest.json b/assets/manifest.json index 40b8618d..2fc515d5 100644 --- a/assets/manifest.json +++ b/assets/manifest.json @@ -467,7 +467,7 @@ "orientation": "portrait", "background_color": "#ffffff", "display": "standalone", - "id": "0.8.18", + "id": "0.8.19", "description": "CBE Clima Tool: a free and open-source web application for climate analysis tailored to sustainable building design", "start_url": "/", "scope": "/", diff --git a/pages/lib/layout.py b/pages/lib/layout.py index 1edea71d..d44af6c5 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -75,7 +75,7 @@ def footer(): dmc.Group( [ dmc.Anchor( - "Version: 0.8.18", + "Version: 0.8.19", href="https://center-for-the-built-environment.gitbook.io/clima/version/changelog", underline=True, c="white", From cb224486255b855603b2ee09ffa2586e652db724 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 21 Aug 2025 10:32:53 +1000 Subject: [PATCH 81/91] =?UTF-8?q?Bump=20version:=200.8.19=20=E2=86=92=200.?= =?UTF-8?q?9.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- assets/manifest.json | 2 +- pages/lib/layout.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index c887b56e..c6fbd8fa 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.8.19 +current_version = 0.9.0 commit = True tag = True diff --git a/assets/manifest.json b/assets/manifest.json index 2fc515d5..591f5946 100644 --- a/assets/manifest.json +++ b/assets/manifest.json @@ -467,7 +467,7 @@ "orientation": "portrait", "background_color": "#ffffff", "display": "standalone", - "id": "0.8.19", + "id": "0.9.0", "description": "CBE Clima Tool: a free and open-source web application for climate analysis tailored to sustainable building design", "start_url": "/", "scope": "/", diff --git a/pages/lib/layout.py b/pages/lib/layout.py index d44af6c5..ea7488b7 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -75,7 +75,7 @@ def footer(): dmc.Group( [ dmc.Anchor( - "Version: 0.8.19", + "Version: 0.9.0", href="https://center-for-the-built-environment.gitbook.io/clima/version/changelog", underline=True, c="white", From be9a3486cd0f6eb5a4751d352301f232b3df92b2 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 21 Aug 2025 11:15:18 +1000 Subject: [PATCH 82/91] docs: Update documentation for clarity and formatting Refine text in various markdown files to improve readability and remove unnecessary formatting characters. Ensure consistency in presentation across the documentation. --- docs/README.md | 4 +++- docs/contributing/contributing.md | 12 ++++++++++-- docs/contributing/run-project-locally.md | 2 +- docs/documentation/tabs-explained/README.md | 2 +- .../tabs-explained/natural-ventilation.md | 2 -- .../outdoor-comfort/utci-explained.md | 6 +++--- .../tabs-explained/psychrometric-chart/README.md | 4 ++-- .../psychrometric-chart-explained.md | 4 ++-- .../tabs-explained/sun-and-cloud/README.md | 2 +- .../sun-and-cloud/cloud-coverage.md | 2 +- .../customizable-daily-and-hourly-maps.md | 2 +- .../README.md | 2 +- docs/documentation/tabs-explained/tab-home.md | 6 +++--- .../tabs-explained/tab-summary/README.md | 3 ++- .../tab-summary/clima-dataframe.md | 16 ++++++++-------- .../tab-summary/climate-profiles-explained.md | 4 +++- .../tab-summary/degree-days-explained.md | 2 +- .../wind/how-to-read-a-wind-rose.md | 2 +- pages/not_found_404.py | 4 +--- 19 files changed, 45 insertions(+), 36 deletions(-) diff --git a/docs/README.md b/docs/README.md index 12986645..05ce6e22 100644 --- a/docs/README.md +++ b/docs/README.md @@ -10,7 +10,7 @@ The CBE Clima Tool is a web-based application built to support climate analysis The CBE Clima Tool is open source. We have released the source code on a [public repository](https://github.com/CenterForTheBuiltEnvironment/clima). We welcome contributions from the community ([more info here](contributing/contributing.md)). -### Video Tutorial +## Video Tutorial Learn more about the CBE Clima Tool by watching the following video. @@ -18,6 +18,8 @@ Learn more about the CBE Clima Tool by watching the following video. CBE Clima tool tutorial and overview {% endembed %} +[Watch on YouTube](https://www.youtube.com/watch?v=VJ_wOHadVdw) + ## Contributions This ongoing project results from the collaboration and contributions of the people listed below. diff --git a/docs/contributing/contributing.md b/docs/contributing/contributing.md index bc625143..48aa2860 100644 --- a/docs/contributing/contributing.md +++ b/docs/contributing/contributing.md @@ -23,8 +23,16 @@ pipenv run ruff check . pipenv run ruff format . ``` -To ensure that your code is formatted correctly, we have a pre-commit hook that will run ruff before every commit. -Hence, you will need to make that the code is formatted correctly before committing your changes otherwise the commit will fail. +To ensure that the code is formatted correctly, we use a pre-commit hook that runs Ruff before every commit. +Run the following once to enable hooks in your local repo: + +```bash +pipenv run pre-commit install +# optional: run on all files +pipenv run pre-commit run --all-files +``` + +Hence, you will need to make sure that the code is formatted correctly before committing your changes; otherwise, the commit will fail. More information about pre-commit hooks can be found [here](https://pre-commit.com/). ## Submitting changes diff --git a/docs/contributing/run-project-locally.md b/docs/contributing/run-project-locally.md index fef988bd..e125cd23 100644 --- a/docs/contributing/run-project-locally.md +++ b/docs/contributing/run-project-locally.md @@ -27,7 +27,7 @@ This guide is for Mac OSX, Linux, or Windows. 2. **Create a virtual environment using pipenv and install dependencies:** ```text - pipenv install + pipenv install --dev ``` 3. **Run tool locally** diff --git a/docs/documentation/tabs-explained/README.md b/docs/documentation/tabs-explained/README.md index 12b13e7a..b10d8a35 100644 --- a/docs/documentation/tabs-explained/README.md +++ b/docs/documentation/tabs-explained/README.md @@ -8,7 +8,7 @@ description: >- The Clima app is organized in a series of tabs that allow the exploration of various topics. All the tabs other than "Select Weather File" are active after a weather file has been selected. -Although there is a logical sequence in the organization of the tabs, thy can be accessed in any order. +Although there is a logical sequence in the organization of the tabs, thy can be accessed in any order. The Followin section will explain the content and the usage of each tab. diff --git a/docs/documentation/tabs-explained/natural-ventilation.md b/docs/documentation/tabs-explained/natural-ventilation.md index f863de59..c1fbc341 100644 --- a/docs/documentation/tabs-explained/natural-ventilation.md +++ b/docs/documentation/tabs-explained/natural-ventilation.md @@ -33,5 +33,3 @@ Learn more about the Natural Ventilation tab by watching the following video. {% embed url="https://youtu.be/VJ_wOHadVdw?si=_cUoFQGyxJD7V85a&t=703" %} - - diff --git a/docs/documentation/tabs-explained/outdoor-comfort/utci-explained.md b/docs/documentation/tabs-explained/outdoor-comfort/utci-explained.md index 08adc0d6..3e1b5dc8 100644 --- a/docs/documentation/tabs-explained/outdoor-comfort/utci-explained.md +++ b/docs/documentation/tabs-explained/outdoor-comfort/utci-explained.md @@ -1,6 +1,6 @@ # UTCI explained -The UTCI tab allows users to analyze outdoor thermal comfort for a combination of different meteorological conditions based on the presence or absence of sun and wind. +The UTCI tab allows users to analyze outdoor thermal comfort for a combination of different meteorological conditions based on the presence or absence of sun and wind. ![Logos highlighting the different scenarios which can be displayed in Clima](<../../../.gitbook/assets/UTCI 01-01.jpg>) @@ -9,7 +9,7 @@ Clima leverages the several models implemented in [Pythermalcomfort](https://pyt * The "[Solar gain on people](https://pythermalcomfort.readthedocs.io/en/latest/reference/pythermalcomfort.html#solar-gain-on-people)" calculates the solar gain to the human body, so the mean radiant temperature. To simulate a sunless situation, Clima considers the person surrounded by surfaces that shade him, all of which tend toward dry bulb temperature; * Wind data is obtained directly from the weather file. The windless situation sets the value at 0.5 m/s, which is the minimum value allowed by the UTCI model. -The UTCI can then be visualized for the entire year for the scenario chosen. +The UTCI can then be visualized for the entire year for the scenario chosen.

UTCI perceived temperature annual heatmap in the four conditions for Rome, ITA

@@ -17,4 +17,4 @@ The values are then converted into a scale assessing thermal stress, either beca

UTCI heat stress index heatmap in the four conditions for Rome, ITA

-The UTCI is a useful tool to design the outdoor space, to maximize the number of comfortable hours. The designer can influence two factors out of the four driving outdoor comfort: radiant temperature (i.e. exposure to the sun) and wind speed (i.e. exposure to the wind). +The UTCI is a useful tool to design the outdoor space, to maximize the number of comfortable hours. The designer can influence two factors out of the four driving outdoor comfort: radiant temperature (i.e. exposure to the sun) and wind speed (i.e. exposure to the wind). diff --git a/docs/documentation/tabs-explained/psychrometric-chart/README.md b/docs/documentation/tabs-explained/psychrometric-chart/README.md index 1ef00ac0..13041107 100644 --- a/docs/documentation/tabs-explained/psychrometric-chart/README.md +++ b/docs/documentation/tabs-explained/psychrometric-chart/README.md @@ -1,12 +1,12 @@ # Psychrometric Chart -**Clima** allows the user to visualize all annual weather conditions on a [psychometric diagram.](psychrometric-chart-explained.md) +**Clima** allows the user to visualize all annual weather conditions on a [psychrometric diagram.](psychrometric-chart-explained.md) The default diagram allows the users to overlay the frequency with which weather conditions recur throughout the year.

Example: Frequency of climatic condition in the Psychrometric chart of New York, USA

-With the first choice in the drop-down list, "None", it is possible to view temperature conditions in the psychometric diagram over the entire year. The visualized dots have the same gradient with a transparency rate, they are not colored according to a legend. Multiplying them when overlaid provides a visualization of their frequency, so the most common conditions. +With the first choice in the drop-down list, "None", it is possible to view temperature conditions in the psychrometric diagram over the entire year. The visualized dots have the same gradient with a transparency rate, they are not colored according to a legend. Multiplying them when overlaid provides a visualization of their frequency, so the most common conditions.

Example: Psychrometric chart with climatic conditions of New York, USA

diff --git a/docs/documentation/tabs-explained/psychrometric-chart/psychrometric-chart-explained.md b/docs/documentation/tabs-explained/psychrometric-chart/psychrometric-chart-explained.md index 9defd6e1..08ba2630 100644 --- a/docs/documentation/tabs-explained/psychrometric-chart/psychrometric-chart-explained.md +++ b/docs/documentation/tabs-explained/psychrometric-chart/psychrometric-chart-explained.md @@ -2,7 +2,7 @@ A [psychrometric diagram](https://en.wikipedia.org/wiki/Psychrometrics) is a psychrometry tool used to understand the relationship between humidity and air temperature conditions. Through the use of the psychrometric diagram and appropriate calculations, it is possible to know the amount of heat or cooling needed to achieve the desired temperature and humidity. -The **Clima** psychometric diagram shows dry bulb temperature on the abscissae, specific humidity on the ordinates, and relative humidity as parametric curves inside the graph. +The **Clima** psychrometric diagram shows dry bulb temperature on the abscissae, specific humidity on the ordinates, and relative humidity as parametric curves inside the graph.

Temperature line in the Psychrometric diagram

@@ -14,7 +14,7 @@ All air conditions cannot go beyond the 100% saturation curve, which means that

Relative humidity curves in the Psychrometric diagram

-The simplest transformation to be analyzed on the psychometric diagram is the heating and cooling processes. The transition from the starting condition (1) to the final one (2) occurs horizontally, at constant humidity ratio values. The final condition (2) can be inspected as a function of the starting one. +The simplest transformation to be analyzed on the psychrometric diagram is the heating and cooling processes. The transition from the starting condition (1) to the final one (2) occurs horizontally, at constant humidity ratio values. The final condition (2) can be inspected as a function of the starting one.

Cooling and heating process

diff --git a/docs/documentation/tabs-explained/sun-and-cloud/README.md b/docs/documentation/tabs-explained/sun-and-cloud/README.md index ff1f17f2..9f354fd7 100644 --- a/docs/documentation/tabs-explained/sun-and-cloud/README.md +++ b/docs/documentation/tabs-explained/sun-and-cloud/README.md @@ -23,7 +23,7 @@ This allows the user to identify climatic patterns in relation to the apparent s ### Video Tutorial -Learn more about the Sun and Cloud tab by watching the following video. +Learn more about the Sun and Clouds tab by watching the following video. {% embed url="https://youtu.be/VJ_wOHadVdw?si=mB2xNH57MWW_4CRR&t=447" %} diff --git a/docs/documentation/tabs-explained/sun-and-cloud/cloud-coverage.md b/docs/documentation/tabs-explained/sun-and-cloud/cloud-coverage.md index 3228741a..25755eca 100644 --- a/docs/documentation/tabs-explained/sun-and-cloud/cloud-coverage.md +++ b/docs/documentation/tabs-explained/sun-and-cloud/cloud-coverage.md @@ -2,7 +2,7 @@ The cloud coverage diagram reports, for every month of the year the frequency of "clear", "cloudy" or "intermediate" conditions. -As the Cloud cover is reported in tenths of coverage (i.e. 0 is 0/10 covered. 10 is total coverage) for the purpose of this graph we have simplified the scale as per the table below. +As the Cloud cover is reported in tenths of coverage (i.e. 0 is 0/10 covered. 10 is total coverage) for the purpose of this graph we have simplified the scale as per the table below. | Categorization | Color | Tenth of coverage | | ----------------------- | ------------------------------------------------------------------------------- | ----------------- | diff --git a/docs/documentation/tabs-explained/sun-and-cloud/customizable-daily-and-hourly-maps.md b/docs/documentation/tabs-explained/sun-and-cloud/customizable-daily-and-hourly-maps.md index bdc56435..34481ad7 100644 --- a/docs/documentation/tabs-explained/sun-and-cloud/customizable-daily-and-hourly-maps.md +++ b/docs/documentation/tabs-explained/sun-and-cloud/customizable-daily-and-hourly-maps.md @@ -8,7 +8,7 @@ The chart above shows the [scatter plot](https://en.wikipedia.org/wiki/Scatter\_

Example: Heat map of the hourly Global Horizontal radiation on all days of the year for San Francisco, USA

-[Heat maps](https://en.wikipedia.org/wiki/Heat\_map) allow the intensity of values to be perceived through color palettes. These graphs are very helpful in seeing how magnitudes vary throughout the year. +[Heat maps](https://en.wikipedia.org/wiki/Heat\_map) allow the intensity of values to be perceived through color palettes. These graphs are very helpful in seeing how magnitudes vary throughout the year.

Examples of daily graphs with different variables (from top left to bottom right): global horizontal radiation, global horizontal illuminance, zenith luminance, opaque sky cover

diff --git a/docs/documentation/tabs-explained/sun-and-cloud/global-and-diffuse-horizontal-solar-radiation/README.md b/docs/documentation/tabs-explained/sun-and-cloud/global-and-diffuse-horizontal-solar-radiation/README.md index d9f65b5a..fcb336eb 100644 --- a/docs/documentation/tabs-explained/sun-and-cloud/global-and-diffuse-horizontal-solar-radiation/README.md +++ b/docs/documentation/tabs-explained/sun-and-cloud/global-and-diffuse-horizontal-solar-radiation/README.md @@ -11,4 +11,4 @@ Typical daily graphs showing the amount of energy gained from the sun have many * manage the **indirect solar gain** transfer into the building with a time shift, exploiting the thermal mass, heating thick walls or concrete floors, or designing special rooms adjacent to the main spaces that rely on convection to transfer the heat, such as sunroom or [Trombe wall](https://en.wikipedia.org/wiki/Trombe\_wall); * evaluating sustainable **renewable energy solutions** such as solar thermal or photovoltaic panels. - The integral of the curves in the graphs is the total energy (in Wh/m²), supplied by the sun. Be careful in considering the [different types of solar radiation.](global-diffuse-and-normal-solar-radiation-explained.md) +The integral of the curves in the graphs is the total energy (in Wh/m²), supplied by the sun. Be careful in considering the [different types of solar radiation.](global-diffuse-and-normal-solar-radiation-explained.md) diff --git a/docs/documentation/tabs-explained/tab-home.md b/docs/documentation/tabs-explained/tab-home.md index c0e73350..a7c1106a 100644 --- a/docs/documentation/tabs-explained/tab-home.md +++ b/docs/documentation/tabs-explained/tab-home.md @@ -4,13 +4,13 @@ description: This page explains how a user can load an EPW file in the Clima too # Select Weather File -Users can either choose to analyse the climate of the locations displayed on the map or upload a custom EPW file. After loading an EPW file the user can then access the other tabs to generate dynamic visualisations of the data. +Users can either choose to analyse the climate of the locations displayed on the map or upload a custom EPW file. After loading an EPW file the user can then access the other tabs to generate dynamic visualisations of the data. -### Video Tutorial +## Video Tutorial Learn more about how to analyse the climate of a specific location and uploading your custom EPW file by watching the following video. {% embed url="https://youtu.be/VJ_wOHadVdw?si=SxvUzaI9rCNIFFs0&t=136" %} -How to select or upload a EPW file +How to select or upload an EPW file {% endembed %} diff --git a/docs/documentation/tabs-explained/tab-summary/README.md b/docs/documentation/tabs-explained/tab-summary/README.md index 0f251189..06a3b3fd 100644 --- a/docs/documentation/tabs-explained/tab-summary/README.md +++ b/docs/documentation/tabs-explained/tab-summary/README.md @@ -8,9 +8,10 @@ The bottom section of the page comprises the heating and cooling degree day char ![Tab summary top](../../../.gitbook/assets/clima-summary-bottom.png) -### Video Tutorial +## Video Tutorial Learn more about the Climate Summary tab by watching the following video. + {% embed url="https://youtu.be/VJ_wOHadVdw?si=H-93XRhh5Neuby_b&t=220" %} diff --git a/docs/documentation/tabs-explained/tab-summary/clima-dataframe.md b/docs/documentation/tabs-explained/tab-summary/clima-dataframe.md index 11cc1d82..2f985e61 100644 --- a/docs/documentation/tabs-explained/tab-summary/clima-dataframe.md +++ b/docs/documentation/tabs-explained/tab-summary/clima-dataframe.md @@ -1,6 +1,6 @@ # Clima Dataframe -**Clima** calculates new variables and creates a new **dataframe** containing the variables already inside the original EPW files and other we calculate. Users can overlay all the variables on the [sun path](../sun-and-cloud/), on the [psychometric chart](../psychrometric-chart/), and on the customizable graphs in the [data explorer](../data-explorer.md). +**Clima** calculates new variables and creates a new **dataframe** containing the variables already inside the original EPW files and other we calculate. Users can overlay all the variables on the [sun path](../sun-and-cloud/), on the [psychrometric chart](../psychrometric-chart/), and on the customizable graphs in the [data explorer](../data-explorer.md). All the variables in the new Clima dataframe are listed below. @@ -13,15 +13,15 @@ All the variables in the new Clima dataframe are listed below. * [Horizontal Infrared Radiation ](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-horizontal-infrared-radiation-intensity) * [Global Horizontal Radiation ](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-global-horizontal-radiation) * [Direct Normal Radiation ](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-direct-normal-radiation) -* [Diffuse Horizontal Radiation](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-diffuse-horizontal-radiation) -* [Global Horizontal Illuminance](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-global-horizontal-illuminance) -* [Direct Normal Illuminance](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-direct-normal-illuminance) +* [Diffuse Horizontal Radiation](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-diffuse-horizontal-radiation) +* [Global Horizontal Illuminance](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-global-horizontal-illuminance) +* [Direct Normal Illuminance](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-direct-normal-illuminance) * [Diffuse Horizontal Illuminance ](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-diffuse-horizontal-illuminance) * [Zenith Luminance ](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-zenith-luminance) -* [Wind Direction](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-wind-direction) -* [Wind Speed](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-wind-speed) -* [Total Sky Cover](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-total-sky-cover) -* [Opaque Sky Cover](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-opaque-sky-cover) +* [Wind Direction](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-wind-direction) +* [Wind Speed](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-wind-speed) +* [Total Sky Cover](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-total-sky-cover) +* [Opaque Sky Cover](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-opaque-sky-cover) * [Visibility](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-visibility) * [UTCI, Universal Thermal Climate Index](../outdoor-comfort/utci-explained.md) * [Vapor partial pressure](https://en.wikipedia.org/wiki/Vapor\_pressure) diff --git a/docs/documentation/tabs-explained/tab-summary/climate-profiles-explained.md b/docs/documentation/tabs-explained/tab-summary/climate-profiles-explained.md index 6eb090b7..1334a7dd 100644 --- a/docs/documentation/tabs-explained/tab-summary/climate-profiles-explained.md +++ b/docs/documentation/tabs-explained/tab-summary/climate-profiles-explained.md @@ -2,7 +2,9 @@ The Climate Profiles graph gives the user the opportunity to observe at a glance the distribution of the data in the EPW file for four key variables and their variation between day and night. -The Climate Profiles graph are [Violin Plots](https://en.wikipedia.org/wiki/Violin\_plot). They show the [probability density](https://en.wikipedia.org/wiki/Probability\_density\_function) of the data at different values, usually smoothed by a [kernel density estimator](https://en.wikipedia.org/wiki/Kernel\_density\_estimator). Wider sections of the violin plot represent a higher probability that members of the population will take on the given value; the skinnier sections represent a lower probability. +The Climate Profiles graph are [Violin Plots](https://en.wikipedia.org/wiki/Violin\_plot). +They show the [probability density](https://en.wikipedia.org/wiki/Probability\_density\_function) of the data at different values, usually smoothed by a [kernel density estimator](https://en.wikipedia.org/wiki/Kernel\_density\_estimator). +Wider sections of the violin plot represent a higher probability that members of the population will take on the given value; the skinnier sections represent a lower probability. On mouse hover, they display various statistical properties of the data: diff --git a/docs/documentation/tabs-explained/tab-summary/degree-days-explained.md b/docs/documentation/tabs-explained/tab-summary/degree-days-explained.md index 21d70bc9..115d98d0 100644 --- a/docs/documentation/tabs-explained/tab-summary/degree-days-explained.md +++ b/docs/documentation/tabs-explained/tab-summary/degree-days-explained.md @@ -4,7 +4,7 @@ you might want to start understanding what degree days are here: {% embed url="https://en.wikipedia.org/wiki/Heating_degree_day" %} -**Degree days** are calculated as the _integral of the difference between the outside air temperature and a base temperature over time_. +**Degree days** are calculated as the _integral of the difference between the outside air temperature and a base temperature over time_. If you can define a **base temperature** (the outside temperature above which a building needs no heating or cooling) then you can use this to estimate degree days. The building requires heating if the outside air temperature falls below the heating base temperature, and the heating degree days accrue; if the outside air temperature rises above the cooling base temperature, the structure requires cooling, and the cooling degree days accumulate. diff --git a/docs/documentation/tabs-explained/wind/how-to-read-a-wind-rose.md b/docs/documentation/tabs-explained/wind/how-to-read-a-wind-rose.md index 617e30e0..e7838698 100644 --- a/docs/documentation/tabs-explained/wind/how-to-read-a-wind-rose.md +++ b/docs/documentation/tabs-explained/wind/how-to-read-a-wind-rose.md @@ -14,7 +14,7 @@ The length of each radius around the circle shows how often the wind blew from t

Frequency of wind intensity recurrence in the wind rose

-As most graphs in **Clima Tool**, the wind rose is strongly interactive. Clicking on the legend will hide or highlight the selected category. As such, it is easy to go from a wind rose showing all the wind directions and frequency to one that highlights only the selected speed range. This can be particularly useful to identify low-frequency, high-speed wind patterns. +As most graphs in **Clima Tool**, the wind rose is strongly interactive. Clicking on the legend will hide or highlight the selected category. As such, it is easy to go from a wind rose showing all the wind directions and frequency to one that highlights only the selected speed range. This can be particularly useful to identify low-frequency, high-speed wind patterns.

Left: wind rose showing all velocity ranges
Right: the same data can be easily filtered (by clicking on the legend) to show only direction and frequency of wind speeds above 10.7m/s

diff --git a/pages/not_found_404.py b/pages/not_found_404.py index d28a630e..ff3b389c 100644 --- a/pages/not_found_404.py +++ b/pages/not_found_404.py @@ -13,9 +13,7 @@ layout = [ dmc.Title("I could not find the page you are currently looking for", order=4), - dmc.Text( - "Please navigate the the home page by using the button below", className="mb-2" - ), + dmc.Text("Use the button below to return to the home page.", className="mb-2"), Lottie( options=dict( loop=True, From 25716dcbcf2d8b321e258cd77b4116ea6d08a15a Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 21 Aug 2025 11:15:31 +1000 Subject: [PATCH 83/91] refactor(config): Update order of climate data constants Adjust the order of climate data constants to ensure proper display sequence in the application. --- config.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/config.py b/config.py index bc7c61e8..bc794545 100644 --- a/config.py +++ b/config.py @@ -61,21 +61,21 @@ class PageInfo: TEMP_RH_NAME = "Temperature and Humidity" TEMP_RH_ORDER = 2 SOLAR_RADIATION_NAME = "Solar Radiation" - SOLAR_RADIATION_ORDER = 2 + SOLAR_RADIATION_ORDER = 3 SUN_NAME = "Sun and Clouds" - SUN_ORDER = 3 + SUN_ORDER = 4 WIND_NAME = "Wind" - WIND_ORDER = 4 + WIND_ORDER = 5 PSYCHROMETRIC_NAME = "Psychrometric Chart" - PSYCHROMETRIC_ORDER = 5 + PSYCHROMETRIC_ORDER = 6 NATURAL_VENTILATION_NAME = "Natural Ventilation" - NATURAL_VENTILATION_ORDER = 6 + NATURAL_VENTILATION_ORDER = 7 UTCI_NAME = "Outdoor Comfort" - UTCI_ORDER = 7 + UTCI_ORDER = 8 EXPLORER_NAME = "Data Explorer" - EXPLORER_ORDER = 8 + EXPLORER_ORDER = 9 CHANGELOG_NAME = "Changelog" - CHANGELOG_ORDER = 9 + CHANGELOG_ORDER = 10 NOT_FOUND_NAME = "404" From 62e3578efdad64d08a9d797a89db006ec84fd143 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 21 Aug 2025 11:15:49 +1000 Subject: [PATCH 84/91] ci(python): Update Ruff commands for output format and verification Change Ruff check command to output format compatible with GitHub and modify the format command to verify changes instead of applying them. --- .github/workflows/python.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 7a8ced32..32f7e7c3 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -25,11 +25,11 @@ jobs: - name: Ruff Check run: |- - pipenv run ruff check . + pipenv run ruff check . --output-format=github - - name: Ruff Format + - name: Ruff Format (verify) run: |- - pipenv run ruff format . + pipenv run ruff format --check . - name: Test Clima run: |- From 3ba5e03e43fda246085e8eca46d548cbbb7da029 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 21 Aug 2025 11:16:14 +1000 Subject: [PATCH 85/91] fix(select): Improve error handling for EPW file parsing Refine exception handling to specifically catch ValueError, IndexError, and KeyError, providing clearer error messages. Also, adjust latitude calculation to ensure consistent geographical positioning. --- pages/select.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pages/select.py b/pages/select.py index cfc770d7..d4e4af13 100644 --- a/pages/select.py +++ b/pages/select.py @@ -190,8 +190,8 @@ def submitted_data( messages_alert["invalid_format"], "warning", ) - except Exception: - # print(e) + except (ValueError, IndexError, KeyError) as e: + print(f"Error parsing EPW file: {e}") return ( None, None, @@ -330,8 +330,7 @@ def plot_location_epw_files(pathname): df = json_normalize(data["features"]) df[["lon", "lat"]] = pd.DataFrame(df["geometry.coordinates"].tolist()) - df["lat"] += 0.005 - df["lat"] += 0.005 + df["lat"] += 0.010 df = df.rename(columns={"properties.epw": "Source"}) fig = px.scatter_mapbox( From ee649801ef8ac36cf2a97970cd2aff02e82356f6 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 21 Aug 2025 11:16:23 +1000 Subject: [PATCH 86/91] fix(summary): Improve calculation of diffuse solar radiation percentage Ensure that the percentage of diffuse horizontal solar radiation is calculated correctly by checking for a zero denominator, preventing division errors and ensuring accurate reporting. --- pages/summary.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pages/summary.py b/pages/summary.py index c6611c11..733056ad 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -261,7 +261,14 @@ def update_location_info(ts, df, meta, si_ip): total_solar_rad_unit = "k" + mapping_dictionary["glob_hor_rad"][si_ip]["unit"] total_solar_rad = f"Annual cumulative horizontal solar radiation: {total_solar_rad_value} {total_solar_rad_unit}" - total_diffuse_rad = f"Percentage of diffuse horizontal solar radiation: {round(df['dif_hor_rad'].sum() / df['glob_hor_rad'].sum() * 100, 1)} %" + glob_sum = df["glob_hor_rad"].sum() + if glob_sum > 0: + diffuse_percentage = round(df["dif_hor_rad"].sum() / glob_sum * 100, 1) + else: + diffuse_percentage = 0 + total_diffuse_rad = ( + f"Percentage of diffuse horizontal solar radiation: {diffuse_percentage} %" + ) tmp_unit = mapping_dictionary["DBT"][si_ip]["unit"] average_yearly_tmp = ( f"Average yearly temperature: {df['DBT'].mean().round(1)} " + tmp_unit From 3c8926299926d37d1d82d1748d23d17e7ead48a3 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 21 Aug 2025 11:16:50 +1000 Subject: [PATCH 87/91] fix(viz): Improve error handling for graph data retrieval Specify exception types to catch in order to prevent unhandled exceptions and ensure smoother graph rendering. --- pages/lib/template_graphs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index 5f06e73c..514b0ad4 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -850,5 +850,5 @@ def catch(func, handle=lambda e: e, *args, **kwargs): # Handle category not in dictionary try: return func(*args, **kwargs) - except Exception: + except (KeyError, IndexError, TypeError): return 0 From f618ae3fb247cd68e657fe526ac4e595d97eb0ca Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 21 Aug 2025 11:17:09 +1000 Subject: [PATCH 88/91] ci(deploy): Update Google Auth action to v2 --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 29843937..ee39d462 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -15,7 +15,7 @@ jobs: uses: actions/checkout@v4 - id: auth - uses: google-github-actions/auth@v1 + uses: google-github-actions/auth@v2 with: credentials_json: ${{ secrets.GCP_SA_KEY_JSON }} From d1012bbcff4805d385cb97869848d96d0b328dbf Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 21 Aug 2025 11:17:22 +1000 Subject: [PATCH 89/91] ci(cloudbuild): Fix formatting in cloudbuild.yaml Ensure proper formatting by adding a newline at the end of the file to comply with YAML standards. --- cloudbuild.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index a60de353..8eb6ac4a 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -12,4 +12,4 @@ steps: '--image', '$_GCR/$_PROJ_NAME/$_REPO_NAME/$_IMG_NAME', '--region', '$_REGION', '--memory', '$_MEMORY', - '--cpu', '$_CPU',] \ No newline at end of file + '--cpu', '$_CPU'] \ No newline at end of file From 5eff48b36eeaff1e54adc251cfd6a07df8e65045 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 21 Aug 2025 11:17:31 +1000 Subject: [PATCH 90/91] fix(layout): Correct grammar in user survey prompt Update the text in the user survey prompt to improve clarity and correct grammatical errors. --- pages/lib/layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/lib/layout.py b/pages/lib/layout.py index ea7488b7..558e3745 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -14,7 +14,7 @@ def alert(): children=[ dbc.Toast( [ - "If you have a moment, help us improving Clima and take a ", + "If you have a moment, help us improve Clima and take a ", html.A( "quick user survey", href="https://forms.gle/k289zP3R92jdu14M7", From 232247fbc9c72c7e7019c679554dd2f7463ab1d4 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 21 Aug 2025 11:17:42 +1000 Subject: [PATCH 91/91] refactor(tests): Remove unnecessary sys.path modification Eliminate the code that modifies sys.path to improve clarity and maintainability of the test utilities. This change does not affect functionality. --- tests/python/test_utils.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/python/test_utils.py b/tests/python/test_utils.py index 0b6cd488..ab085c42 100644 --- a/tests/python/test_utils.py +++ b/tests/python/test_utils.py @@ -1,4 +1,3 @@ -import sys import os import pandas as pd @@ -11,11 +10,6 @@ from pages.lib.extract_df import get_data, create_df -root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) -if root_dir not in sys.path: - sys.path.append(root_dir) - - def save_epw_test(path_file): test_url = "http://climate.onebuilding.org/WMO_Region_6_Europe/ITA_Italy/ER_Emilia-Romagna/ITA_ER_Bologna-Marconi.AP.161400_TMYx.2004-2018.zip"