Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.10.1
current_version = 0.10.2
commit = True
tag = True

Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# This pickle file is produced by test runs:
epw_test.pkl

# Data maintenance scripts
check_links.py
fix_broken_urls.py

venv
.idea
cache-directory
Expand Down
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pandas = "==2.2.0"
numpy = "==1.26.3"
dash-iconify = "*"
scipy = "==1.12.0"
kgcpy = "*"

[dev-packages]
cleanpy = "*"
Expand Down
1,540 changes: 911 additions & 629 deletions Pipfile.lock

Large diffs are not rendered by default.

Binary file modified assets/data/OneBuilding files.zip
Binary file not shown.
Binary file modified assets/data/one_building.csv
Binary file not shown.
2 changes: 1 addition & 1 deletion assets/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@
"orientation": "portrait",
"background_color": "#ffffff",
"display": "standalone",
"id": "0.10.1",
"id": "0.10.2",
"description": "CBE Clima Tool: a free and open-source web application for climate analysis tailored to sustainable building design",
"start_url": "/",
"scope": "/",
Expand Down
5 changes: 4 additions & 1 deletion docs/contributing/run-project-locally.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,10 @@ docker logs -f clima-test

# if everything is ok push to google container registry
docker push us-central1-docker.pkg.dev/clima-316917/cloud-run-source-deploy/clima:latest
gcloud run deploy clima-test --image us-central1-docker.pkg.dev/clima-316917/cloud-run-source-deploy/clima:latest --region us-central1 --memory 4Gi --cpu 2 --platform managed --allow-unauthenticated
gcloud run deploy clima-test --image us-central1-docker.pkg.dev/clima-316917/cloud-run-source-deploy/clima:latest --region us-central1 --memory 4Gi --cpu 2 --platform managed --allow-unauthenticated --tag "v0-10-2"

# you can then deploy the main version changing the name and tag
gcloud run deploy clima --image us-central1-docker.pkg.dev/clima-316917/cloud-run-source-deploy/clima:latest --region us-central1 --memory 4Gi --cpu 2 --platform managed --allow-unauthenticated --tag "v0-10-2"
```

### Deploy test version of the project
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@

The annual graph allows the different months' relative humidity ranges to be evaluated. Moreover, overlaying the average value trend for each day helps visualize the differences between the minimum and maximum daily values for the investigated location.

The humidity comfort band is overlaid, considering 30-70% RH the comfortable range. With these trends, climates can be assessed, whether too dry or too humid, then evaluating design solutions that include humidification or dehumidification system.
The humidity band, defined as 30–70% RH per [ANSI/ASHRAE Standard 160](https://webstore.ansi.org/standards/ashrae/ansiashrae1602021), is overlaid to provide a baseline for analysis. These trends allow users to assess whether a climate is excessively dry or humid and evaluate design solutions, such as integrated humidification or dehumidification systems, to control for mold, static electricity, skin comfort, and other potential humidity-related issues.

<figure><img src="../../../.gitbook/assets/RH yearly.png" alt=""><figcaption><p>Yearly relative humidity trend in four common climatic conditions: hot dry, tropical, temperate, and continental</p></figcaption></figure>

Outdoor relative humidity varies greatly depending on the amount of rainfall. As expected, hot dry and tropical climates have diametrically opposite trends, always outside the comfort range, and some measures must be taken to recreate comfortable situations in buildings.

### Daily chart

Monthly [scatterplots](https://en.wikipedia.org/wiki/Scatter\_plot) show all hourly relative humidity. The humidity excursion is much more evident than in the annual graphs. Daily medians, i.e., the most frequently occurring values, help evaluate the outliers.
Monthly [scatterplots](https://en.wikipedia.org/wiki/Scatter_plot) show all hourly relative humidity. The humidity excursion is much more evident than in the annual graphs. Daily medians, i.e., the most frequently occurring values, help evaluate the outliers.

<figure><img src="../../../.gitbook/assets/RH daily.png" alt=""><figcaption><p>Daily relative humidity trend in four common climatic conditions: hot dry, tropical, temperate, and continental</p></figcaption></figure>

Expand All @@ -31,10 +31,12 @@ The four heatmaps give a clear idea of the comparison of the four climate types.
The last tool for relative humidity assessment is the statistics table. The earlier graphically made evaluations can be supported by the numbers. The following are listed, for each month:

* the relative humidity means;
* the [standard deviations](https://en.wikipedia.org/wiki/Standard\_deviation);
* the [standard deviations](https://en.wikipedia.org/wiki/Standard_deviation);
* the minimum values;
* the [percentiles values](https://en.wikipedia.org/wiki/Percentile) (1%, 25%, 50%, 75%, 99%);
* the maximum values.
*

<figure><img src="../../../.gitbook/assets/Desc stat RH.png" alt=""><figcaption><p>Descriptive statistics of relative humidity trend in a temperate climate, Berkeley (USA)</p></figcaption></figure>
```
<figure><img src="../../../.gitbook/assets/Desc stat RH.png" alt=""><figcaption><p>Descriptive statistics of relative humidity trend in a temperate climate, Berkeley (USA)</p></figcaption></figure>
```
Comment on lines +40 to +42
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove code fences around the <figure> block.

Wrapping the figure in fenced code makes it render as literal code instead of an image/figure block. Keep the HTML figure block unfenced.

Proposed fix
-```
 <figure><img src="../../../.gitbook/assets/Desc stat RH.png" alt=""><figcaption><p>Descriptive statistics of relative humidity trend in a temperate climate, Berkeley (USA)</p></figcaption></figure>
