Skip to content

#159 Code sanitisation (Updated)#240

Merged
FedericoTartarini merged 79 commits intoCenterForTheBuiltEnvironment:developmentfrom
LeoLuosifen:yluo0664
Sep 1, 2025
Merged

#159 Code sanitisation (Updated)#240
FedericoTartarini merged 79 commits intoCenterForTheBuiltEnvironment:developmentfrom
LeoLuosifen:yluo0664

Conversation

@LeoLuosifen
Copy link
Contributor

@LeoLuosifen LeoLuosifen commented Aug 25, 2025

Code Sanitisation #159

  • Created a global column names python file to store the columns, such as YEAR = "year", MONTH = "month", etc. These colunm names are considered constant, so use uppercase words.
  • Created a global element ids python file to store id variables in different files, and when a variable appears in different files, store the variable as ID_filename_variable value, such as ID_T_RH_DF_STORE = "df-store", ID_EXPLORER_DF_STORE = "df-store".
  • Replaced all of these values that using these two files. New variable names can be added in these two files, which allows for expansion and prevents hard coding.

Contributing.md

  • Add the content of Fork & branch processing and Pull Request Regulation, updated the content of Submitting changes, that can guide developers who want to improve or contribute to this project.

We are not sure if these changes meet your needs. If there are any parts that need improvement, please comment and we will check and make changes in time.

Summary by CodeRabbit

  • New Features

    • Navigation styling refreshed (pill-style, justified tabs); minor UI text refinements.
  • Bug Fixes

    • More stable solar-radiation summaries (divide-by-zero guarded).
    • Terminology corrected (psychrometric) and multiple small formatting fixes reducing UI/document glitches.
  • Documentation

    • Expanded contributing guide (workflow, tooling, Python 3.11); added/updated videos, images, links, and clarity improvements.
  • Refactor

    • Improved UI consistency and stability via centralized identifiers and standardized data naming.

LeoLuosifen and others added 30 commits August 19, 2025 16:52
@coderabbitai
Copy link

coderabbitai bot commented Aug 28, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
pages/lib/template_graphs.py (2)

410-415: Bug: margin becomes None due to dict.update() return value

dict.update() returns None; Plotly receives margin=None.

-    fig.update_layout(
-        template=template,
-        title=title,
-        margin=tight_margins.copy().update({"t": 55}),
-        yaxis_nticks=13,
-    )
+    fig.update_layout(
+        template=template,
+        title=title,
+        margin={**tight_margins, "t": 55},
+        yaxis_nticks=13,
+    )

696-703: Same bug: margin None via dict.update()

Repeat of the margin issue in thermal_stress_stacked_barchart.

