Spaces:
Sleeping
Sleeping
atodorov284 commited on
Commit ·
15d578c
1
Parent(s): 5227116
refactor user view and controller to adhere to MVC
Browse files
streamlit_src/controllers/user_controller.py
CHANGED
|
@@ -1,56 +1,353 @@
|
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
import numpy as np
|
| 3 |
from models.air_quality_model import AirQualityModel
|
| 4 |
from views.user_view import UserView
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
|
| 7 |
class UserController:
|
| 8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
self.model = AirQualityModel()
|
| 10 |
self.view = UserView()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
-
# Ensure session state for
|
| 13 |
if "is_first_run" not in st.session_state:
|
| 14 |
st.session_state.is_first_run = True
|
| 15 |
-
|
| 16 |
if "question_choice" not in st.session_state:
|
| 17 |
st.session_state.question_choice = np.random.randint(0, 5)
|
| 18 |
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
-
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
"Pollutant": ["NO2 (µg/m³)", "O3 (µg/m³)"],
|
| 27 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
-
|
| 31 |
-
self
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
)
|
| 42 |
|
| 43 |
-
#
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
-
|
| 56 |
-
self
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Callable, Dict, List, Tuple
|
| 2 |
import streamlit as st
|
| 3 |
import numpy as np
|
| 4 |
from models.air_quality_model import AirQualityModel
|
| 5 |
from views.user_view import UserView
|
| 6 |
+
import os
|
| 7 |
+
import pandas as pd
|
| 8 |
+
import random
|
| 9 |
+
import json
|
| 10 |
+
from datetime import date, timedelta
|
| 11 |
+
import plotly.graph_objects as go
|
| 12 |
|
| 13 |
|
| 14 |
class UserController:
|
| 15 |
+
"""
|
| 16 |
+
A class to handle the user interface.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
def __init__(self) -> None:
|
| 20 |
+
"""
|
| 21 |
+
Initializes the UserController class.
|
| 22 |
+
"""
|
| 23 |
self.model = AirQualityModel()
|
| 24 |
self.view = UserView()
|
| 25 |
+
self.today_data = self.model.get_today_data()
|
| 26 |
+
self.next_three_days = self.model.next_three_day_predictions()
|
| 27 |
+
|
| 28 |
+
self.who_guidelines = {
|
| 29 |
+
"Pollutant": ["NO2 (µg/m³)", "O3 (µg/m³)"],
|
| 30 |
+
"WHO Guideline": [self.model.WHO_NO2_LEVEL, self.model.WHO_O3_LEVEL],
|
| 31 |
+
}
|
| 32 |
|
| 33 |
+
# Ensure session state for quiz and quiz answer tracking
|
| 34 |
if "is_first_run" not in st.session_state:
|
| 35 |
st.session_state.is_first_run = True
|
|
|
|
| 36 |
if "question_choice" not in st.session_state:
|
| 37 |
st.session_state.question_choice = np.random.randint(0, 5)
|
| 38 |
|
| 39 |
+
# Paths for external data
|
| 40 |
+
self.interactions_path = os.path.join(
|
| 41 |
+
os.path.dirname(os.path.dirname(__file__)), "json_interactions/"
|
| 42 |
+
)
|
| 43 |
+
self.facts_path = os.path.join(self.interactions_path, "facts.json")
|
| 44 |
+
self.questions_path = os.path.join(self.interactions_path, "question.json")
|
| 45 |
+
self.awareness_path = os.path.join(self.interactions_path, "awareness.json")
|
| 46 |
+
|
| 47 |
+
def show_dashboard(self) -> None:
|
| 48 |
+
"""
|
| 49 |
+
Shows the main page of the user interface.
|
| 50 |
+
"""
|
| 51 |
+
self.show_current_data()
|
| 52 |
+
|
| 53 |
+
self.two_columns_layout(0.7, self.raise_awareness, self.quiz)
|
| 54 |
+
|
| 55 |
+
# Plot selection and rendering
|
| 56 |
+
plot_type = self.view.view_option_selection(["Line Plot", "Gauge Plot"])
|
| 57 |
+
if plot_type == "Line Plot":
|
| 58 |
+
line_fig = self.prepare_line_plot()
|
| 59 |
+
self.view.display_predictions_lineplot(line_fig)
|
| 60 |
+
elif plot_type == "Gauge Plot":
|
| 61 |
+
gauge_plots = self.prepare_gauge_plots()
|
| 62 |
+
self.view.display_predictions_gaugeplot(gauge_plots)
|
| 63 |
+
|
| 64 |
+
# WHO comparison
|
| 65 |
+
who_comparisons = self.compare_to_who()
|
| 66 |
+
self.view.compare_to_who(who_comparisons)
|
| 67 |
+
|
| 68 |
+
# Sources
|
| 69 |
+
sources = [
|
| 70 |
+
(
|
| 71 |
+
"WHO Air Quality Guidelines",
|
| 72 |
+
"https://www.who.int/news-room/fact-sheets/detail/ambient-(outdoor)-air-quality-and-health",
|
| 73 |
+
),
|
| 74 |
+
(
|
| 75 |
+
"Air Pollution Facts",
|
| 76 |
+
"https://www.un.org/sustainabledevelopment/air-pollution/",
|
| 77 |
+
),
|
| 78 |
+
]
|
| 79 |
+
self.view.print_sources(sources)
|
| 80 |
+
|
| 81 |
+
def show_current_data(self) -> None:
|
| 82 |
+
"""
|
| 83 |
+
Shows the current data on the main page of the user interface.
|
| 84 |
+
"""
|
| 85 |
+
merged_data_df = self.prepare_data_for_view()
|
| 86 |
+
self.view.show_current_data(merged_data_df)
|
| 87 |
|
| 88 |
+
def prepare_data_for_view(self) -> pd.DataFrame:
|
| 89 |
+
"""
|
| 90 |
+
Prepares the current data for the view.
|
| 91 |
+
|
| 92 |
+
Returns
|
| 93 |
+
-------
|
| 94 |
+
pd.DataFrame
|
| 95 |
+
The current data in a pandas DataFrame.
|
| 96 |
+
"""
|
| 97 |
+
merged_data = {
|
| 98 |
"Pollutant": ["NO2 (µg/m³)", "O3 (µg/m³)"],
|
| 99 |
+
"Current Concentration": [
|
| 100 |
+
self.today_data["NO2 (µg/m³)"],
|
| 101 |
+
self.today_data["O3 (µg/m³)"],
|
| 102 |
+
],
|
| 103 |
+
"WHO Guideline": self.who_guidelines["WHO Guideline"],
|
| 104 |
}
|
| 105 |
+
return pd.DataFrame(merged_data)
|
| 106 |
+
|
| 107 |
+
def raise_awareness(self) -> None:
|
| 108 |
+
"""
|
| 109 |
+
Shows the awareness content on the main page of the user interface.
|
| 110 |
+
"""
|
| 111 |
+
random_fact, awareness_expanders, health_message = (
|
| 112 |
+
self.prepare_awareness_content()
|
| 113 |
+
)
|
| 114 |
+
self.view.raise_awareness(random_fact, awareness_expanders, health_message)
|
| 115 |
|
| 116 |
+
def prepare_awareness_content(
|
| 117 |
+
self,
|
| 118 |
+
) -> Tuple[str, List[Tuple[str, str]], Dict[str, str]]:
|
| 119 |
+
"""
|
| 120 |
+
Prepare awareness content including a random fact, expanders, and health message based on air quality data.
|
| 121 |
|
| 122 |
+
Returns
|
| 123 |
+
-------
|
| 124 |
+
Tuple[str, List[Tuple[str, str]], Dict[str, str]]
|
| 125 |
+
A tuple containing the random fact, awareness expanders, and health message.
|
| 126 |
+
"""
|
| 127 |
+
with open(self.facts_path, "r") as facts_file:
|
| 128 |
+
facts = json.load(facts_file)
|
| 129 |
+
random_fact = random.choice(facts["facts"])
|
| 130 |
+
|
| 131 |
+
with open(self.awareness_path, "r") as awareness_file:
|
| 132 |
+
awareness = json.load(awareness_file)
|
| 133 |
+
awareness_expanders = [
|
| 134 |
+
(title, "\n".join(text)) for title, text in awareness.items()
|
| 135 |
+
]
|
| 136 |
+
|
| 137 |
+
health_message = {"message": "", "type": ""}
|
| 138 |
+
if (
|
| 139 |
+
self.today_data["NO2 (µg/m³)"] > self.who_guidelines["WHO Guideline"][0]
|
| 140 |
+
or self.today_data["O3 (µg/m³)"] > self.who_guidelines["WHO Guideline"][1]
|
| 141 |
+
):
|
| 142 |
+
health_message["message"] = (
|
| 143 |
+
"🚨 High pollution levels today. Avoid outdoor activities if possible, especially for vulnerable groups."
|
| 144 |
+
)
|
| 145 |
+
health_message["type"] = "error"
|
| 146 |
+
else:
|
| 147 |
+
health_message["message"] = (
|
| 148 |
+
"✅ Air quality is within safe limits today. Enjoy your outdoor activities!"
|
| 149 |
+
)
|
| 150 |
+
health_message["type"] = "success"
|
| 151 |
+
|
| 152 |
+
return random_fact, awareness_expanders, health_message
|
| 153 |
+
|
| 154 |
+
def quiz(self) -> None:
|
| 155 |
+
"""
|
| 156 |
+
Show a quiz question and return the answer and whether the answer was correct.
|
| 157 |
|
| 158 |
+
Returns
|
| 159 |
+
-------
|
| 160 |
+
tuple
|
| 161 |
+
A tuple containing the answer and a boolean indicating whether the answer was correct.
|
| 162 |
+
"""
|
| 163 |
+
question_number = st.session_state.question_choice
|
| 164 |
+
with open(self.questions_path, "r") as questions_file:
|
| 165 |
+
quiz_data = json.load(questions_file)
|
| 166 |
+
question = quiz_data["quiz"][question_number]["question"]
|
| 167 |
+
options = quiz_data["quiz"][question_number]["options"]
|
| 168 |
+
submitted, answer = self.view.quiz(question, options)
|
| 169 |
+
|
| 170 |
+
if submitted:
|
| 171 |
+
correct_answer = quiz_data["quiz"][question_number]["answer"]
|
| 172 |
+
if answer == correct_answer:
|
| 173 |
+
self.view.success("Correct answer!")
|
| 174 |
+
else:
|
| 175 |
+
self.view.error(
|
| 176 |
+
f"Wrong answer! The correct answer was {correct_answer[0].lower() + correct_answer[1:]}."
|
| 177 |
+
)
|
| 178 |
+
|
| 179 |
+
def two_columns_layout(
|
| 180 |
+
self, ratio: float, left_function: Callable, right_function: Callable
|
| 181 |
+
) -> None:
|
| 182 |
+
"""
|
| 183 |
+
Divide the page into two columns and call the left and right functions within them.
|
| 184 |
+
|
| 185 |
+
Parameters
|
| 186 |
+
----------
|
| 187 |
+
ratio : float
|
| 188 |
+
The ratio of the left to the right column.
|
| 189 |
+
left_function : Callable
|
| 190 |
+
The function to be called in the left column.
|
| 191 |
+
right_function : Callable
|
| 192 |
+
The function to be called in the right column.
|
| 193 |
+
"""
|
| 194 |
+
left, right = st.columns([ratio, 1 - ratio], gap="large")
|
| 195 |
+
|
| 196 |
+
with left:
|
| 197 |
+
left_function()
|
| 198 |
+
|
| 199 |
+
with right:
|
| 200 |
+
right_function()
|
| 201 |
+
|
| 202 |
+
def prepare_line_plot(self) -> go.Figure:
|
| 203 |
+
"""
|
| 204 |
+
Prepare a line plot for the next three days' NO2 and O3 levels.
|
| 205 |
+
|
| 206 |
+
Returns:
|
| 207 |
+
go.Figure: A plotly figure object.
|
| 208 |
+
"""
|
| 209 |
+
tomorrow, day_after_tomorrow, two_days_after_tomorrow = (
|
| 210 |
+
self.get_next_three_days_dates()
|
| 211 |
+
)
|
| 212 |
+
self.next_three_days["Date"] = [
|
| 213 |
+
tomorrow,
|
| 214 |
+
day_after_tomorrow,
|
| 215 |
+
two_days_after_tomorrow,
|
| 216 |
+
]
|
| 217 |
+
fig = go.Figure()
|
| 218 |
+
fig.add_trace(
|
| 219 |
+
go.Scatter(
|
| 220 |
+
x=self.next_three_days["Date"],
|
| 221 |
+
y=self.next_three_days["NO2 (µg/m³)"],
|
| 222 |
+
mode="lines+markers+text",
|
| 223 |
+
name="NO2",
|
| 224 |
+
line=dict(color="blue"),
|
| 225 |
+
)
|
| 226 |
+
)
|
| 227 |
+
fig.add_trace(
|
| 228 |
+
go.Scatter(
|
| 229 |
+
x=self.next_three_days["Date"],
|
| 230 |
+
y=self.next_three_days["O3 (µg/m³)"],
|
| 231 |
+
mode="lines+markers+text",
|
| 232 |
+
name="O3",
|
| 233 |
+
line=dict(color="lightblue"),
|
| 234 |
+
)
|
| 235 |
)
|
| 236 |
|
| 237 |
+
# WHO guideline as horizontal dotted lines
|
| 238 |
+
fig.add_hline(
|
| 239 |
+
y=self.who_guidelines["WHO Guideline"][0],
|
| 240 |
+
line_dash="dot",
|
| 241 |
+
line_color="blue",
|
| 242 |
+
annotation_text="WHO NO2 Guideline",
|
| 243 |
+
)
|
| 244 |
+
fig.add_hline(
|
| 245 |
+
y=self.who_guidelines["WHO Guideline"][1],
|
| 246 |
+
line_dash="dot",
|
| 247 |
+
line_color="lightblue",
|
| 248 |
+
annotation_text="WHO O3 Guideline",
|
| 249 |
+
)
|
| 250 |
+
|
| 251 |
+
fig.update_layout(
|
| 252 |
+
title="Predictions for the Next 3 Days",
|
| 253 |
+
xaxis_title="Date",
|
| 254 |
+
yaxis_title="Pollutant Concentration (µg/m³)",
|
| 255 |
+
hovermode="x unified",
|
| 256 |
+
)
|
| 257 |
+
return fig
|
| 258 |
+
|
| 259 |
+
def get_next_three_days_dates(self) -> tuple:
|
| 260 |
+
"""
|
| 261 |
+
Get the next three days' dates.
|
| 262 |
+
|
| 263 |
+
Returns:
|
| 264 |
+
tuple: A tuple of three date objects.
|
| 265 |
+
"""
|
| 266 |
+
tomorrow = date.today() + timedelta(days=1)
|
| 267 |
+
day_after_tomorrow = date.today() + timedelta(days=2)
|
| 268 |
+
two_days_after_tomorrow = date.today() + timedelta(days=3)
|
| 269 |
+
return tomorrow, day_after_tomorrow, two_days_after_tomorrow
|
| 270 |
+
|
| 271 |
+
def compare_to_who(self) -> list:
|
| 272 |
+
"""
|
| 273 |
+
Compare the current pollutant levels to WHO guidelines.
|
| 274 |
|
| 275 |
+
Returns:
|
| 276 |
+
list: A list of tuples containing the pollutant name, comparison message, and message type.
|
| 277 |
+
"""
|
| 278 |
+
comparisons = []
|
| 279 |
+
for i, pollutant in enumerate(["NO2 (µg/m³)", "O3 (µg/m³)"]):
|
| 280 |
+
if self.today_data[pollutant] > self.who_guidelines["WHO Guideline"][i]:
|
| 281 |
+
comparisons.append(
|
| 282 |
+
(
|
| 283 |
+
pollutant,
|
| 284 |
+
f"🚨 {pollutant} levels exceed WHO guidelines!",
|
| 285 |
+
"error",
|
| 286 |
+
)
|
| 287 |
+
)
|
| 288 |
+
else:
|
| 289 |
+
comparisons.append(
|
| 290 |
+
(
|
| 291 |
+
pollutant,
|
| 292 |
+
f"✅ {pollutant} levels are within safe limits",
|
| 293 |
+
"success",
|
| 294 |
+
)
|
| 295 |
+
)
|
| 296 |
+
return comparisons
|
| 297 |
+
|
| 298 |
+
def prepare_gauge_plots(self) -> list:
|
| 299 |
+
"""
|
| 300 |
+
Prepare gauge plots for the next three days' NO2 and O3 levels.
|
| 301 |
+
|
| 302 |
+
Returns:
|
| 303 |
+
list: A list of tuples containing the day index, formatted date, and two plotly figures (for NO2 and O3).
|
| 304 |
+
"""
|
| 305 |
+
tomorrow, day_after_tomorrow, two_days_after_tomorrow = (
|
| 306 |
+
self.get_next_three_days_dates()
|
| 307 |
)
|
| 308 |
+
self.next_three_days["Date"] = [
|
| 309 |
+
tomorrow,
|
| 310 |
+
day_after_tomorrow,
|
| 311 |
+
two_days_after_tomorrow,
|
| 312 |
+
]
|
| 313 |
+
|
| 314 |
+
gauge_plots = []
|
| 315 |
+
for i, day in enumerate(
|
| 316 |
+
[tomorrow, day_after_tomorrow, two_days_after_tomorrow]
|
| 317 |
+
):
|
| 318 |
+
no2_value = self.next_three_days["NO2 (µg/m³)"][i]
|
| 319 |
+
o3_value = self.next_three_days["O3 (µg/m³)"][i]
|
| 320 |
+
fig_no2 = self.create_gauge_plot(
|
| 321 |
+
no2_value, self.who_guidelines["WHO Guideline"][0], "NO2 (µg/m³)"
|
| 322 |
+
)
|
| 323 |
+
fig_o3 = self.create_gauge_plot(
|
| 324 |
+
o3_value, self.who_guidelines["WHO Guideline"][1], "O3 (µg/m³)"
|
| 325 |
+
)
|
| 326 |
+
gauge_plots.append((i + 1, day.strftime("%B %d, %Y"), fig_no2, fig_o3))
|
| 327 |
+
return gauge_plots
|
| 328 |
|
| 329 |
+
def create_gauge_plot(
|
| 330 |
+
self, value: float, guideline: float, title: str
|
| 331 |
+
) -> go.Figure:
|
| 332 |
+
"""
|
| 333 |
+
Create a gauge plot for a given pollutant value and guideline.
|
| 334 |
+
|
| 335 |
+
Args:
|
| 336 |
+
value (float): The pollutant concentration value.
|
| 337 |
+
guideline (float): The WHO guideline value for the pollutant.
|
| 338 |
+
title (str): The title of the gauge plot.
|
| 339 |
+
|
| 340 |
+
Returns:
|
| 341 |
+
go.Figure: A Plotly figure representing the gauge plot.
|
| 342 |
+
"""
|
| 343 |
+
color = "green" if value <= guideline else "red"
|
| 344 |
+
fig = go.Figure(
|
| 345 |
+
go.Indicator(
|
| 346 |
+
mode="gauge+number",
|
| 347 |
+
value=value,
|
| 348 |
+
title={"text": title},
|
| 349 |
+
gauge={"axis": {"range": [0, 2 * guideline]}, "bar": {"color": color}},
|
| 350 |
+
)
|
| 351 |
+
)
|
| 352 |
+
fig.update_layout(height=250, width=250, margin=dict(t=0, b=0, l=0, r=0))
|
| 353 |
+
return fig
|
streamlit_src/json_interactions/awareness.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"What is Air Pollution?": [
|
| 3 |
+
"Air pollution is a serious concern that affects the environment and public health. High levels of pollutants, such as ozone (O\u2083) and nitrogen dioxide (NO\u2082), can lead to respiratory problems, aggravate pre-existing conditions like asthma, and contribute to cardiovascular diseases."
|
| 4 |
+
],
|
| 5 |
+
"Why O\u2083 and NO\u2082 Matter": [
|
| 6 |
+
"Ozone (O\u2083): Formed by chemical reactions in the atmosphere, particularly on sunny days. High levels can cause chest pain, coughing, throat irritation, and airway inflammation. \nNitrogen Dioxide (NO\u2082): Mostly emitted from vehicles and industrial activities, this can cause irritation of the respiratory system and decrease lung function, especially during long-term exposure."
|
| 7 |
+
]
|
| 8 |
+
}
|
streamlit_src/views/user_view.py
CHANGED
|
@@ -1,335 +1,148 @@
|
|
| 1 |
-
# views/user_view.py
|
| 2 |
import streamlit as st
|
| 3 |
import pandas as pd
|
| 4 |
-
from datetime import date, datetime, timedelta
|
| 5 |
import plotly.graph_objects as go
|
| 6 |
-
import
|
| 7 |
-
import random
|
| 8 |
-
import os
|
| 9 |
-
|
| 10 |
-
FACTS_PATH = os.path.join(
|
| 11 |
-
os.path.dirname(os.path.dirname(__file__)), "json_interactions/", "facts.json"
|
| 12 |
-
)
|
| 13 |
-
|
| 14 |
-
QUESTIONS_PATH = os.path.join(
|
| 15 |
-
os.path.dirname(os.path.dirname(__file__)), "json_interactions/", "question.json"
|
| 16 |
-
)
|
| 17 |
|
| 18 |
|
| 19 |
class UserView:
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
# Merge today_data and who_guidelines into one DataFrame
|
| 24 |
-
merged_data = {
|
| 25 |
-
"Pollutant": ["NO2 (µg/m³)", "O3 (µg/m³)"],
|
| 26 |
-
"Current Concentration": [
|
| 27 |
-
today_data["NO2 (µg/m³)"],
|
| 28 |
-
today_data["O3 (µg/m³)"],
|
| 29 |
-
],
|
| 30 |
-
"WHO Guideline": [
|
| 31 |
-
who_guidelines["WHO Guideline"][0], # NO2 guideline
|
| 32 |
-
who_guidelines["WHO Guideline"][1], # O3 guideline
|
| 33 |
-
],
|
| 34 |
-
}
|
| 35 |
|
| 36 |
-
|
|
|
|
|
|
|
| 37 |
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
st.sidebar.markdown("### Current Pollutant Concentrations and WHO Guidelines")
|
| 40 |
st.sidebar.dataframe(merged_data_df, hide_index=True)
|
| 41 |
|
| 42 |
-
def
|
| 43 |
"""
|
| 44 |
-
|
| 45 |
|
| 46 |
-
:
|
|
|
|
| 47 |
"""
|
| 48 |
-
|
| 49 |
-
tomorrow = today + timedelta(days=1)
|
| 50 |
-
day_after_tomorrow = today + timedelta(days=2)
|
| 51 |
-
two_days_after_tomorrow = today + timedelta(days=3)
|
| 52 |
-
return tomorrow, day_after_tomorrow, two_days_after_tomorrow
|
| 53 |
|
| 54 |
-
def
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
)
|
| 58 |
-
|
| 59 |
-
# Update the dataframe with actual dates in datetime format
|
| 60 |
-
next_three_days["Date"] = [
|
| 61 |
-
tomorrow,
|
| 62 |
-
day_after_tomorrow,
|
| 63 |
-
two_days_after_tomorrow,
|
| 64 |
-
]
|
| 65 |
-
|
| 66 |
-
# Create the plot using Plotly for more customization
|
| 67 |
-
fig = go.Figure()
|
| 68 |
-
|
| 69 |
-
# Add NO2 line
|
| 70 |
-
fig.add_trace(
|
| 71 |
-
go.Scatter(
|
| 72 |
-
x=next_three_days["Date"],
|
| 73 |
-
y=next_three_days["NO2 (µg/m³)"],
|
| 74 |
-
mode="lines+markers+text",
|
| 75 |
-
name="NO2 (µg/m³)",
|
| 76 |
-
text=[f"{v:.2f} µg/m³" for v in next_three_days["NO2 (µg/m³)"]],
|
| 77 |
-
textposition="top right",
|
| 78 |
-
line=dict(color="blue"),
|
| 79 |
-
)
|
| 80 |
-
)
|
| 81 |
-
|
| 82 |
-
# Add O3 line
|
| 83 |
-
fig.add_trace(
|
| 84 |
-
go.Scatter(
|
| 85 |
-
x=next_three_days["Date"],
|
| 86 |
-
y=next_three_days["O3 (µg/m³)"],
|
| 87 |
-
mode="lines+markers+text",
|
| 88 |
-
name="O3 (µg/m³)",
|
| 89 |
-
text=[f"{v:.2f} µg/m³" for v in next_three_days["O3 (µg/m³)"]],
|
| 90 |
-
textposition="top right",
|
| 91 |
-
line=dict(color="lightblue"),
|
| 92 |
-
)
|
| 93 |
-
)
|
| 94 |
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
line_color="blue",
|
| 100 |
-
annotation_text="WHO NO2 Guideline",
|
| 101 |
-
annotation_position="bottom right",
|
| 102 |
-
)
|
| 103 |
-
fig.add_hline(
|
| 104 |
-
y=who_guidelines["WHO Guideline"][1],
|
| 105 |
-
line_dash="dot",
|
| 106 |
-
line_color="lightblue",
|
| 107 |
-
annotation_text="WHO O3 Guideline",
|
| 108 |
-
annotation_position="bottom right",
|
| 109 |
-
)
|
| 110 |
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
xaxis_title="Date",
|
| 115 |
-
yaxis_title="Pollutant Concentration (µg/m³)",
|
| 116 |
-
hovermode="x unified",
|
| 117 |
-
)
|
| 118 |
|
| 119 |
-
|
|
|
|
|
|
|
| 120 |
st.plotly_chart(fig)
|
| 121 |
|
| 122 |
-
def
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
if value <= half_who_limit:
|
| 126 |
-
# Green -> Yellow gradient
|
| 127 |
-
return f"rgba({int(255 * value / half_who_limit)}, 255, 0, 1)" # Gradient from green to yellow
|
| 128 |
-
elif value <= who_limit:
|
| 129 |
-
# Yellow -> Red gradient
|
| 130 |
-
excess_value = value - half_who_limit
|
| 131 |
-
return f"rgba(255, {int(255 - (255 * excess_value / half_who_limit))}, 0, 1)" # Gradient from yellow to red
|
| 132 |
-
else:
|
| 133 |
-
# Beyond the WHO limit, fully red
|
| 134 |
-
return "rgba(255, 0, 0, 1)" # Fully red
|
| 135 |
-
|
| 136 |
-
def display_predictions_gaugeplot(self, next_three_days, who_guidelines):
|
| 137 |
-
st.markdown("### Predictions for the Next 3 Days")
|
| 138 |
-
# Convert date to datetime and calculate future dates
|
| 139 |
-
tomorrow, day_after_tomorrow, two_days_after_tomorrow = (
|
| 140 |
-
self.get_next_three_days_dates()
|
| 141 |
-
)
|
| 142 |
-
|
| 143 |
-
# Update the dataframe with actual dates in datetime format
|
| 144 |
-
next_three_days["Date"] = [
|
| 145 |
-
tomorrow,
|
| 146 |
-
day_after_tomorrow,
|
| 147 |
-
two_days_after_tomorrow,
|
| 148 |
-
]
|
| 149 |
-
for i in range(3):
|
| 150 |
-
formatted_date = next_three_days["Date"][i].strftime("%B %d, %Y")
|
| 151 |
-
|
| 152 |
-
st.markdown(f"#### Day {i+1}: {formatted_date}")
|
| 153 |
-
|
| 154 |
-
# use multiple columns for centering
|
| 155 |
-
_, col1, _, col2, _ = st.columns([0.2, 1, 0.2, 1, 0.2], gap="small")
|
| 156 |
|
| 157 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
with col1:
|
| 159 |
-
# Get color based on NO2 value
|
| 160 |
-
no2_value = next_three_days["NO2 (µg/m³)"][i]
|
| 161 |
-
no2_color = self.get_color(
|
| 162 |
-
no2_value, who_guidelines["WHO Guideline"][0]
|
| 163 |
-
)
|
| 164 |
-
fig_no2 = go.Figure(
|
| 165 |
-
go.Indicator(
|
| 166 |
-
mode="gauge+number",
|
| 167 |
-
value=next_three_days["NO2 (µg/m³)"][i],
|
| 168 |
-
title={"text": "NO2 (µg/m³)"},
|
| 169 |
-
gauge={
|
| 170 |
-
"axis": {
|
| 171 |
-
"range": [
|
| 172 |
-
0,
|
| 173 |
-
2 * who_guidelines["WHO Guideline"][0],
|
| 174 |
-
]
|
| 175 |
-
},
|
| 176 |
-
"bar": {"color": no2_color},
|
| 177 |
-
},
|
| 178 |
-
domain={"x": [0, 1], "y": [0, 1]}, # Controls size
|
| 179 |
-
)
|
| 180 |
-
)
|
| 181 |
-
fig_no2.update_layout(
|
| 182 |
-
height=250, width=250, margin=dict(t=0, b=0, l=0, r=0)
|
| 183 |
-
)
|
| 184 |
st.plotly_chart(fig_no2)
|
| 185 |
-
|
| 186 |
-
# O3 Gauge
|
| 187 |
with col2:
|
| 188 |
-
o3_value = next_three_days["O3 (µg/m³)"][i]
|
| 189 |
-
o3_color = self.get_color(o3_value, who_guidelines["WHO Guideline"][1])
|
| 190 |
-
fig_o3 = go.Figure(
|
| 191 |
-
go.Indicator(
|
| 192 |
-
mode="gauge+number",
|
| 193 |
-
value=next_three_days["O3 (µg/m³)"][i],
|
| 194 |
-
title={"text": "O3 (µg/m³)"},
|
| 195 |
-
gauge={
|
| 196 |
-
"axis": {
|
| 197 |
-
"range": [
|
| 198 |
-
0,
|
| 199 |
-
1.5 * who_guidelines["WHO Guideline"][1],
|
| 200 |
-
]
|
| 201 |
-
},
|
| 202 |
-
"bar": {"color": o3_color},
|
| 203 |
-
},
|
| 204 |
-
domain={"x": [0, 1], "y": [0, 1]}, # Controls size
|
| 205 |
-
)
|
| 206 |
-
)
|
| 207 |
-
fig_o3.update_layout(
|
| 208 |
-
height=250, width=250, margin=dict(t=0, b=0, l=0, r=0)
|
| 209 |
-
)
|
| 210 |
st.plotly_chart(fig_o3)
|
| 211 |
|
| 212 |
-
def view_option_selection(self) -> str:
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
return plot_type
|
| 216 |
-
|
| 217 |
-
def compare_to_who(self, today_data, no2_level, o3_level):
|
| 218 |
-
if today_data["NO2 (µg/m³)"] > no2_level:
|
| 219 |
-
st.sidebar.error("⚠️ NO2 levels are above WHO guidelines!")
|
| 220 |
-
else:
|
| 221 |
-
st.sidebar.success("✅ NO2 levels are within safe limits.")
|
| 222 |
-
|
| 223 |
-
if today_data["O3 (µg/m³)"] > o3_level:
|
| 224 |
-
st.sidebar.error("⚠️ O3 levels are above WHO guidelines!")
|
| 225 |
-
else:
|
| 226 |
-
st.sidebar.success("✅ O3 levels are within safe limits.")
|
| 227 |
-
|
| 228 |
-
def raise_awareness(self, today_data, who_guidelines):
|
| 229 |
-
st.markdown("### Air Quality Awareness")
|
| 230 |
-
|
| 231 |
-
# Load facts from the JSON file
|
| 232 |
-
with open(FACTS_PATH, "r") as f:
|
| 233 |
-
facts = json.load(f)["facts"]
|
| 234 |
|
| 235 |
-
|
| 236 |
-
|
| 237 |
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
respiratory problems, aggravate pre-existing conditions like asthma, and contribute to
|
| 244 |
-
cardiovascular diseases.
|
| 245 |
-
""")
|
| 246 |
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
High levels can cause chest pain, coughing, throat irritation, and airway inflammation.
|
| 251 |
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
|
| 256 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
|
| 258 |
-
#
|
| 259 |
st.markdown("### Did You Know?")
|
| 260 |
-
st.info(random_fact)
|
| 261 |
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
# Show real-time suggestions for high pollution days
|
| 265 |
st.markdown("### Health Recommendations Based on Current Levels")
|
| 266 |
-
if
|
| 267 |
-
|
| 268 |
-
or today_data["O3 (µg/m³)"] > who_guidelines["WHO Guideline"][1]
|
| 269 |
-
):
|
| 270 |
-
st.error(
|
| 271 |
-
"🚨 High pollution levels today. Avoid outdoor activities if possible, especially for vulnerable groups."
|
| 272 |
-
)
|
| 273 |
else:
|
| 274 |
-
st.success(
|
| 275 |
-
"✅ Air quality is within safe limits today. Enjoy your outdoor activities!"
|
| 276 |
-
)
|
| 277 |
-
|
| 278 |
-
self.add_spaces(num_lines=3)
|
| 279 |
|
| 280 |
-
def
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
st.markdown(
|
| 284 |
-
"[WHO Air Quality Guidelines](https://www.who.int/news-room/fact-sheets/detail/ambient-(outdoor)-air-quality-and-health)"
|
| 285 |
-
)
|
| 286 |
-
st.markdown(
|
| 287 |
-
"[Air Pollution Facts](https://www.un.org/sustainabledevelopment/air-pollution/)"
|
| 288 |
-
)
|
| 289 |
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
# Access the quiz questions
|
| 294 |
-
questions = quiz_data["quiz"]
|
| 295 |
-
random_question = questions[question_nr]
|
| 296 |
|
| 297 |
-
|
|
|
|
|
|
|
| 298 |
st.markdown("### Quick Quiz: How Much Do You Know About Air Pollution?")
|
| 299 |
with st.form(key="quiz_form"):
|
| 300 |
-
|
| 301 |
-
st.write(random_question["question"])
|
| 302 |
-
options = random_question["options"]
|
| 303 |
answer = st.radio("Choose an option:", options)
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
if submitted:
|
| 307 |
-
if answer == random_question["answer"]:
|
| 308 |
-
st.success("Correct!")
|
| 309 |
-
else:
|
| 310 |
-
st.error(
|
| 311 |
-
"Incorrect. The correct answer is: " + random_question["answer"]
|
| 312 |
-
)
|
| 313 |
-
|
| 314 |
-
def raise_awareness_and_quiz(self, today_data, who_guidelines, question_nr=0):
|
| 315 |
-
# Create two columns: main column for awareness and right column for the quiz
|
| 316 |
-
col_main, col_right = st.columns(
|
| 317 |
-
[0.7, 0.3], gap="large"
|
| 318 |
-
) # 70% for awareness, 30% for quiz
|
| 319 |
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
# Right column: Quiz
|
| 325 |
-
with col_right:
|
| 326 |
-
self.quiz(question_nr)
|
| 327 |
-
|
| 328 |
-
def add_spaces(self, num_lines=1):
|
| 329 |
-
"""Add vertical space between sections by adding empty lines.
|
| 330 |
|
| 331 |
Args:
|
| 332 |
-
|
| 333 |
"""
|
| 334 |
-
|
| 335 |
-
|
|
|
|
|
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
import pandas as pd
|
|
|
|
| 3 |
import plotly.graph_objects as go
|
| 4 |
+
import datetime
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
|
| 7 |
class UserView:
|
| 8 |
+
"""
|
| 9 |
+
A class to handle all user interface elements.
|
| 10 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
+
def show_current_data(self, merged_data_df: pd.DataFrame) -> None:
|
| 13 |
+
"""
|
| 14 |
+
Show the current pollutant concentrations along with WHO guidelines.
|
| 15 |
|
| 16 |
+
Args:
|
| 17 |
+
merged_data_df (pd.DataFrame): A pandas DataFrame containing the current pollutant concentrations and WHO guidelines.
|
| 18 |
+
"""
|
| 19 |
+
st.sidebar.markdown(
|
| 20 |
+
f"Today's Date: **{datetime.date.today().strftime('%B %d, %Y')}**"
|
| 21 |
+
)
|
| 22 |
st.sidebar.markdown("### Current Pollutant Concentrations and WHO Guidelines")
|
| 23 |
st.sidebar.dataframe(merged_data_df, hide_index=True)
|
| 24 |
|
| 25 |
+
def success(self, message: str) -> None:
|
| 26 |
"""
|
| 27 |
+
Show a success message.
|
| 28 |
|
| 29 |
+
Args:
|
| 30 |
+
message (str): The message to be displayed.
|
| 31 |
"""
|
| 32 |
+
st.success(message)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
+
def error(self, message: str) -> None:
|
| 35 |
+
"""
|
| 36 |
+
Show an error message.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
+
Args:
|
| 39 |
+
message (str): The message to be displayed.
|
| 40 |
+
"""
|
| 41 |
+
st.error(message)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
+
def display_predictions_lineplot(self, fig: go.Figure) -> None:
|
| 44 |
+
"""
|
| 45 |
+
Show a line plot of the predictions.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
|
| 47 |
+
Args:
|
| 48 |
+
fig (go.Figure): The plotly figure to be displayed.
|
| 49 |
+
"""
|
| 50 |
st.plotly_chart(fig)
|
| 51 |
|
| 52 |
+
def display_predictions_gaugeplot(self, gauge_plots: list) -> None:
|
| 53 |
+
"""
|
| 54 |
+
Show a gauge plot of the predictions.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
|
| 56 |
+
Args:
|
| 57 |
+
gauge_plots (list): A list of tuples containing the day, formatted date, and two plotly figures (for NO2 and O3).
|
| 58 |
+
"""
|
| 59 |
+
for day, formatted_date, fig_no2, fig_o3 in gauge_plots:
|
| 60 |
+
st.markdown(f"#### Day {day}: {formatted_date}")
|
| 61 |
+
col1, col2 = st.columns([1, 1])
|
| 62 |
with col1:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
st.plotly_chart(fig_no2)
|
|
|
|
|
|
|
| 64 |
with col2:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
st.plotly_chart(fig_o3)
|
| 66 |
|
| 67 |
+
def view_option_selection(self, plot_type: list) -> str:
|
| 68 |
+
"""
|
| 69 |
+
Ask the user to select a plot type.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
|
| 71 |
+
Args:
|
| 72 |
+
plot_type (list): A list of strings containing the options to be displayed.
|
| 73 |
|
| 74 |
+
Returns:
|
| 75 |
+
str: The selected option.
|
| 76 |
+
"""
|
| 77 |
+
st.markdown("### Visualizing Air Quality Predictions")
|
| 78 |
+
return st.selectbox("", plot_type)
|
|
|
|
|
|
|
|
|
|
| 79 |
|
| 80 |
+
def compare_to_who(self, warnings: list) -> None:
|
| 81 |
+
"""
|
| 82 |
+
Compare the current pollutant concentrations with WHO guidelines and display the results.
|
|
|
|
| 83 |
|
| 84 |
+
Args:
|
| 85 |
+
warnings (list): A list of tuples containing the pollutant, message, and level (error or success) of the warning.
|
| 86 |
+
"""
|
| 87 |
+
for pollutant, message, level in warnings:
|
| 88 |
+
if level == "error":
|
| 89 |
+
st.sidebar.error(message)
|
| 90 |
+
elif level == "success":
|
| 91 |
+
st.sidebar.success(message)
|
| 92 |
+
|
| 93 |
+
def raise_awareness(
|
| 94 |
+
self, random_fact: str, awareness_expanders: list, health_message: dict
|
| 95 |
+
) -> None:
|
| 96 |
+
"""
|
| 97 |
+
Raise awareness about air quality issues and provide health recommendations.
|
| 98 |
|
| 99 |
+
Args:
|
| 100 |
+
random_fact (str): A random fact about air quality.
|
| 101 |
+
awareness_expanders (list): A list of tuples containing the title and content of the expanders.
|
| 102 |
+
health_message (dict): A dictionary containing the health recommendation message and type (error or success).
|
| 103 |
+
"""
|
| 104 |
+
st.markdown("### Air Quality Awareness")
|
| 105 |
+
# Awareness sections
|
| 106 |
+
for expander_title, expander_content in awareness_expanders:
|
| 107 |
+
with st.expander(expander_title):
|
| 108 |
+
st.write(expander_content)
|
| 109 |
|
| 110 |
+
# Fact section
|
| 111 |
st.markdown("### Did You Know?")
|
| 112 |
+
st.info(random_fact)
|
| 113 |
|
| 114 |
+
# Health recommendation section
|
|
|
|
|
|
|
| 115 |
st.markdown("### Health Recommendations Based on Current Levels")
|
| 116 |
+
if health_message["type"] == "error":
|
| 117 |
+
st.error(health_message["message"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
else:
|
| 119 |
+
st.success(health_message["message"])
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
|
| 121 |
+
def quiz(self, question: str, options: list) -> tuple:
|
| 122 |
+
"""
|
| 123 |
+
Ask a quiz question and return the answer and whether the answer was correct.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
|
| 125 |
+
Args:
|
| 126 |
+
question (str): The question to be asked.
|
| 127 |
+
options (list): A list of strings containing the options.
|
|
|
|
|
|
|
|
|
|
| 128 |
|
| 129 |
+
Returns:
|
| 130 |
+
tuple: A tuple containing the answer and a boolean indicating whether the answer was correct.
|
| 131 |
+
"""
|
| 132 |
st.markdown("### Quick Quiz: How Much Do You Know About Air Pollution?")
|
| 133 |
with st.form(key="quiz_form"):
|
| 134 |
+
st.write(question)
|
|
|
|
|
|
|
| 135 |
answer = st.radio("Choose an option:", options)
|
| 136 |
+
submit_button = st.form_submit_button("Submit Answer")
|
| 137 |
+
return submit_button, answer
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
|
| 139 |
+
def print_sources(self, sources: list) -> None:
|
| 140 |
+
"""
|
| 141 |
+
Print the sources used in the application.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
|
| 143 |
Args:
|
| 144 |
+
sources (list): A list of tuples containing the source text and URL.
|
| 145 |
"""
|
| 146 |
+
st.markdown("### Learn More")
|
| 147 |
+
for source_text, source_url in sources:
|
| 148 |
+
st.markdown(f"[{source_text}]({source_url})")
|