-```
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 40-40: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@docs/documentation/tabs-explained/temperature-and-humidity/relative-humidity-explained.md`
around lines 40 - 42, The fenced code block around the HTML figure causes it to
render as literal code; remove the surrounding triple backticks so the
<figure>...<figcaption> block is raw HTML (i.e., delete the opening ``` before
<figure> and the closing ``` after </figure>) leaving the <figure> tag and its
contents unchanged.

Comment on lines +40 to +42
5 changes: 4 additions & 1 deletion pages/lib/import_one_building_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ def import_kml_files(file_name):

data = []
for location in locations:
url_match = re.findall(r"<td>URL (.+?)<\/td>", location)
if not url_match:
continue
location_info = []
# lat
location_info.append(
Expand All @@ -26,7 +29,7 @@ def import_kml_files(file_name):
# url
location_info.append(
"<a href="
+ re.findall(r"<td>URL (.+?)<\/td>", location)[0]
+ url_match[0]
+ ' style="color: #fff">Climate.OneBuilding.Org</a>'
)
# description
Expand Down
18 changes: 11 additions & 7 deletions pages/lib/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,14 @@ def create_navbar():


def create_header():
return dmc.Group(
[
return dmc.Flex(
justify="flex-start",
align="center",
gap={"base": "xs", "md": "lg"},
wrap="nowrap",
direction="row",
p="xs",
children=[
dmc.Burger(
id=ElementIds.BURGER_BUTTON,
size="sm",
Expand All @@ -282,14 +288,13 @@ def create_header():
c="white",
),
dmc.Text(
"Current Location: N/A",
"Location: N/A",
id=ElementIds.ID_SELECT_BANNER_SUBTITLE,
size="sm",
style={"overflow": "hidden"},
truncate="end",
c="white",
),
],
p="xs",
),
dmc.Alert(
[
Expand Down Expand Up @@ -318,7 +323,6 @@ def create_header():
style={"zIndex": 1002, "display": "none"},
),
],
pl="md",
)


Expand All @@ -331,7 +335,7 @@ def create_footer():

footer_links = [
(
"Version: 0.10.1",
"Version: 0.10.2",
"https://center-for-the-built-environment.gitbook.io/clima/version/changelog",
),
("Contributors", "https://cbe-berkeley.gitbook.io/clima/#contributions"),
Expand Down
2 changes: 1 addition & 1 deletion pages/lib/template_graphs.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ def yearly_profile(df, var, global_local, si_ip):
x=all_dates,
y=np.array(hi_rh) - np.array(lo_rh),
base=lo_rh,
name="humidity comfort band",
name="ASHRAE 160 humidity range",
marker_opacity=0.3,
marker_color="silver",
)
Expand Down
60 changes: 35 additions & 25 deletions pages/lib/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
import functools
import time
import math
from typing import Optional, Tuple, List

import pandas as pd
from dash import html, dash_table, dcc
from dash import html, dcc
import dash_mantine_components as dmc

from config import UnitSystem
Expand Down Expand Up @@ -274,27 +275,36 @@ def summary_table_tmp_rh_tab(df, value, si_ip):
.replace("<sup>", "")
.replace("</sup>", "")
)
return dash_table.DataTable(
columns=[
(
{"name": i, "id": i}
if i == Variables.MONTH.col_name
else {"name": f"{i} ({unit})", "id": i}
)
for i in df_summary.columns
],
style_table={"overflowX": "auto"},
data=df_summary.to_dict("records"),
style_cell={"textAlign": "center", "padding": "5px 10px"},
style_cell_conditional=[{"if": {"column_id": "month"}, "textAlign": "right"}],
style_header={"backgroundColor": "rgb(220, 220, 220)", "fontWeight": "bold"},
style_data_conditional=[
{"if": {"row_index": "odd"}, "backgroundColor": "white"},
{"if": {"row_index": "even"}, "backgroundColor": "rgb(250, 250, 250)"},
{"if": {"row_index": [12]}, "backgroundColor": "rgb(220, 220, 220)"},
],
style_as_list_view=True,
)

# Build Mantine table head (add unit to non-month headers)
header_cells = []
for col in df_summary.columns:
if col == Variables.MONTH.col_name:
header_cells.append(dmc.TableTh(col))
else:
header_cells.append(dmc.TableTh(f"{col} ({unit})"))
head = dmc.TableThead(dmc.TableTr(header_cells))

# Build rows from the DataFrame records
records = df_summary.round(1).to_dict("records")
rows = []
for rec in records:
cells = []
for col in df_summary.columns:
val = rec.get(col, "")
# Convert NaN to empty string to avoid 'nan' in table
if pd.isna(val):
display_val = ""
else:
display_val = val
cells.append(dmc.TableTd(display_val))
rows.append(dmc.TableTr(cells, tableProps={"align": "center"}))

body = dmc.TableTbody(rows)

# Wrap table in a ScrollArea to mimic overflowX:auto behavior
table = dmc.Table([head, body], striped=True, highlightOnHover=True)
return dmc.ScrollArea(table, style={"width": "100%"})


def determine_month_and_hour_filter(month, hour, invert_month, invert_hour):
Expand Down Expand Up @@ -359,7 +369,7 @@ def get_default_global_filter_store_data() -> dict:
}