-    fig.update_layout(
+    fig.update_layout(
         legend=dict(orientation="h", yanchor="bottom", y=1.05, xanchor="right", x=1),
         barmode="stack",
         dragmode=False,
         title=title,
-        margin=tight_margins.copy().update({"t": 55}),
+        margin={**tight_margins, "t": 55},
     )
pages/select.py (3)

143-166: Replace hard-coded ctx.prop_id strings with ElementIds to stay consistent with centralization

Use constants instead of literal IDs and guard the upload button trigger to avoid unnecessary work.

-    if ctx.triggered[0][ColNames.PROP_ID] == "modal-yes-button.n_clicks":
+    if ctx.triggered[0][ColNames.PROP_ID] == f"{ElementIds.MODAL_YES_BUTTON}.n_clicks":
@@
-    elif (
-        ctx.triggered[0][ColNames.PROP_ID] == "upload-data.contents"
-        and list_of_contents is not None
-    ):
+    elif ctx.triggered[0][ColNames.PROP_ID] == f"{ElementIds.UPLOAD_DATA_BUTTON}.n_clicks":
+        raise PreventUpdate
+    elif (
+        ctx.triggered[0][ColNames.PROP_ID] == f"{ElementIds.UPLOAD_DATA}.contents"
+        and list_of_contents is not None
+    ):

172-188: Harden EPW file-type detection; current "epw in name" is brittle

Use a strict, case-insensitive extension check; also fix the misleading comment.

-            if "epw" in list_of_names[0]:
-                # Assume that the user uploaded a CSV file
+            name = (list_of_names[0] or "").lower()
+            if name.endswith(".epw"):
+                # User uploaded an EPW file

195-203: Return the correct alert on parse errors

Parsing failures for .epw should use the "invalid_format" message, not "wrong_extension".

-        except (ValueError, IndexError, KeyError) as e:
+        except (ValueError, IndexError, KeyError) as e:
             print(f"Error parsing EPW file: {e}")
             return (
                 None,
                 None,
                 True,
-                messages_alert["wrong_extension"],
+                messages_alert["invalid_format"],
                 "warning",
             )
♻️ Duplicate comments (8)
docs/contributing/contributing.md (2)

21-24: Fix clone URL placeholder (space breaks copy/paste).

Replace the placeholder with a safe, copyable template.

-git clone https://github.com/Your Account name/clima.git
+git clone https://github.com/<your-account-name>/clima.git

54-61: Fix typo and clarify instructions for checking out development.

“checktout” → “checkout”; rephrase for clarity.

-Pull the development branch first, and if the terminal does not notice you that you should try the second command.
+Pull the development branch first. If that fails, try the second command.
@@
-git checktout development
+git checkout development
pages/lib/charts_sun.py (2)

77-101: monthly_solar (Diffuse): apply same customdata fix as above.

-                customdata=epw_df.loc[
-                    epw_df[ColNames.MONTH] == i + 1, ColNames.MONTH_NAMES
-                ],
+                customdata=[month_lst[i]] * int(
+                    dif_h_rad_month_ave.loc[
+                        dif_h_rad_month_ave[ColNames.MONTH] == i + 1
+                    ].shape[0]
+                ),

45-66: monthly_solar: customdata length mismatch; broadcast month label to 24 points.

g_h_rad_month_ave has 24 rows per month, but epw_df[...] yields 672–744 rows. Plotly requires equal lengths.

-                customdata=epw_df.loc[
-                    epw_df[ColNames.MONTH] == i + 1, ColNames.MONTH_NAMES
-                ],
+                customdata=[month_lst[i]] * int(
+                    g_h_rad_month_ave.loc[
+                        g_h_rad_month_ave[ColNames.MONTH] == i + 1
+                    ].shape[0]
+                ),
pages/lib/extract_df.py (1)

164-165: Bug: FAKE_YEAR column is assigned the literal "year" string

This writes the string constant instead of copying the dataframe values.

-    epw_df[ColNames.FAKE_YEAR] = ColNames.YEAR
+    epw_df[ColNames.FAKE_YEAR] = epw_df[ColNames.YEAR]
pages/psy-chart.py (1)

176-176: Trailing space after ElementIds.PSY_VAR_DROPDOWN

Remove the stray space to avoid accidental ID/key mismatches.

-                                id=ElementIds.PSY_VAR_DROPDOWN ,
+                                id=ElementIds.PSY_VAR_DROPDOWN,
-        State(ElementIds.PSY_VAR_DROPDOWN , "value"),
+        State(ElementIds.PSY_VAR_DROPDOWN, "value"),

Also applies to: 252-252

pages/wind.py (2)

405-417: Wind direction callback should also react to DF updates

Without the DF timestamp Input, graph won’t refresh on data changes. This was flagged before.

-@callback(
-    Output(ElementIds.WIND_DIRECTION, "children"),
-    # General
-    [
-        Input(ElementIds.ID_WIND_GLOBAL_LOCAL_RADIO_INPUT, "value"),
-    ],
+@callback(
+    Output(ElementIds.WIND_DIRECTION, "children"),
+    # General
+    [
+        Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"),
+        Input(ElementIds.ID_WIND_GLOBAL_LOCAL_RADIO_INPUT, "value"),
+    ],
@@
-def update_tab_wind_direction(global_local, df, meta, si_ip):
+def update_tab_wind_direction(_, global_local, df, meta, si_ip):

631-633: Bug: Noon selection uses morning time range

Use noon_times for noon_df.

-    noon_df = df.loc[
-        (df[ColNames.HOUR] >= morning_times[0]) & (df[ColNames.HOUR] <= morning_times[1])
-    ]
+    noon_df = df.loc[
+        (df[ColNames.HOUR] >= noon_times[0]) & (df[ColNames.HOUR] <= noon_times[1])
+    ]
🧹 Nitpick comments (20)
docs/contributing/contributing.md (4)

48-52: Replace hard tabs with spaces in branch list.

Tabs violate MD010 and render inconsistently.

-> * main
-  	remotes/origin/HEAD -> origin/main
-  	remotes/origin/development
-  	remotes/origin/main
+> * main
+  remotes/origin/HEAD -> origin/main
+  remotes/origin/development
+  remotes/origin/main

64-66: Use angle-bracket placeholders and avoid parentheses in commands.

-git checkout -b (your branch name)
+git checkout -b <your-branch-name>

68-72: Tighten wording and fix placeholder in push command.

-Finally update and push to your repository branch if you modify the files.
+Finally, push your branch to your fork after committing changes.
@@
-git push origin (your branch name)
+git push origin <your-branch-name>

99-119: Remove duplicated Ruff section under “Install Black”.

This block repeats lines 80–98 verbatim and adds noise.

-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/).
-
+Install Black:
pages/lib/global_column_names.py (3)

4-12: Consider separating DataFrame column names from metadata/UI keys.

ColNames mixes true columns (e.g., YEAR, DBT) with generic dict keys (NAME, UNIT, RANGE, COLOR). This coupling leaks UI concerns into data schema and invites misuse.

Proposed direction:

  • Keep ColNames for DataFrame columns only.
  • Introduce a separate Keys (or MetaKeys) Enum for {'name','unit','range','color','conversion_function','prop_id',...}.
    I can draft a small patch once you confirm the split is acceptable.

86-109: Minor consistency: group “calculation” vs. “meta” keys.

Entries like TWENTY_FOUR_HOUR, TIMES, TO_IMAGE_BUTTON_OPTIONS, PROP_ID are not columns. If keeping a single Enum, at least add a comment section header to avoid accidental use in pandas ops.


61-76: Naming consistency for UTCI members.

UTCI_* members mix camelCase in values (e.g., utci_Sun_noWind). If feasible, adopt a consistent snake_case value convention across new data being generated to reduce future mapping shims.

pages/lib/charts_summary.py (1)

7-12: Meta access via ColNames is correct; consider tolerant parsing for time zone.

Casting meta[ColNames.TIME_ZONE] to float will fail if the value is like "Etc/GMT-8". If upstream may provide TZ names, add a try/except with a default.

-    time_zone = float(meta[ColNames.TIME_ZONE])
+    try:
+        time_zone = float(meta[ColNames.TIME_ZONE])
+    except (TypeError, ValueError):
+        time_zone = 0.0  # fallback; display unaffected
pages/lib/charts_sun.py (2)

121-149: Redundant computation and duplicate filter.

times is computed then unused; solpos = df.loc[df[APPARENT_ELEVATION] > 0] appears twice.

-    tz = "UTC"
-    times = pd.date_range(
-        "2019-01-01 00:00:00", "2020-01-01", inclusive="left", freq="h", tz=tz
-    )
-    delta = timedelta(days=0, hours=time_zone - 1, minutes=0)
-    times = times - delta
-    solpos = df.loc[df[ColNames.APPARENT_ELEVATION] > 0, :]
+    tz = "UTC"
+    delta = timedelta(days=0, hours=time_zone - 1, minutes=0)
+    solpos = df.loc[df[ColNames.APPARENT_ELEVATION] > 0, :]

428-459: Minor: month list uses mixed zero-padded dates.

"2019-4-21"/"2019-5-21" parse fine but are inconsistent with zero-padded forms used elsewhere. Optional cleanup.

-    for date in pd.to_datetime(["2019-01-21", "2019-02-21", "2019-4-21", "2019-5-21"]):
+    for date in pd.to_datetime(["2019-01-21", "2019-02-21", "2019-04-21", "2019-05-21"]):
pages/lib/extract_df.py (3)

253-254: Prefer ColNames over attribute access for index alignment

Using epw_df.times breaks the intent of centralizing column names and can fail if the column is renamed.

-    mrt_df = pd.DataFrame.from_records(mrt)
-    mrt_df = mrt_df.set_index(epw_df.times)
+    mrt_df = pd.DataFrame.from_records(mrt)
+    mrt_df = mrt_df.set_index(epw_df[ColNames.TIMES])

265-267: Simplify “no-wind” speed assignment

Masking with >= 0 replaces all values; set the column once for clarity and speed.

-    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[ColNames.WIND_SPEED_UTCI_0] = 0.5

381-388: Avoid double JSON parsing and use the already loaded dict

Reduces overhead and is clearer.

-    mapping_dict = json.loads(mapping_json)
-    for key in json.loads(mapping_json):
+    mapping_dict = json.loads(mapping_json)
+    for key in mapping_dict:
         if ColNames.CONVERSION_FUNCTION in mapping_dict[key]:
             conversion_function_name = mapping_dict[key][ColNames.CONVERSION_FUNCTION]
             if conversion_function_name is not None:
                 conversion_function = globals()[conversion_function_name]
                 conversion_function(df, key)
     return json.dumps(mapping_dict)
pages/psy-chart.py (1)

281-283: Confirm intent: filtering sets ALL columns to None

Passing df.columns to filter_df_by_month_and_hour nulls every column for out-of-range rows. This is heavier than masking just the plotted variable(s) and can impact downstream computations.

If the goal is to filter only plotted/needed fields, pass a list of those columns instead of df.columns.

pages/select.py (4)

217-217: Use ElementIds for State instead of the raw string

Keep identifiers centralized.

-    [State(ElementIds.ID_SELECT_URL_STORE, "data"), State("lines-store", "data")],
+    [State(ElementIds.ID_SELECT_URL_STORE, "data"), State(ElementIds.LINES_STORE, "data")],

69-72: Avoid placeholder Div with the same ID that later becomes a Graph

Using a Div with id=TAB_ONE_MAP while callbacks read clickData from that id is fragile. Initialize a Graph placeholder instead to prevent property mismatches.

-    dmc.Skeleton(
-        visible=False,
-        id=ElementIds.SKELETON_GRAPH_CONTAINER,
-        height=500,
-        children=html.Div(id=ElementIds.TAB_ONE_MAP),
-    ),
+    dmc.Skeleton(
+        visible=False,
+        id=ElementIds.SKELETON_GRAPH_CONTAINER,
+        height=500,
+        children=dcc.Graph(id=ElementIds.TAB_ONE_MAP, figure={"data": [], "layout": {}}),
+    ),

Also applies to: 322-377


355-355: Pass the column name, not a Series, to hover_name

This keeps Plotly Express semantics and aligns with constant usage.

-        hover_name=df_one_building[ColNames.NAME],
+        hover_name=ColNames.NAME,

278-279: Guard against missing meta keys to avoid KeyError

If meta is None or missing keys, this will error. Consider safe access.

-            "Current Location: " + meta[ColNames.CITY] + ", " + meta[ColNames.COUNTRY],
+            f"Current Location: {meta.get(ColNames.CITY, 'N/A')}, {meta.get(ColNames.COUNTRY, 'N/A')}",
pages/outdoor.py (1)

315-325: Replace if/elif chain with a mapping for image selection

Simpler, clearer, and easier to extend.

-def change_image_based_on_selection(value):
-    if value == "utci_Sun_Wind":
-        source = "./assets/img/sun_and_wind.png"
-    elif value == "utci_Sun_noWind":
-        source = "./assets/img/sun_no_wind.png"
-    elif value == "utci_noSun_Wind":
-        source = "./assets/img/no_sun_and_wind.png"
-    else:
-        source = "./assets/img/no_sun_no_wind.png"
-
-    return html.Img(src=source, height=50)
+def change_image_based_on_selection(value):
+    mapping = {
+        "utci_Sun_Wind": "./assets/img/sun_and_wind.png",
+        "utci_Sun_noWind": "./assets/img/sun_no_wind.png",
+        "utci_noSun_Wind": "./assets/img/no_sun_and_wind.png",
+        "utci_noSun_noWind": "./assets/img/no_sun_no_wind.png",
+    }
+    return html.Img(src=mapping.get(value, mapping["utci_Sun_Wind"]), height=50)
pages/wind.py (1)

514-514: Use ColNames in query strings for calm wind detection

Keeps naming centralized and avoids accidental drift.

-    query_calm_wind = "wind_speed == 0"
+    query_calm_wind = f"{ColNames.WIND_SPEED} == 0"

Also applies to: 624-624

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 54ce1d8 and 81b2ec8.

📒 Files selected for processing (18)
  • docs/contributing/contributing.md (3 hunks)
  • docs/documentation/tabs-explained/outdoor-comfort/utci-explained.md (1 hunks)
  • docs/documentation/tabs-explained/sun-and-cloud/global-and-diffuse-horizontal-solar-radiation/README.md (1 hunks)
  • pages/lib/charts_data_explorer.py (6 hunks)
  • pages/lib/charts_summary.py (1 hunks)
  • pages/lib/charts_sun.py (16 hunks)
  • pages/lib/extract_df.py (8 hunks)
  • pages/lib/global_column_names.py (1 hunks)
  • pages/lib/global_scheme.py (2 hunks)
  • pages/lib/layout.py (2 hunks)
  • pages/lib/template_graphs.py (25 hunks)
  • pages/lib/utils.py (7 hunks)
  • pages/natural_ventilation.py (25 hunks)
  • pages/outdoor.py (18 hunks)
  • pages/psy-chart.py (20 hunks)
  • pages/select.py (15 hunks)
  • pages/summary.py (20 hunks)
  • pages/wind.py (27 hunks)
✅ Files skipped from review due to trivial changes (1)
  • docs/documentation/tabs-explained/sun-and-cloud/global-and-diffuse-horizontal-solar-radiation/README.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • docs/documentation/tabs-explained/outdoor-comfort/utci-explained.md
  • pages/lib/layout.py
  • pages/lib/charts_data_explorer.py
  • pages/natural_ventilation.py
🧰 Additional context used
🪛 markdownlint-cli2 (0.17.2)
docs/contributing/contributing.md

49-49: Hard tabs
Column: 3

(MD010, no-hard-tabs)


50-50: Hard tabs
Column: 3

(MD010, no-hard-tabs)


51-51: Hard tabs
Column: 3

(MD010, no-hard-tabs)

🪛 LanguageTool
docs/contributing/contributing.md

[grammar] ~133-~133: There might be a mistake here.
Context: ...ease make sure: - All tests should pass. - You have installed project dependencies:...

(QB_NEW_EN)


[grammar] ~164-~164: There might be a mistake here.
Context: ...iew and CI; merge and release every time, - Develop: Continuous Integration branch for dail...

(QB_NEW_EN)


[grammar] ~166-~166: There might be a mistake here.
Context: ...o merge in after completing the feature. - Fix/*: defect repair branch, the same process...

(QB_NEW_EN)


[grammar] ~167-~167: There might be a mistake here.
Context: ...pair branch, the same process as feature - Release/*: release preparation branch for freezin...

(QB_NEW_EN)


[grammar] ~168-~168: There might be a mistake here.
Context: ...entation, doing regressions and tagging. - docs/*, chore/*, refactor/*, test/*: doc...

(QB_NEW_EN)


[grammar] ~169-~169: There might be a mistake here.
Context: ...ellaneous, refactor, test type branches. - Style: style modification (does not affect th...

(QB_NEW_EN)


[grammar] ~171-~171: There might be a mistake here.
Context: ...xisting code to improve maintainability. - Test: Add or modify tests: add unit tests, i...

(QB_NEW_EN)


[grammar] ~174-~174: There might be a mistake here.
Context: ...de execution efficiency or memory usage. - Ci: CI Configuration Related: Changing Con...

(QB_NEW_EN)


[grammar] ~175-~175: There might be a mistake here.
Context: ... memory usage. - Ci: CI Configuration Related: Changing Continuous Integration Configu...

(QB_NEW_EN)


[grammar] ~175-~175: There might be a mistake here.
Context: ...or Github Actions, Travis, Jenkins, etc. - Build: build system related: modify build scr...

(QB_NEW_EN)


[grammar] ~177-~177: There might be a mistake here.
Context: ...llback Commit: Undoing a Previous Commit - Security: Security fixes, fixing security vulner...

(QB_NEW_EN)


[grammar] ~178-~178: There might be a mistake here.
Context: ...pdating dependencies to prevent attacks. - Deps: Dependency Management: Dependency Mana...

(QB_NEW_EN)


[grammar] ~179-~179: There might be a mistake here.
Context: ...ating, and removing dependency libraries - Infra: Infrastructure related: changes to dev...

(QB_NEW_EN)


[grammar] ~182-~182: There might be a mistake here.
Context: ...ations, etc. ## Pull Request Regulation Time to submit PR: - User requirement...

(QB_NEW_EN)


[grammar] ~185-~185: There might be a mistake here.
Context: ...in Issue and consensus has been reached. - Changes have been minimised (small steps...

(QB_NEW_EN)

🔇 Additional comments (10)
pages/lib/global_scheme.py (3)

2-2: LGTM: centralized key usage via ColNames.NAME.

Importing ColNames and indexing with ColNames.NAME removes magic strings and improves consistency.


903-906: Comprehension uses trusted mapping keys; add guard only if inputs are dynamic.

If variable lists are static (as here), this is fine. If they become user-driven, wrap with .get(..., {}) to avoid KeyError.


907-920: LGTM: consistent replacement of ["name"] with ColNames.NAME across maps.

No functional change; improves maintainability.

pages/lib/charts_sun.py (1)

129-133: Key indirection via ColNames works; verify all mapping_dictionary entries use these keys.

Especially for older entries, ensure keys “name”, “unit”, “range”, “color” all exist to avoid KeyError.

pages/lib/utils.py (1)

261-269: LGTM: invert flags centralized via ColNames

Using ColNames.INVERT for both month and hour improves consistency with the new constants.

pages/summary.py (1)

260-277: Defensive calc for solar radiation: LGTM

Good guard against division by zero and consistent unit handling with ColNames.

pages/select.py (1)

333-336: LGTM: Good use of ColNames for features/coordinates/lat/lon

Consistent with the new constants approach.

pages/outdoor.py (1)

239-243: LGTM: Good migration to ColNames for UTCI category columns

Consistent and future-proof.

pages/wind.py (2)

381-401: LGTM: Output now targets ElementIds.WIND_SPEED

Fixes the earlier mismatch with a non-existent layout id.


55-63: Confirm hour indexing consistency (1–24 vs 0–24)

Sliders here use 1–24 (wind tab) while outdoor uses 0–24. Ensure alignment with DF hour encoding.

Also applies to: 613-617

FedericoTartarini and others added 3 commits August 28, 2025 12:22
Refactor multiple files to enhance code clarity by adjusting line breaks,
spacing, and indentation. This includes consistent formatting in function
calls and variable assignments across various modules.
Update the label text from "Yearly_chart" to "Yearly Chart" for better
readability and consistency in the user interface.
@LeoLuosifen
Copy link
Contributor Author

@CodeRabbit review

@coderabbitai
Copy link

coderabbitai bot commented Aug 28, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Introduce a new constant for the seasonal wind rose document ID in
global_element_ids.py and update the wind.py to utilize this ID
for the corresponding button.
@FedericoTartarini
Copy link
Contributor

FedericoTartarini commented Aug 28, 2025

@LeoLuosifen Thank you for working on this, nice work. However please note the following issues:

  • you did not created an ID for the id_button in the title_with_tooltip functions. Please add these to the ElementsIds and commit the changes. For example, in line 61 of the sun.py file.
  • always trigger the coderabbit review and address all the comments related to the changes you implemented.
  • Resolve conversations, for example, it does not look like you extracted in a class the time in line 241 of the chart_sun. Do not ignore them
  • Files were not properly formatted with ruff. I did that.
  • Line 221 for the sun.py file should not contain a string, replace with the column name class. Check the project since this issue is also in line 237 and 233 of the same file. This issue is also present in other files.
  • pull the latest changes since I did a couple of commits.
  • t_rh.py line 95 you should also be using the columns names.
  • never reply to comments with messages like "sorry I will fix it" just fix it to keep messages to a minimum

Please review all these issues and make sure that error looks like the above are all fixed so i can do a final review.

Tianchi Liu and others added 9 commits August 29, 2025 16:04
…atural_ventilation.py, psy-chart.py, summary.py, sun.py, wind.py, t-rh.py and outdoor.py
- add global tab names python file to store tab name value
- replaced the tab_name via using global_tab_names.py
- removed unused import: `enum.Enum`
- formatted the code style via running ruff and pre-commit
@LeoLuosifen
Copy link
Contributor Author

@CodeRabbit review

@coderabbitai
Copy link

coderabbitai bot commented Aug 30, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (12)
pages/lib/extract_df.py (1)

195-223: Unify change_to_float with ColNames and guard missing columns.

Use constants and intersect with existing columns to avoid KeyErrors for short files.

-    change_to_float = [
-        "DBT","DPT","RH","p_atm","extr_hor_rad","hor_ir_rad","glob_hor_rad",
-        "dir_nor_rad","dif_hor_rad","glob_hor_ill","dir_nor_ill","dif_hor_ill",
-        "Zlumi","wind_dir","wind_speed","tot_sky_cover","Oskycover","Vis",
-        "Cheight","PWobs","PWcodes","Pwater","AsolOptD","SnowD","DaySSnow",
-    ]
-    epw_df[change_to_float] = epw_df[change_to_float].astype(float)
+    change_to_float = [
+        ColNames.DBT, ColNames.DPT, ColNames.RH, ColNames.P_ATM,
+        ColNames.EXTR_HOR_RAD, ColNames.HOR_IR_RAD, ColNames.GLOB_HOR_RAD,
+        ColNames.DIR_NOR_RAD, ColNames.DIF_HOR_RAD, ColNames.GLOB_HOR_ILL,
+        ColNames.DIR_NOR_ILL, ColNames.DIF_HOR_ILL, ColNames.ZLUMI,
+        ColNames.WIND_DIR, ColNames.WIND_SPEED, ColNames.TOT_SKY_COVER,
+        ColNames.OSKY_COVER, ColNames.VIS, ColNames.CHEIGHT, ColNames.PWOBS,
+        ColNames.PWCODES, ColNames.PWATER, ColNames.ASOL_OPTD, ColNames.SNOWD,
+        ColNames.DAYSSNOW,
+    ]
+    present = [c for c in change_to_float if c in epw_df.columns]
+    epw_df[present] = epw_df[present].astype(float)
pages/lib/global_element_ids.py (1)

1-226: Centralize all remaining literal component IDs

  • main.py (ln 17): dcc.Location(id="url", …)
  • main.py (ln 19): html.Div(id="page-content", …)
  • pages/select.py (ln 327): Input("url", "pathname")
  • pages/summary.py (ln 194–195, commented): Output('input-hdd-set-point', …), Input('si-ip-radio-input', …)

Replace each literal with the appropriate ElementIds constant (adding new entries for URL and PAGE_CONTENT if they don’t yet exist).

pages/lib/layout.py (2)

14-25: Add rel="noopener noreferrer" to external links.

Prevents reverse tabnabbing with target="_blank".

-                    html.A(
+                    html.A(
                         "quick user survey",
                         href="https://forms.gle/k289zP3R92jdu14M7",
                         className="alert-link",
-                        target="_blank",
+                        target="_blank",
+                        rel="noopener noreferrer",
                     ),

Also applies to: 27-39


82-124: Add rel to all footer anchors opened in new tabs.

-                                    dmc.Anchor(
+                                    dmc.Anchor(
                                         "Version: 0.9.0",
                                         href="https://center-for-the-built-environment.gitbook.io/clima/version/changelog",
                                         underline=True,
                                         c="white",
-                                        target="_blank",
+                                        target="_blank",
+                                        rel="noopener noreferrer",
                                     ),
-                                    dmc.Anchor(
+                                    dmc.Anchor(
                                         "Contributors",
                                         href="https://cbe-berkeley.gitbook.io/clima/#contributions",
                                         underline=True,
                                         c="white",
-                                        target="_blank",
+                                        target="_blank",
+                                        rel="noopener noreferrer",
                                     ),
-                                    dmc.Anchor(
+                                    dmc.Anchor(
                                         "Report issues on GitHub",
                                         href="https://github.com/CenterForTheBuiltEnvironment/clima/issues",
                                         underline=True,
                                         c="white",
-                                        target="_blank",
+                                        target="_blank",
+                                        rel="noopener noreferrer",
                                     ),
-                                    dmc.Anchor(
+                                    dmc.Anchor(
                                         "Contact us",
                                         href="https://github.com/CenterForTheBuiltEnvironment/clima/discussions",
                                         underline=True,
                                         c="white",
-                                        target="_blank",
+                                        target="_blank",
+                                        rel="noopener noreferrer",
                                     ),
-                                    dmc.Anchor(
+                                    dmc.Anchor(
                                         "Documentation",
                                         href="https://center-for-the-built-environment.gitbook.io/clima/",
                                         underline=True,
                                         c="white",
-                                        target="_blank",
+                                        target="_blank",
+                                        rel="noopener noreferrer",
                                     ),
-                                    dmc.Anchor(
+                                    dmc.Anchor(
                                         "License",
                                         href="https://center-for-the-built-environment.gitbook.io/clima/#license",
                                         underline=True,
                                         c="white",
-                                        target="_blank",
+                                        target="_blank",
+                                        rel="noopener noreferrer",
                                     ),

Also applies to: 89-95, 97-103, 103-110, 110-116, 117-123

pages/select.py (2)

144-167: Remove hard-coded prop_id strings; bind to ElementIds to avoid desync.

Comparing ctx.triggered against literal IDs undermines the centralization effort and will break if IDs change.

Apply:

-    if ctx.triggered[0][ColNames.PROP_ID] == "modal-yes-button.n_clicks":
+    triggered = ctx.triggered[0].get(ColNames.PROP_ID, "")
+    if triggered == f"{ElementIds.MODAL_YES_BUTTON}.n_clicks":
@@
-    elif (
-        ctx.triggered[0][ColNames.PROP_ID] == "upload-data.contents"
-        and list_of_contents is not None
-    ):
+    elif triggered == f"{ElementIds.UPLOAD_DATA}.contents" and list_of_contents is not None:

189-205: Fix swapped error messages.

“Not EPW extension” should return wrong_extension; “parsing failure” should return invalid_format.

Apply:

-            else:
-                return (
-                    None,
-                    None,
-                    True,
-                    messages_alert["invalid_format"],
-                    "warning",
-                )
-        except (ValueError, IndexError, KeyError) as e:
+            else:
+                return (
+                    None,
+                    None,
+                    True,
+                    messages_alert["wrong_extension"],
+                    "warning",
+                )
+        except (ValueError, IndexError, KeyError) as e:
             print(f"Error parsing EPW file: {e}")
             return (
                 None,
                 None,
                 True,
-                messages_alert["wrong_extension"],
+                messages_alert["invalid_format"],
                 "warning",
             )

Also applies to: 197-204

pages/natural_ventilation.py (4)

345-355: Fix wrong emptiness check and user-facing typo

  • The emptiness check uses MONTH, but rows are nulled on var (DBT). The alert may never trigger. Check var instead.
  • Typo: “thedew-point” → “the dew-point”.
-        if df.dropna(subset=[ColNames.MONTH]).shape[0] == 0:
+        if df[var].dropna().shape[0] == 0:
             return (
                 dbc.Alert(
-                    "Natural ventilation is not available in this location under these"
+                    "Natural ventilation is not available in this location under these"
                     " conditions. Please either select a different outdoor dry-bulb air"
                     " temperature range, change the month and hour filter, or increase"
-                    " thedew-point temperature.",
+                    " the dew-point temperature.",
                     color="danger",
                     style={"text-align": "center", "marginTop": "2rem"},
                 ),
             )

421-426: margin dict.update() bug: returns None

dict.update() returns None, so layout margin becomes None.

-        margin=tight_margins.copy().update({"t": 55}),
+        margin={**tight_margins, "t": 55},

541-541: Guard against divide-by-zero when normalizing

If a month has zero hours after filtering, this produces inf/NaN.

-    per_time_nv_allowed = np.round(100 * (n_hours_nv_allowed / tot_month_hours))
+    with np.errstate(divide="ignore", invalid="ignore"):
+        per_time_nv_allowed = np.round(
+            100
+            * np.divide(
+                n_hours_nv_allowed,
+                tot_month_hours,
+                out=np.zeros_like(n_hours_nv_allowed, dtype=float),
+                where=tot_month_hours != 0,
+            )
+        )

598-604: Same margin bug in bar chart

Apply the same fix as in nv_heatmap.

-        margin=tight_margins.copy().update({"t": 55}),
+        margin={**tight_margins, "t": 55},
pages/summary.py (2)

247-261: Add timeout and exception handling for climate API

Prevent server hangs and handle failures gracefully.

-    r = requests.get(
-        f"http://climateapi.scottpinkelman.com/api/v1/location/{meta[ColNames.LAT]}/{meta[ColNames.LON]}"
-    )
+    try:
+        r = requests.get(
+            f"http://climateapi.scottpinkelman.com/api/v1/location/{meta[ColNames.LAT]}/{meta[ColNames.LON]}",
+            timeout=5,
+        )
+    except requests.RequestException:
+        r = None

And guard on r is not None before status_code.


338-345: Trigger detection should not use ColNames.PROP_ID

Use Dash’s prop_id directly and the ElementIds constant for the button id.

-    ctx = dash.callback_context
-
-    if (
-        ctx.triggered[0][ColNames.PROP_ID] == "submit-set-points.n_clicks_timestamp"
-        or n_clicks is None
-    ):
+    ctx = dash.callback_context
+    prop_id = (ctx.triggered[0]["prop_id"] if ctx.triggered else "")
+    if prop_id == f"{ElementIds.SUBMIT_SET_POINTS}.n_clicks_timestamp" or n_clicks is None:
♻️ Duplicate comments (7)
pages/lib/extract_df.py (1)

168-168: Bug: FAKE_YEAR assigns a literal, not the column values.

This sets the cell values to the string "year" instead of copying the year column. Move after dtype cast or copy directly now.

Apply one of the following diffs:

-epw_df[ColNames.FAKE_YEAR] = ColNames.YEAR
+epw_df[ColNames.FAKE_YEAR] = epw_df[ColNames.YEAR]

or (ensuring int, placed after the int cast block):

-epw_df[ColNames.FAKE_YEAR] = ColNames.YEAR
+# after casting YEAR/MONTH/DAY/HOUR to int
+epw_df[ColNames.FAKE_YEAR] = epw_df[ColNames.YEAR]
pages/psy-chart.py (2)

78-85: Trailing-space fixes look good.

The prior whitespace issues on ElementIds.PSY_VAR_DROPDOWN and State refs are resolved.

Also applies to: 95-99, 104-116, 174-182, 240-259


391-394: Bug: hovertemplate repeats name; unit missing (was flagged earlier).

Replace the second DBT name with the DBT unit so hover reads “Name: value unit”.

Apply:

-                hovertemplate=mapping_dictionary[ColNames.DBT][ColNames.NAME]
-                + ": %{x:.2f}"
-                + mapping_dictionary[ColNames.DBT][ColNames.NAME],
+                hovertemplate=mapping_dictionary[ColNames.DBT][ColNames.NAME]
+                + ": %{x:.2f}"
+                + mapping_dictionary[ColNames.DBT][si_ip][ColNames.UNIT],
pages/t_rh.py (1)

55-57: Normalize title casing to match others.

Use “Yearly chart” (lowercase “chart”) to be consistent with “Daily chart” and “Heatmap chart.”

Apply:

-                            text="Yearly Chart",
+                            text="Yearly chart",
pages/natural_ventilation.py (1)

515-519: Replace string "nv_allowed" with constant (repeat)

The filter_df_by_month_and_hour call still passes the string literal. Use ColNames.NV_ALLOWED.

-    df = filter_df_by_month_and_hour(
-        df, time_filter, month, hour, invert_month, invert_hour, "nv_allowed"
-    )
+    df = filter_df_by_month_and_hour(
+        df, time_filter, month, hour, invert_month, invert_hour, ColNames.NV_ALLOWED
+    )
pages/wind.py (2)

423-435: Wind direction callback should react to DF updates (repeat)

Add DF store timestamp as an Input and adjust signature.

 @callback(
-    Output(ElementIds.WIND_DIRECTION, "children"),
+    Output(ElementIds.WIND_DIRECTION, "children"),
     # General
-    [
-        Input(ElementIds.ID_WIND_GLOBAL_LOCAL_RADIO_INPUT, "value"),
-    ],
+    [
+        Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"),
+        Input(ElementIds.ID_WIND_GLOBAL_LOCAL_RADIO_INPUT, "value"),
+    ],
     [
         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):
+def update_tab_wind_direction(_, global_local, df, meta, si_ip):

660-665: Noon selection uses morning time range (repeat)

Use noon_times for noon_df.

-    noon_df = df.loc[
-        (df[ColNames.HOUR] >= morning_times[0]) & (df[ColNames.HOUR] <= morning_times[1])
-    ]
+    noon_df = df.loc[
+        (df[ColNames.HOUR] >= noon_times[0]) & (df[ColNames.HOUR] <= noon_times[1])
+    ]
🧹 Nitpick comments (28)
pages/lib/extract_df.py (8)

182-193: Simplify and harden DOY computation.

Current approach relies on groupby order; use the timestamp directly for determinism and fewer ops.

Apply:

-    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[[ColNames.MONTH, ColNames.DAY, ColNames.DOY]],
-        on=[ColNames.MONTH, ColNames.DAY],
-        how="left",
-    )
+    epw_df[ColNames.DOY] = epw_df[ColNames.TIMES].dt.dayofyear

228-234: Prefer bracket access over attribute access for columns.

Use epw_df[ColNames.TIMES] consistently to avoid attr/column name collisions and improve rename safety.

-epw_df.set_index(
-    ColNames.TIMES, drop=False, append=False, inplace=True, verify_integrity=False
-)
+m_times = ColNames.TIMES
+epw_df.set_index(m_times, drop=False, append=False, inplace=True, verify_integrity=False)
-mrt_df = mrt_df.set_index(epw_df.times)
+mrt_df = mrt_df.set_index(epw_df[ColNames.TIMES])

Also applies to: 268-269


243-254: Avoid 8760 magic number; size from data.

Derive length from epw_df to handle leap years or non-standard files.

-sol_altitude = epw_df[ColNames.ELEVATION].mask(epw_df[ColNames.ELEVATION] <= 0, 0)
-sharp = [45] * 8760
+sol_altitude = epw_df[ColNames.ELEVATION].mask(epw_df[ColNames.ELEVATION] <= 0, 0)
+n = len(epw_df)
+sharp = [45] * n
 ...
-sol_transmittance = [1] * 8760  # CHECK VALUE
-f_svv = [1] * 8760  # CHECK VALUE
-f_bes = [1] * 8760  # CHECK VALUE
-asw = [0.7] * 8760  # CHECK VALUE
+sol_transmittance = [1] * n  # CHECK VALUE
+f_svv = [1] * n  # CHECK VALUE
+f_bes = [1] * n  # CHECK VALUE
+asw = [0.7] * n  # CHECK VALUE
 ...
-posture = ["standing"] * 8760
-floor_reflectance = [0.6] * 8760  # EXPOSE AS A VARIABLE?
+posture = ["standing"] * n
+floor_reflectance = [0.6] * n  # EXPOSE AS A VARIABLE?

272-279: WIND_SPEED_UTCI clamping is fine; set zero-wind column directly.

The mask with >= 0 sets almost all rows to 0.5 anyway; assign constant to be explicit (and include NaNs if desired using fillna).

Choose one:

-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[ColNames.WIND_SPEED_UTCI_0] = 0.5

or preserve NaNs:

-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[ColNames.WIND_SPEED_UTCI_0] = epw_df[ColNames.WIND_SPEED_UTCI].where(
+    epw_df[ColNames.WIND_SPEED_UTCI].isna(), 0.5
+)

Also applies to: 280-282


324-328: psy_ta_rh vectorization may be slow.

If performance matters, consider batching without np.vectorize (if psy supports arrays) or using DataFrame.apply on smaller chunks. Keep as-is if not a hot path.


330-338: Use ColNames when querying DOY and ensure symbol consistency.

Minor style/consistency: prefer bracket access.

-for day in epw_df.DOY.unique():
+for day in epw_df[ColNames.DOY].unique():

410-415: Avoid double json.loads and simplify lookup.

Minor efficiency/readability tweak.

-    mapping_dict = json.loads(mapping_json)
-    for key in json.loads(mapping_json):
-        if ColNames.CONVERSION_FUNCTION in mapping_dict[key]:
-            conversion_function_name = mapping_dict[key][ColNames.CONVERSION_FUNCTION]
+    mapping_dict = json.loads(mapping_json)
+    for key in mapping_dict:
+        conversion_function_name = mapping_dict[key].get(ColNames.CONVERSION_FUNCTION)

Also applies to: 418-420


116-146: Prefer ColNames for column creation to fully eliminate literals.

If ColNames defines these, initialize with constants for consistency across the module.

-    col_names = [
-        "year","month","day","hour","DBT","DPT","RH","p_atm","extr_hor_rad",
-        "hor_ir_rad","glob_hor_rad","dir_nor_rad","dif_hor_rad","glob_hor_ill",
-        "dir_nor_ill","dif_hor_ill","Zlumi","wind_dir","wind_speed","tot_sky_cover",
-        "Oskycover","Vis","Cheight","PWobs","PWcodes","Pwater","AsolOptD","SnowD","DaySSnow",
-    ]
+    col_names = [
+        ColNames.YEAR, ColNames.MONTH, ColNames.DAY, ColNames.HOUR,
+        ColNames.DBT, ColNames.DPT, ColNames.RH, ColNames.P_ATM,
+        ColNames.EXTR_HOR_RAD, ColNames.HOR_IR_RAD, ColNames.GLOB_HOR_RAD,
+        ColNames.DIR_NOR_RAD, ColNames.DIF_HOR_RAD, ColNames.GLOB_HOR_ILL,
+        ColNames.DIR_NOR_ILL, ColNames.DIF_HOR_ILL, ColNames.ZLUMI,
+        ColNames.WIND_DIR, ColNames.WIND_SPEED, ColNames.TOT_SKY_COVER,
+        ColNames.OSKY_COVER, ColNames.VIS, ColNames.CHEIGHT,
+        ColNames.PWOBS, ColNames.PWCODES, ColNames.PWATER,
+        ColNames.ASOL_OPTD, ColNames.SNOWD, ColNames.DAYSSNOW,
+    ]
pages/lib/global_id_buttons.py (4)

9-9: Rename and normalize PSYCHROMETRIC_CHART_CHART.

Duplicate “_CHART” in the name and TitleCase in the value are inconsistent with the rest. Prefer a single “_LABEL” suffix and kebab-case value.

-    PSYCHROMETRIC_CHART_CHART = "Psychrometric-Chart-chart"
+    PSYCHROMETRIC_CHART_LABEL = "psychrometric-chart-label"

5-5: Align naming with ElementIds.TABLE_TMP_HUM.

“TMP_RH” vs “TMP_HUM” is inconsistent and easy to misuse.

-    TABLE_TMP_RH = "table-tmp-rh"
+    TABLE_TMP_HUM = "table-tmp-hum"

1-29: Unify constants strategy (Enum vs plain class).

Elsewhere you use a str Enum (ElementIds); consider the same here to improve type safety and introspection, or merge these into ElementIds under a “labels” section to avoid two sources of truth.


3-4: Use consistent kebab-case for NV_NORMALIZE
Change the constant’s value in pages/lib/global_id_buttons.py from "nv_normalize" to "nv-normalize". No string literals or other references to "nv_normalize" were found—updating the definition will propagate automatically.

pages/lib/global_element_ids.py (3)

62-63: Clarify intentional aliasing.

MONTH_HOUR_FILTER and ID_OUTDOOR_MONTH_HOUR_FILTER share the same value. Aliases are fine, but add a brief comment/docstring explaining the convention to reduce confusion when iterating the Enum (aliases are skipped).

 class ElementIds(str, Enum):
+    """Centralized element IDs.
+    Notes:
+      - Several members intentionally alias the same underlying ID (e.g., df-store) for semantic clarity per page.
+      - When iterating this Enum, Python skips aliases; access members by name.
+    """

5-7: Add missing ID for title_with_tooltip buttons.

Reviewer requested an ID for the id_button used in title_with_tooltip; add it here to standardize usage.

 class ElementIds(str, Enum):
@@
     OUTDOOR_COMFORT_OUTPUT = "outdoor-comfort-output"
+    # Title-with-tooltip button (used across pages)
+    ID_TITLE_WITH_TOOLTIP_BUTTON = "id_button"

Also applies to: 205-209


14-14: Optional: namespace the generic “dropdown” ID
No direct "dropdown" literals were found outside this alias—safe to refactor. Consider renaming the constant at pages/lib/global_element_ids.py:14 to something like "t-rh-dropdown" and updating all references to minimize collision risk.

main.py (1)

17-21: Optional: centralize dcc.Location('url').

Consider adding ElementIds.URL = "url" and using it here to avoid a stray literal.

-        dcc.Location(id="url", refresh=False),  # connected to callback below
+        dcc.Location(id=ElementIds.URL, refresh=False),  # connected to callback below
pages/lib/layout.py (1)

35-39: Unify single source for interval/alert IDs.

Both ID_LAYOUT_* and ID_MAIN_* map to the same underlying values. Consider keeping just one canonical pair to reduce cognitive load.

Also applies to: 205-209

pages/lib/global_tab_names.py (1)

1-48: Optional: Standardize TabNames casing or convert to StrEnum

  • Normalize all TabNames values to snake_case for consistency
  • Convert TabNames to a StrEnum to enforce safer reuse
  • Update generate_chart_name calls in pages/t_rh.py, pages/summary.py, and pages/wind.py to match the new values
pages/sun.py (1)

40-54: Minor: simplify sc_dropdown_names construction.

Avoid multiple update/pop calls to reduce churn and risk of missing keys.

Apply:

 sc_dropdown_names = {
-    "None": "None",
-    "Frequency": "Frequency",
-}
-sc_dropdown_names.update(deepcopy(dropdown_names))
-sc_dropdown_names.update(deepcopy(sun_cloud_tab_dropdown_names))
-sc_dropdown_names.update(deepcopy(sun_cloud_tab_explore_dropdown_names))
-# Remove the keys from the dictionary
-sc_dropdown_names.pop("Vapor partial pressure", None)
-sc_dropdown_names.pop("Absolute humidity", None)
-sc_dropdown_names.pop("UTCI: Sun & Wind : categories", None)
-sc_dropdown_names.pop("UTCI: no Sun & Wind : categories", None)
-sc_dropdown_names.pop("UTCI: Sun & no Wind : categories", None)
-sc_dropdown_names.pop("UTCI: no Sun & no Wind : categories", None)
+    "None": "None",
+    "Frequency": "Frequency",
+    **deepcopy(dropdown_names),
+    **deepcopy(sun_cloud_tab_dropdown_names),
+    **deepcopy(sun_cloud_tab_explore_dropdown_names),
+}
+for k in (
+    "Vapor partial pressure",
+    "Absolute humidity",
+    "UTCI: Sun & Wind : categories",
+    "UTCI: no Sun & Wind : categories",
+    "UTCI: Sun & no Wind : categories",
+    "UTCI: no Sun & no Wind : categories",
+):
+    sc_dropdown_names.pop(k, None)
pages/psy-chart.py (1)

456-474: Prefer constants over raw strings for customdata keys.

"h" and "t_dp" are still string literals; for consistency with the refactor, consider adding ColNames for these if available and using them everywhere (mapping_dictionary and df access).

pages/t_rh.py (1)

195-206: Single-output callback declared as list of Outputs.

Style-only: declare a single Output directly for readability.

Example:

-@callback(
-    [Output(ElementIds.HEATMAP, "children")],
+@callback(
+    Output(ElementIds.HEATMAP, "children"),
pages/select.py (2)

356-362: Use a column name for hover_name, not a Series.

Pass the column key string so Plotly can handle labels consistently.

Apply:

-    fig2 = px.scatter_mapbox(
+    fig2 = px.scatter_mapbox(
         df_one_building,
         lat="lat",
         lon="lon",
-        hover_name=df_one_building[ColNames.NAME],
+        hover_name=ColNames.NAME,

223-234: map_json is computed then unused.

Drop dead code or persist it if needed.

Apply:

-        map_json = json.dumps(mapping_dictionary)
-        if si_ip_input == UnitSystem.IP:
-            map_json = convert_data(df, map_json)
+        # If mapping conversion is needed for client-side use, persist it to a store; otherwise remove.
pages/outdoor.py (1)

350-401: Avoid suffix concatenation in multiple places.

var + "_categories" appears in two callbacks; consider a helper (e.g., to_categories(var)) or a ColNames.MAKE_CATEGORY(var) API to prevent typos.

pages/natural_ventilation.py (1)

535-539: Be explicit with subset list

Avoid passing a bare string to subset to prevent ambiguity.

-    n_hours_nv_allowed = (
-        df.dropna(subset=ColNames.NV_ALLOWED)
+    n_hours_nv_allowed = (
+        df.dropna(subset=[ColNames.NV_ALLOWED])
         .groupby(df[ColNames.UTC_TIME].dt.month)[ColNames.NV_ALLOWED]
         .sum()
         .values
     )
pages/explorer.py (2)

72-75: Default dropdown value should use constant

Use ColNames.DBT for consistency.

-                value="DBT",
+                value=ColNames.DBT,

452-458: Use ColNames for color-by default

Align with constants.

-                                value="glob_hor_rad",
+                                value=ColNames.GLOB_HOR_RAD,
pages/summary.py (1)

534-542: CSV downloads: exclude the index

Avoid including the implicit index column.

-                df.to_csv,
+                df.to_csv,
                 f"df_{meta[ColNames.CITY]}_{meta[ColNames.COUNTRY]}_Clima_SIunit.csv",
+                index=False,
             )
-                df.to_csv,
+                df.to_csv,
                 f"df_{meta[ColNames.CITY]}_{meta[ColNames.COUNTRY]}_Clima_IPunit.csv",
+                index=False,
             )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 81b2ec8 and d5eda1e.

📒 Files selected for processing (21)
  • main.py (2 hunks)
  • pages/explorer.py (43 hunks)
  • pages/lib/charts_summary.py (1 hunks)
  • pages/lib/charts_sun.py (16 hunks)
  • pages/lib/extract_df.py (8 hunks)
  • pages/lib/global_column_names.py (1 hunks)
  • pages/lib/global_element_ids.py (1 hunks)
  • pages/lib/global_id_buttons.py (1 hunks)
  • pages/lib/global_scheme.py (2 hunks)
  • pages/lib/global_tab_names.py (1 hunks)
  • pages/lib/layout.py (8 hunks)
  • pages/lib/template_graphs.py (25 hunks)
  • pages/lib/utils.py (7 hunks)
  • pages/natural_ventilation.py (25 hunks)
  • pages/outdoor.py (18 hunks)
  • pages/psy-chart.py (21 hunks)
  • pages/select.py (15 hunks)
  • pages/summary.py (17 hunks)
  • pages/sun.py (12 hunks)
  • pages/t_rh.py (4 hunks)
  • pages/wind.py (28 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • pages/lib/charts_summary.py
  • pages/lib/global_scheme.py
  • pages/lib/utils.py
  • pages/lib/template_graphs.py
  • pages/lib/charts_sun.py
  • pages/lib/global_column_names.py
🔇 Additional comments (28)
pages/lib/extract_df.py (8)

22-22: Good move centralizing column names.

Importing ColNames here aligns the file with the new constants strategy.


71-73: Consistent use of ColNames.PERIOD.

Looks good; verify that ColNames.PERIOD equals "period" to match the dict keys initialized above.

Also applies to: 99-101


156-166: EnergyPlus years → PERIOD computed via ColNames.YEAR.

LGTM; logic preserved while removing literals.


172-174: Month names mapping via ColNames.

Looks correct and keeps mapping centralized.


177-179: Type casting via ColNames.

Good consistency; no issues spotted.


283-306: UTCI calculations migrated to ColNames.

Reads clean and consistent.


310-321: Categories refactor looks good.

Consistent ColNames usage; bins/labels unchanged.


358-371: Adaptive outputs mapped via ColNames.

Nicely standardized; no issues.

pages/lib/global_element_ids.py (1)

68-71: Rename SDATA_FILTER → SEC1_DATA_FILTER_INPUT and update its literal

-    SDATA_FILTER = "data-filter"
+    SEC1_DATA_FILTER_INPUT = "sec1-data-filter-input"

No other occurrences of SDATA_FILTER or SEC1_DATA_FILTER_INPUT were found; verify with

rg -nP '\bSDATA_FILTER\b'

and update any remaining references.

main.py (1)

26-29: LGTM: callback wired to ElementIds.

Switching to ElementIds keeps IDs centralized and consistent.

pages/lib/layout.py (1)

239-266: ColNames usage is correct – no changes needed. ColNames.NAME and ColNames.PATH are defined in pages/lib/global_column_names.py as "name" and "path" and are intentionally reused for UI dict keys.

pages/sun.py (2)

61-67: Good: centralized id_button via IdButtons.

Using IdButtons for title_with_link resolves the reviewer’s “missing ID entries” note and avoids ad-hoc IDs.


78-86: IDs/ColNames refactor looks consistent.

Inputs/Outputs/States and chart labels all reference ElementIds/ColNames/TabNames; this aligns with the PR goal and fixes prior string literals in sun.py (e.g., TOT_SKY_COVER).

Also applies to: 97-103, 106-111, 152-159, 164-168, 171-174, 187-191, 198-202, 205-218, 239-247, 250-281, 284-305, 307-326

pages/t_rh.py (1)

262-268: Remove redundant dd_value validation—dropdown_names only outputs valid df columns. dd_value is sourced from dropdown_names built from variables_dropdown (which only includes “DBT” and “RH”), matching actual DataFrame columns, so no extra check is needed.

Likely an incorrect or invalid review comment.

pages/select.py (1)

248-283: Good: banner subtitle uses ColNames for dynamic location.

Centralizing keys avoids brittle string indexing.

pages/outdoor.py (2)

221-263: Nice: descriptive string returned from data analysis.

Robust to missing columns and types; exception handling is appropriate.


296-312: TabNames/units wiring is consistent.

Config generation aligns with the refactor; LGTM.

Also applies to: 395-401, 438-440

pages/natural_ventilation.py (3)

21-24: Consistent constants import

Good move centralizing names via ColNames, ElementIds, IdButtons, and TabNames.


394-418: LGTM: axes/data wiring

Using ColNames for axis/customdata is correct and consistent.


522-525: tot_month_hours computation

Looks correct post time-filter; keep.

pages/explorer.py (4)

110-117: Docs/title wiring

IdButtons usage looks correct; nice.


237-241: Good: default to ColNames.RH

Consistent with the constants strategy.


965-975: Table summary uses constants correctly

MONTH, HOUR, MONTH_NAMES constants applied properly.


884-886: filter_df_by_month_and_hour supports iterable column selectors: Pandas .loc accepts both single labels and array-like column selectors, so passing df.columns correctly masks all columns. Performance is typical for a bulk .loc assignment; if profiling shows a bottleneck, narrow var to only the needed subset of columns.

pages/wind.py (1)

57-65: Hour slider range differs from other tabs

Wind uses 1–24 while others (e.g., NV) use 0–24. Confirm the intended domain across the app to avoid off-by-one filtering mismatches.

pages/summary.py (3)

266-282: Solar radiation summary update

Unit derivation with “k” prefix and DBT stats look correct.


420-423: Chart config/id

Good usage of TabNames and ElementIds for the degree-days chart.


520-563: Download filenames use constants correctly

City/country via ColNames is consistent.

Comment on lines 503 to 512
var = "DBT"
filter_var = "DPT"

var_unit = mapping_dictionary[var][si_ip]["unit"]
filter_unit = mapping_dictionary[filter_var][si_ip]["unit"]
var_unit = mapping_dictionary[var][si_ip][ColNames.UNIT]
filter_unit = mapping_dictionary[filter_var][si_ip][ColNames.UNIT]

var_name = mapping_dictionary[var]["name"]
var_name = mapping_dictionary[var][ColNames.NAME]

filter_name = mapping_dictionary[filter_var]["name"]
filter_name = mapping_dictionary[filter_var][ColNames.NAME]

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Align var/filter_var to constants

Keep consistency with nv_heatmap by using ColNames.

-    var = "DBT"
-    filter_var = "DPT"
+    var = ColNames.DBT
+    filter_var = ColNames.DPT
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var = "DBT"
filter_var = "DPT"
var_unit = mapping_dictionary[var][si_ip]["unit"]
filter_unit = mapping_dictionary[filter_var][si_ip]["unit"]
var_unit = mapping_dictionary[var][si_ip][ColNames.UNIT]
filter_unit = mapping_dictionary[filter_var][si_ip][ColNames.UNIT]
var_name = mapping_dictionary[var]["name"]
var_name = mapping_dictionary[var][ColNames.NAME]
filter_name = mapping_dictionary[filter_var]["name"]
filter_name = mapping_dictionary[filter_var][ColNames.NAME]
var = ColNames.DBT
filter_var = ColNames.DPT
var_unit = mapping_dictionary[var][si_ip][ColNames.UNIT]
filter_unit = mapping_dictionary[filter_var][si_ip][ColNames.UNIT]
var_name = mapping_dictionary[var][ColNames.NAME]
filter_name = mapping_dictionary[filter_var][ColNames.NAME]
🤖 Prompt for AI Agents
In pages/natural_ventilation.py around lines 503–512, replace the hard-coded
string keys "DBT" and "DPT" with the ColNames constants used elsewhere (as done
in nv_heatmap) — e.g. set var = ColNames.DBT and filter_var = ColNames.DPT (or
the exact DBT/DPT constant names used in ColNames in this codebase), then use
those constants for the mapping_dictionary lookups for UNIT and NAME so all
accesses remain mapping_dictionary[var][si_ip][ColNames.UNIT] and
mapping_dictionary[var][ColNames.NAME], keeping consistency with nv_heatmap.

Comment on lines 151 to 156
children=title_with_link(
text="UTCI heatmap chart",
id_button="utci-charts-label",
id_button=IdButtons.UTCI_CHARTS_LABEL,
doc_link=DocLinks.UTCI_CHART,
)
),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Duplicate id_button for two different titles → DOM ID collision.

Both titles use IdButtons.UTCI_CHARTS_LABEL; this creates duplicate IDs on the same page and can break callbacks/tooltip bindings.

Apply:

-                children=title_with_link(
-                    text="UTCI heatmap chart",
-                    id_button=IdButtons.UTCI_CHARTS_LABEL,
+                children=title_with_link(
+                    text="UTCI heatmap chart",
+                    id_button=IdButtons.UTCI_HEATMAP_CHART_LABEL,
@@
-                children=title_with_link(
-                    text="UTCI thermal stress chart",
-                    id_button=IdButtons.UTCI_CHARTS_LABEL,
+                children=title_with_link(
+                    text="UTCI thermal stress chart",
+                    id_button=IdButtons.UTCI_THERMAL_STRESS_CHART_LABEL,

Add these in pages/lib/global_id_buttons.py:

# New IDs to avoid collisions
UTCI_HEATMAP_CHART_LABEL = "id-button-utci-heatmap-chart"
UTCI_THERMAL_STRESS_CHART_LABEL = "id-button-utci-thermal-stress-chart"

Also applies to: 162-167

🤖 Prompt for AI Agents
In pages/outdoor.py around lines 151-156 (and similarly lines 162-167) there is
a DOM ID collision because both title components use
IdButtons.UTCI_CHARTS_LABEL; replace those duplicates with two distinct IDs
(e.g., UTCI_HEATMAP_CHART_LABEL and UTCI_THERMAL_STRESS_CHART_LABEL) and update
the title_with_link calls accordingly; also add the corresponding constants to
pages/lib/global_id_buttons.py (define the two new string IDs) so the imports
resolve and avoid duplicate IDs on the page.

Comment on lines +211 to 213
id=ElementIds.GH_RAD_PROFILE_GRAPH,
config=generate_chart_name(TabNames.MAP, meta),
figure=world_map(meta),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Duplicate component id: GH_RAD_PROFILE_GRAPH used for world map

This id is also used for the solar radiation graph, causing duplicate IDs. Remove the id or use a dedicated one.

-        id=ElementIds.GH_RAD_PROFILE_GRAPH,
+        # id intentionally omitted to avoid collision with the solar radiation graph
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
id=ElementIds.GH_RAD_PROFILE_GRAPH,
config=generate_chart_name(TabNames.MAP, meta),
figure=world_map(meta),
# id intentionally omitted to avoid collision with the solar radiation graph
config=generate_chart_name(TabNames.MAP, meta),
figure=world_map(meta),
🤖 Prompt for AI Agents
In pages/summary.py around lines 211 to 213 the component is assigned
id=ElementIds.GH_RAD_PROFILE_GRAPH which duplicates the solar radiation graph
id; change this to a unique id (either remove the id attribute entirely or use a
dedicated constant such as ElementIds.GH_WORLD_MAP_GRAPH), update the ElementIds
enum/consts if needed, and replace the id reference here so each component has a
distinct id.

Comment on lines 362 to 372
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)
hdd_array.append(hdd)

# 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)
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid string queries; use boolean indexing and constants

String-based DataFrame.query is brittle and mixes literals. Use ColNames and boolean masks.

Example for HDD loop (apply similarly to CDD):

-            query = query_month + str(i) + " and DBT<=" + str(hdd_setpoint)
-            a = df.query(query)[ColNames.DBT].sub(hdd_setpoint)
+            mask = (df[ColNames.MONTH] == i) & (df[ColNames.DBT] <= hdd_setpoint)
+            a = df.loc[mask, ColNames.DBT].sub(hdd_setpoint)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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)
hdd_array.append(hdd)
# 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)
# calculates HDD per month using boolean indexing
mask = (df[ColNames.MONTH] == i) & (df[ColNames.DBT] <= hdd_setpoint)
a = df.loc[mask, ColNames.DBT].sub(hdd_setpoint)
hdd = a.sum(axis=0, skipna=True)
hdd = hdd / 24
hdd = int(hdd)
hdd_array.append(hdd)
# calculates CDD per month (apply the same pattern here)
query = query_month + str(i) + " and DBT>=" + str(cdd_setpoint)
a = df.query(query)[ColNames.DBT].sub(cdd_setpoint)
cdd = a.sum(axis=0, skipna=True)

Comment on lines 79 to 82
text="Seasonal Wind Rose",
id_button="seasonal-rose-chart",
id_button=ElementIds.SEASONAL_WIND_ROSE_DOC,
doc_link=DocLinks.WIND_ROSE,
),
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Doc button should use IdButtons, not ElementIds

Keep id_button aligned with IdButtons.* like elsewhere.

-                    id_button=ElementIds.SEASONAL_WIND_ROSE_DOC,
+                    id_button=IdButtons.SEASONAL_WIND_ROSE_DOC,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
text="Seasonal Wind Rose",
id_button="seasonal-rose-chart",
id_button=ElementIds.SEASONAL_WIND_ROSE_DOC,
doc_link=DocLinks.WIND_ROSE,
),
text="Seasonal Wind Rose",
id_button=IdButtons.SEASONAL_WIND_ROSE_DOC,
doc_link=DocLinks.WIND_ROSE,
),
🤖 Prompt for AI Agents
In pages/wind.py around lines 79 to 82, the doc button uses
ElementIds.SEASONAL_WIND_ROSE_DOC but should use IdButtons.* to match other
usages; replace id_button=ElementIds.SEASONAL_WIND_ROSE_DOC with
id_button=IdButtons.SEASONAL_WIND_ROSE_DOC (preserve the same
alignment/indentation as surrounding args).

LeoLuosifen and others added 3 commits August 30, 2025 15:08
Update instances of "wind_speed" to ColNames.WIND_SPEED in multiple files
to improve code consistency and maintainability.
Update the label for the yearly chart to use consistent capitalization
for improved readability and consistency in the UI.
@FedericoTartarini FedericoTartarini merged commit 795199f into CenterForTheBuiltEnvironment:development Sep 1, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants