-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
276 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# this is needed for local development with docker | ||
[server] | ||
# if you don't want to start the default browser: | ||
headless = true | ||
# you will need this for local development: | ||
runOnSave = true | ||
# you will need this if running docker on windows host: | ||
fileWatcherType = "poll" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
version: '3.4' | ||
services: | ||
frontend: | ||
volumes: | ||
- ./frontend:/app/frontend | ||
- ./lib:/app/lib |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
version: '3.4' | ||
services: | ||
frontend: # offloads api callbacks to a scalable service | ||
build: | ||
context: . | ||
dockerfile: frontend/Dockerfile | ||
env_file: | ||
- .env | ||
ports: | ||
- "8501:8501" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
FROM python:3.9-slim | ||
|
||
WORKDIR /app | ||
|
||
COPY requirements/ requirements/ | ||
|
||
RUN pip install --no-cache-dir -r requirements/base-requirements.txt -r requirements/fe-requirements.txt | ||
|
||
COPY .streamlit/ .streamlit/ | ||
COPY frontend/ frontend/ | ||
COPY lib/ lib/ | ||
|
||
ENV PYTHONPATH=/app | ||
|
||
CMD ["streamlit", "run", "frontend/app.py", "--server.port", "8501", "--server.address", "0.0.0.0"] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
from typing import Literal, Optional | ||
|
||
from pydantic import Field | ||
from pydantic_settings import BaseSettings | ||
|
||
FrontendEnvs = Literal["dev", "prod"] | ||
|
||
|
||
class FrontendSettings(BaseSettings): | ||
|
||
env: str = Field("dev", env="ENV") | ||
password: str = Field(..., env="PASSWORD") | ||
|
||
class Config: | ||
env_prefix = "FRONTEND_" | ||
env_file = ".env" | ||
|
||
|
||
frontend_settings = FrontendSettings() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,33 +1,239 @@ | ||
import json | ||
import os | ||
|
||
import pandas as pd | ||
import plotly.express as px | ||
import streamlit as st | ||
|
||
from frontend import frontend_settings as settings | ||
from lib.client import client | ||
from lib.supa import Building | ||
|
||
st.set_page_config(layout="wide", page_title="MIT Decarbonization") | ||
|
||
|
||
@st.cache_data | ||
def get_all_buildings(): | ||
df = pd.DataFrame(client.table("Building").select("*").execute().data).set_index('id') | ||
df = pd.DataFrame(client.table("Building").select("*").execute().data).set_index( | ||
"id" | ||
) | ||
# create csv bytes | ||
csv = df.to_csv().encode() | ||
return df, csv | ||
|
||
|
||
@st.cache_data | ||
def get_building_scenarios(building_id: int): | ||
ids = ( | ||
client.table("DemandScenarioBuilding") | ||
.select("id, demand_scenario_id") | ||
.eq("building_id", building_id) | ||
.execute() | ||
.data | ||
) | ||
scenario_ids = [d["demand_scenario_id"] for d in ids] | ||
results_ids = [d["id"] for d in ids] | ||
return scenario_ids, results_ids | ||
|
||
|
||
@st.cache_data | ||
def get_scenarios(): | ||
return client.table("DemandScenario").select("*").execute().data | ||
|
||
|
||
@st.cache_data | ||
def get_scenario_results(scenario_id: int): | ||
ids = ( | ||
client.table("DemandScenarioBuilding") | ||
.select("id") | ||
.eq("demand_scenario_id", scenario_id) | ||
.execute() | ||
.data | ||
) | ||
results_ids = [d["id"] for d in ids] | ||
results = ( | ||
client.table("BuildingSimulationResult") | ||
.select("*") | ||
.in_("id", results_ids) | ||
.execute() | ||
.data | ||
) | ||
dfs = [] | ||
for result in results: | ||
result["heating"] = json.loads(result["heating"]) | ||
result["cooling"] = json.loads(result["cooling"]) | ||
result["lighting"] = json.loads(result["lighting"]) | ||
result["equipment"] = json.loads(result["equipment"]) | ||
df = pd.DataFrame( | ||
{ | ||
"heating": result["heating"], | ||
"cooling": result["cooling"], | ||
"lighting": result["lighting"], | ||
"equipment": result["equipment"], | ||
} | ||
) | ||
df["Timestamp"] = pd.date_range(start="2024-01-01", periods=len(df), freq="h") | ||
df = df.set_index("Timestamp") | ||
df["result_id"] = result["id"] | ||
df = df.set_index("result_id", append=True) | ||
dfs.append(df) | ||
df_buildings = pd.concat(dfs) | ||
df = df_buildings.groupby("Timestamp").sum() | ||
df.columns = [x.capitalize() for x in df.columns] | ||
df = df.reset_index("Timestamp") | ||
df_melted = df.melt( | ||
id_vars=["Timestamp"], var_name="End Use", value_name="Energy [J]" | ||
) | ||
return df, df_melted, df_buildings | ||
|
||
|
||
@st.cache_data | ||
def get_scenario_building_result(scenario_id: int, building_id: int): | ||
ids = ( | ||
client.table("DemandScenarioBuilding") | ||
.select("id") | ||
.eq("demand_scenario_id", scenario_id) | ||
.eq("building_id", building_id) | ||
.execute() | ||
.data | ||
) | ||
results_ids = [d["id"] for d in ids] | ||
results = ( | ||
client.table("BuildingSimulationResult") | ||
.select("*") | ||
.in_("id", results_ids) | ||
.execute() | ||
.data | ||
) | ||
df = pd.DataFrame( | ||
{ | ||
"heating": json.loads(results[0]["heating"]), | ||
"cooling": json.loads(results[0]["cooling"]), | ||
"lighting": json.loads(results[0]["lighting"]), | ||
"equipment": json.loads(results[0]["equipment"]), | ||
} | ||
) | ||
df["Timestamp"] = pd.date_range(start="2024-01-01", periods=len(df), freq="h") | ||
df.columns = [x.capitalize() for x in df.columns] | ||
df_melted = df.melt( | ||
id_vars=["Timestamp"], var_name="End Use", value_name="Energy [J]" | ||
) | ||
|
||
return df, df_melted | ||
|
||
|
||
ENDUSE_PASTEL_COLORS = { | ||
"Heating": "#FF7671", | ||
"Cooling": "#6D68E6", | ||
"Lighting": "#FFD700", | ||
"Equipment": "#90EE90", | ||
} | ||
|
||
|
||
def render_title(): | ||
st.title("MIT Decarbonization") | ||
|
||
|
||
def render_buildings(): | ||
st.header("Buildings") | ||
all_buildings, all_buildings_csv = get_all_buildings() | ||
st.download_button("Download all buildings", all_buildings_csv, "buildings_metadata.csv", "Download all buildings", use_container_width=True, type="primary") | ||
building_id = st.selectbox('Building', all_buildings.index, format_func=lambda x: all_buildings.loc[x, 'name'], help='Select a building to view its data') | ||
st.download_button( | ||
"Download all building metadata", | ||
all_buildings_csv, | ||
"buildings_metadata.csv", | ||
"Download all buildings", | ||
use_container_width=True, | ||
type="primary", | ||
) | ||
building_id = st.selectbox( | ||
"Building", | ||
all_buildings.index, | ||
format_func=lambda x: all_buildings.loc[x, "name"], | ||
help="Select a building to view its data", | ||
) | ||
building = all_buildings.loc[building_id] | ||
st.dataframe(building) | ||
scenario_ids, results_ids = get_building_scenarios(building_id) | ||
all_scenarios = get_scenarios() | ||
filtered_scenarios = [s for s in all_scenarios if s["id"] in scenario_ids] | ||
l, r = st.columns(2) | ||
with l: | ||
scenario_id = st.selectbox( | ||
"Building Demand Scenario", | ||
scenario_ids, | ||
format_func=lambda x: [ | ||
s["name"] for s in filtered_scenarios if s["id"] == x | ||
][0], | ||
help="Select a demand scenario to view its data", | ||
) | ||
result, result_melted = get_scenario_building_result(scenario_id, building_id) | ||
fig = px.line( | ||
result_melted, | ||
x="Timestamp", | ||
y="Energy [J]", | ||
color="End Use", | ||
title=f"Building {building['name']} Energy Use", | ||
color_discrete_map=ENDUSE_PASTEL_COLORS, | ||
) | ||
st.plotly_chart(fig, use_container_width=True) | ||
|
||
with r: | ||
st.dataframe(building) | ||
|
||
render_title() | ||
st.divider() | ||
render_buildings() | ||
|
||
def render_building_scenarios(): | ||
all_scenarios = get_scenarios() | ||
scenario = st.selectbox( | ||
"Demand Scenario", | ||
all_scenarios, | ||
format_func=lambda x: x["name"], | ||
help="Select a demand scenario to view its data", | ||
) | ||
df, df_melted, df_buildings = get_scenario_results(scenario["id"]) | ||
l, r = st.columns(2) | ||
with l: | ||
st.download_button( | ||
"Download scenario results", | ||
df.to_csv().encode(), | ||
f"{scenario['name']}_results.csv", | ||
"Download scenario results", | ||
use_container_width=True, | ||
type="primary", | ||
) | ||
with r: | ||
st.download_button( | ||
"Download scenario buildings results", | ||
df_buildings.to_csv().encode(), | ||
f"{scenario['name']}_buildings_results.csv", | ||
"Download scenario buildings results", | ||
use_container_width=True, | ||
type="primary", | ||
) | ||
fig = px.line( | ||
df_melted, | ||
x="Timestamp", | ||
y="Energy [J]", | ||
color="End Use", | ||
title=f"{scenario['name']} Demand Scenario", | ||
color_discrete_map=ENDUSE_PASTEL_COLORS, | ||
) | ||
st.plotly_chart(fig, use_container_width=True) | ||
|
||
|
||
def password_protect(): | ||
if "password" not in st.session_state: | ||
st.session_state.password = None | ||
if st.session_state.password == settings.password: | ||
return True | ||
else: | ||
password = st.text_input("Password", type="password") | ||
st.session_state.password = password | ||
return st.session_state.password == settings.password | ||
|
||
|
||
render_title() | ||
logged_in = password_protect() | ||
if logged_in: | ||
buildings_tab, scenarios_tab = st.tabs(["Buildings", "Scenarios"]) | ||
with buildings_tab: | ||
render_buildings() | ||
with scenarios_tab: | ||
render_building_scenarios() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
streamlit | ||
streamlit | ||
plotly |