def get_global_filter_state(filter_store_data: dict | None) -> dict:
def get_global_filter_state(filter_store_data: Optional[dict]) -> dict:
"""Normalize filter store data into a consistent, easy-to-use structure.

Ensures defaults are applied and types are coerced to booleans where appropriate.
Expand All @@ -380,8 +390,8 @@ def get_global_filter_state(filter_store_data: dict | None) -> dict:


def get_time_filter_from_store(
filter_store_data: dict | None,
) -> tuple[bool, list[int], list[int], bool, bool]:
filter_store_data: Optional[dict],
) -> Tuple[bool, List[int], List[int], bool, bool]:
"""Return normalized time filter arguments from the global filter store.

Returns (time_filter, month, hour, invert_month, invert_hour).
Expand Down
45 changes: 17 additions & 28 deletions pages/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,34 +265,23 @@ def switch_si_ip(_, si_ip_input, url_store, lines):
)
def enable_tabs_when_data_is_loaded(meta, data):
"""Hide tabs when data are not loaded"""
default = "Current Location: N/A"
if data is None:
return (
True,
True,
True,
True,
True,
True,
True,
True,
default,
)
else:
return (
False,
False,
False,
False,
False,
False,
False,
False,
"Current Location: "
+ meta[Variables.CITY.col_name]
+ ", "
+ meta[Variables.COUNTRY.col_name],
)
location_string = "Location: N/A"
disable_links = True
if data is not None:
location_string = f"Location: {meta[Variables.CITY.col_name]}, {meta[Variables.COUNTRY.col_name]}"
disable_links = False
Comment on lines +270 to +272
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard meta before building the location string.

Line 271 can throw when data is present but meta is still None/incomplete. Add a defensive check before indexing meta.

