diff --git a/.bumpversion.cfg b/.bumpversion.cfg index a862796a..c6fbd8fa 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.8.18 +current_version = 0.9.0 commit = True tag = True 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 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e550f2d6..bc24325c 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@v2 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" \ No newline at end of file diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 919693a6..32f7e7c3 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 . --output-format=github + + - name: Ruff Format (verify) + run: |- + pipenv run ruff format --check . - - 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 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..89f3c053 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": [ @@ -146,6 +133,14 @@ "markers": "python_version >= '3.10'", "version": "==8.2.1" }, + "colorama": { + "hashes": [ + "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", + "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" + ], + "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": "==0.4.6" + }, "dash": { "hashes": [ "sha256:d38891337fc855d5673f75e5346354daa063c4ff45a8a6a21f25e858fcae41c2", @@ -286,7 +281,7 @@ "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd" ], - "markers": "python_version >= '3.7'", + "markers": "python_version >= '3.9'", "version": "==8.7.0" }, "itsdangerous": { @@ -589,11 +584,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 +726,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 +751,37 @@ "markers": "python_version >= '3.10'", "version": "==8.2.1" }, + "colorama": { + "hashes": [ + "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", + "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" + ], + "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": "==0.4.6" + }, + "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 +798,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 +838,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 +864,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:059e863ea3a9ade41407ad71c1de2badfbe01539117f38f763ba42a1206f7559", + "sha256:141ce3d88803c625257b8a6debf4a0473eb6eed9643a6189b68838b43e78165a", + "sha256:189ab65149d11ea69a2d775343adf5f49bb2426fc4780f65ee33b423ad2e47f9", + "sha256:1bef6161e297c68908b7218fa6e0e93e99a286e5ed9653d4be71e687dff101cf", + "sha256:1f68433c4fbc63efbfa3ba5db31727db229fa4e61000f452c540474b03de52a9", + "sha256:2c6f4064c69d2542029b2a61d39920c85240c39837599d7f2e32e80d36401d6e", + "sha256:37b4a64f4062a50c75019c61c7017ff598cb444984b638511f48539d3a1c98db", + "sha256:4f1345fbf8fb0531cd722285b5f15af49b2932742fc96b633e883da8d841896b", + "sha256:7837eca8787f076f67aba2ca559cefd9c5cbc3a9852fd66186f4201b87c1563e", + "sha256:7d1a4e0bdfafcd2e3e235ecf50bf0176f74dd37902f241588ae1f6c827a36c56", + "sha256:822d9677b560f1fdeab69b89d1f444bf5459da4aa04e06e766cf0121771ab844", + "sha256:8b593cb0fb55cc8692dac7b06deb29afda78c721c7ccfed22db941201b7b8f7b", + "sha256:9de785e95dc2f09846c5e6e1d3a3d32ecd0b283a979898ad427a9be7be22b266", + "sha256:ae479e1a18b439c59138f066ae79cc0f3ee250712a873d00dbafadaad9481e5b", + "sha256:cc138cc06ed9d4bfa9d667a65af7172b47840e1a98b02ce7011c391e54635ffc", + "sha256:d59e58586829f8e4a9920788f6efba97a13d1fa320b047814e8afede381c6839", + "sha256:e67d96827854f50b9e3e8327b031647e7bcc090dbe7bb11101a81a3a2cbf1cc9", + "sha256:ebb7333a45d56efc7c110a46a69a1b32365d5c5161e7244aaf3aa20ce62399c1", + "sha256:f3fc21178cd44c98142ae7590f42ddcb587b8e09a3b849cbc84edb62ee95de60" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==0.12.7" + "version": "==0.12.10" + }, + "virtualenv": { + "hashes": [ + "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026", + "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a" + ], + "markers": "python_version >= '3.8'", + "version": "==20.34.0" } } } diff --git a/assets/manifest.json b/assets/manifest.json index 40b8618d..591f5946 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.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/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 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" 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 fa8fb538..d005aa08 100644 --- a/docs/contributing/contributing.md +++ b/docs/contributing/contributing.md @@ -5,11 +5,72 @@ description: Guide on how to contribute to this project # How to contribute First off, thanks for taking the time to contribute! +We use GitHub as our main collaboration platform. Please work from the `development` branch, create small feature branches, and open focused pull requests. Follow Conventional Commit messages (e.g., `feat:`, `fix:`, `docs:`), format Python code with Black, and add tests where needed. Never merge your own PRβ€”wait for review and address all comments (including AI reviewer suggestions). Use Issues and Projects to track tasks and discussions. + +> This project requires Python 3.11. Do not use Python 3.12 or newer, as it may cause dependency incompatibilities, build failure or runtime errors + ## General Feedback If you have a general feedback about our project, please do not open an issue but instead please fill in this [form](https://forms.gle/LRUq3vsFnE1QCLiA6) +## Fork & branch processing + +First fork the origin repository to your own github repository, then clone the repository to your local computer. + +```bash +git clone https://github.com/Your Account name/clima.git +cd clima +``` + +Set up the upstream repository and check the output repositories. + +```bash +git remote add upstream https://github.com/CenterForTheBuiltEnvironment/clima.git + +git remote -v +``` + +The terminal should output a list: + +- `origin β†’ your Fork repository` +- `upstream β†’ origin repository` + +Check all branches. + +```bash +git branch -a +``` + +The terminal will show a list of branches: + +```bash +> * main + remotes/origin/HEAD -> origin/main + remotes/origin/development + remotes/origin/main +``` + +Pull the development branch first, and if the terminal does not notice you that you should try the second command. + +```bash +git checktout development + +git checkout -b development origin/development +``` + +Create a new branch in the development branch. + +```bash +git checkout -b (your branch name) +``` + +Finally update and push to your repository branch if you modify the files. + +```bash +git push origin (your branch name) +``` + ## Code of Conduct Available [here](code_of_conduct.md) @@ -18,6 +79,57 @@ Available [here](code_of_conduct.md) We use Black.exe to format the code. +Install Black: + +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 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/). + +```bash +pipenv install black +``` + +Format your code before committing: + +```bash +black . +``` + +## Testing + +Before submitting a Pull Request, please make sure: +- All tests should pass. +- You have installed project dependencies: + +```bash +npm install + +pipenv install -r requirements.txt +``` + +From the root directory, run: + +```bash +cd tests/node + +npx cypress run +``` + ## Submitting changes Please send a Pull Request with a clear list of what you've done. Always write a clear log message for your commits. One-line messages are fine for small changes, but bigger changes should look like this: @@ -28,6 +140,43 @@ $ git commit -m "A brief summary of the commit > A paragraph describing what changed and its impact." ``` +> Detailed requirements for submitting a PR are described in the [Pull Request Regulation](#pull-request-regulation) section below + +Classification of Common Commit Types: + +- `Main (Master)`: Stable branch, merge code that passes review and CI; merge and release every time, +- `Develop`: Continuous Integration branch for daily integration with multiple collaborators. +- `Feature/*`: feature development branch, cut out from main or develop, send PR to merge in after completing the feature. +- `Fix/*`: defect repair branch, the same process as feature +- `Release/*`: release preparation branch for freezing versions, fixing documentation, doing regressions and tagging. +- `docs/*`, `chore/*`, `refactor/*`, `test/*`: documentation, miscellaneous, refactor, test type branches. +- `Style`: style modification (does not affect the function): code formatting, space adjustment, naming rules unity. +- `Refactor`: Code Refactoring: Refactor existing code to improve maintainability. +- `Test`: Add or modify tests: add unit tests, integration tests, or modify test logic. +- `Chore`: Build Configuration, Dependency Management, CI/CD Configuration Updates. +- `Perf`: Performance Optimisation: Optimising code execution efficiency or memory usage. +- `Ci`: CI Configuration Related: Changing Continuous Integration Configurations for Github Actions, Travis, Jenkins, etc. +- `Build`: build system related: modify build scripts, packaging configuration. +- `Revert`: Rollback Commit: Undoing a Previous Commit +- `Security`: Security fixes, fixing security vulnerabilities, updating dependencies to prevent attacks. +- `Deps`: Dependency Management: Dependency Management/Adding, updating, and removing dependency libraries +- `Infra`: Infrastructure related: changes to development environments, containers, server configurations, etc. + +## Pull Request Regulation +**Time to submit PR:** + +- User requirements/issues have been addressed or discussed in Issue and consensus has been reached. +- Changes have been minimised (small steps/phased submission) to avoid "mega PRs". + +**The pull request should include the following information:** + +- **Description:** Provide a brief summary of the changes, related issues, and motivation. List any required dependencies. **Fixes # (issue)** + +- **Type of Change:** Bug fix (non-breaking); New feature (non-breaking); Breaking change; Documentation update. + +- **Testing:** Describe how you tested your changes and how we can reproduce them. Include test details if necessary. + + ## Thanks Thank you again for being interested in this project! You are awesome! 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..3d140408 100644 --- a/docs/documentation/tabs-explained/natural-ventilation.md +++ b/docs/documentation/tabs-explained/natural-ventilation.md @@ -32,6 +32,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..bb6df199 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). \ No newline at end of file diff --git a/docs/documentation/tabs-explained/psychrometric-chart/README.md b/docs/documentation/tabs-explained/psychrometric-chart/README.md index 1ef00ac0..a0817de6 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