Proposed fix
-    if data is not None:
-        location_string = f"Location: {meta[Variables.CITY.col_name]}, {meta[Variables.COUNTRY.col_name]}"
+    if (
+        data is not None
+        and meta is not None
+        and Variables.CITY.col_name in meta
+        and Variables.COUNTRY.col_name in meta
+    ):
+        location_string = (
+            f"Location: {meta[Variables.CITY.col_name]}, "
+            f"{meta[Variables.COUNTRY.col_name]}"
+        )
         disable_links = False
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pages/select.py` around lines 270 - 272, The current block builds
location_string using meta without verifying meta is present or contains the
expected keys; update the guard so you only construct location_string when meta
is non-None and contains the required keys (e.g., Variables.CITY.col_name and
Variables.COUNTRY.col_name); otherwise set location_string to a safe default
(empty string or "Location unavailable") and keep disable_links behavior
consistent. Locate the usage around the variables data, meta, location_string
and disable_links and change the condition from checking data alone to a
combined check (or add an inner if) that ensures meta and the specific meta keys
exist before indexing.


return (
disable_links,
disable_links,
disable_links,
disable_links,
disable_links,
disable_links,
disable_links,
disable_links,
location_string,
)


@callback(
Expand Down
47 changes: 41 additions & 6 deletions pages/summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import pandas as pd
import dash_mantine_components as dmc
import plotly.graph_objects as go
import requests
from kgcpy import lookupCZ

from dash.exceptions import PreventUpdate
from dash_extensions.enrich import dcc, Output, Input, State, callback
Expand All @@ -24,6 +24,41 @@
)


KG_DESCRIPTIONS = {
"Af": "Tropical rainforest",
"Am": "Tropical monsoon",
"Aw": "Tropical savanna, dry winter",
"As": "Tropical savanna, dry summer",
"BWh": "Hot desert",
"BWk": "Cold desert",
"BSh": "Hot semi-arid steppe",
"BSk": "Cold semi-arid steppe",
"Csa": "Hot-summer Mediterranean",
"Csb": "Warm-summer Mediterranean",
"Csc": "Cold-summer Mediterranean",
"Cwa": "Monsoon-influenced humid subtropical",
"Cwb": "Subtropical highland",
"Cwc": "Cold subtropical highland",
"Cfa": "Humid subtropical, no dry season",
"Cfb": "Temperate oceanic",
"Cfc": "Subpolar oceanic",
"Dsa": "Hot-summer continental",
"Dsb": "Warm-summer continental",
"Dsc": "Subarctic",
"Dsd": "Extremely cold subarctic",
"Dwa": "Monsoon-influenced hot-summer continental",
"Dwb": "Monsoon-influenced warm-summer continental",
"Dwc": "Monsoon-influenced subarctic",
"Dwd": "Monsoon-influenced extremely cold subarctic",
"Dfa": "Hot-summer humid continental",
"Dfb": "Warm-summer humid continental",
"Dfc": "Subarctic boreal",
"Dfd": "Extremely cold subarctic boreal",
"ET": "Tundra",
"EF": "Ice cap",
}


dash.register_page(
__name__,
name=PageInfo.SUMMARY_NAME,
Expand Down Expand Up @@ -228,12 +263,12 @@ def update_location_info(ts, df, meta, si_ip):

climate_text = ""
try:
r = requests.get(
f"http://climateapi.scottpinkelman.com/api/v1/location/{meta[Variables.LAT.col_name]}/{meta[Variables.LON.col_name]}"
zone = lookupCZ(
float(meta[Variables.LAT.col_name]), float(meta[Variables.LON.col_name])
)
if r.status_code == 200:
j = r.json()["return_values"][0]
climate_text = f"Köppen-Geiger climate zone: {j['koppen_geiger_zone']}. {j['zone_description']}."
desc = KG_DESCRIPTIONS.get(zone, "")
if zone and desc:
climate_text = f"Köppen-Geiger climate zone: {zone}. {desc}."
except Exception:
Comment thread
t-kramer marked this conversation as resolved.
pass

Expand Down
2 changes: 1 addition & 1 deletion pages/t_rh.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def layout():
),
dmc.Skeleton(
visible=False,
h=450,
h=550,
children=dmc.Stack(id=ElementIds.TABLE_TMP_HUM),
),
],
Expand Down
Loading
Loading