@@ -22,5 +22,4 @@ Moreover, data can be filtered by date, time, or one of the [Clima dataframe](.. Learn more about the Psychrometric tab by watching the following video. -{% embed url="https://youtu.be/VJ_wOHadVdw?si=iAcBQpq3IgCNY-H6&t=582" %} - +{% embed url="https://youtu.be/VJ_wOHadVdw?si=iAcBQpq3IgCNY-H6&t=582" %} \ No newline at end of file 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..28a3b285 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

@@ -28,4 +28,4 @@ The main application of the psychrometric diagram is in the design of large all- The diagram is applied whenever the humidity of a particular environment needs to be studied, for reasons of thermal comfort or for the preservation of valuable objects, such as in museums. -

Stradivari Violin, stored under precise temperature and humidity conditions to prevent the valuable wood from warping. Source: Frammentirivista

+

Stradivari Violin, stored under precise temperature and humidity conditions to prevent the valuable wood from warping. Source: Frammentirivista

\ No newline at end of file diff --git a/docs/documentation/tabs-explained/sun-and-cloud/README.md b/docs/documentation/tabs-explained/sun-and-cloud/README.md index ff1f17f2..c54f5571 100644 --- a/docs/documentation/tabs-explained/sun-and-cloud/README.md +++ b/docs/documentation/tabs-explained/sun-and-cloud/README.md @@ -23,7 +23,6 @@ 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. - -{% embed url="https://youtu.be/VJ_wOHadVdw?si=mB2xNH57MWW_4CRR&t=447" %} +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" %} \ No newline at end of file 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..bc250e1a 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 | | ----------------------- | ------------------------------------------------------------------------------- | ----------------- | @@ -18,4 +18,4 @@ As the Cloud cover is reported in tenths of coverage (i.e. 0 is 0/10 covered. 10 | Cloudy (ABOVE range) | | 9 | | Cloudy (ABOVE range) | | 10 | -

Example cloud coverage graph for San Francisco, USA

+

Example cloud coverage graph for San Francisco, USA

\ No newline at end of file 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..c1dfc256 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,6 @@ 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. - -

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

+[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

\ No newline at end of file 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..5013530d 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) \ No newline at end of file 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..4d474c10 100644 --- a/docs/documentation/tabs-explained/tab-summary/README.md +++ b/docs/documentation/tabs-explained/tab-summary/README.md @@ -8,9 +8,9 @@ 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" %} - + +{% embed url="https://youtu.be/VJ_wOHadVdw?si=H-93XRhh5Neuby_b&t=220" %} \ No newline at end of file diff --git a/docs/documentation/tabs-explained/tab-summary/clima-dataframe.md b/docs/documentation/tabs-explained/tab-summary/clima-dataframe.md index 11cc1d82..0806f435 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) @@ -29,4 +29,4 @@ All the variables in the new Clima dataframe are listed below. * [Wet-bulb temperature](https://en.wikipedia.org/wiki/Wet-bulb\_temperature) * [Elevation](https://en.wikipedia.org/wiki/Solar\_zenith\_angle) * [Azimuth](https://en.wikipedia.org/wiki/Solar\_azimuth\_angle) -* [Saturation pressure](https://en.wikipedia.org/wiki/Vapour\_pressure\_of\_water) +* [Saturation pressure](https://en.wikipedia.org/wiki/Vapour\_pressure\_of\_water) \ No newline at end of file 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..dc45d8e4 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,7 @@ 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: @@ -13,4 +13,4 @@ On mouse hover, they display various statistical properties of the data: * 1st quartile * 3rd quartile -![Climate Profiles for Jerusalem Center, ISRAEL](<../../../.gitbook/assets/image (2) (1) (1).png>) +![Climate Profiles for Jerusalem Center, ISRAEL](<../../../.gitbook/assets/image (2) (1) (1).png>) \ No newline at end of file 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..0d6d9d09 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. @@ -12,4 +12,4 @@ The base temperature does not necessarily correspond to the desired building int ![example deegree days for New York, Downtown Manhattan, NY, USA](<../../../.gitbook/assets/image (3).png>) -![example deegree days for Palermo Boccadifalco Airport, ITALY](<../../../.gitbook/assets/image (1) (1).png>) +![example deegree days for Palermo Boccadifalco Airport, ITALY](<../../../.gitbook/assets/image (1) (1).png>) \ No newline at end of file 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/explorer.py b/pages/explorer.py index f2c899bd..32ae86db 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -12,6 +12,8 @@ two_var_graph, three_var_graph, ) +from pages.lib.global_element_ids import ElementIds +from pages.lib.global_column_names import ColNames from pages.lib.global_scheme import ( fig_config, dropdown_names, @@ -65,7 +67,7 @@ def section_one_inputs(): children=[ html.H4(className="text-next-to-input", children=["Select a variable: "]), dropdown( - id="sec1-var-dropdown", + id=ElementIds.SEC1_VAR_DROPDOWN, options=explore_dropdown_names, value="DBT", ), @@ -88,7 +90,7 @@ def section_one(): ), dcc.Loading( type="circle", - children=html.Div(id="yearly-explore", className="full-width"), + children=html.Div(id=ElementIds.YEARLY_EXPLORE, className="full-width"), ), html.Div( children=title_with_link( @@ -98,7 +100,7 @@ def section_one(): ), ), dcc.Loading( - html.Div(className="full-width", id="query-daily"), + html.Div(className="full-width", id=ElementIds.QUERY_DAILY), type="circle", ), html.Div( @@ -109,7 +111,7 @@ def section_one(): ), ), dcc.Loading( - html.Div(className="full-width", id="query-heatmap"), + html.Div(className="full-width", id=ElementIds.QUERY_HEATMAP), type="circle", ), html.Div( @@ -128,7 +130,7 @@ def section_one(): dbc.Button( "Apply month and hour filter", color="primary", - id="sec1-time-filter-input", + id=ElementIds.SEC1_TIME_FILTER_INPUT, className="mb-2", n_clicks=0, ), @@ -140,7 +142,7 @@ def section_one(): html.H6("Month Range", style={"flex": "20%"}), html.Div( dcc.RangeSlider( - id="sec1-month-slider", + id=ElementIds.SEC1_MONTH_SLIDER, min=1, max=12, step=1, @@ -159,7 +161,7 @@ def section_one(): {"label": "Invert", "value": "invert"}, ], value=[], - id="invert-month-explore-descriptive", + id=ElementIds.INVERT_MONTH_EXPLORE_DESCRIPTIVE, labelStyle={"flex": "30%"}, ), ], @@ -170,7 +172,7 @@ def section_one(): html.H6("Hour Range", style={"flex": "20%"}), html.Div( dcc.RangeSlider( - id="sec1-hour-slider", + id=ElementIds.SEC1_HOUR_SLIDER, min=0, max=24, step=1, @@ -189,7 +191,7 @@ def section_one(): {"label": "Invert", "value": "invert"}, ], value=[], - id="invert-hour-explore-descriptive", + id=ElementIds.INVERT_HOUR_EXPLORE_DESCRIPTIVE, labelStyle={"flex": "30%"}, ), ], @@ -199,7 +201,7 @@ def section_one(): ], ), html.Div( - id="table-data-explorer", + id=ElementIds.TABLE_DATA_EXPLORER, ), ], ) @@ -230,9 +232,9 @@ def section_two_inputs(): style={"flex": "30%"}, ), dropdown( - id="sec2-var-dropdown", + id=ElementIds.SEC2_VAR_DROPDOWN, options=explore_dropdown_names, - value="RH", + value=ColNames.RH, style={"flex": "70%"}, ), ], @@ -245,7 +247,7 @@ def section_two_inputs(): dbc.Button( "Apply month and hour filter", color="primary", - id="sec2-time-filter-input", + id=ElementIds.SEC2_TIME_FILTER_INPUT, className="mb-2", n_clicks=0, ), @@ -257,7 +259,7 @@ def section_two_inputs(): html.H6("Month Range", style={"flex": "20%"}), html.Div( dcc.RangeSlider( - id="sec2-month-slider", + id=ElementIds.SEC2_MONTH_SLIDER, min=1, max=12, step=1, @@ -276,7 +278,7 @@ def section_two_inputs(): {"label": "Invert", "value": "invert"}, ], value=[], - id="invert-month-explore-heatmap", + id=ElementIds.INVERT_MONTH_EXPLORE_HEATMAP, labelStyle={"flex": "30%"}, ), ], @@ -287,7 +289,7 @@ def section_two_inputs(): html.H6("Hour Range", style={"flex": "20%"}), html.Div( dcc.RangeSlider( - id="sec2-hour-slider", + id=ElementIds.SEC2_HOUR_SLIDER, min=0, max=24, step=1, @@ -306,7 +308,7 @@ def section_two_inputs(): {"label": "Invert", "value": "invert"}, ], value=[], - id="invert-hour-explore-heatmap", + id=ElementIds.INVERT_HOUR_EXPLORE_HEATMAP, labelStyle={"flex": "30%"}, ), ], @@ -319,7 +321,7 @@ def section_two_inputs(): dbc.Button( "Apply filter", color="primary", - id="sec2-data-filter-input", + id=ElementIds.SEC2_DATA_FILTER_INPUT, className="mb-2", n_clicks=0, ), @@ -331,9 +333,9 @@ def section_two_inputs(): style={"flex": "30%"}, ), dropdown( - id="sec2-data-filter-var", + id=ElementIds.SEC2_DATA_FILTER_VAR, options=explore_dropdown_names, - value="RH", + value=ColNames.RH, style={"flex": "70%"}, ), ], @@ -345,7 +347,7 @@ def section_two_inputs(): children=["Min Value:"], style={"flex": "30%"} ), dbc.Input( - id="sec2-min-val", + id=ElementIds.SEC2_MIN_VAL, placeholder="Enter a number for the min val", type="number", value=0, @@ -361,7 +363,7 @@ def section_two_inputs(): children=["Max Value:"], style={"flex": "30%"} ), dbc.Input( - id="sec2-max-val", + id=ElementIds.SEC2_MAX_VAL, placeholder="Enter a number for the max val", type="number", value=100, @@ -381,26 +383,28 @@ def section_two_inputs(): def section_two(): """Return the two graphs in section two.""" return html.Div( - id="tab6-sec2-container", + id=ElementIds.TAB6_SEC2_CONTAINER, className="container-col justify-center full-width", children=[ section_two_inputs(), dcc.Loading( type="circle", - children=html.Div(className="full-width", id="custom-heatmap"), + children=html.Div(className="full-width", id=ElementIds.CUSTOM_HEATMAP), ), dbc.Checklist( options=[ {"label": "Normalize", "value": "normal"}, ], value=[], - id="normalize", + id=ElementIds.NORMALIZE, ), dcc.Loading( type="circle", children=[ dcc.Graph( - className="full-width", id="custom-summary", config=fig_config + className="full-width", + id=ElementIds.CUSTOM_SUMMARY, + config=fig_config, ), ], ), @@ -421,7 +425,7 @@ def section_three_inputs(): children=[ html.H6(style={"flex": "30%"}, children=["X Variable:"]), dropdown( - id="tab6-sec3-var-x-dropdown", + id=ElementIds.TAB6_SEC3_VAR_X_DROPDOWN, options=explore_dropdown_names, value="DBT", style={"flex": "70%"}, @@ -433,9 +437,9 @@ def section_three_inputs(): children=[ html.H6(style={"flex": "30%"}, children=["Y Variable:"]), dropdown( - id="tab6-sec3-var-y-dropdown", + id=ElementIds.TAB6_SEC3_VAR_Y_DROPDOWN, options=explore_dropdown_names, - value="RH", + value=ColNames.RH, style={"flex": "70%"}, ), ], @@ -445,7 +449,7 @@ def section_three_inputs(): children=[ html.H6(style={"flex": "30%"}, children=["Color By:"]), dropdown( - id="tab6-sec3-colorby-dropdown", + id=ElementIds.TAB6_SEC3_COLORBY_DROPDOWN, options=explore_dropdown_names, value="glob_hor_rad", style={"flex": "70%"}, @@ -460,7 +464,7 @@ def section_three_inputs(): dbc.Button( "Apply month and hour filter", color="primary", - id="tab6-sec3-time-filter-input", + id=ElementIds.TAB6_SEC3_TIME_FILTER_INPUT, className="mb-2", n_clicks=0, ), @@ -470,7 +474,7 @@ def section_three_inputs(): html.H6("Month Range", style={"flex": "20%"}), html.Div( dcc.RangeSlider( - id="tab6-sec3-query-month-slider", + id=ElementIds.TAB6_SEC3_QUERY_MONTH_SLIDER, min=1, max=12, step=1, @@ -489,7 +493,7 @@ def section_three_inputs(): {"label": "Invert", "value": "invert"}, ], value=[], - id="invert-month-explore-more-charts", + id=ElementIds.INVERT_MONTH_EXPLORE_MORE_CHARTS, labelStyle={"flex": "30%"}, ), ], @@ -500,7 +504,7 @@ def section_three_inputs(): html.H6("Hour Range", style={"flex": "20%"}), html.Div( dcc.RangeSlider( - id="tab6-sec3-query-hour-slider", + id=ElementIds.TAB6_SEC3_QUERY_HOUR_SLIDER, min=0, max=24, step=1, @@ -519,7 +523,7 @@ def section_three_inputs(): {"label": "Invert", "value": "invert"}, ], value=[], - id="invert-hour-explore-more-charts", + id=ElementIds.INVERT_HOUR_EXPLORE_MORE_CHARTS, labelStyle={"flex": "30%"}, ), ], @@ -532,7 +536,7 @@ def section_three_inputs(): dbc.Button( "Apply filter", color="primary", - id="tab6-sec3-data-filter-input", + id=ElementIds.TAB6_SEC3_DATA_FILTER_INPUT, className="mb-2", n_clicks=0, ), @@ -543,9 +547,9 @@ def section_three_inputs(): children=["Filter Variable:"], style={"flex": "30%"} ), dropdown( - id="tab6-sec3-filter-var-dropdown", + id=ElementIds.TAB6_SEC3_FILTER_VAR_DROPDOWN, options=explore_dropdown_names, - value="RH", + value=ColNames.RH, style={"flex": "70%"}, ), ], @@ -556,7 +560,7 @@ def section_three_inputs(): html.H6(children=["Min Value:"], style={"flex": "30%"}), dbc.Input( className="num-input", - id="tab6-sec3-min-val", + id=ElementIds.TAB6_SEC3_MIN_VAL, placeholder="Enter a number for the min val", type="number", step=1, @@ -571,7 +575,7 @@ def section_three_inputs(): html.H6(children=["Max Value:"], style={"flex": "30%"}), dbc.Input( className="num-input", - id="tab6-sec3-max-val", + id=ElementIds.TAB6_SEC3_MAX_VAL, placeholder="Enter a number for the max val", type="number", value=100, @@ -600,11 +604,11 @@ def section_three(): ), section_three_inputs(), dcc.Loading( - html.Div(id="three-var"), + html.Div(id=ElementIds.THREE_VAR), type="circle", ), dcc.Loading( - html.Div(id="two-var"), + html.Div(id=ElementIds.TWO_VAR), type="circle", ), ], @@ -620,17 +624,17 @@ def layout(): @callback( - Output("yearly-explore", "children"), + Output(ElementIds.YEARLY_EXPLORE, "children"), # Section One [ - Input("df-store", "modified_timestamp"), - Input("sec1-var-dropdown", "value"), - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_EXPLORER_DF_STORE, "modified_timestamp"), + Input(ElementIds.SEC1_VAR_DROPDOWN, "value"), + Input(ElementIds.ID_EXPLORER_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_EXPLORER_DF_STORE, "data"), + State(ElementIds.ID_EXPLORER_META_STORE, "data"), + State(ElementIds.ID_EXPLORER_SI_IP_UNIT_STORE, "data"), ], ) def update_tab_yearly(_, var, global_local, df, meta, si_ip): @@ -653,16 +657,16 @@ def update_tab_yearly(_, var, global_local, df, meta, si_ip): @callback( - Output("query-daily", "children"), + Output(ElementIds.QUERY_DAILY, "children"), [ - Input("df-store", "modified_timestamp"), - Input("sec1-var-dropdown", "value"), - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_EXPLORER_DF_STORE, "modified_timestamp"), + Input(ElementIds.SEC1_VAR_DROPDOWN, "value"), + Input(ElementIds.ID_EXPLORER_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_EXPLORER_DF_STORE, "data"), + State(ElementIds.ID_EXPLORER_META_STORE, "data"), + State(ElementIds.ID_EXPLORER_SI_IP_UNIT_STORE, "data"), ], ) def update_tab_daily(_, var, global_local, df, meta, si_ip): @@ -678,16 +682,16 @@ def update_tab_daily(_, var, global_local, df, meta, si_ip): @callback( - Output("query-heatmap", "children"), + Output(ElementIds.QUERY_HEATMAP, "children"), [ - Input("df-store", "modified_timestamp"), - Input("sec1-var-dropdown", "value"), - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_EXPLORER_DF_STORE, "modified_timestamp"), + Input(ElementIds.SEC1_VAR_DROPDOWN, "value"), + Input(ElementIds.ID_EXPLORER_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_EXPLORER_DF_STORE, "data"), + State(ElementIds.ID_EXPLORER_META_STORE, "data"), + State(ElementIds.ID_EXPLORER_SI_IP_UNIT_STORE, "data"), ], ) def update_tab_heatmap(_, var, global_local, df, meta, si_ip): @@ -704,31 +708,31 @@ def update_tab_heatmap(_, var, global_local, df, meta, si_ip): @callback( [ - Output("custom-heatmap", "children"), - Output("custom-summary", "style"), - Output("custom-summary", "figure"), - Output("normalize", "style"), + Output(ElementIds.CUSTOM_HEATMAP, "children"), + Output(ElementIds.CUSTOM_SUMMARY, "style"), + Output(ElementIds.CUSTOM_SUMMARY, "figure"), + Output(ElementIds.NORMALIZE, "style"), ], [ - Input("df-store", "modified_timestamp"), - Input("sec2-var-dropdown", "value"), - Input("sec2-time-filter-input", "n_clicks"), - Input("sec2-data-filter-input", "n_clicks"), - Input("normalize", "value"), - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_EXPLORER_DF_STORE, "modified_timestamp"), + Input(ElementIds.SEC2_VAR_DROPDOWN, "value"), + Input(ElementIds.SEC2_TIME_FILTER_INPUT, "n_clicks"), + Input(ElementIds.SEC2_DATA_FILTER_INPUT, "n_clicks"), + Input(ElementIds.NORMALIZE, "value"), + Input(ElementIds.ID_EXPLORER_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], # General [ - State("df-store", "data"), - State("sec2-month-slider", "value"), - State("sec2-hour-slider", "value"), - State("sec2-data-filter-var", "value"), - State("sec2-min-val", "value"), - State("sec2-max-val", "value"), - State("meta-store", "data"), - State("invert-month-explore-heatmap", "value"), - State("invert-hour-explore-heatmap", "value"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_EXPLORER_DF_STORE, "data"), + State(ElementIds.SEC2_MONTH_SLIDER, "value"), + State(ElementIds.SEC2_HOUR_SLIDER, "value"), + State(ElementIds.SEC2_DATA_FILTER_VAR, "value"), + State(ElementIds.SEC2_MIN_VAL, "value"), + State(ElementIds.SEC2_MAX_VAL, "value"), + State(ElementIds.ID_EXPLORER_META_STORE, "data"), + State(ElementIds.INVERT_MONTH_EXPLORE_HEATMAP, "value"), + State(ElementIds.INVERT_HOUR_EXPLORE_HEATMAP, "value"), + State(ElementIds.ID_EXPLORER_SI_IP_UNIT_STORE, "data"), ], ) def update_heatmap( @@ -820,27 +824,27 @@ def update_heatmap( @callback( - [Output("three-var", "children"), Output("two-var", "children")], + [Output(ElementIds.THREE_VAR, "children"), Output(ElementIds.TWO_VAR, "children")], [ - Input("df-store", "modified_timestamp"), - Input("tab6-sec3-var-x-dropdown", "value"), - Input("tab6-sec3-var-y-dropdown", "value"), - Input("tab6-sec3-colorby-dropdown", "value"), - Input("tab6-sec3-time-filter-input", "n_clicks"), - Input("tab6-sec3-data-filter-input", "n_clicks"), - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_EXPLORER_DF_STORE, "modified_timestamp"), + Input(ElementIds.TAB6_SEC3_VAR_X_DROPDOWN, "value"), + Input(ElementIds.TAB6_SEC3_VAR_Y_DROPDOWN, "value"), + Input(ElementIds.TAB6_SEC3_COLORBY_DROPDOWN, "value"), + Input(ElementIds.TAB6_SEC3_TIME_FILTER_INPUT, "n_clicks"), + Input(ElementIds.TAB6_SEC3_DATA_FILTER_INPUT, "n_clicks"), + Input(ElementIds.ID_EXPLORER_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State("df-store", "data"), - State("tab6-sec3-query-month-slider", "value"), - State("tab6-sec3-query-hour-slider", "value"), - State("tab6-sec3-filter-var-dropdown", "value"), - State("tab6-sec3-min-val", "value"), - State("tab6-sec3-max-val", "value"), - State("meta-store", "data"), - State("invert-month-explore-more-charts", "value"), - State("invert-hour-explore-more-charts", "value"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_EXPLORER_DF_STORE, "data"), + State(ElementIds.TAB6_SEC3_QUERY_MONTH_SLIDER, "value"), + State(ElementIds.TAB6_SEC3_QUERY_HOUR_SLIDER, "value"), + State(ElementIds.TAB6_SEC3_FILTER_VAR_DROPDOWN, "value"), + State(ElementIds.TAB6_SEC3_MIN_VAL, "value"), + State(ElementIds.TAB6_SEC3_MAX_VAL, "value"), + State(ElementIds.ID_EXPLORER_META_STORE, "data"), + State(ElementIds.INVERT_MONTH_EXPLORE_MORE_CHARTS, "value"), + State(ElementIds.INVERT_HOUR_EXPLORE_MORE_CHARTS, "value"), + State(ElementIds.ID_EXPLORER_SI_IP_UNIT_STORE, "data"), ], ) def update_more_charts( @@ -912,19 +916,19 @@ def update_more_charts( @callback( - Output("table-data-explorer", "children"), + Output(ElementIds.TABLE_DATA_EXPLORER, "children"), [ - Input("df-store", "modified_timestamp"), - Input("sec1-var-dropdown", "value"), - Input("sec1-time-filter-input", "n_clicks"), + Input(ElementIds.ID_EXPLORER_DF_STORE, "modified_timestamp"), + Input(ElementIds.SEC1_VAR_DROPDOWN, "value"), + Input(ElementIds.SEC1_TIME_FILTER_INPUT, "n_clicks"), ], [ - State("df-store", "data"), - State("si-ip-unit-store", "data"), - State("sec1-month-slider", "value"), - State("sec1-hour-slider", "value"), - State("invert-month-explore-descriptive", "value"), - State("invert-hour-explore-descriptive", "value"), + State(ElementIds.ID_EXPLORER_DF_STORE, "data"), + State(ElementIds.ID_EXPLORER_SI_IP_UNIT_STORE, "data"), + State(ElementIds.SEC1_MONTH_SLIDER, "value"), + State(ElementIds.SEC1_HOUR_SLIDER, "value"), + State(ElementIds.INVERT_MONTH_EXPLORE_DESCRIPTIVE, "value"), + State(ElementIds.INVERT_HOUR_EXPLORE_DESCRIPTIVE, "value"), ], ) def update_table( @@ -943,11 +947,11 @@ def update_table( ) filtered_df = df[ - (df["month"] >= start_month) - & (df["month"] <= end_month) - & (df["hour"] >= start_hour) - & (df["hour"] <= end_hour) + (df[ColNames.MONTH] >= start_month) + & (df[ColNames.MONTH] <= end_month) + & (df[ColNames.HOUR] >= start_hour) + & (df[ColNames.HOUR] <= end_hour) ] return summary_table_tmp_rh_tab( - filtered_df[["month", "hour", dd_value, "month_names"]], dd_value, si_ip + filtered_df[[ColNames.MONTH, ColNames.HOUR, dd_value, ColNames.MONTH_NAMES]], dd_value, si_ip ) diff --git a/pages/lib/charts_data_explorer.py b/pages/lib/charts_data_explorer.py index 2a085bd3..d1534057 100644 --- a/pages/lib/charts_data_explorer.py +++ b/pages/lib/charts_data_explorer.py @@ -5,6 +5,7 @@ import plotly.express as px import plotly.graph_objects as go from pages.lib.global_scheme import template, mapping_dictionary, month_lst +from pages.lib.global_column_names import ColNames def custom_heatmap(df, global_local, var, time_filter_info, data_filter_info, si_ip): @@ -60,15 +61,15 @@ def custom_heatmap(df, global_local, var, time_filter_info, data_filter_info, si fig = go.Figure( data=go.Heatmap( - y=df["hour"], - x=df["DOY"], + y=df[ColNames.HOUR], + x=df[ColNames.DOY], z=df[var], colorscale=var_color, zmin=range_z[0], zmax=range_z[1], connectgaps=False, hoverongaps=False, - customdata=np.stack((df["month_names"], df["day"]), axis=-1), + customdata=np.stack((df[ColNames.MONTH_NAMES], df[ColNames.DAY]), axis=-1), hovertemplate=( "" + var @@ -132,7 +133,7 @@ def three_var_graph( else: df.loc[(df[filter_var] >= max_val) & (df[filter_var] <= min_val)] = None - if df.dropna(subset=["month"]).shape[0] == 0: + if df.dropna(subset=[ColNames.MONTH]).shape[0] == 0: return None title = ( diff --git a/pages/lib/charts_sun.py b/pages/lib/charts_sun.py index 73d43a24..805fd717 100644 --- a/pages/lib/charts_sun.py +++ b/pages/lib/charts_sun.py @@ -15,14 +15,15 @@ ) from plotly.subplots import make_subplots from pvlib import solarposition +from pages.lib.global_column_names import ColNames def monthly_solar(epw_df, si_ip): g_h_rad_month_ave = ( - epw_df.groupby(["month", "hour"])["glob_hor_rad"].median().reset_index() + epw_df.groupby([ColNames.MONTH, ColNames.HOUR])[ColNames.GLOB_HOR_RAD].median().reset_index() ) dif_h_rad_month_ave = ( - epw_df.groupby(["month", "hour"])["dif_hor_rad"].median().reset_index() + epw_df.groupby([ColNames.MONTH, ColNames.HOUR])[ColNames.DIF_HOR_RAD].median().reset_index() ) fig = make_subplots( rows=1, @@ -37,9 +38,9 @@ def monthly_solar(epw_df, si_ip): fig.add_trace( go.Scatter( - x=g_h_rad_month_ave.loc[g_h_rad_month_ave["month"] == i + 1, "hour"], + x=g_h_rad_month_ave.loc[g_h_rad_month_ave[ColNames.MONTH] == i + 1, ColNames.HOUR], y=g_h_rad_month_ave.loc[ - g_h_rad_month_ave["month"] == i + 1, "glob_hor_rad" + g_h_rad_month_ave[ColNames.MONTH] == i + 1, ColNames.GLOB_HOR_RAD ], fill="tozeroy", mode="lines", @@ -47,12 +48,12 @@ def monthly_solar(epw_df, si_ip): line_width=2, name="Global", showlegend=is_first, - customdata=epw_df.loc[epw_df["month"] == i + 1, "month_names"], + customdata=epw_df.loc[epw_df[ColNames.MONTH] == i + 1, ColNames.MONTH_NAMES], hovertemplate=( "" + "Global Horizontal Solar Radiation" + ": %{y:.2f} " - + mapping_dictionary["glob_hor_rad"][si_ip]["unit"] + + mapping_dictionary[ColNames.GLOB_HOR_RAD][si_ip]["unit"] + "
" + "Month: %{customdata}
" + "Hour: %{x}:00
" @@ -66,10 +67,10 @@ def monthly_solar(epw_df, si_ip): fig.add_trace( go.Scatter( x=dif_h_rad_month_ave.loc[ - dif_h_rad_month_ave["month"] == i + 1, "hour" + dif_h_rad_month_ave[ColNames.MONTH] == i + 1, ColNames.HOUR ], y=dif_h_rad_month_ave.loc[ - dif_h_rad_month_ave["month"] == i + 1, "dif_hor_rad" + dif_h_rad_month_ave[ColNames.MONTH] == i + 1, ColNames.DIF_HOR_RAD ], fill="tozeroy", mode="lines", @@ -77,12 +78,12 @@ def monthly_solar(epw_df, si_ip): line_width=2, name="Diffuse", showlegend=is_first, - customdata=epw_df.loc[epw_df["month"] == i + 1, "month_names"], + customdata=epw_df.loc[epw_df[ColNames.MONTH] == i + 1, ColNames.MONTH_NAMES], hovertemplate=( "" + "Diffuse Horizontal Solar Radiation" + ": %{y:.2f} " - + mapping_dictionary["dif_hor_rad"][si_ip]["unit"] + + mapping_dictionary[ColNames.DIF_HOR_RAD][si_ip]["unit"] + "
" + "Month: %{customdata}
" + "Hour: %{x}:00
" @@ -112,7 +113,7 @@ def polar_graph(df, meta, global_local, var, si_ip): latitude = float(meta["lat"]) longitude = float(meta["lon"]) time_zone = float(meta["time_zone"]) - solpos = df.loc[df["apparent_elevation"] > 0, :] + solpos = df.loc[df[ColNames.APPARENT_ELEVATION] > 0, :] if var != "None": var_unit = mapping_dictionary[var][si_ip]["unit"] @@ -134,7 +135,7 @@ def polar_graph(df, meta, global_local, var, si_ip): ) delta = timedelta(days=0, hours=time_zone - 1, minutes=0) times = times - delta - solpos = df.loc[df["apparent_elevation"] > 0, :] + solpos = df.loc[df[ColNames.APPARENT_ELEVATION] > 0, :] if var == "None": var_color = "orange" @@ -166,18 +167,18 @@ def polar_graph(df, meta, global_local, var, si_ip): fig.add_trace( go.Scatterpolar( r=90 * np.cos(np.radians(90 - solpos["apparent_zenith"])), - theta=solpos["azimuth"], + theta=solpos[ColNames.AZIMUTH], mode="markers", marker_color="orange", marker_size=marker_size, marker_line_width=0, customdata=np.stack( ( - solpos["day"], - solpos["month_names"], - solpos["hour"], - solpos["elevation"], - solpos["azimuth"], + solpos[ColNames.DAY], + solpos[ColNames.MONTH_NAMES], + solpos[ColNames.HOUR], + solpos[ColNames.ELEVATION], + solpos[ColNames.AZIMUTH], ), axis=-1, ), @@ -196,7 +197,7 @@ def polar_graph(df, meta, global_local, var, si_ip): fig.add_trace( go.Scatterpolar( r=90 * np.cos(np.radians(90 - solpos["apparent_zenith"])), - theta=solpos["azimuth"], + theta=solpos[ColNames.AZIMUTH], mode="markers", marker=dict( color=solpos[var], @@ -209,11 +210,11 @@ def polar_graph(df, meta, global_local, var, si_ip): ), customdata=np.stack( ( - solpos["day"], - solpos["month_names"], - solpos["hour"], - solpos["elevation"], - solpos["azimuth"], + solpos[ColNames.DAY], + solpos[ColNames.MONTH_NAMES], + solpos[ColNames.HOUR], + solpos[ColNames.ELEVATION], + solpos[ColNames.AZIMUTH], solpos[var], ), axis=-1, @@ -240,7 +241,7 @@ def polar_graph(df, meta, global_local, var, si_ip): times = pd.date_range(date, date + pd.Timedelta("24h"), freq="5min", tz=tz) times = times - delta solpos = solarposition.get_solarposition(times, latitude, longitude) - solpos = solpos.loc[solpos["apparent_elevation"] > 0, :] + solpos = solpos.loc[solpos[ColNames.APPARENT_ELEVATION] > 0, :] fig.add_trace( go.Scatterpolar( @@ -264,7 +265,7 @@ def polar_graph(df, meta, global_local, var, si_ip): times = pd.date_range(date, date + pd.Timedelta("24h"), freq="5min", tz=tz) times = times - delta solpos = solarposition.get_solarposition(times, latitude, longitude) - solpos = solpos.loc[solpos["apparent_elevation"] > 0, :] + solpos = solpos.loc[solpos[ColNames.APPARENT_ELEVATION] > 0, :] fig.add_trace( go.Scatterpolar( @@ -344,19 +345,19 @@ def custom_cartesian_solar(df, meta, global_local, var, si_ip): if var == "None": fig.add_trace( go.Scatter( - y=df["elevation"], - x=df["azimuth"], + y=df[ColNames.ELEVATION], + x=df[ColNames.AZIMUTH], mode="markers", marker_color="orange", marker_size=marker_size, marker_line_width=0, customdata=np.stack( ( - df["day"], - df["month_names"], - df["hour"], - df["elevation"], - df["azimuth"], + df[ColNames.DAY], + df[ColNames.MONTH_NAMES], + df[ColNames.HOUR], + df[ColNames.ELEVATION], + df[ColNames.AZIMUTH], ), axis=-1, ), @@ -374,8 +375,8 @@ def custom_cartesian_solar(df, meta, global_local, var, si_ip): else: fig.add_trace( go.Scatter( - y=df["elevation"], - x=df["azimuth"], + y=df[ColNames.ELEVATION], + x=df[ColNames.AZIMUTH], mode="markers", marker=dict( color=df[var], @@ -388,11 +389,11 @@ def custom_cartesian_solar(df, meta, global_local, var, si_ip): ), customdata=np.stack( ( - df["day"], - df["month_names"], - df["hour"], - df["elevation"], - df["azimuth"], + df[ColNames.DAY], + df[ColNames.MONTH_NAMES], + df[ColNames.HOUR], + df[ColNames.ELEVATION], + df[ColNames.AZIMUTH], df[var], ), axis=-1, @@ -420,7 +421,7 @@ def custom_cartesian_solar(df, meta, global_local, var, si_ip): delta = timedelta(days=0, hours=time_zone - 1, minutes=0) times = times - delta solpos = solarposition.get_solarposition(times, latitude, longitude) - solpos = solpos.loc[solpos["apparent_elevation"] > 0, :] + solpos = solpos.loc[solpos[ColNames.APPARENT_ELEVATION] > 0, :] fig.add_trace( go.Scatter( @@ -444,7 +445,7 @@ def custom_cartesian_solar(df, meta, global_local, var, si_ip): delta = timedelta(days=0, hours=time_zone - 1, minutes=0) times = times - delta solpos = solarposition.get_solarposition(times, latitude, longitude) - solpos = solpos.loc[solpos["apparent_elevation"] > 0, :] + solpos = solpos.loc[solpos[ColNames.APPARENT_ELEVATION] > 0, :] fig.add_trace( go.Scatter( diff --git a/pages/lib/extract_df.py b/pages/lib/extract_df.py index a0def908..ce2df0fa 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 @@ -18,6 +19,7 @@ from pages.lib.global_scheme import month_lst from pages.lib.utils import code_timer +from pages.lib.global_column_names import ColNames @code_timer @@ -42,7 +44,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 @@ -148,7 +151,7 @@ def create_df(lst, file_name): # from EnergyPlus files extract info about reference years if not location_info["period"]: - years = epw_df["year"].astype("int").unique() + years = epw_df[ColNames.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}" @@ -158,22 +161,22 @@ def create_df(lst, file_name): location_info["period"] = f"{min_year}-{max_year}" # Add fake_year - epw_df["fake_year"] = "year" + epw_df[ColNames.FAKE_YEAR] = ColNames.YEAR # Add in month names month_look_up = {ix + 1: month for ix, month in enumerate(month_lst)} - epw_df["month_names"] = epw_df["month"].astype("int").map(month_look_up) + epw_df[ColNames.MONTH_NAMES] = epw_df[ColNames.MONTH].astype("int").map(month_look_up) # Change to int type - epw_df[["year", "day", "month", "hour"]] = epw_df[ - ["year", "day", "month", "hour"] + epw_df[[ColNames.YEAR, ColNames.DAY, ColNames.MONTH, ColNames.HOUR]] = epw_df[ + [ColNames.YEAR, ColNames.DAY, ColNames.MONTH, ColNames.HOUR] ].astype(int) # Add in DOY - df_doy = epw_df.groupby(["month", "day"])["hour"].count().reset_index() - df_doy["DOY"] = df_doy.index + 1 + df_doy = epw_df.groupby([ColNames.MONTH, ColNames.DAY])[ColNames.HOUR].count().reset_index() + df_doy[ColNames.DOY] = df_doy.index + 1 epw_df = pd.merge( - epw_df, df_doy[["month", "day", "DOY"]], on=["month", "day"], how="left" + epw_df, df_doy[[ColNames.MONTH, ColNames.DAY, ColNames.DOY]], on=[ColNames.MONTH, ColNames.DAY], how="left" ) change_to_float = [ @@ -209,7 +212,7 @@ def create_df(lst, file_name): times = pd.date_range( "2019-01-01 00:00:00", "2020-01-01", inclusive="left", freq="h", tz="UTC" ) - epw_df["UTC_time"] = pd.to_datetime(times) + epw_df[ColNames.UTC_TIME] = pd.to_datetime(times) delta = timedelta(days=0, hours=location_info["time_zone"] - 1, minutes=0) times = times - delta epw_df["times"] = times @@ -224,9 +227,9 @@ def create_df(lst, file_name): epw_df = pd.concat([epw_df, solar_position], axis=1) # Add in UTCI - sol_altitude = epw_df["elevation"].mask(epw_df["elevation"] <= 0, 0) + sol_altitude = epw_df[ColNames.ELEVATION].mask(epw_df[ColNames.ELEVATION] <= 0, 0) sharp = [45] * 8760 - sol_radiation_dir = epw_df["dir_nor_rad"] + sol_radiation_dir = epw_df[ColNames.DIR_NOR_RAD] sol_transmittance = [1] * 8760 # CHECK VALUE f_svv = [1] * 8760 # CHECK VALUE f_bes = [1] * 8760 # CHECK VALUE @@ -246,65 +249,65 @@ def create_df(lst, file_name): floor_reflectance, ) mrt_df = pd.DataFrame.from_records(mrt) - mrt_df["delta_mrt"] = mrt_df["delta_mrt"].mask(mrt_df["delta_mrt"] >= 70, 70) + mrt_df[ColNames.DELTA_MRT] = mrt_df[ColNames.DELTA_MRT].mask(mrt_df[ColNames.DELTA_MRT] >= 70, 70) mrt_df = mrt_df.set_index(epw_df.times) epw_df = epw_df.join(mrt_df) - epw_df["MRT"] = epw_df["delta_mrt"] + epw_df["DBT"] - epw_df["wind_speed_utci"] = epw_df["wind_speed"] - epw_df["wind_speed_utci"] = epw_df["wind_speed_utci"].mask( - epw_df["wind_speed_utci"] >= 17, 16.9 + epw_df[ColNames.MRT] = epw_df[ColNames.DELTA_MRT] + epw_df[ColNames.DBT] + epw_df[ColNames.WIND_SPEED_UTCI] = epw_df[ColNames.WIND_SPEED] + epw_df[ColNames.WIND_SPEED_UTCI] = epw_df[ColNames.WIND_SPEED_UTCI].mask( + epw_df[ColNames.WIND_SPEED_UTCI] >= 17, 16.9 ) - epw_df["wind_speed_utci"] = epw_df["wind_speed_utci"].mask( - epw_df["wind_speed_utci"] <= 0.5, 0.6 + epw_df[ColNames.WIND_SPEED_UTCI] = epw_df[ColNames.WIND_SPEED_UTCI].mask( + epw_df[ColNames.WIND_SPEED_UTCI] <= 0.5, 0.6 ) - epw_df["wind_speed_utci_0"] = epw_df["wind_speed_utci"].mask( - epw_df["wind_speed_utci"] >= 0, 0.5 + epw_df[ColNames.WIND_SPEED_UTCI_0] = epw_df[ColNames.WIND_SPEED_UTCI].mask( + epw_df[ColNames.WIND_SPEED_UTCI] >= 0, 0.5 ) - epw_df["utci_noSun_Wind"] = utci( - epw_df["DBT"], epw_df["DBT"], epw_df["wind_speed_utci"], epw_df["RH"] + epw_df[ColNames.UTCI_NO_SUN_WIND] = utci( + epw_df[ColNames.DBT], epw_df[ColNames.DBT], epw_df[ColNames.WIND_SPEED_UTCI], epw_df[ColNames.RH] ) - epw_df["utci_noSun_noWind"] = utci( - epw_df["DBT"], epw_df["DBT"], epw_df["wind_speed_utci_0"], epw_df["RH"] + epw_df[ColNames.UTCI_NO_SUN_NO_WIND] = utci( + epw_df[ColNames.DBT], epw_df[ColNames.DBT], epw_df[ColNames.WIND_SPEED_UTCI_0], epw_df[ColNames.RH] ) - epw_df["utci_Sun_Wind"] = utci( - epw_df["DBT"], epw_df["MRT"], epw_df["wind_speed_utci"], epw_df["RH"] + epw_df[ColNames.UTCI_SUN_WIND] = utci( + epw_df[ColNames.DBT], epw_df[ColNames.MRT], epw_df[ColNames.WIND_SPEED_UTCI], epw_df[ColNames.RH] ) - epw_df["utci_Sun_noWind"] = utci( - epw_df["DBT"], epw_df["MRT"], epw_df["wind_speed_utci_0"], epw_df["RH"] + epw_df[ColNames.UTCI_SUN_NO_WIND] = utci( + epw_df[ColNames.DBT], epw_df[ColNames.MRT], epw_df[ColNames.WIND_SPEED_UTCI_0], epw_df[ColNames.RH] ) utci_bins = [-999, -40, -27, -13, 0, 9, 26, 32, 38, 46, 999] utci_labels = [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4] epw_df["utci_noSun_Wind_categories"] = pd.cut( - x=epw_df["utci_noSun_Wind"], bins=utci_bins, labels=utci_labels + x=epw_df[ColNames.UTCI_NO_SUN_WIND], bins=utci_bins, labels=utci_labels ) epw_df["utci_noSun_noWind_categories"] = pd.cut( - x=epw_df["utci_noSun_noWind"], bins=utci_bins, labels=utci_labels + x=epw_df[ColNames.UTCI_NO_SUN_NO_WIND], bins=utci_bins, labels=utci_labels ) epw_df["utci_Sun_Wind_categories"] = pd.cut( - x=epw_df["utci_Sun_Wind"], bins=utci_bins, labels=utci_labels + x=epw_df[ColNames.UTCI_SUN_WIND], bins=utci_bins, labels=utci_labels ) epw_df["utci_Sun_noWind_categories"] = pd.cut( - x=epw_df["utci_Sun_noWind"], bins=utci_bins, labels=utci_labels + x=epw_df[ColNames.UTCI_SUN_NO_WIND], bins=utci_bins, labels=utci_labels ) # Add psy values - ta_rh = np.vectorize(psy.psy_ta_rh)(epw_df["DBT"], epw_df["RH"]) + ta_rh = np.vectorize(psy.psy_ta_rh)(epw_df[ColNames.DBT], epw_df[ColNames.RH]) psy_df = pd.DataFrame.from_records(ta_rh) psy_df = psy_df.set_index(epw_df.times) epw_df = epw_df.join(psy_df) # calculate adaptive data - dbt_day_ave = epw_df.groupby(["DOY"])["DBT"].mean().to_list() + dbt_day_ave = epw_df.groupby([ColNames.DOY])[ColNames.DBT].mean().to_list() n = 7 - epw_df["adaptive_comfort"] = np.nan - epw_df["adaptive_cmf_80_low"] = np.nan - epw_df["adaptive_cmf_80_up"] = np.nan - epw_df["adaptive_cmf_90_low"] = np.nan - epw_df["adaptive_cmf_90_up"] = np.nan - epw_df["adaptive_cmf_rmt"] = np.nan + epw_df[ColNames.ADAPTIVE_COMFORT] = np.nan + epw_df[ColNames.ADAPTIVE_CMF_80_LOW] = np.nan + epw_df[ColNames.ADAPTIVE_CMF_80_UP] = np.nan + epw_df[ColNames.ADAPTIVE_CMF_90_LOW] = np.nan + epw_df[ColNames.ADAPTIVE_CMF_90_UP] = np.nan + epw_df[ColNames.ADAPTIVE_CMF_RMT] = np.nan for day in epw_df.DOY.unique(): i = day - 1 if i < n: @@ -325,12 +328,12 @@ def create_df(lst, file_name): v=0.5, limit_inputs=False, ) - epw_df.loc[epw_df.DOY == day, "adaptive_cmf_rmt"] = rmt - epw_df.loc[epw_df.DOY == day, "adaptive_comfort"] = r["tmp_cmf"] - epw_df.loc[epw_df.DOY == day, "adaptive_cmf_80_low"] = r["tmp_cmf_80_low"] - epw_df.loc[epw_df.DOY == day, "adaptive_cmf_80_up"] = r["tmp_cmf_80_up"] - epw_df.loc[epw_df.DOY == day, "adaptive_cmf_90_low"] = r["tmp_cmf_90_low"] - epw_df.loc[epw_df.DOY == day, "adaptive_cmf_90_up"] = r["tmp_cmf_90_up"] + epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_RMT] = rmt + epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_COMFORT] = r["tmp_cmf"] + epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_80_LOW] = r["tmp_cmf_80_low"] + epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_80_UP] = r["tmp_cmf_80_up"] + epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_90_LOW] = r["tmp_cmf_90_low"] + epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_90_UP] = r["tmp_cmf_90_up"] return epw_df, location_info @@ -369,11 +372,11 @@ def enthalpy(df, name): def convert_data(df, mapping_json): - df["adaptive_comfort"] = df["adaptive_comfort"] * 1.8 + 32 - df["adaptive_cmf_80_low"] = df["adaptive_cmf_80_low"] * 1.8 + 32 - df["adaptive_cmf_80_up"] = df["adaptive_cmf_80_up"] * 1.8 + 32 - df["adaptive_cmf_90_low"] = df["adaptive_cmf_90_low"] * 1.8 + 32 - df["adaptive_cmf_90_up"] = df["adaptive_cmf_90_up"] * 1.8 + 32 + df[ColNames.ADAPTIVE_COMFORT] = df[ColNames.ADAPTIVE_COMFORT] * 1.8 + 32 + df[ColNames.ADAPTIVE_CMF_80_LOW] = df[ColNames.ADAPTIVE_CMF_80_LOW] * 1.8 + 32 + df[ColNames.ADAPTIVE_CMF_80_UP] = df[ColNames.ADAPTIVE_CMF_80_UP] * 1.8 + 32 + df[ColNames.ADAPTIVE_CMF_90_LOW] = df[ColNames.ADAPTIVE_CMF_90_LOW] * 1.8 + 32 + df[ColNames.ADAPTIVE_CMF_90_UP] = df[ColNames.ADAPTIVE_CMF_90_UP] * 1.8 + 32 mapping_dict = json.loads(mapping_json) for key in json.loads(mapping_json): diff --git a/pages/lib/global_column_names.py b/pages/lib/global_column_names.py new file mode 100644 index 00000000..c8c627d0 --- /dev/null +++ b/pages/lib/global_column_names.py @@ -0,0 +1,77 @@ +from enum import Enum + + +class ColNames(str, Enum): + # ==================== Time related column ==================== + YEAR = "year" # year + MONTH = "month" # month + DAY = "day" # day + HOUR = "hour" # hour + MINUTE = "minute" # minute + + # ==================== Meteorological data column ==================== + DBT = "DBT" # Dry Bulb Temperature + DPT = "DPT" # Dew Point Temperature + RH = "RH" # Relative Humidity + HI_RH = "hiRH" # High Relative Humidity + LO_RH = "loRH" # Low Relative Humidity + HR = "hr" + P_ATM = "p_atm" # Atmospheric Pressure + + # ==================== Radiation-related column ==================== + EXTR_HOR_RAD = "extr_hor_rad" # Extraterrestrial Horizontal Radiation + HOR_IR_RAD = "hor_ir_rad" # Horizontal Infrared Radiation + GLOB_HOR_RAD = "glob_hor_rad" # Global Horizontal Radiation + DIR_NOR_RAD = "dir_nor_rad" # Direct Normal Radiation + DIF_HOR_RAD = "dif_hor_rad" # Diffuse Horizontal Radiation + + # ==================== Lighting-related columns ==================== + GLOB_HOR_ILL = "glob_hor_ill" # Global Horizontal Illuminance + DIR_NOR_ILL = "dir_nor_ill" # Direct Normal Illuminance + DIF_HOR_ILL = "dif_hor_ill" # Diffuse Horizontal Illuminance + + # ==================== Other columns ==================== + ZLUMI = "Zlumi" # Luminance + WIND_DIR = "wind_dir" # Wind Direction + WIND_SPEED = "wind_speed" # Wind Speed + WIND_SPEED_UTCI = "wind_speed_utci" # Wind Speed Utci + WIND_SPEED_UTCI_0 = "wind_speed_utci_0" # Wind Speed Utci 0 + TOT_SKY_COVER = "tot_sky_cover" # Total Sky Cover + OSKYCOVER = "Oskycover" # Opaque Sky Cover + VIS = "Vis" # Visibility + CHEIGHT = "Cheight" # Cloud Height + PWobs = "PWobs" # Precipitation Observation + PWcodes = "PWcodes" # Precipitation Codes + Pwater = "Pwater" # Precipitation Water + AsolOptD = "AsolOptD" # Aerosol Optical Depth + SnowD = "SnowD" # Snow Depth + DaySSnow = "DaySSnow" # Daily Snow + ELEVATION = "elevation" # Elevation + APPARENT_ELEVATION = "apparent_elevation" # Apparent Elevation + AZIMUTH = "azimuth" # Azimuth + MRT = "MRT" + DELTA_MRT = "delta_mrt" + UTCI_SUN_WIND = "utci_Sun_Wind" # Utci Sun Wind + UTCI_SUN_NO_WIND = "utci_Sun_noWind" # Utci Sun no Wind + UTCI_NO_SUN_WIND = "utci_noSun_Wind" # Utci no Sun Wind + UTCI_NO_SUN_NO_WIND = "utci_noSun_noWind" # Utci no Sun no Wind + ADAPTIVE_COMFORT = "adaptive_comfort" # Adaptive comfort + ADAPTIVE_CMF_80_LOW = "adaptive_cmf_80_low" # Adaptive comfort 80 low + ADAPTIVE_CMF_80_UP = "adaptive_cmf_80_up" # Adaptive comfort 80 up + ADAPTIVE_CMF_90_LOW = "adaptive_cmf_90_low" # Adaptive comfort 90 low + ADAPTIVE_CMF_90_UP = "adaptive_cmf_90_up" # Adaptive comfort 90 up + ADAPTIVE_CMF_RMT = "adaptive_cmf_rmt" # Adaptive comfort rmt + NV_ALLOWED = "nv_allowed" + + # ==================== Calculation column ==================== + FAKE_YEAR = "fake_year" # Fake Year + MONTH_NAMES = "month_names" # Month names + UTC_TIME = "UTC_time" # UTC Time + DOY = "DOY" # Day of Year + + + + + + + diff --git a/pages/lib/global_element_ids.py b/pages/lib/global_element_ids.py new file mode 100644 index 00000000..6fcce955 --- /dev/null +++ b/pages/lib/global_element_ids.py @@ -0,0 +1,200 @@ +from enum import Enum + + +class ElementIds(str, Enum): + # ==================== Defines the unique ID constant for each element in the front-end page ==================== + OUTDOOR_COMFORT_OUTPUT = "outdoor-comfort-output" + DAILY = "daily" + ID_T_RH_DF_STORE = "df-store" + ENABLE_CONDENSATION = "enable-condensation" + ID_EXPLORER_DF_STORE = "df-store" + ID_EXPLORER_META_STORE = "meta-store" + ID_NATURAL_VENTILATION_DF_STORE = "df-store" + ID_NATURAL_VENTILATION_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" + ID_T_RH_DROPDOWN = "dropdown" + CUSTOM_SUMMARY = "custom-summary" + CUSTOM_HEATMAP = "custom-heatmap" + ID_T_RH_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" + HEATMAP = "heatmap" + MAIN_NV_SECTION = "main-nv-section" + ID_NATURAL_VENTILATION_META_STORE = "meta-store" + NORMALIZE = "normalize" + NV_BAR_CHART = "nv-bar-chart" + NV_DBT_FILTER = "nv-dbt-filter" + NV_DPT_FILTER = "nv-dpt-filter" + NV_DPT_MAX_VAL = "nv-dpt-max-val" + NV_TDB_MIN_VAL = "nv-tdb-min-val" + NV_TDB_MAX_VAL = "nv-tdb-max-val" + NV_HEATMAP_CHART = "nv-heatmap-chart" + OUTDOOR_COMFORT_HOUR_SLIDER = "outdoor-comfort-hour-slider" + NV_MONTH_HOUR_FILTER = "nv-month-hour-filter" + NV_MONTH_SLIDER = "nv-month-slider" + IMAGE_SELECTION = "image-selection" + INVERT_HOUR_OUTDOOR_COMFORT = "invert-hour-outdoor-comfort" + INVERT_MONTH_OUTDOOR_COMFORT = "invert-month-outdoor-comfort" + INVERT_MONTH_NV = "invert-month-nv" + ID_EXPLORER_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" + INVERT_HOUR_EXPLORE_DESCRIPTIVE = "invert-hour-explore-descriptive" + INVERT_MONTH_EXPLORE_DESCRIPTIVE = "invert-month-explore-descriptive" + INVERT_MONTH_EXPLORE_HEATMAP = "invert-month-explore-heatmap" + INVERT_HOUR_EXPLORE_HEATMAP = "invert-hour-explore-heatmap" + INVERT_MONTH_EXPLORE_MORE_CHARTS = "invert-month-explore-more-charts" + INVERT_HOUR_EXPLORE_MORE_CHARTS = "invert-hour-explore-more-charts" + INVERT_HOUR_NV = "invert-hour-nv" + ID_EXPLORER_SI_IP_UNIT_STORE = "si-ip-unit-store" + ID_NATURAL_VENTILATION_SI_IP_UNIT_STORE = "si-ip-unit-store" + ID_NATURAL_VENTILATION_SI_IP_RADIO_INPUT = "si-ip-radio-input" + ID_T_RH_META_STORE = "meta-store" + OUTDOOR_COMFORT_MONTH_SLIDER = "outdoor-comfort-month-slider" + OUTDOOR_COMFORT_SWITCHES_INPUT = "outdoor-comfort-switches-input" + NV_HOUR_SLIDER = "nv-hour-slider" + PSYCH_CHART = "psych-chart" + PSY_CHART_BTN = "psy-chart-btn" + PSY_COLOR_BY_DROPDOWN = "psy-color-by-dropdown" + DATA_FILTER = "data-filter" + PSY_VAR_DROPDOWN = "psy-var-dropdown" + PSY_HOUR_SLIDER = "psy-hour-slider" + INVERT_HOUR_PSY = "invert-hour-psy" + INVERT_MONTH_PSY = "invert-month-psy" + PSY_MAX_VAL = "psy-max-val" + PSY_MIN_VAL = "psy-min-val" + PSY_MONTH_SLIDER = "psy-month-slider" + ID_OUTDOOR_MONTH_HOUR_FILTER = "month-hour-filter" + MONTH_HOUR_FILTER = "month-hour-filter" + SEC1_HOUR_SLIDER = "sec1-hour-slider" + SEC1_MONTH_SLIDER = "sec1-month-slider" + SEC1_TIME_FILTER_INPUT = "sec1-time-filter-input" + SEC1_VAR_DROPDOWN = "sec1-var-dropdown" + SDATA_FILTER = "data-filter" + SEC2_DATA_FILTER_INPUT = "sec2-data-filter-input" + SEC2_DATA_FILTER_VAR = "sec2-data-filter-var" + SEC2_VAR_DROPDOWN = "sec2-var-dropdown" + SEC2_HOUR_SLIDER = "sec2-hour-slider" + SEC2_TIME_FILTER_INPUT = "sec2-time-filter-input" + SEC2_MONTH_SLIDER = "sec2-month-slider" + SEC2_MIN_VAL = "sec2-min-val" + SEC2_MAX_VAL = "sec2-max-val" + TAB7_DROPDOWN = "tab7-dropdown" + ID_T_RH_SI_IP_UNIT_STORE = "si-ip-unit-store" + SWITCHES_INPUT = "switches-input" + TABLE_TMP_HUM = "table-tmp-hum" + TABLE_DATA_EXPLORER = "table-data-explorer" + TAB6_SEC2_CONTAINER = "tab6-sec2-container" + TAB6_SEC3_DATA_FILTER_INPUT = "tab6-sec3-data-filter-input" + TAB6_SEC3_FILTER_VAR_DROPDOWN = "tab6-sec3-filter-var-dropdown" + TAB6_SEC3_MIN_VAL = "tab6-sec3-min-val" + TAB6_SEC3_MAX_VAL = "tab6-sec3-max-val" + TAB6_SEC3_TIME_FILTER_INPUT = "tab6-sec3-time-filter-input" + TAB6_SEC3_QUERY_HOUR_SLIDER = "tab6-sec3-query-hour-slider" + TAB6_SEC3_QUERY_MONTH_SLIDER = "tab6-sec3-query-month-slider" + TAB6_SEC3_VAR_X_DROPDOWN = "tab6-sec3-var-x-dropdown" + TAB6_SEC3_VAR_Y_DROPDOWN = "tab6-sec3-var-y-dropdown" + TAB6_SEC3_COLORBY_DROPDOWN = "tab6-sec3-colorby-dropdown" + MONTH_HOUR_FILTER_OUTDOOR_COMFORT = "month-hour-filter-outdoor-comfort" + TWO_VAR = "two-var" + THREE_VAR = "three-var" + QUERY_DAILY = "query-daily" + QUERY_HEATMAP = "query-heatmap" + UTCI_CATEGORY_HEATMAP = "utci-category-heatmap" + UTCI_HEATMAP = "utci-heatmap" + UTCI_SUMMARY_CHART = "utci-summary-chart" + YEARLY_CHART = "yearly-chart" + YEARLY_EXPLORE = "yearly-explore" + ID_OUTDOOR_DF_STORE = "df-store" + ID_OUTDOOR_META_STORE = "meta-store" + ID_OUTDOOR_SI_IP_UNIT_STORE = "si-ip-unit-store" + ID_PSY_CHART_SI_IP_UNIT_STORE = "si-ip-unit-store" + ID_OUTDOOR_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" + ID_PSY_CHART_DF_STORE = "df-store" + ID_PSY_CHART_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" + ID_PSY_CHART_META_STORE = "meta-store" + CUSTOM_SUN_VIEW_DROPDOWN = "custom-sun-view-dropdown" + CUSTOM_SUN_VAR_DROPDOWN = "custom-sun-var-dropdown" + CUSTOM_SUNPATH = "custom-sunpath" + TAB_EXPLORE_DROPDOWN = "tab4-explore-dropdown" + TAB4_DAILY = "tab4-daily" + TAB4_HEATMAP = "tab4-heatmap" + STATIC_SECTION = "static-section" + TAB_FOUR_CONTAINER = "tab-four-container" + MONTHLY_SOLAR = "monthly-solar" + CLOUD_COVER = "cloud-cover" + ID_SUN_SI_IP_RADIO_INPUT = "si-ip-radio-input" + ID_SUN_DF_STORE = "df-store" + ID_SUN_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" + ID_SUN_SI_IP_UNIT_STORE = "si-ip-unit-store" + ID_SUN_META_STORE = "meta-store" + SLIDER_CONTAINER = "slider-container" + MONTH_SLIDER = "month-slider" + HOUR_SLIDER = "hour-slider" + WINTER_WIND_ROSE = "winter-wind-rose" + WINTER_WIND_ROSE_TEXT = "winter-wind-rose-text" + SPRING_WIND_ROSE = "spring-wind-rose" + SPRING_WIND_ROSE_TEXT = "spring-wind-rose-text" + SUMMER_WIND_ROSE = "summer-wind-rose" + SUMMER_WIND_ROSE_TEXT = "summer-wind-rose-text" + FALL_WIND_ROSE = "fall-wind-rose" + FALL_WIND_ROSE_TEXT = "fall-wind-rose-text" + TAB5_DAILY_CONTAINER = "tab5-daily-container" + DAILY_WIND_ROSE_OUTER_CONTAINER = "daily-wind-rose-outer-container" + MORNING_WIND_ROSE = "morning-wind-rose" + MORNING_WIND_ROSE_TEXT = "morning-wind-rose-text" + NOON_WIND_ROSE = "noon-wind-rose" + NOON_WIND_ROSE_TEXT = "noon-wind-rose-text" + NIGHT_WIND_ROSE = "night-wind-rose" + NIGHT_WIND_ROSE_TEXT = "night-wind-rose-text" + TAB5_CUSTOM_DROPDOWN_CONTAINER = "tab5-custom-dropdown-container" + TAB5_CUSTOM_START_MONTH = "tab5-custom-start-month" + TAB5_CUSTOM_START_HOUR = "tab5-custom-start-hour" + TAB5_CUSTOM_END_MONTH = "tab5-custom-end-month" + TAB5_CUSTOM_END_HOUR = "tab5-custom-end-hour" + CUSTOM_WIND_ROSE = "custom-wind-rose" + WIND_ROSE = "wind-rose" + WIND_SPEED = "wind-speed" + WIND_DIRECTION = "wind-direction" + ID_WIND_DF_STORE = "df-store" + ID_WIND_META_STORE = "meta-store" + ID_WIND_SI_IP_UNIT_STORE = "si-ip-unit-store" + ID_WIND_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" + LOADING_ONE = "loading-1" + UPLOAD_DATA = "upload-data" + UPLOAD_DATA_BUTTON = "upload-data-button" + TAB_ONE_MAP = "tab-one-map" + SKELETON_GRAPH_CONTAINER = "skeleton-graph-container" + MODAL_HEADER = "modal-header" + MODAL_CLOSE_BUTTON = "modal-close-button" + MODAL_YES_BUTTON = "modal-yes-button" + MODAL = "modal" + ALERT = "alert" + ID_SELECT_META_STORE = "meta-store" + LINES_STORE = "lines-store" + ID_SELECT_URL_STORE = "url-store" + ID_SELECT_DF_STORE = "df-store" + ID_SELECT_SI_IP_UNIT_STORE = "si-ip-unit-store" + ID_SELECT_SI_IP_RADIO_INPUT = "si-ip-radio-input" + BANNER_SUBTITLE = "banner-subtitle" + TABLE_TWO_CONTAINER = "tab-two-container" + ID_SUMMARY_SI_IP_RADIO_INPUT = "si-ip-radio-input" + TAB2_SCE1_CONTAINER = "tab2-sec1-container" + LOCATION_INFO = "location-info" + WORLD_MAP = "world-map" + DOWN_EPW_BUTTON = "download-epw-button" + DOWNLOAD_BUTTON = "download-button" + DOWNLOAD_DATAFRAME_CSV = "download-dataframe-csv" + DOWNLOAD_EPW = "download-epw" + WARNING_CDD_HIGHER_HDD = "warning-cdd-higher-hdd" + INPUT_HDD_SET_POINT = "input-hdd-set-point" + INPUT_CDD_SET_POINT = "input-cdd-set-point" + SUBMIT_SET_POINTS = "submit-set-points" + GRAPH_CONTAINER = "graph-container" + DEGREE_DAYS_CHART_WRAPPER = "degree-days-chart-wrapper" + TEMP_PROFILE_GRAPH = "temp-profile-graph" + HUMIDITY_PROFILE_GRAPH = "humidity-profile-graph" + SOLAR_RADIATION_GRAPH = "solar-radiation-graph" + WIND_SPEED_GRAPH = "wind-speed-graph" + ID_SUMMARY_META_STORE = "meta-store" + GH_RAD_PROFILE_GRAPH = "gh_rad-profile-graph" + ID_SUMMARY_DF_STORE = "df-store" + ID_SUMMARY_SI_IP_UNIT_STORE = "si-ip-unit-store" + ID_SUMMARY_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" + TDB_PROFILE_GRAPH = "tdb-profile-graph" + RH_PROFILE_GRAPH = "rh-profile-graph" diff --git a/pages/lib/layout.py b/pages/lib/layout.py index 1edea71d..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", @@ -75,7 +75,7 @@ def footer(): dmc.Group( [ dmc.Anchor( - "Version: 0.8.18", + "Version: 0.9.0", href="https://center-for-the-built-environment.gitbook.io/clima/version/changelog", underline=True, c="white", diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index 5f06e73c..ae840591 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -9,14 +9,14 @@ from pages.lib.global_scheme import mapping_dictionary import dash_bootstrap_components as dbc from .global_scheme import month_lst, template, tight_margins - +from pages.lib.global_column_names import ColNames from .utils import code_timer, determine_month_and_hour_filter def violin(df, var, global_local, si_ip): """Return day night violin based on the 'var' col""" - mask_day = (df["hour"] >= 8) & (df["hour"] < 20) - mask_night = (df["hour"] < 8) | (df["hour"] >= 20) + mask_day = (df[ColNames.HOUR] >= 8) & (df[ColNames.HOUR] < 20) + mask_night = (df[ColNames.HOUR] < 8) | (df[ColNames.HOUR] >= 20) var_unit = mapping_dictionary[var][si_ip]["unit"] var_range = mapping_dictionary[var][si_ip]["range"] var_name = mapping_dictionary[var]["name"] @@ -32,7 +32,7 @@ def violin(df, var, global_local, si_ip): fig = go.Figure() fig.add_trace( go.Violin( - x=df["fake_year"], + x=df[ColNames.FAKE_YEAR], y=data_day, line_color="#ffaa00", name="Day", @@ -44,7 +44,7 @@ def violin(df, var, global_local, si_ip): fig.add_trace( go.Violin( - x=df["fake_year"], + x=df[ColNames.FAKE_YEAR], y=data_night, line_color="#00264d", name="Night", @@ -107,14 +107,14 @@ def yearly_profile(df, var, global_local, si_ip): ) trace1 = go.Bar( - x=df["UTC_time"].dt.date.unique(), + x=df[ColNames.UTC_TIME].dt.date.unique(), y=dbt_day["max"] - dbt_day["min"], base=dbt_day["min"], marker_color=var_single_color, marker_opacity=0.3, name=var_name + " Range", customdata=np.stack( - (dbt_day["mean"], df.iloc[::24, :]["month_names"], df.iloc[::24, :]["day"]), + (dbt_day["mean"], df.iloc[::24, :][ColNames.MONTH_NAMES], df.iloc[::24, :][ColNames.DAY]), axis=-1, ), hovertemplate=( @@ -129,14 +129,14 @@ def yearly_profile(df, var, global_local, si_ip): ) trace2 = go.Scatter( - x=df["UTC_time"].dt.date.unique(), + x=df[ColNames.UTC_TIME].dt.date.unique(), y=dbt_day["mean"], name="Average " + var_name, mode="lines", marker_color=var_single_color, marker_opacity=1, customdata=np.stack( - (dbt_day["mean"], df.iloc[::24, :]["month_names"], df.iloc[::24, :]["day"]), + (dbt_day["mean"], df.iloc[::24, :][ColNames.MONTH_NAMES], df.iloc[::24, :][ColNames.DAY]), axis=-1, ), hovertemplate=( @@ -146,16 +146,16 @@ def yearly_profile(df, var, global_local, si_ip): ), ) - if var == "DBT": + if var == ColNames.DBT: # plot ashrae adaptive comfort limits (80%) - lo80 = df.groupby("DOY")["adaptive_cmf_80_low"].mean().values - hi80 = df.groupby("DOY")["adaptive_cmf_80_up"].mean().values - rmt = df.groupby("DOY")["adaptive_cmf_rmt"].mean().values + lo80 = df.groupby(ColNames.DOY)["adaptive_cmf_80_low"].mean().values + hi80 = df.groupby(ColNames.DOY)["adaptive_cmf_80_up"].mean().values + rmt = df.groupby(ColNames.DOY)["adaptive_cmf_rmt"].mean().values # set color https://github.com/CenterForTheBuiltEnvironment/clima/issues/113 implementation var_bar_colors = np.where((rmt > 40) | (rmt < 10), "lightgray", "darkgray") trace3 = go.Bar( - x=df["UTC_time"].dt.date.unique(), + x=df[ColNames.UTC_TIME].dt.date.unique(), y=hi80 - lo80, base=lo80, name="ASHRAE adaptive comfort (80%)", @@ -167,11 +167,11 @@ def yearly_profile(df, var, global_local, si_ip): ) # plot ashrae adaptive comfort limits (90%) - lo90 = df.groupby("DOY")["adaptive_cmf_90_low"].mean().values - hi90 = df.groupby("DOY")["adaptive_cmf_90_up"].mean().values + lo90 = df.groupby(ColNames.DOY)["adaptive_cmf_90_low"].mean().values + hi90 = df.groupby(ColNames.DOY)["adaptive_cmf_90_up"].mean().values trace4 = go.Bar( - x=df["UTC_time"].dt.date.unique(), + x=df[ColNames.UTC_TIME].dt.date.unique(), y=hi90 - lo90, base=lo90, name="ASHRAE adaptive comfort (90%)", @@ -183,17 +183,17 @@ def yearly_profile(df, var, global_local, si_ip): ) data = [trace3, trace4, trace1, trace2] - elif var == "RH": + elif var == ColNames.RH: # plot relative Humidity limits (30-70%) lo_rh = [30] * 365 hi_rh = [70] * 365 - lo_rh_df = pd.DataFrame({"loRH": lo_rh}) - hi_rh_df = pd.DataFrame({"hiRH": hi_rh}) + lo_rh_df = pd.DataFrame({ColNames.LO_RH: lo_rh}) + hi_rh_df = pd.DataFrame({ColNames.HI_RH: hi_rh}) trace3 = go.Bar( - x=df["UTC_time"].dt.date.unique(), - y=hi_rh_df["hiRH"] - lo_rh_df["loRH"], - base=lo_rh_df["loRH"], + x=df[ColNames.UTC_TIME].dt.date.unique(), + y=hi_rh_df[ColNames.HI_RH] - lo_rh_df[ColNames.LO_RH], + base=lo_rh_df[ColNames.LO_RH], name="humidity comfort band", marker_opacity=0.3, marker_color="silver", @@ -252,7 +252,7 @@ def daily_profile(df, var, global_local, si_ip): range_y = [data_min, data_max] var_single_color = var_color[len(var_color) // 2] - var_month_ave = df.groupby(["month", "hour"])[var].median().reset_index() + var_month_ave = df.groupby([ColNames.MONTH, ColNames.HOUR])[var].median().reset_index() fig = make_subplots( rows=1, cols=12, @@ -263,15 +263,15 @@ def daily_profile(df, var, global_local, si_ip): for i in range(12): fig.add_trace( go.Scatter( - x=df.loc[df["month"] == i + 1, "hour"], - y=df.loc[df["month"] == i + 1, var], + x=df.loc[df[ColNames.MONTH] == i + 1, ColNames.HOUR], + y=df.loc[df[ColNames.MONTH] == i + 1, var], mode="markers", marker_color=var_single_color, opacity=0.5, marker_size=3, name=month_lst[i], showlegend=False, - customdata=df.loc[df["month"] == i + 1, "month_names"], + customdata=df.loc[df[ColNames.MONTH] == i + 1, ColNames.MONTH_NAMES], hovertemplate=( "" + var @@ -286,8 +286,8 @@ def daily_profile(df, var, global_local, si_ip): fig.add_trace( go.Scatter( - x=var_month_ave.loc[var_month_ave["month"] == i + 1, "hour"], - y=var_month_ave.loc[var_month_ave["month"] == i + 1, var], + x=var_month_ave.loc[var_month_ave[ColNames.MONTH] == i + 1, ColNames.HOUR], + y=var_month_ave.loc[var_month_ave[ColNames.MONTH] == i + 1, var], mode="lines", line_color=var_single_color, line_width=3, @@ -343,7 +343,7 @@ def heatmap_with_filter( month, hour, invert_month, invert_hour ) - if df.dropna(subset=["month"]).shape[0] == 0: + if df.dropna(subset=[ColNames.MONTH]).shape[0] == 0: return ( dbc.Alert( "No data is available in this location under these conditions. Please " @@ -364,13 +364,13 @@ def heatmap_with_filter( range_z = [data_min, data_max] fig = go.Figure( data=go.Heatmap( - y=df["hour"] - 0.5, # Offset by 0.5 to center the hour labels - x=df["UTC_time"].dt.date, + y=df[ColNames.HOUR] - 0.5, # Offset by 0.5 to center the hour labels + x=df[ColNames.UTC_TIME].dt.date, z=df[var], colorscale=var_color, zmin=range_z[0], zmax=range_z[1], - customdata=np.stack((df["month_names"], df["day"]), axis=-1), + customdata=np.stack((df[ColNames.MONTH_NAMES], df[ColNames.DAY]), axis=-1), hovertemplate=( "" + var @@ -423,13 +423,13 @@ def heatmap(df, var, global_local, si_ip): range_z = [data_min, data_max] fig = go.Figure( data=go.Heatmap( - y=df["hour"], - x=df["UTC_time"].dt.date, + y=df[ColNames.HOUR], + x=df[ColNames.UTC_TIME].dt.date, z=df[var], colorscale=var_color, zmin=range_z[0], zmax=range_z[1], - customdata=np.stack((df["month_names"], df["day"]), axis=-1), + customdata=np.stack((df[ColNames.MONTH_NAMES], df[ColNames.DAY]), axis=-1), hovertemplate=( "" + var @@ -478,16 +478,16 @@ def wind_rose(df, title, month, hour, labels, si_ip): start_hour = hour[0] end_hour = hour[1] if start_month <= end_month: - df = df.loc[(df["month"] >= start_month) & (df["month"] <= end_month)] + df = df.loc[(df[ColNames.MONTH] >= start_month) & (df[ColNames.MONTH] <= end_month)] else: - df = df.loc[(df["month"] <= end_month) | (df["month"] >= start_month)] + df = df.loc[(df[ColNames.MONTH] <= end_month) | (df[ColNames.MONTH] >= start_month)] if start_hour <= end_hour: - df = df.loc[(df["hour"] > start_hour) & (df["hour"] <= end_hour)] + df = df.loc[(df[ColNames.HOUR] > start_hour) & (df[ColNames.HOUR] <= end_hour)] else: - df = df.loc[(df["hour"] <= end_hour) | (df["hour"] >= start_hour)] + df = df.loc[(df[ColNames.HOUR] <= end_hour) | (df[ColNames.HOUR] >= start_hour)] - spd_colors = mapping_dictionary["wind_speed"]["color"] - spd_unit = mapping_dictionary["wind_speed"][si_ip]["unit"] + spd_colors = mapping_dictionary[ColNames.WIND_SPEED]["color"] + spd_unit = mapping_dictionary[ColNames.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 == UnitSystem.IP: spd_bins = convert_bins(spd_bins) @@ -501,10 +501,10 @@ def wind_rose(df, title, month, hour, labels, si_ip): # 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 + d[ColNames.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 + d[ColNames.WIND_DIR], bins=dir_bins, labels=dir_labels, right=False ), ) @@ -618,7 +618,7 @@ def thermal_stress_stacked_barchart( month, hour, invert_month, invert_hour ) - if df.dropna(subset=["month"]).shape[0] == 0: + if df.dropna(subset=[ColNames.MONTH]).shape[0] == 0: return ( dbc.Alert( "No data is available in this location under these conditions. Please " @@ -631,12 +631,12 @@ def thermal_stress_stacked_barchart( isNormalized = True if len(normalize) != 0 else False if isNormalized: new_df = ( - df.groupby("month")[var].value_counts(normalize=True).unstack(var).fillna(0) + df.groupby(ColNames.MONTH)[var].value_counts(normalize=True).unstack(var).fillna(0) ) new_df = new_df.set_axis(categories, axis=1) new_df.reset_index(inplace=True) else: - new_df = df.groupby("month")[var].value_counts().unstack(var).fillna(0) + new_df = df.groupby(ColNames.MONTH)[var].value_counts().unstack(var).fillna(0) new_df = new_df.set_axis(categories, axis=1) new_df.reset_index(inplace=True) @@ -740,13 +740,13 @@ def barchart(df, var, time_filter_info, data_filter_info, normalize, si_ip): query = ( f"month=={str(i)} and ({filter_var}>={min_val} and {filter_var}<={max_val})" ) - a = new_df.query(query)["DOY"].count() + a = new_df.query(query)[ColNames.DOY].count() month_in.append(a) query = f"month=={str(i)} and ({filter_var}<{min_val})" - b = new_df.query(query)["DOY"].count() + b = new_df.query(query)[ColNames.DOY].count() month_below.append(b) query = f"month=={str(i)} and {filter_var}>{max_val}" - c = new_df.query(query)["DOY"].count() + c = new_df.query(query)[ColNames.DOY].count() month_above.append(c) go.Figure() @@ -830,17 +830,17 @@ def filter_df_by_month_and_hour( if time_filter: if start_month <= end_month: - mask = (df["month"] < start_month) | (df["month"] > end_month) + mask = (df[ColNames.MONTH] < start_month) | (df[ColNames.MONTH] > end_month) df.loc[mask, var] = None else: - mask = (df["month"] >= end_month) & (df["month"] <= start_month) + mask = (df[ColNames.MONTH] >= end_month) & (df[ColNames.MONTH] <= start_month) df.loc[mask, var] = None if start_hour <= end_hour: - mask = (df["hour"] <= start_hour) | (df["hour"] > end_hour) + mask = (df[ColNames.HOUR] <= start_hour) | (df[ColNames.HOUR] > end_hour) df.loc[mask, var] = None else: - mask = (df["hour"] > end_hour) & (df["hour"] <= start_hour) + mask = (df[ColNames.HOUR] > end_hour) & (df[ColNames.HOUR] <= start_hour) df.loc[mask, var] = None return df @@ -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 diff --git a/pages/lib/utils.py b/pages/lib/utils.py index 860a0409..89038bae 100644 --- a/pages/lib/utils.py +++ b/pages/lib/utils.py @@ -8,6 +8,7 @@ from config import UnitSystem from pages.lib.global_scheme import fig_config, mapping_dictionary, month_lst +from pages.lib.global_column_names import ColNames def code_timer(func): @@ -215,13 +216,13 @@ def title_with_link( def summary_table_tmp_rh_tab(df, value, si_ip): df_summary = ( - df.groupby(["month_names", "month"])[value] + df.groupby([ColNames.MONTH_NAMES, ColNames.MONTH])[value] .describe(percentiles=[0.01, 0.25, 0.5, 0.75, 0.99]) .round(2) ) - df_summary = df_summary.reset_index(level="month_names").sort_index() + df_summary = df_summary.reset_index(level=ColNames.MONTH_NAMES).sort_index() df_summary = df_summary.drop(["count"], axis=1) - df_summary = df_summary.rename(columns={"month_names": "month"}) + df_summary = df_summary.rename(columns={ColNames.MONTH_NAMES: ColNames.MONTH}) df_sum = ( df[value] @@ -229,7 +230,7 @@ def summary_table_tmp_rh_tab(df, value, si_ip): .round(2) .to_frame() ) - df_sum = df_sum.T.assign(count="Year").rename(columns={"count": "month"}) + df_sum = df_sum.T.assign(count="Year").rename(columns={"count": ColNames.MONTH}) df_summary = pd.concat([df_summary, df_sum]) @@ -240,7 +241,7 @@ def summary_table_tmp_rh_tab(df, value, si_ip): ) return dash_table.DataTable( columns=[ - {"name": i, "id": i} if i == "month" else {"name": f"{i} ({unit})", "id": i} + {"name": i, "id": i} if i == ColNames.MONTH else {"name": f"{i} ({unit})", "id": i} for i in df_summary.columns ], style_table={"overflowX": "auto"}, diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 32c62cdb..091d8fa4 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -1,3 +1,4 @@ + import math import dash @@ -18,6 +19,8 @@ container_col_center_one_of_three, ) from pages.lib.template_graphs import filter_df_by_month_and_hour +from pages.lib.global_column_names import ColNames +from pages.lib.global_element_ids import ElementIds from pages.lib.utils import ( title_with_tooltip, generate_chart_name, @@ -40,14 +43,17 @@ def layout(): return html.Div( className="container-col", - id="main-nv-section", + id=ElementIds.MAIN_NV_SECTION, children=[ # ], ) -@callback(Output("main-nv-section", "children"), [Input("si-ip-radio-input", "value")]) +@callback( + Output(ElementIds.MAIN_NV_SECTION, "children"), + [Input(ElementIds.ID_NATURAL_VENTILATION_SI_IP_RADIO_INPUT, "value")], +) def update_layout(si_ip): if si_ip == UnitSystem.IP: tdb_set_min = 50 @@ -69,7 +75,7 @@ def update_layout(si_ip): inputs_tab(tdb_set_min, tdb_set_max, dpt_set), dcc.Loading( html.Div( - id="nv-heatmap-chart", + id=ElementIds.NV_HEATMAP_CHART, style={"marginTop": "1rem"}, ), type="circle", @@ -82,7 +88,7 @@ def update_layout(si_ip): {"label": "", "value": 1}, ], value=[1], - id="switches-input", + id=ElementIds.SWITCHES_INPUT, switch=True, style={ "padding": "1rem", @@ -104,7 +110,7 @@ def update_layout(si_ip): ), dcc.Loading( html.Div( - id="nv-bar-chart", + id=ElementIds.NV_BAR_CHART, style={"marginTop": "1rem"}, ), type="circle", @@ -122,7 +128,7 @@ def inputs_tab(t_min, t_max, d_set): dbc.Button( "Apply filter", color="primary", - id="nv-dbt-filter", + id=ElementIds.NV_DBT_FILTER, className="mb-2", n_clicks=1, ), @@ -132,7 +138,7 @@ def inputs_tab(t_min, t_max, d_set): children=[ html.H6(children=["Min Value:"], style={"flex": "30%"}), dbc.Input( - id="nv-tdb-min-val", + id=ElementIds.NV_TDB_MIN_VAL, placeholder="Enter a number for the min val", type="number", step=1, @@ -146,7 +152,7 @@ def inputs_tab(t_min, t_max, d_set): children=[ html.H6(children=["Max Value:"], style={"flex": "30%"}), dbc.Input( - id="nv-tdb-max-val", + id=ElementIds.NV_TDB_MAX_VAL, placeholder="Enter a number for the max val", type="number", value=t_max, @@ -163,7 +169,7 @@ def inputs_tab(t_min, t_max, d_set): dbc.Button( "Apply month and hour filter", color="primary", - id="nv-month-hour-filter", + id=ElementIds.NV_MONTH_HOUR_FILTER, className="mb-2", n_clicks=0, ), @@ -173,7 +179,7 @@ def inputs_tab(t_min, t_max, d_set): html.H6("Month Range", style={"flex": "20%"}), html.Div( dcc.RangeSlider( - id="nv-month-slider", + id=ElementIds.NV_MONTH_SLIDER, min=1, max=12, step=1, @@ -192,7 +198,7 @@ def inputs_tab(t_min, t_max, d_set): {"label": "Invert", "value": "invert"}, ], value=[], - id="invert-month-nv", + id=ElementIds.INVERT_MONTH_NV, labelStyle={"flex": "30%"}, ), ], @@ -203,7 +209,7 @@ def inputs_tab(t_min, t_max, d_set): html.H6("Hour Range", style={"flex": "20%"}), html.Div( dcc.RangeSlider( - id="nv-hour-slider", + id=ElementIds.NV_HOUR_SLIDER, min=0, max=24, step=1, @@ -222,7 +228,7 @@ def inputs_tab(t_min, t_max, d_set): {"label": "Invert", "value": "invert"}, ], value=[], - id="invert-hour-nv", + id=ElementIds.INVERT_HOUR_NV, labelStyle={"flex": "30%"}, ), ], @@ -235,7 +241,7 @@ def inputs_tab(t_min, t_max, d_set): dbc.Button( "Apply filter", color="primary", - id="nv-dpt-filter", + id=ElementIds.NV_DPT_FILTER, className="mb-2", n_clicks=0, disabled=True, @@ -253,7 +259,7 @@ def inputs_tab(t_min, t_max, d_set): }, ], value=[], - id="enable-condensation", + id=ElementIds.ENABLE_CONDENSATION, ), html.Div( className=container_row_center_full, @@ -263,7 +269,7 @@ def inputs_tab(t_min, t_max, d_set): style={"marginRight": "1rem"}, ), dbc.Input( - id="nv-dpt-max-val", + id=ElementIds.NV_DPT_MAX_VAL, placeholder="Enter a number for the max val", type="number", value=d_set, @@ -279,26 +285,26 @@ def inputs_tab(t_min, t_max, d_set): @callback( - Output("nv-heatmap-chart", "children"), + Output(ElementIds.NV_HEATMAP_CHART, "children"), [ - Input("df-store", "modified_timestamp"), - Input("nv-month-hour-filter", "n_clicks"), - Input("nv-dbt-filter", "n_clicks"), - Input("nv-dpt-filter", "n_clicks"), - Input("global-local-radio-input", "value"), - Input("enable-condensation", "value"), + Input(ElementIds.ID_NATURAL_VENTILATION_DF_STORE, "modified_timestamp"), + Input(ElementIds.NV_MONTH_HOUR_FILTER, "n_clicks"), + Input(ElementIds.NV_DBT_FILTER, "n_clicks"), + Input(ElementIds.NV_DPT_FILTER, "n_clicks"), + Input(ElementIds.ID_NATURAL_VENTILATION_GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.ENABLE_CONDENSATION, "value"), ], [ - State("df-store", "data"), - State("nv-month-slider", "value"), - State("nv-hour-slider", "value"), - State("nv-tdb-min-val", "value"), - State("nv-tdb-max-val", "value"), - State("nv-dpt-max-val", "value"), - State("meta-store", "data"), - State("invert-month-nv", "value"), - State("invert-hour-nv", "value"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_NATURAL_VENTILATION_DF_STORE, "data"), + State(ElementIds.NV_MONTH_SLIDER, "value"), + State(ElementIds.NV_HOUR_SLIDER, "value"), + State(ElementIds.NV_TDB_MIN_VAL, "value"), + State(ElementIds.NV_TDB_MAX_VAL, "value"), + State(ElementIds.NV_DPT_MAX_VAL, "value"), + State(ElementIds.ID_NATURAL_VENTILATION_META_STORE, "data"), + State(ElementIds.INVERT_MONTH_NV, "value"), + State(ElementIds.INVERT_HOUR_NV, "value"), + State(ElementIds.ID_NATURAL_VENTILATION_SI_IP_UNIT_STORE, "data"), ], ) def nv_heatmap( @@ -326,8 +332,8 @@ def nv_heatmap( month, hour, invert_month, invert_hour ) - var = "DBT" - filter_var = "DPT" + var = ColNames.DBT + filter_var = ColNames.DPT if dbt_data_filter and (min_dbt_val <= max_dbt_val): df.loc[(df[var] < min_dbt_val) | (df[var] > max_dbt_val), var] = None @@ -335,7 +341,7 @@ def nv_heatmap( if dpt_data_filter: df.loc[(df[filter_var] < -200) | (df[filter_var] > max_dpt_val), var] = None - if df.dropna(subset=["month"]).shape[0] == 0: + if df.dropna(subset=[ColNames.MONTH]).shape[0] == 0: return ( dbc.Alert( "Natural ventilation is not available in this location under these" @@ -386,15 +392,15 @@ def nv_heatmap( fig = go.Figure( data=go.Heatmap( - y=df["hour"] - 0.5, # Offset by 0.5 to center the hour labels - x=df["UTC_time"].dt.date, + y=df[ColNames.HOUR] - 0.5, # Offset by 0.5 to center the hour labels + x=df[ColNames.UTC_TIME].dt.date, z=df[var], colorscale=var_color, zmin=range_z[0], zmax=range_z[1], connectgaps=False, hoverongaps=False, - customdata=np.stack((df["month_names"], df["day"]), axis=-1), + customdata=np.stack((df[ColNames.MONTH_NAMES], df[ColNames.DAY]), axis=-1), hovertemplate=( "" + var @@ -446,26 +452,26 @@ def nv_heatmap( @callback( - Output("nv-bar-chart", "children"), + Output(ElementIds.NV_BAR_CHART, "children"), [ - Input("df-store", "modified_timestamp"), - Input("nv-month-hour-filter", "n_clicks"), - Input("nv-dbt-filter", "n_clicks"), - Input("nv-dpt-filter", "n_clicks"), - Input("switches-input", "value"), - Input("enable-condensation", "value"), + Input(ElementIds.ID_NATURAL_VENTILATION_DF_STORE, "modified_timestamp"), + Input(ElementIds.NV_MONTH_HOUR_FILTER, "n_clicks"), + Input(ElementIds.NV_DBT_FILTER, "n_clicks"), + Input(ElementIds.NV_DPT_FILTER, "n_clicks"), + Input(ElementIds.SWITCHES_INPUT, "value"), + Input(ElementIds.ENABLE_CONDENSATION, "value"), ], [ - State("df-store", "data"), - State("nv-month-slider", "value"), - State("nv-hour-slider", "value"), - State("nv-tdb-min-val", "value"), - State("nv-tdb-max-val", "value"), - State("nv-dpt-max-val", "value"), - State("meta-store", "data"), - State("invert-month-nv", "value"), - State("invert-hour-nv", "value"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_NATURAL_VENTILATION_DF_STORE, "data"), + State(ElementIds.NV_MONTH_SLIDER, "value"), + State(ElementIds.NV_HOUR_SLIDER, "value"), + State(ElementIds.NV_TDB_MIN_VAL, "value"), + State(ElementIds.NV_TDB_MAX_VAL, "value"), + State(ElementIds.NV_DPT_MAX_VAL, "value"), + State(ElementIds.ID_NATURAL_VENTILATION_META_STORE, "data"), + State(ElementIds.INVERT_MONTH_NV, "value"), + State(ElementIds.INVERT_HOUR_NV, "value"), + State(ElementIds.ID_NATURAL_VENTILATION_SI_IP_UNIT_STORE, "data"), ], ) def nv_bar_chart( @@ -505,24 +511,26 @@ def nv_bar_chart( color_in = "dodgerblue" - df["nv_allowed"] = 1 + df[ColNames.NV_ALLOWED] = 1 df = filter_df_by_month_and_hour( df, time_filter, month, hour, invert_month, invert_hour, "nv_allowed" ) # this should be the total after filtering by time - tot_month_hours = df.groupby(df["UTC_time"].dt.month)["nv_allowed"].sum().values + tot_month_hours = ( + df.groupby(df[ColNames.UTC_TIME].dt.month)[ColNames.NV_ALLOWED].sum().values + ) if dbt_data_filter and (min_dbt_val <= max_dbt_val): - df.loc[(df[var] < min_dbt_val) | (df[var] > max_dbt_val), "nv_allowed"] = 0 + df.loc[(df[var] < min_dbt_val) | (df[var] > max_dbt_val), ColNames.NV_ALLOWED] = 0 if dpt_data_filter: - df.loc[(df[filter_var] > max_dpt_val), "nv_allowed"] = 0 + df.loc[(df[filter_var] > max_dpt_val), ColNames.NV_ALLOWED] = 0 n_hours_nv_allowed = ( - df.dropna(subset="nv_allowed") - .groupby(df["UTC_time"].dt.month)["nv_allowed"] + df.dropna(subset=ColNames.NV_ALLOWED) + .groupby(df[ColNames.UTC_TIME].dt.month)[ColNames.NV_ALLOWED] .sum() .values ) @@ -532,7 +540,7 @@ def nv_bar_chart( if len(normalize) == 0: fig = go.Figure( go.Bar( - x=df["month_names"].unique(), + x=df[ColNames.MONTH_NAMES].unique(), y=n_hours_nv_allowed, name="", marker_color=color_in, @@ -554,7 +562,7 @@ def nv_bar_chart( else: trace1 = go.Bar( - x=df["month_names"].unique(), + x=df[ColNames.MONTH_NAMES].unique(), y=per_time_nv_allowed, name="", marker_color=color_in, @@ -609,7 +617,10 @@ def nv_bar_chart( ) -@callback(Output("nv-dpt-filter", "disabled"), Input("enable-condensation", "value")) +@callback( + Output(ElementIds.NV_DPT_FILTER, "disabled"), + Input(ElementIds.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 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, diff --git a/pages/outdoor.py b/pages/outdoor.py index 59153930..33d1fd5c 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -6,6 +6,7 @@ import numpy as np from config import PageUrls, DocLinks, PageInfo +from pages.lib.global_element_ids import ElementIds from pages.lib.global_scheme import ( outdoor_dropdown_names, ) @@ -47,12 +48,12 @@ def inputs_outdoor_comfort(): style={"flex": "30%"}, ), dropdown( - id="tab7-dropdown", + id=ElementIds.TAB7_DROPDOWN, style={"flex": "60%"}, options=outdoor_dropdown_names, value="utci_Sun_Wind", ), - html.Div(id="image-selection", style={"flex": "10%"}), + html.Div(id=ElementIds.IMAGE_SELECTION, style={"flex": "10%"}), ], ), ], @@ -67,7 +68,7 @@ def inputs_outdoor_comfort(): style={ "width": "100%", }, - id="month-hour-filter-outdoor-comfort", + id=ElementIds.MONTH_HOUR_FILTER_OUTDOOR_COMFORT, className="mb-2", n_clicks=0, ), @@ -77,7 +78,7 @@ def inputs_outdoor_comfort(): html.H6("Month Range", style={"flex": "5%"}), html.Div( dcc.RangeSlider( - id="outdoor-comfort-month-slider", + id=ElementIds.OUTDOOR_COMFORT_MONTH_SLIDER, min=1, max=12, step=1, @@ -96,7 +97,7 @@ def inputs_outdoor_comfort(): {"label": "Invert", "value": "invert"}, ], value=[], - id="invert-month-outdoor-comfort", + id=ElementIds.INVERT_MONTH_OUTDOOR_COMFORT, labelStyle={"flex": "30%"}, ), ], @@ -107,7 +108,7 @@ def inputs_outdoor_comfort(): html.H6("Hour Range", style={"flex": "5%"}), html.Div( dcc.RangeSlider( - id="outdoor-comfort-hour-slider", + id=ElementIds.OUTDOOR_COMFORT_HOUR_SLIDER, min=0, max=24, step=1, @@ -126,7 +127,7 @@ def inputs_outdoor_comfort(): {"label": "Invert", "value": "invert"}, ], value=[], - id="invert-hour-outdoor-comfort", + id=ElementIds.INVERT_HOUR_OUTDOOR_COMFORT, labelStyle={"flex": "30%"}, ), ], @@ -140,7 +141,7 @@ def inputs_outdoor_comfort(): def outdoor_comfort_chart(): return html.Div( children=[ - html.Div(id="outdoor-comfort-output"), + html.Div(id=ElementIds.OUTDOOR_COMFORT_OUTPUT), html.Div( children=title_with_link( text="UTCI heatmap chart", @@ -149,7 +150,7 @@ def outdoor_comfort_chart(): ) ), dcc.Loading( - html.Div(id="utci-heatmap"), + html.Div(id=ElementIds.UTCI_HEATMAP), type="circle", ), html.Div( @@ -160,7 +161,7 @@ def outdoor_comfort_chart(): ) ), dcc.Loading( - html.Div(id="utci-category-heatmap"), + html.Div(id=ElementIds.UTCI_CATEGORY_HEATMAP), type="circle", ), html.Div( @@ -171,7 +172,7 @@ def outdoor_comfort_chart(): {"label": "", "value": 1}, ], value=[1], - id="outdoor-comfort-switches-input", + id=ElementIds.OUTDOOR_COMFORT_SWITCHES_INPUT, switch=True, style={ "padding": "1rem", @@ -192,7 +193,7 @@ def outdoor_comfort_chart(): ], ), dcc.Loading( - html.Div(id="utci-summary-chart"), + html.Div(id=ElementIds.UTCI_SUMMARY_CHART), type="circle", ), ], @@ -212,15 +213,27 @@ def layout(): @callback( - Output("outdoor-comfort-output", "children"), + Output(ElementIds.OUTDOOR_COMFORT_OUTPUT, "children"), [ - Input("df-store", "modified_timestamp"), + Input(ElementIds.ID_OUTDOOR_DF_STORE, "modified_timestamp"), ], [ - State("df-store", "data"), + State(ElementIds.ID_OUTDOOR_DF_STORE, "data"), ], ) 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", @@ -244,21 +257,21 @@ def update_outdoor_comfort_output(_, df): @callback( - Output("utci-heatmap", "children"), + Output(ElementIds.UTCI_HEATMAP, "children"), [ - Input("df-store", "modified_timestamp"), - Input("tab7-dropdown", "value"), - Input("global-local-radio-input", "value"), - Input("month-hour-filter-outdoor-comfort", "n_clicks"), + Input(ElementIds.ID_OUTDOOR_DF_STORE, "modified_timestamp"), + Input(ElementIds.TAB7_DROPDOWN, "value"), + Input(ElementIds.ID_OUTDOOR_GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.MONTH_HOUR_FILTER_OUTDOOR_COMFORT, "n_clicks"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), - State("outdoor-comfort-month-slider", "value"), - State("outdoor-comfort-hour-slider", "value"), - State("invert-month-outdoor-comfort", "value"), - State("invert-hour-outdoor-comfort", "value"), + State(ElementIds.ID_OUTDOOR_DF_STORE, "data"), + State(ElementIds.ID_OUTDOOR_META_STORE, "data"), + State(ElementIds.ID_OUTDOOR_SI_IP_UNIT_STORE, "data"), + State(ElementIds.OUTDOOR_COMFORT_MONTH_SLIDER, "value"), + State(ElementIds.OUTDOOR_COMFORT_HOUR_SLIDER, "value"), + State(ElementIds.INVERT_MONTH_OUTDOOR_COMFORT, "value"), + State(ElementIds.INVERT_HOUR_OUTDOOR_COMFORT, "value"), ], ) def update_tab_utci_value( @@ -294,8 +307,8 @@ def update_tab_utci_value( @callback( - Output("image-selection", "children"), - Input("tab7-dropdown", "value"), + Output(ElementIds.IMAGE_SELECTION, "children"), + Input(ElementIds.TAB7_DROPDOWN, "value"), ) def change_image_based_on_selection(value): if value == "utci_Sun_Wind": @@ -311,21 +324,21 @@ def change_image_based_on_selection(value): @callback( - Output("utci-category-heatmap", "children"), + Output(ElementIds.UTCI_CATEGORY_HEATMAP, "children"), [ - Input("df-store", "modified_timestamp"), - Input("tab7-dropdown", "value"), - Input("global-local-radio-input", "value"), - Input("month-hour-filter-outdoor-comfort", "n_clicks"), + Input(ElementIds.ID_OUTDOOR_DF_STORE, "modified_timestamp"), + Input(ElementIds.TAB7_DROPDOWN, "value"), + Input(ElementIds.ID_OUTDOOR_GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.MONTH_HOUR_FILTER_OUTDOOR_COMFORT, "n_clicks"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), - State("outdoor-comfort-month-slider", "value"), - State("outdoor-comfort-hour-slider", "value"), - State("invert-month-outdoor-comfort", "value"), - State("invert-hour-outdoor-comfort", "value"), + State(ElementIds.ID_OUTDOOR_DF_STORE, "data"), + State(ElementIds.ID_OUTDOOR_META_STORE, "data"), + State(ElementIds.ID_OUTDOOR_SI_IP_UNIT_STORE, "data"), + State(ElementIds.OUTDOOR_COMFORT_MONTH_SLIDER, "value"), + State(ElementIds.OUTDOOR_COMFORT_HOUR_SLIDER, "value"), + State(ElementIds.INVERT_MONTH_OUTDOOR_COMFORT, "value"), + State(ElementIds.INVERT_HOUR_OUTDOOR_COMFORT, "value"), ], ) def update_tab_utci_category( @@ -381,20 +394,20 @@ def update_tab_utci_category( @callback( - Output("utci-summary-chart", "children"), + Output(ElementIds.UTCI_SUMMARY_CHART, "children"), [ - Input("tab7-dropdown", "value"), - Input("month-hour-filter-outdoor-comfort", "n_clicks"), - Input("outdoor-comfort-switches-input", "value"), + Input(ElementIds.TAB7_DROPDOWN, "value"), + Input(ElementIds.MONTH_HOUR_FILTER_OUTDOOR_COMFORT, "n_clicks"), + Input(ElementIds.OUTDOOR_COMFORT_SWITCHES_INPUT, "value"), ], [ - State("df-store", "data"), - State("outdoor-comfort-month-slider", "value"), - State("outdoor-comfort-hour-slider", "value"), - State("meta-store", "data"), - State("invert-month-outdoor-comfort", "value"), - State("invert-hour-outdoor-comfort", "value"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_OUTDOOR_DF_STORE, "data"), + State(ElementIds.OUTDOOR_COMFORT_MONTH_SLIDER, "value"), + State(ElementIds.OUTDOOR_COMFORT_HOUR_SLIDER, "value"), + State(ElementIds.ID_OUTDOOR_META_STORE, "data"), + State(ElementIds.INVERT_MONTH_OUTDOOR_COMFORT, "value"), + State(ElementIds.INVERT_HOUR_OUTDOOR_COMFORT, "value"), + State(ElementIds.ID_OUTDOOR_SI_IP_UNIT_STORE, "data"), ], ) def update_tab_utci_summary_chart( diff --git a/pages/psy-chart.py b/pages/psy-chart.py index 2c46b885..7fbd802d 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -12,6 +12,8 @@ from pythermalcomfort import psychrometrics as psy from config import PageUrls, DocLinks, PageInfo, UnitSystem +from pages.lib.global_element_ids import ElementIds +from pages.lib.global_column_names import ColNames from pages.lib.global_scheme import ( container_row_center_full, container_col_center_one_of_three, @@ -71,7 +73,7 @@ def inputs(): style={"flex": "30%"}, ), dropdown( - id="psy-color-by-dropdown", + id=ElementIds.PSY_COLOR_BY_DROPDOWN, options=psy_dropdown_names, value="Frequency", style={"flex": "70%"}, @@ -88,7 +90,7 @@ def inputs(): dbc.Button( "Apply month and hour filter", color="primary", - id="month-hour-filter", + id=ElementIds.MONTH_HOUR_FILTER, className="mb-2", n_clicks=0, ), @@ -98,7 +100,7 @@ def inputs(): html.H6("Month Range", style={"flex": "20%"}), html.Div( dcc.RangeSlider( - id="psy-month-slider", + id=ElementIds.PSY_MONTH_SLIDER, min=1, max=12, step=1, @@ -117,7 +119,7 @@ def inputs(): {"label": "Invert", "value": "invert"}, ], value=[], - id="invert-month-psy", + id=ElementIds.INVERT_MONTH_PSY, labelStyle={"flex": "30%"}, ), ], @@ -128,7 +130,7 @@ def inputs(): html.H6("Hour Range", style={"flex": "20%"}), html.Div( dcc.RangeSlider( - id="psy-hour-slider", + id=ElementIds.PSY_HOUR_SLIDER, min=0, max=24, step=1, @@ -147,7 +149,7 @@ def inputs(): {"label": "Invert", "value": "invert"}, ], value=[], - id="invert-hour-psy", + id=ElementIds.INVERT_HOUR_PSY, labelStyle={"flex": "30%"}, ), ], @@ -160,7 +162,7 @@ def inputs(): dbc.Button( "Apply filter", color="primary", - id="data-filter", + id=ElementIds.DATA_FILTER, className="mb-2", n_clicks=0, ), @@ -171,9 +173,9 @@ def inputs(): children=["Filter Variable:"], style={"flex": "30%"} ), dropdown( - id="psy-var-dropdown", + id=ElementIds.PSY_VAR_DROPDOWN , options=dropdown_names, - value="RH", + value=ColNames.RH, style={"flex": "70%"}, ), ], @@ -183,7 +185,7 @@ def inputs(): children=[ html.H6(children=["Min Value:"], style={"flex": "30%"}), dbc.Input( - id="psy-min-val", + id=ElementIds.PSY_MIN_VAL, placeholder="Enter a number for the min val", type="number", step=1, @@ -197,7 +199,7 @@ def inputs(): children=[ html.H6(children=["Max Value:"], style={"flex": "30%"}), dbc.Input( - id="psy-max-val", + id=ElementIds.PSY_MAX_VAL, placeholder="Enter a number for the max val", type="number", value=100, @@ -225,7 +227,7 @@ def layout(): type="circle", children=html.Div( className="container-col", - children=[inputs(), html.Div(id="psych-chart")], + children=[inputs(), html.Div(id=ElementIds.PSYCH_CHART)], ), ), ) @@ -233,25 +235,25 @@ def layout(): # psychrometric chart @callback( - Output("psych-chart", "children"), + Output(ElementIds.PSYCH_CHART, "children"), [ - Input("df-store", "modified_timestamp"), - Input("psy-color-by-dropdown", "value"), - Input("month-hour-filter", "n_clicks"), - Input("data-filter", "n_clicks"), - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_PSY_CHART_DF_STORE, "modified_timestamp"), + Input(ElementIds.PSY_COLOR_BY_DROPDOWN, "value"), + Input(ElementIds.MONTH_HOUR_FILTER, "n_clicks"), + Input(ElementIds.DATA_FILTER, "n_clicks"), + Input(ElementIds.ID_PSY_CHART_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State("df-store", "data"), - State("psy-month-slider", "value"), - State("psy-hour-slider", "value"), - State("psy-min-val", "value"), - State("psy-max-val", "value"), - State("psy-var-dropdown", "value"), - State("meta-store", "data"), - State("invert-month-psy", "value"), - State("invert-hour-psy", "value"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_PSY_CHART_DF_STORE, "data"), + State(ElementIds.PSY_MONTH_SLIDER, "value"), + State(ElementIds.PSY_HOUR_SLIDER, "value"), + State(ElementIds.PSY_MIN_VAL, "value"), + State(ElementIds.PSY_MAX_VAL, "value"), + State(ElementIds.PSY_VAR_DROPDOWN , "value"), + State(ElementIds.ID_PSY_CHART_META_STORE, "data"), + State(ElementIds.INVERT_MONTH_PSY, "value"), + State(ElementIds.INVERT_HOUR_PSY, "value"), + State(ElementIds.ID_PSY_CHART_SI_IP_UNIT_STORE, "data"), ], ) def update_psych_chart( @@ -312,17 +314,17 @@ def update_psych_chart( if global_local == "global": # Set Global values for Max and minimum - var_range_x = mapping_dictionary["DBT"][si_ip]["range"] - var_range_y = mapping_dictionary["hr"][si_ip]["range"] + var_range_x = mapping_dictionary[ColNames.DBT][si_ip]["range"] + var_range_y = mapping_dictionary[ColNames.HR][si_ip]["range"] else: # Set maximum and minimum according to data - data_max = 5 * ceil(df["DBT"].max() / 5) - data_min = 5 * floor(df["DBT"].min() / 5) + data_max = 5 * ceil(df[ColNames.DBT].max() / 5) + data_min = 5 * floor(df[ColNames.DBT].min() / 5) var_range_x = [data_min, data_max] - data_max = round(df["hr"].max(), 4) - data_min = round(df["hr"].min(), 4) + data_max = round(df[ColNames.HR].max(), 4) + data_min = round(df[ColNames.HR].min(), 4) var_range_y = [data_min * 1000, data_max * 1000] title = "Psychrometric Chart" @@ -338,7 +340,7 @@ def update_psych_chart( hr_list = np.vectorize(psy.psy_ta_rh)(dbt_list, rh) hr_df = pd.DataFrame.from_records(hr_list) name = "rh" + str(rh) - rh_df[name] = hr_df["hr"] + rh_df[name] = hr_df[ColNames.HR] fig = go.Figure() @@ -368,13 +370,13 @@ def update_psych_chart( ) ) - df_hr_multiply = list(df["hr"]) + df_hr_multiply = list(df[ColNames.HR]) for k in range(len(df_hr_multiply)): df_hr_multiply[k] = df_hr_multiply[k] * 1000 if var == "None": fig.add_trace( go.Scatter( - x=df["DBT"], + x=df[ColNames.DBT], y=df_hr_multiply, showlegend=False, mode="markers", @@ -384,16 +386,16 @@ def update_psych_chart( showscale=False, opacity=0.2, ), - hovertemplate=mapping_dictionary["DBT"]["name"] + hovertemplate=mapping_dictionary[ColNames.DBT]["name"] + ": %{x:.2f}" - + mapping_dictionary["DBT"]["name"], + + mapping_dictionary[ColNames.DBT]["name"], name="", ) ) elif var == "Frequency": fig.add_trace( go.Histogram2d( - x=df["DBT"], + x=df[ColNames.DBT], y=df_hr_multiply, name="", colorscale=var_color, @@ -437,7 +439,7 @@ def update_psych_chart( fig.add_trace( go.Scatter( - x=df["DBT"], + x=df[ColNames.DBT], y=df_hr_multiply, showlegend=False, mode="markers", @@ -449,14 +451,14 @@ def update_psych_chart( colorscale=var_color, colorbar=var_colorbar, ), - customdata=np.stack((df["RH"], df["h"], df[var], df["t_dp"]), axis=-1), - hovertemplate=mapping_dictionary["DBT"]["name"] + customdata=np.stack((df[ColNames.RH], df["h"], df[var], df["t_dp"]), axis=-1), + hovertemplate=mapping_dictionary[ColNames.DBT]["name"] + ": %{x:.2f}" - + mapping_dictionary["DBT"][si_ip]["unit"] + + mapping_dictionary[ColNames.DBT][si_ip]["unit"] + "
" - + mapping_dictionary["RH"]["name"] + + mapping_dictionary[ColNames.RH]["name"] + ": %{customdata[0]:.2f}" - + mapping_dictionary["RH"][si_ip]["unit"] + + mapping_dictionary[ColNames.RH][si_ip]["unit"] + "
" + mapping_dictionary["h"]["name"] + ": %{customdata[1]:.2f}" @@ -474,8 +476,8 @@ def update_psych_chart( ) ) - xtitle_name = "Temperature" + " " + mapping_dictionary["DBT"][si_ip]["unit"] - ytitle_name = "Humidity Ratio" + " " + mapping_dictionary["hr"][si_ip]["unit"] + xtitle_name = "Temperature" + " " + mapping_dictionary[ColNames.DBT][si_ip]["unit"] + ytitle_name = "Humidity Ratio" + " " + mapping_dictionary[ColNames.HR][si_ip]["unit"] fig.update_layout(template=template, margin=tight_margins) fig.update_xaxes( title_text=xtitle_name, diff --git a/pages/select.py b/pages/select.py index cfc770d7..89fd67f0 100644 --- a/pages/select.py +++ b/pages/select.py @@ -13,6 +13,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_element_ids import ElementIds from pages.lib.global_scheme import mapping_dictionary from config import PageUrls, PageInfo, UnitSystem from pages.lib.utils import generate_chart_name @@ -40,19 +41,19 @@ def layout(): className="container-col tab-container", children=[ dcc.Loading( - id="loading-1", + id=ElementIds.LOADING_ONE, type="circle", fullscreen=True, children=alert(), ), dcc.Upload( - id="upload-data", + id=ElementIds.UPLOAD_DATA, children=dbc.Button( [ "Drag and Drop or ", html.A("Select an EPW file from your computer"), ], - id="upload-data-button", + id=ElementIds.UPLOAD_DATA_BUTTON, outline=True, color="secondary", className="mt-2", @@ -64,31 +65,31 @@ def layout(): ), dmc.Skeleton( visible=False, - id="skeleton-graph-container", + id=ElementIds.SKELETON_GRAPH_CONTAINER, height=500, - children=html.Div(id="tab-one-map"), + children=html.Div(id=ElementIds.TAB_ONE_MAP), ), dbc.Modal( [ - dbc.ModalHeader(id="modal-header"), + dbc.ModalHeader(id=ElementIds.MODAL_HEADER), dbc.ModalFooter( children=[ dbc.Button( "Close", - id="modal-close-button", + id=ElementIds.MODAL_CLOSE_BUTTON, className="ml-2", color="light", ), dbc.Button( "Yes", - id="modal-yes-button", + id=ElementIds.MODAL_YES_BUTTON, className="ml-2", color="primary", ), ] ), ], - id="modal", + id=ElementIds.MODAL, is_open=False, ), ], @@ -100,7 +101,7 @@ def alert(): return dbc.Alert( messages_alert["start"], color="primary", - id="alert", + id=ElementIds.ALERT, dismissable=False, is_open=True, style={"maxHeight": "66px"}, @@ -110,20 +111,20 @@ def alert(): # add si-ip and map dictionary in the output @callback( [ - Output("meta-store", "data"), - Output("lines-store", "data"), - Output("alert", "is_open"), - Output("alert", "children"), - Output("alert", "color"), + Output(ElementIds.ID_SELECT_META_STORE, "data"), + Output(ElementIds.LINES_STORE, "data"), + Output(ElementIds.ALERT, "is_open"), + Output(ElementIds.ALERT, "children"), + Output(ElementIds.ALERT, "color"), ], [ - Input("modal-yes-button", "n_clicks"), - Input("upload-data-button", "n_clicks"), - Input("upload-data", "contents"), + Input(ElementIds.MODAL_YES_BUTTON, "n_clicks"), + Input(ElementIds.UPLOAD_DATA_BUTTON, "n_clicks"), + Input(ElementIds.UPLOAD_DATA, "contents"), ], [ - State("upload-data", "filename"), - State("url-store", "data"), + State(ElementIds.UPLOAD_DATA, "filename"), + State(ElementIds.ID_SELECT_URL_STORE, "data"), ], prevent_initial_call=True, ) @@ -190,8 +191,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, @@ -205,14 +206,14 @@ def submitted_data( # add switch_si_ip function and convert the data-store @callback( [ - Output("df-store", "data"), - Output("si-ip-unit-store", "data"), + Output(ElementIds.ID_SELECT_DF_STORE, "data"), + Output(ElementIds.ID_SELECT_SI_IP_UNIT_STORE, "data"), ], [ - Input("lines-store", "modified_timestamp"), - Input("si-ip-radio-input", "value"), + Input(ElementIds.LINES_STORE, "modified_timestamp"), + Input(ElementIds.ID_SELECT_SI_IP_RADIO_INPUT, "value"), ], - [State("url-store", "data"), State("lines-store", "data")], + [State(ElementIds.ID_SELECT_URL_STORE, "data"), State("lines-store", "data")], ) def switch_si_ip(_, si_ip_input, url_store, lines): if lines is not None: @@ -239,11 +240,11 @@ def switch_si_ip(_, si_ip_input, url_store, lines): Output("/explorer", "disabled"), Output("/outdoor", "disabled"), Output("/natural-ventilation", "disabled"), - Output("banner-subtitle", "children"), + Output(ElementIds.BANNER_SUBTITLE, "children"), ], [ - Input("meta-store", "data"), - Input("df-store", "data"), + Input(ElementIds.ID_SELECT_META_STORE, "data"), + Input(ElementIds.ID_SELECT_DF_STORE, "data"), ], ) def enable_tabs_when_data_is_loaded(meta, data): @@ -279,15 +280,15 @@ def enable_tabs_when_data_is_loaded(meta, data): @callback( [ - Output("modal", "is_open"), - Output("url-store", "data"), + Output(ElementIds.MODAL, "is_open"), + Output(ElementIds.ID_SELECT_URL_STORE, "data"), ], [ - Input("modal-yes-button", "n_clicks"), - Input("tab-one-map", "clickData"), - Input("modal-close-button", "n_clicks"), + Input(ElementIds.MODAL_YES_BUTTON, "n_clicks"), + Input(ElementIds.TAB_ONE_MAP, "clickData"), + Input(ElementIds.MODAL_CLOSE_BUTTON, "n_clicks"), ], - [State("modal", "is_open")], + [State(ElementIds.MODAL, "is_open")], prevent_initial_call=True, ) def display_modal_when_data_clicked(_, click_map, __, is_open): @@ -302,10 +303,10 @@ def display_modal_when_data_clicked(_, click_map, __, is_open): @callback( [ - Output("modal-header", "children"), + Output(ElementIds.MODAL_HEADER, "children"), ], [ - Input("tab-one-map", "clickData"), + Input(ElementIds.TAB_ONE_MAP, "clickData"), ], prevent_initial_call=True, ) @@ -317,7 +318,7 @@ def change_text_modal(click_map): @callback( - Output("skeleton-graph-container", "children"), + Output(ElementIds.SKELETON_GRAPH_CONTAINER, "children"), Input("url", "pathname"), ) def plot_location_epw_files(pathname): @@ -330,8 +331,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( @@ -369,7 +369,7 @@ def plot_location_epw_files(pathname): return ( dcc.Graph( - id="tab-one-map", + id=ElementIds.TAB_ONE_MAP, figure=fig, config=generate_chart_name("epw_location_select"), ), diff --git a/pages/summary.py b/pages/summary.py index c6611c11..cce46c88 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -11,6 +11,8 @@ 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.global_column_names import ColNames +from pages.lib.global_element_ids import ElementIds from pages.lib.utils import ( generate_chart_name, generate_units, @@ -41,7 +43,7 @@ def layout(): @callback( - Output("tab-two-container", "children"), [Input("si-ip-radio-input", "value")] + Output(ElementIds.TABLE_TWO_CONTAINER, "children"), [Input(ElementIds.ID_SUMMARY_SI_IP_RADIO_INPUT, "value")] ) def update_layout(si_ip): if si_ip == UnitSystem.SI: @@ -53,19 +55,19 @@ def update_layout(si_ip): return html.Div( className="container-col", - id="tab2-sec1-container", + id=ElementIds.TAB2_SCE1_CONTAINER, children=[ dcc.Loading( type="circle", children=html.Div( className="container-col", - id="location-info", + id=ElementIds.LOCATION_INFO, style={"padding": "12px"}, ), ), dcc.Loading( type="circle", - children=html.Div(className="tab-two-section", id="world-map"), + children=html.Div(className="tab-two-section", id=ElementIds.WORLD_MAP), ), html.Div( children=title_with_tooltip( @@ -82,7 +84,7 @@ def update_layout(si_ip): dbc.Button( "Download EPW", color="primary", - id="download-epw-button", + id=ElementIds.DOWN_EPW_BUTTON, ), width="auto", ), @@ -90,14 +92,14 @@ def update_layout(si_ip): dbc.Button( "Download Clima dataframe", color="primary", - id="download-button", + id=ElementIds.DOWNLOAD_BUTTON, ), width="auto", ), dbc.Col( [ - dcc.Download(id="download-dataframe-csv"), - dcc.Download(id="download-epw"), + dcc.Download(id=ElementIds.DOWNLOAD_DATAFRAME_CSV), + dcc.Download(id=ElementIds.DOWNLOAD_EPW), ], width=1, ), @@ -115,7 +117,7 @@ def update_layout(si_ip): "WARNING: Invalid Results! The CDD setpoint should be higher than the HDD setpoint!", color="warning", is_open=False, - id="warning-cdd-higher-hdd", + id=ElementIds.WARNING_CDD_HIGHER_HDD, ), dbc.Row( [ @@ -127,7 +129,7 @@ def update_layout(si_ip): ), dbc.Col( dbc.Input( - id="input-hdd-set-point", + id=ElementIds.INPUT_HDD_SET_POINT, type="number", value=heating_setpoint, style={"width": "4rem"}, @@ -142,7 +144,7 @@ def update_layout(si_ip): ), dbc.Col( dbc.Input( - id="input-cdd-set-point", + id=ElementIds.INPUT_CDD_SET_POINT, type="number", value=cooling_setpoint, style={"width": "4rem"}, @@ -151,7 +153,7 @@ def update_layout(si_ip): ), dbc.Col( dbc.Button( - id="submit-set-points", + id=ElementIds.SUBMIT_SET_POINTS, children="Submit", color="primary", ), @@ -163,7 +165,7 @@ def update_layout(si_ip): ), dcc.Loading( type="circle", - children=html.Div(id="degree-days-chart-wrapper"), + children=html.Div(id=ElementIds.DEGREE_DAYS_CHART_WRAPPER), ), html.Div( children=title_with_link( @@ -173,12 +175,12 @@ def update_layout(si_ip): ), ), dbc.Row( - id="graph-container", + id=ElementIds.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(id=ElementIds.TEMP_PROFILE_GRAPH, width=12, md=6, lg=3), + dbc.Col(id=ElementIds.HUMIDITY_PROFILE_GRAPH, width=12, md=6, lg=3), + dbc.Col(id=ElementIds.SOLAR_RADIATION_GRAPH, width=12, md=6, lg=3), + dbc.Col(id=ElementIds.WIND_SPEED_GRAPH, width=12, md=6, lg=3), ], ), ], @@ -197,13 +199,13 @@ def update_layout(si_ip): @callback( - Output("world-map", "children"), - Input("meta-store", "data"), + Output(ElementIds.WORLD_MAP, "children"), + Input(ElementIds.ID_SUMMARY_META_STORE, "data"), ) def update_map(meta): """Update the contents of tab two. Passing in the general info (df, meta).""" map_world = dcc.Graph( - id="gh_rad-profile-graph", + id=ElementIds.GH_RAD_PROFILE_GRAPH, config=generate_chart_name("map", meta), figure=world_map(meta), ) @@ -212,12 +214,12 @@ def update_map(meta): @callback( - Output("location-info", "children"), - Input("df-store", "modified_timestamp"), + Output(ElementIds.LOCATION_INFO, "children"), + Input(ElementIds.ID_SUMMARY_DF_STORE, "modified_timestamp"), [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_SUMMARY_DF_STORE, "data"), + State(ElementIds.ID_SUMMARY_META_STORE, "data"), + State(ElementIds.ID_SUMMARY_SI_IP_UNIT_STORE, "data"), ], ) def update_location_info(ts, df, meta, si_ip): @@ -257,12 +259,19 @@ def update_location_info(ts, df, meta, si_ip): # global horizontal irradiance # Note that the value is divided by 1000, so a corresponding change is made in the unit: - total_solar_rad_value = round(df["glob_hor_rad"].sum() / 1000, 2) - total_solar_rad_unit = "k" + mapping_dictionary["glob_hor_rad"][si_ip]["unit"] + total_solar_rad_value = round(df[ColNames.GLOB_HOR_RAD].sum() / 1000, 2) + total_solar_rad_unit = "k" + mapping_dictionary[ColNames.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)} %" - tmp_unit = mapping_dictionary["DBT"][si_ip]["unit"] + glob_sum = df[ColNames.GLOB_HOR_RAD].sum() + if glob_sum > 0: + diffuse_percentage = round(df[ColNames.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[ColNames.DBT][si_ip]["unit"] average_yearly_tmp = ( f"Average yearly temperature: {df['DBT'].mean().round(1)} " + tmp_unit ) @@ -302,20 +311,20 @@ def update_location_info(ts, df, meta, si_ip): @callback( [ - Output("degree-days-chart-wrapper", "children"), - Output("warning-cdd-higher-hdd", "is_open"), + Output(ElementIds.DEGREE_DAYS_CHART_WRAPPER, "children"), + Output(ElementIds.WARNING_CDD_HIGHER_HDD, "is_open"), ], [ - Input("df-store", "modified_timestamp"), - Input("submit-set-points", "n_clicks_timestamp"), + Input(ElementIds.ID_SUMMARY_DF_STORE, "modified_timestamp"), + Input(ElementIds.SUBMIT_SET_POINTS, "n_clicks_timestamp"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("input-hdd-set-point", "value"), - State("input-cdd-set-point", "value"), - State("submit-set-points", "n_clicks"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_SUMMARY_DF_STORE, "data"), + State(ElementIds.ID_SUMMARY_META_STORE, "data"), + State(ElementIds.INPUT_HDD_SET_POINT, "value"), + State(ElementIds.INPUT_CDD_SET_POINT, "value"), + State(ElementIds.SUBMIT_SET_POINTS, "n_clicks"), + State(ElementIds.ID_SUMMARY_SI_IP_UNIT_STORE, "data"), ], ) def degree_day_chart(ts, ts_click, df, meta, hdd_value, cdd_value, n_clicks, si_ip): @@ -339,14 +348,14 @@ def degree_day_chart(ts, ts_click, df, meta, hdd_value, cdd_value, n_clicks, si_ hdd_array = [] cdd_array = [] - months = df["month_names"].unique() + months = df[ColNames.MONTH_NAMES].unique() for i in range(1, 13): query_month = "month==" # calculates HDD per month query = query_month + str(i) + " and DBT<=" + str(hdd_setpoint) - a = df.query(query)["DBT"].sub(hdd_setpoint) + a = df.query(query)[ColNames.DBT].sub(hdd_setpoint) hdd = a.sum(axis=0, skipna=True) hdd = hdd / 24 hdd = int(hdd) @@ -354,7 +363,7 @@ def degree_day_chart(ts, ts_click, df, meta, hdd_value, cdd_value, n_clicks, si_ # calculates CDD per month query = query_month + str(i) + " and DBT>=" + str(cdd_setpoint) - a = df.query(query)["DBT"].sub(cdd_setpoint) + a = df.query(query)[ColNames.DBT].sub(cdd_setpoint) cdd = a.sum(axis=0, skipna=True) cdd = cdd / 24 cdd = int(cdd) @@ -412,37 +421,37 @@ def degree_day_chart(ts, ts_click, df, meta, hdd_value, cdd_value, n_clicks, si_ @callback( - Output("temp-profile-graph", "children"), + Output(ElementIds.TEMP_PROFILE_GRAPH, "children"), [ - Input("df-store", "modified_timestamp"), - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_SUMMARY_DF_STORE, "modified_timestamp"), + Input(ElementIds.ID_SUMMARY_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_SUMMARY_DF_STORE, "data"), + State(ElementIds.ID_SUMMARY_META_STORE, "data"), + State(ElementIds.ID_SUMMARY_SI_IP_UNIT_STORE, "data"), ], ) def update_violin_tdb(ts, global_local, df, meta, si_ip): units = generate_units_degree(si_ip) return dcc.Graph( - id="tdb-profile-graph", + id=ElementIds.TDB_PROFILE_GRAPH, className="violin-container", config=generate_chart_name("DryBulbTemperature", meta, units), - figure=violin(df, "DBT", global_local, si_ip), + figure=violin(df, ColNames.DBT, global_local, si_ip), ) @callback( - Output("wind-speed-graph", "children"), + Output(ElementIds.WIND_SPEED_GRAPH, "children"), [ - Input("df-store", "modified_timestamp"), - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_SUMMARY_DF_STORE, "modified_timestamp"), + Input(ElementIds.ID_SUMMARY_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_SUMMARY_DF_STORE, "data"), + State(ElementIds.ID_SUMMARY_META_STORE, "data"), + State(ElementIds.ID_SUMMARY_SI_IP_UNIT_STORE, "data"), ], ) def update_tab_wind(ts, global_local, df, meta, si_ip): @@ -452,63 +461,63 @@ def update_tab_wind(ts, global_local, df, meta, si_ip): id="wind-profile-graph", className="violin-container", config=generate_chart_name("WindSpeed", meta, units), - figure=violin(df, "wind_speed", global_local, si_ip), + figure=violin(df, ColNames.WIND_SPEED, global_local, si_ip), ) @callback( - Output("humidity-profile-graph", "children"), + Output(ElementIds.HUMIDITY_PROFILE_GRAPH, "children"), [ - Input("df-store", "modified_timestamp"), - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_SUMMARY_DF_STORE, "modified_timestamp"), + Input(ElementIds.ID_SUMMARY_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_SUMMARY_DF_STORE, "data"), + State(ElementIds.ID_SUMMARY_META_STORE, "data"), + State(ElementIds.ID_SUMMARY_SI_IP_UNIT_STORE, "data"), ], ) 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) return dcc.Graph( - id="rh-profile-graph", + id=ElementIds.RH_PROFILE_GRAPH, className="violin-container", config=generate_chart_name("RelativeHumidity", meta, units), - figure=violin(df, "RH", global_local, si_ip), + figure=violin(df, ColNames.RH, global_local, si_ip), ) @callback( - Output("solar-radiation-graph", "children"), + Output(ElementIds.SOLAR_RADIATION_GRAPH, "children"), [ - Input("df-store", "modified_timestamp"), - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_SUMMARY_DF_STORE, "modified_timestamp"), + Input(ElementIds.ID_SUMMARY_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_SUMMARY_DF_STORE, "data"), + State(ElementIds.ID_SUMMARY_META_STORE, "data"), + State(ElementIds.ID_SUMMARY_SI_IP_UNIT_STORE, "data"), ], ) 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) return dcc.Graph( - id="gh_rad-profile-graph", + id=ElementIds.GH_RAD_PROFILE_GRAPH, className="violin-container", config=generate_chart_name("GlobalHorizontalRadiation", meta, units), - figure=violin(df, "glob_hor_rad", global_local, si_ip), + figure=violin(df, ColNames.GLOB_HOR_RAD, global_local, si_ip), ) @callback( - Output("download-dataframe-csv", "data"), - [Input("download-button", "n_clicks")], + Output(ElementIds.DOWNLOAD_DATAFRAME_CSV, "data"), + [Input(ElementIds.DOWNLOAD_BUTTON, "n_clicks")], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_SUMMARY_DF_STORE, "data"), + State(ElementIds.ID_SUMMARY_META_STORE, "data"), + State(ElementIds.ID_SUMMARY_SI_IP_UNIT_STORE, "data"), ], prevent_initial_call=True, ) @@ -529,9 +538,9 @@ def download_clima_dataframe(n_clicks, df, meta, si_ip): @callback( - Output("download-epw", "data"), - [Input("download-epw-button", "n_clicks")], - [State("meta-store", "data")], + Output(ElementIds.DOWNLOAD_EPW, "data"), + [Input(ElementIds.DOWN_EPW_BUTTON, "n_clicks")], + [State(ElementIds.ID_SUMMARY_META_STORE, "data")], prevent_initial_call=True, ) def download_epw(n_clicks, meta): diff --git a/pages/sun.py b/pages/sun.py index a77c4c7f..b6663e76 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -1,4 +1,5 @@ from copy import deepcopy +from pages.lib.global_element_ids import ElementIds import dash import dash_bootstrap_components as dbc @@ -71,7 +72,7 @@ def sun_path(): style={"width": "10rem"}, ), dropdown( - id="custom-sun-view-dropdown", + id= ElementIds.CUSTOM_SUN_VIEW_DROPDOWN, options={ "Spherical": "polar", "Cartesian": "cartesian", @@ -91,7 +92,7 @@ def sun_path(): style={"width": "10rem"}, ), dropdown( - id="custom-sun-var-dropdown", + id= ElementIds.CUSTOM_SUN_VAR_DROPDOWN, options=sc_dropdown_names, value="None", style={"width": "20rem"}, @@ -101,7 +102,7 @@ def sun_path(): dcc.Loading( type="circle", children=html.Div( - id="custom-sunpath", + id= ElementIds.CUSTOM_SUNPATH, ), ), ], @@ -129,17 +130,17 @@ def explore_daily_heatmap(): style={"width": "10rem"}, ), dropdown( - id="tab4-explore-dropdown", + id=ElementIds.TAB_EXPLORE_DROPDOWN, options=sun_cloud_tab_explore_dropdown_names, value="glob_hor_rad", style={"width": "20rem"}, ), ], ), - dcc.Loading(type="circle", children=html.Div(id="tab4-daily")), + dcc.Loading(type="circle", children=html.Div(id= ElementIds.TAB4_DAILY)), dcc.Loading( type="circle", - children=html.Div(id="tab4-heatmap"), + children=html.Div(id=ElementIds.TAB4_HEATMAP), ), ], ) @@ -147,7 +148,7 @@ def explore_daily_heatmap(): def static_section(): return html.Div( - id="static-section", + id= ElementIds.STATIC_SECTION, className="container-col full-width", children=[ # ... @@ -159,12 +160,12 @@ def layout(): """Contents of tab four.""" return html.Div( className="container-col", - id="tab-four-container", + id= ElementIds.TAB_FOUR_CONTAINER, children=[sun_path(), static_section(), explore_daily_heatmap()], ) -@callback(Output("static-section", "children"), [Input("si-ip-radio-input", "value")]) +@callback(Output(ElementIds.STATIC_SECTION, "children"), [Input(ElementIds.ID_SUN_SI_IP_RADIO_INPUT, "value")]) def update_static_section(si_ip): hor_unit = "Wh/mΒ²" if si_ip == UnitSystem.IP: @@ -179,7 +180,7 @@ def update_static_section(si_ip): ), dcc.Loading( type="circle", - children=html.Div(id="monthly-solar"), + children=html.Div(id= ElementIds.MONTHLY_SOLAR), ), html.Div( children=title_with_link( @@ -190,23 +191,23 @@ def update_static_section(si_ip): ), dcc.Loading( type="circle", - children=html.Div(id="cloud-cover"), + children=html.Div(id= ElementIds.CLOUD_COVER), ), ] @callback( [ - Output("monthly-solar", "children"), - Output("cloud-cover", "children"), + Output(ElementIds.MONTHLY_SOLAR, "children"), + Output(ElementIds.CLOUD_COVER, "children"), ], [ - Input("df-store", "modified_timestamp"), + Input(ElementIds.ID_SUN_DF_STORE, "modified_timestamp"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_SUN_DF_STORE, "data"), + State(ElementIds.ID_SUN_META_STORE, "data"), + State(ElementIds.ID_SUN_SI_IP_UNIT_STORE, "data"), ], ) def monthly_and_cloud_chart(_, df, meta, si_ip): @@ -239,17 +240,17 @@ def monthly_and_cloud_chart(_, df, meta, si_ip): @callback( - Output("custom-sunpath", "children"), + Output(ElementIds.CUSTOM_SUNPATH, "children"), [ - Input("df-store", "modified_timestamp"), - Input("custom-sun-view-dropdown", "value"), - Input("custom-sun-var-dropdown", "value"), - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_SUN_DF_STORE, "modified_timestamp"), + Input(ElementIds.CUSTOM_SUN_VIEW_DROPDOWN, "value"), + Input(ElementIds.CUSTOM_SUN_VAR_DROPDOWN, "value"), + Input(ElementIds.ID_SUN_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_SUN_DF_STORE, "data"), + State(ElementIds.ID_SUN_META_STORE, "data"), + State(ElementIds.ID_SUN_SI_IP_UNIT_STORE, "data"), ], ) def sun_path_chart(_, view, var, global_local, df, meta, si_ip): @@ -269,16 +270,16 @@ def sun_path_chart(_, view, var, global_local, df, meta, si_ip): @callback( - Output("tab4-daily", "children"), + Output(ElementIds.TAB4_DAILY, "children"), [ - Input("df-store", "modified_timestamp"), - Input("tab4-explore-dropdown", "value"), - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_SUN_DF_STORE, "modified_timestamp"), + Input(ElementIds.TAB_EXPLORE_DROPDOWN, "value"), + Input(ElementIds.ID_SUN_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_SUN_DF_STORE, "data"), + State(ElementIds.ID_SUN_META_STORE, "data"), + State(ElementIds.ID_SUN_SI_IP_UNIT_STORE, "data"), ], ) def daily(_, var, global_local, df, meta, si_ip): @@ -292,16 +293,16 @@ def daily(_, var, global_local, df, meta, si_ip): @callback( - Output("tab4-heatmap", "children"), + Output(ElementIds.TAB4_HEATMAP, "children"), [ - Input("df-store", "modified_timestamp"), - Input("tab4-explore-dropdown", "value"), - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_SUN_DF_STORE, "modified_timestamp"), + Input(ElementIds.TAB_EXPLORE_DROPDOWN, "value"), + Input(ElementIds.ID_SUN_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_SUN_DF_STORE, "data"), + State(ElementIds.ID_SUN_META_STORE, "data"), + State(ElementIds.ID_SUN_SI_IP_UNIT_STORE, "data"), ], ) def update_heatmap(_, var, global_local, df, meta, si_ip): diff --git a/pages/t_rh.py b/pages/t_rh.py index c3d90f82..12d89fba 100644 --- a/pages/t_rh.py +++ b/pages/t_rh.py @@ -1,9 +1,10 @@ import dash from dash_extensions.enrich import Output, Input, State, dcc, html, callback - 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.global_column_names import ColNames +from pages.lib.global_element_ids import ElementIds from pages.lib.utils import ( generate_chart_name, generate_units, @@ -37,7 +38,7 @@ def layout(): className="text-next-to-input", children=["Select a variable: "] ), dropdown( - id="dropdown", + id=ElementIds.ID_T_RH_DROPDOWN, className="dropdown-t-rh", options={var: dropdown_names[var] for var in var_to_plot}, value=dropdown_names[var_to_plot[0]], @@ -49,14 +50,14 @@ def layout(): children=[ html.Div( children=title_with_link( - text="Yearly chart", + text="Yearly_chart", id_button="yearly-chart-label", doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, ), ), dcc.Loading( type="circle", - children=html.Div(id="yearly-chart"), + children=html.Div(id=ElementIds.YEARLY_CHART), ), html.Div( children=title_with_link( @@ -67,7 +68,7 @@ def layout(): ), dcc.Loading( type="circle", - children=html.Div(id="daily"), + children=html.Div(id=ElementIds.DAILY), ), html.Div( children=title_with_link( @@ -78,7 +79,7 @@ def layout(): ), dcc.Loading( type="circle", - children=html.Div(id="heatmap"), + children=html.Div(id=ElementIds.HEATMAP), ), html.Div( children=title_with_tooltip( @@ -88,7 +89,7 @@ def layout(): ), ), html.Div( - id="table-tmp-hum", + id=ElementIds.TABLE_TMP_HUM, ), ], ), @@ -97,21 +98,21 @@ def layout(): @callback( - Output("yearly-chart", "children"), + Output(ElementIds.YEARLY_CHART, "children"), [ - Input("df-store", "modified_timestamp"), - Input("global-local-radio-input", "value"), - Input("dropdown", "value"), + Input(ElementIds.ID_T_RH_DF_STORE, "modified_timestamp"), + Input(ElementIds.ID_T_RH_GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.ID_T_RH_DROPDOWN, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_T_RH_DF_STORE, "data"), + State(ElementIds.ID_T_RH_META_STORE, "data"), + State(ElementIds.ID_T_RH_SI_IP_UNIT_STORE, "data"), ], ) 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 = yearly_profile(df, ColNames.DBT, global_local, si_ip) dbt_yearly.update_layout(xaxis=dict(rangeslider=dict(visible=True))) units = generate_units_degree(si_ip) return dcc.Graph( @@ -119,7 +120,7 @@ def update_yearly_chart(_, global_local, dd_value, df, meta, si_ip): figure=dbt_yearly, ) else: - rh_yearly = yearly_profile(df, "RH", global_local, si_ip) + rh_yearly = yearly_profile(df, ColNames.RH, global_local, si_ip) rh_yearly.update_layout(xaxis=dict(rangeslider=dict(visible=True))) units = generate_units(si_ip) return dcc.Graph( @@ -129,16 +130,16 @@ def update_yearly_chart(_, global_local, dd_value, df, meta, si_ip): @callback( - Output("daily", "children"), + Output(ElementIds.DAILY, "children"), [ - Input("df-store", "modified_timestamp"), - Input("global-local-radio-input", "value"), - Input("dropdown", "value"), + Input(ElementIds.ID_T_RH_DF_STORE, "modified_timestamp"), + Input(ElementIds.ID_T_RH_GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.ID_T_RH_DROPDOWN, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_T_RH_DF_STORE, "data"), + State(ElementIds.ID_T_RH_META_STORE, "data"), + State(ElementIds.ID_T_RH_SI_IP_UNIT_STORE, "data"), ], ) def update_daily(_, global_local, dd_value, df, meta, si_ip): @@ -147,8 +148,8 @@ def update_daily(_, global_local, dd_value, df, meta, si_ip): return dcc.Graph( config=generate_chart_name("DryBulbTemperature_daily", meta, units), figure=daily_profile( - df[["DBT", "hour", "UTC_time", "month_names", "day", "month"]], - "DBT", + df[[ColNames.DBT, ColNames.HOUR, ColNames.UTC_TIME, ColNames.MONTH_NAMES, ColNames.DAY, ColNames.MONTH]], + ColNames.DBT, global_local, si_ip, ), @@ -158,8 +159,8 @@ def update_daily(_, global_local, dd_value, df, meta, si_ip): return dcc.Graph( config=generate_chart_name("RelativeHumidity_daily", meta, units), figure=daily_profile( - df[["RH", "hour", "UTC_time", "month_names", "day", "month"]], - "RH", + df[[ColNames.RH, ColNames.HOUR, ColNames.UTC_TIME, ColNames.MONTH_NAMES, ColNames.DAY, ColNames.MONTH]], + ColNames.RH, global_local, si_ip, ), @@ -167,16 +168,16 @@ def update_daily(_, global_local, dd_value, df, meta, si_ip): @callback( - [Output("heatmap", "children")], + [Output(ElementIds.HEATMAP, "children")], [ - Input("df-store", "modified_timestamp"), - Input("global-local-radio-input", "value"), - Input("dropdown", "value"), + Input(ElementIds.ID_T_RH_DF_STORE, "modified_timestamp"), + Input(ElementIds.ID_T_RH_GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.ID_T_RH_DROPDOWN, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_T_RH_DF_STORE, "data"), + State(ElementIds.ID_T_RH_META_STORE, "data"), + State(ElementIds.ID_T_RH_SI_IP_UNIT_STORE, "data"), ], ) def update_heatmap(_, global_local, dd_value, df, meta, si_ip): @@ -186,8 +187,8 @@ def update_heatmap(_, global_local, dd_value, df, meta, si_ip): return dcc.Graph( config=generate_chart_name("DryBulbTemperature_heatmap", meta, units), figure=heatmap( - df[["DBT", "hour", "UTC_time", "month_names", "day"]], - "DBT", + df[[ColNames.DBT, ColNames.HOUR, ColNames.UTC_TIME, ColNames.MONTH_NAMES, ColNames.DAY]], + ColNames.DBT, global_local, si_ip, ), @@ -197,8 +198,8 @@ def update_heatmap(_, global_local, dd_value, df, meta, si_ip): return dcc.Graph( config=generate_chart_name("RelativeHumidity_heatmap", meta, units), figure=heatmap( - df[["RH", "hour", "UTC_time", "month_names", "day"]], - "RH", + df[[ColNames.RH, ColNames.HOUR, ColNames.UTC_TIME, ColNames.MONTH_NAMES, ColNames.DAY]], + ColNames.RH, global_local, si_ip, ), @@ -206,15 +207,15 @@ def update_heatmap(_, global_local, dd_value, df, meta, si_ip): @callback( - Output("table-tmp-hum", "children"), + Output(ElementIds.TABLE_TMP_HUM, "children"), [ - Input("df-store", "modified_timestamp"), - Input("dropdown", "value"), + Input(ElementIds.ID_T_RH_DF_STORE, "modified_timestamp"), + Input(ElementIds.ID_T_RH_DROPDOWN, "value"), ], - [State("df-store", "data"), State("si-ip-unit-store", "data")], + [State(ElementIds.ID_T_RH_DF_STORE, "data"), State(ElementIds.ID_T_RH_SI_IP_UNIT_STORE, "data")], ) 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 + df[[ColNames.MONTH, ColNames.HOUR, dd_value, ColNames.MONTH_NAMES]], dd_value, si_ip ) diff --git a/pages/wind.py b/pages/wind.py index ff90ce5a..8517f510 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -1,10 +1,12 @@ import dash from dash import dcc, html from dash_extensions.enrich import Output, Input, State, callback +from pages.lib.global_element_ids import ElementIds 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.global_column_names import ColNames from pages.lib.utils import ( title_with_tooltip, generate_chart_name, @@ -27,14 +29,14 @@ def sliders(): """Returns 2 sliders for the hour""" return html.Div( className="container-col justify-center", - id="slider-container", + id=ElementIds.SLIDER_CONTAINER, children=[ html.Div( className="container-row each-slider", children=[ html.P("Month Range"), dcc.RangeSlider( - id="month-slider", + id=ElementIds.MONTH_SLIDER, min=1, max=12, step=1, @@ -50,7 +52,7 @@ def sliders(): children=[ html.P("Hour Range"), dcc.RangeSlider( - id="hour-slider", + id=ElementIds.HOUR_SLIDER, min=1, max=24, step=1, @@ -86,12 +88,12 @@ def seasonal_wind_rose(): dcc.Loading( type="circle", children=html.Div( - id="winter-wind-rose", + id=ElementIds.WINTER_WIND_ROSE, className="daily-wind-graph", ), ), html.P( - className="seasonal-text", id="winter-wind-rose-text" + className="seasonal-text", id=ElementIds.WINTER_WIND_ROSE_TEXT ), ], ), @@ -101,12 +103,12 @@ def seasonal_wind_rose(): dcc.Loading( type="circle", children=html.Div( - id="spring-wind-rose", + id=ElementIds.SPRING_WIND_ROSE, className="daily-wind-graph", ), ), html.P( - className="seasonal-text", id="spring-wind-rose-text" + className="seasonal-text", id=ElementIds.SPRING_WIND_ROSE_TEXT ), ], ), @@ -121,12 +123,12 @@ def seasonal_wind_rose(): dcc.Loading( type="circle", children=html.Div( - id="summer-wind-rose", + id= ElementIds.SUMMER_WIND_ROSE, className="daily-wind-graph", ), ), html.P( - className="seasonal-text", id="summer-wind-rose-text" + className="seasonal-text", id=ElementIds.SUMMER_WIND_ROSE_TEXT ), ], ), @@ -136,11 +138,11 @@ def seasonal_wind_rose(): dcc.Loading( type="circle", children=html.Div( - id="fall-wind-rose", + id=ElementIds.FALL_WIND_ROSE, className="daily-wind-graph", ), ), - html.P(className="seasonal-text", id="fall-wind-rose-text"), + html.P(className="seasonal-text", id=ElementIds.FALL_WIND_ROSE_TEXT), ], ), ], @@ -153,7 +155,7 @@ def daily_wind_rose(): """Return the section for the 3 daily wind rose graphs.""" return html.Div( className="container-col full-width", - id="tab5-daily-container", + id=ElementIds.TAB5_DAILY_CONTAINER, children=[ html.Div( children=title_with_link( @@ -163,7 +165,7 @@ def daily_wind_rose(): ), ), html.Div( - id="daily-wind-rose-outer-container", + id=ElementIds.DAILY_WIND_ROSE_OUTER_CONTAINER, className="container-row full-width", children=[ html.Div( @@ -174,11 +176,11 @@ def daily_wind_rose(): type="circle", children=html.Div( className="daily-wind-graph", - id="morning-wind-rose", + id=ElementIds.MORNING_WIND_ROSE, ), ), ), - html.P(className="daily-text", id="morning-wind-rose-text"), + html.P(className="daily-text", id=ElementIds.MORNING_WIND_ROSE_TEXT), ], ), html.Div( @@ -189,11 +191,11 @@ def daily_wind_rose(): type="circle", children=html.Div( className="daily-wind-graph", - id="noon-wind-rose", + id=ElementIds.NOON_WIND_ROSE, ), ), ), - html.P(className="daily-text", id="noon-wind-rose-text"), + html.P(className="daily-text", id=ElementIds.NOON_WIND_ROSE_TEXT), ], ), html.Div( @@ -204,11 +206,11 @@ def daily_wind_rose(): type="circle", children=html.Div( className="daily-wind-graph", - id="night-wind-rose", + id=ElementIds.NIGHT_WIND_ROSE, ), ), ), - html.P(className="daily-text", id="night-wind-rose-text"), + html.P(className="daily-text", id=ElementIds.NIGHT_WIND_ROSE_TEXT), ], ), ], @@ -230,7 +232,7 @@ def custom_wind_rose(): ), html.Div( className="container-row full-width justify-center", - id="tab5-custom-dropdown-container", + id=ElementIds.TAB5_CUSTOM_DROPDOWN_CONTAINER, children=[ html.Div( className="container-col justify-center p-2 mr-2", @@ -243,7 +245,7 @@ def custom_wind_rose(): children=["Start Month:"], ), dropdown( - id="tab5-custom-start-month", + id=ElementIds.TAB5_CUSTOM_START_MONTH, options={ j: i + 1 for i, j in enumerate(month_lst) }, @@ -260,7 +262,7 @@ def custom_wind_rose(): children=["Start Hour:"], ), dropdown( - id="tab5-custom-start-hour", + id=ElementIds.TAB5_CUSTOM_START_HOUR, options={ str(i) + ":00": i for i in range(0, 24) }, @@ -282,7 +284,7 @@ def custom_wind_rose(): children=["End Month:"], ), dropdown( - id="tab5-custom-end-month", + id=ElementIds.TAB5_CUSTOM_END_MONTH, options={ j: i + 1 for i, j in enumerate(month_lst) }, @@ -299,7 +301,7 @@ def custom_wind_rose(): children=["End Hour:"], ), dropdown( - id="tab5-custom-end-hour", + id=ElementIds.TAB5_CUSTOM_END_HOUR, options={ str(i) + ":00": i for i in range(1, 25) }, @@ -314,7 +316,7 @@ def custom_wind_rose(): ), dcc.Loading( type="circle", - children=html.Div(id="custom-wind-rose"), + children=html.Div(id=ElementIds.CUSTOM_WIND_ROSE), ), ], ) @@ -327,7 +329,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, ), @@ -335,16 +337,16 @@ def layout(): dcc.Loading( type="circle", children=html.Div( - id="wind-rose", + id=ElementIds.WIND_ROSE, ), ), dcc.Loading( type="circle", - children=html.Div(id="wind-speed"), + children=html.Div(id=ElementIds.WIND_SPEED), ), dcc.Loading( type="circle", - children=html.Div(id="wind-direction"), + children=html.Div(id=ElementIds.WIND_DIRECTION), ), seasonal_wind_rose(), daily_wind_rose(), @@ -355,12 +357,12 @@ def layout(): # wind rose @callback( - Output("wind-rose", "children"), - Input("df-store", "modified_timestamp"), + Output(ElementIds.WIND_ROSE, "children"), + Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"), [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_WIND_DF_STORE, "data"), + State(ElementIds.ID_WIND_META_STORE, "data"), + State(ElementIds.ID_WIND_SI_IP_UNIT_STORE, "data"), ], ) def update_annual_wind_rose(_, df, meta, si_ip): @@ -376,46 +378,46 @@ def update_annual_wind_rose(_, df, meta, si_ip): # wind speed @callback( - Output("wind-speed", "children"), + Output(ColNames.WIND_SPEED, "children"), # General [ - Input("df-store", "modified_timestamp"), - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"), + Input(ElementIds.ID_WIND_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_WIND_DF_STORE, "data"), + State(ElementIds.ID_WIND_META_STORE, "data"), + State(ElementIds.ID_WIND_SI_IP_UNIT_STORE, "data"), ], ) 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) + speed = heatmap(df, ColNames.WIND_SPEED, global_local, si_ip) units = generate_units(si_ip) return dcc.Graph( - config=generate_chart_name("wind_speed", meta, units), + config=generate_chart_name(ColNames.WIND_SPEED, meta, units), figure=speed, ) # wind direction @callback( - Output("wind-direction", "children"), + Output(ElementIds.WIND_DIRECTION, "children"), # General [ - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_WIND_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_WIND_DF_STORE, "data"), + State(ElementIds.ID_WIND_META_STORE, "data"), + State(ElementIds.ID_WIND_SI_IP_UNIT_STORE, "data"), ], ) 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).""" - direction = heatmap(df, "wind_dir", global_local, si_ip) + direction = heatmap(df, ColNames.WIND_DIR, global_local, si_ip) units = generate_units(si_ip) return dcc.Graph( config=generate_chart_name("wind_direction", meta, units), @@ -425,19 +427,19 @@ def update_tab_wind_direction(global_local, df, meta, si_ip): # Custom Wind rose @callback( - Output("custom-wind-rose", "children"), + Output(ElementIds.CUSTOM_WIND_ROSE, "children"), # Custom Graph Input [ - Input("df-store", "modified_timestamp"), - Input("tab5-custom-start-month", "value"), - Input("tab5-custom-start-hour", "value"), - Input("tab5-custom-end-month", "value"), - Input("tab5-custom-end-hour", "value"), + Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"), + Input(ElementIds.TAB5_CUSTOM_START_MONTH, "value"), + Input(ElementIds.TAB5_CUSTOM_START_HOUR, "value"), + Input(ElementIds.TAB5_CUSTOM_END_MONTH, "value"), + Input(ElementIds.TAB5_CUSTOM_END_HOUR, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_WIND_DF_STORE, "data"), + State(ElementIds.ID_WIND_META_STORE, "data"), + State(ElementIds.ID_WIND_SI_IP_UNIT_STORE, "data"), ], ) def update_custom_wind_rose( @@ -452,13 +454,13 @@ def update_custom_wind_rose( # Wind Rose Graphs if start_month <= end_month: - df = df.loc[(df["month"] >= start_month) & (df["month"] <= end_month)] + df = df.loc[(df[ColNames.MONTH] >= start_month) & (df[ColNames.MONTH] <= end_month)] else: - df = df.loc[(df["month"] <= end_month) | (df["month"] >= start_month)] + df = df.loc[(df[ColNames.MONTH] <= end_month) | (df[ColNames.MONTH] >= start_month)] if start_hour <= end_hour: - df = df.loc[(df["hour"] >= start_hour) & (df["hour"] <= end_hour)] + df = df.loc[(df[ColNames.HOUR] >= start_hour) & (df[ColNames.HOUR] <= end_hour)] else: - df = df.loc[(df["hour"] <= end_hour) | (df["hour"] >= start_hour)] + df = df.loc[(df[ColNames.HOUR] <= end_hour) | (df[ColNames.HOUR] >= start_hour)] custom = wind_rose( df, "", [start_month, end_month], [start_hour, end_hour], True, si_ip ) @@ -474,22 +476,22 @@ def update_custom_wind_rose( @callback( [ - Output("winter-wind-rose", "children"), - Output("spring-wind-rose", "children"), - Output("summer-wind-rose", "children"), - Output("fall-wind-rose", "children"), - Output("winter-wind-rose-text", "children"), - Output("spring-wind-rose-text", "children"), - Output("summer-wind-rose-text", "children"), - Output("fall-wind-rose-text", "children"), + Output(ElementIds.WINTER_WIND_ROSE, "children"), + Output(ElementIds.SPRING_WIND_ROSE, "children"), + Output(ElementIds.SUMMER_WIND_ROSE, "children"), + Output(ElementIds.FALL_WIND_ROSE, "children"), + Output(ElementIds.WINTER_WIND_ROSE_TEXT, "children"), + Output(ElementIds.SPRING_WIND_ROSE_TEXT, "children"), + Output(ElementIds.SUMMER_WIND_ROSE_TEXT, "children"), + Output(ElementIds.FALL_WIND_ROSE_TEXT, "children"), ], [ - Input("df-store", "modified_timestamp"), + Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_WIND_DF_STORE, "data"), + State(ElementIds.ID_WIND_META_STORE, "data"), + State(ElementIds.ID_WIND_SI_IP_UNIT_STORE, "data"), ], ) def update_seasonal_graphs(_, df, meta, si_ip): @@ -507,25 +509,25 @@ def update_seasonal_graphs(_, df, meta, si_ip): # Text winter_df = df.loc[ - (df["month"] <= winter_months[1]) | (df["month"] >= winter_months[0]) + (df[ColNames.MONTH] <= winter_months[1]) | (df[ColNames.MONTH] >= winter_months[0]) ] query_calm_wind = "wind_speed == 0" winter_total_count = winter_df.shape[0] winter_calm_count = winter_df.query(query_calm_wind).shape[0] spring_df = df.loc[ - (df["month"] >= spring_months[0]) & (df["month"] <= spring_months[1]) + (df[ColNames.MONTH] >= spring_months[0]) & (df[ColNames.MONTH] <= spring_months[1]) ] spring_total_count = spring_df.shape[0] spring_calm_count = spring_df.query(query_calm_wind).shape[0] summer_df = df.loc[ - (df["month"] >= summer_months[0]) & (df["month"] <= summer_months[1]) + (df[ColNames.MONTH] >= summer_months[0]) & (df[ColNames.MONTH] <= summer_months[1]) ] summer_total_count = summer_df.shape[0] summer_calm_count = summer_df.query(query_calm_wind).shape[0] - fall_df = df.loc[(df["month"] >= fall_months[0]) & (df["month"] <= fall_months[1])] + fall_df = df.loc[(df[ColNames.MONTH] >= fall_months[0]) & (df[ColNames.MONTH] <= fall_months[1])] fall_total_count = fall_df.shape[0] fall_calm_count = fall_df.query(query_calm_wind).shape[0] @@ -590,19 +592,19 @@ def seasonal_chart_caption(month_start, month_end, count, n_calm): @callback( # Daily Graphs [ - Output("morning-wind-rose", "children"), - Output("noon-wind-rose", "children"), - Output("night-wind-rose", "children"), - Output("morning-wind-rose-text", "children"), - Output("noon-wind-rose-text", "children"), - Output("night-wind-rose-text", "children"), + Output(ElementIds.MORNING_WIND_ROSE, "children"), + Output(ElementIds.NOON_WIND_ROSE, "children"), + Output(ElementIds.NIGHT_WIND_ROSE, "children"), + Output(ElementIds.MORNING_WIND_ROSE_TEXT, "children"), + Output(ElementIds.NOON_WIND_ROSE_TEXT, "children"), + Output(ElementIds.NIGHT_WIND_ROSE_TEXT, "children"), ], # General - Input("df-store", "modified_timestamp"), + Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"), [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_WIND_DF_STORE, "data"), + State(ElementIds.ID_WIND_META_STORE, "data"), + State(ElementIds.ID_WIND_SI_IP_UNIT_STORE, "data"), ], ) def update_daily_graphs(_, df, meta, si_ip): @@ -621,18 +623,18 @@ def update_daily_graphs(_, df, meta, si_ip): # Text query_calm_wind = "wind_speed == 0" morning_df = df.loc[ - (df["hour"] >= morning_times[0]) & (df["hour"] <= morning_times[1]) + (df[ColNames.HOUR] >= morning_times[0]) & (df[ColNames.HOUR] <= morning_times[1]) ] morning_total_count = morning_df.shape[0] morning_calm_count = morning_df.query(query_calm_wind).shape[0] noon_df = df.loc[ - (df["hour"] >= morning_times[0]) & (df["hour"] <= morning_times[1]) + (df[ColNames.HOUR] >= morning_times[0]) & (df[ColNames.HOUR] <= morning_times[1]) ] noon_total_count = noon_df.shape[0] noon_calm_count = noon_df.query(query_calm_wind).shape[0] - night_df = df.loc[(df["hour"] <= night_times[1]) | (df["hour"] >= night_times[0])] + night_df = df.loc[(df[ColNames.HOUR] <= night_times[1]) | (df[ColNames.HOUR] >= night_times[0])] night_total_count = night_df.shape[0] night_calm_count = night_df.query(query_calm_wind).shape[0] diff --git a/tests/python/test_utils.py b/tests/python/test_utils.py index eb0c59bd..ab085c42 100644 --- a/tests/python/test_utils.py +++ b/tests/python/test_utils.py @@ -1,14 +1,9 @@ -import sys import os 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) - import requests from pages.lib.utils import summary_table_tmp_rh_tab