Spaces:
Sleeping
Sleeping
atodorov284 commited on
Commit ·
b003484
1
Parent(s): 15d578c
encapsulate controller
Browse files
streamlit_src/controllers/user_controller.py
CHANGED
|
@@ -20,50 +20,50 @@ class UserController:
|
|
| 20 |
"""
|
| 21 |
Initializes the UserController class.
|
| 22 |
"""
|
| 23 |
-
self.
|
| 24 |
-
self.
|
| 25 |
-
self.
|
| 26 |
-
self.
|
| 27 |
|
| 28 |
-
self.
|
| 29 |
"Pollutant": ["NO2 (µg/m³)", "O3 (µg/m³)"],
|
| 30 |
-
"WHO Guideline": [self.
|
| 31 |
}
|
| 32 |
|
| 33 |
-
# Ensure session state for
|
| 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.
|
| 41 |
os.path.dirname(os.path.dirname(__file__)), "json_interactions/"
|
| 42 |
)
|
| 43 |
-
self.
|
| 44 |
-
self.
|
| 45 |
-
self.
|
| 46 |
|
| 47 |
def show_dashboard(self) -> None:
|
| 48 |
"""
|
| 49 |
Shows the main page of the user interface.
|
| 50 |
"""
|
| 51 |
-
self.
|
| 52 |
|
| 53 |
-
self.
|
| 54 |
|
| 55 |
# Plot selection and rendering
|
| 56 |
-
plot_type = self.
|
| 57 |
if plot_type == "Line Plot":
|
| 58 |
-
line_fig = self.
|
| 59 |
-
self.
|
| 60 |
elif plot_type == "Gauge Plot":
|
| 61 |
-
gauge_plots = self.
|
| 62 |
-
self.
|
| 63 |
|
| 64 |
# WHO comparison
|
| 65 |
-
who_comparisons = self.
|
| 66 |
-
self.
|
| 67 |
|
| 68 |
# Sources
|
| 69 |
sources = [
|
|
@@ -76,16 +76,16 @@ class UserController:
|
|
| 76 |
"https://www.un.org/sustainabledevelopment/air-pollution/",
|
| 77 |
),
|
| 78 |
]
|
| 79 |
-
self.
|
| 80 |
|
| 81 |
-
def
|
| 82 |
"""
|
| 83 |
Shows the current data on the main page of the user interface.
|
| 84 |
"""
|
| 85 |
-
merged_data_df = self.
|
| 86 |
-
self.
|
| 87 |
|
| 88 |
-
def
|
| 89 |
"""
|
| 90 |
Prepares the current data for the view.
|
| 91 |
|
|
@@ -97,23 +97,23 @@ class UserController:
|
|
| 97 |
merged_data = {
|
| 98 |
"Pollutant": ["NO2 (µg/m³)", "O3 (µg/m³)"],
|
| 99 |
"Current Concentration": [
|
| 100 |
-
self.
|
| 101 |
-
self.
|
| 102 |
],
|
| 103 |
-
"WHO Guideline": self.
|
| 104 |
}
|
| 105 |
return pd.DataFrame(merged_data)
|
| 106 |
|
| 107 |
-
def
|
| 108 |
"""
|
| 109 |
Shows the awareness content on the main page of the user interface.
|
| 110 |
"""
|
| 111 |
random_fact, awareness_expanders, health_message = (
|
| 112 |
-
self.
|
| 113 |
)
|
| 114 |
-
self.
|
| 115 |
|
| 116 |
-
def
|
| 117 |
self,
|
| 118 |
) -> Tuple[str, List[Tuple[str, str]], Dict[str, str]]:
|
| 119 |
"""
|
|
@@ -124,11 +124,11 @@ class UserController:
|
|
| 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.
|
| 128 |
facts = json.load(facts_file)
|
| 129 |
random_fact = random.choice(facts["facts"])
|
| 130 |
|
| 131 |
-
with open(self.
|
| 132 |
awareness = json.load(awareness_file)
|
| 133 |
awareness_expanders = [
|
| 134 |
(title, "\n".join(text)) for title, text in awareness.items()
|
|
@@ -136,8 +136,8 @@ class UserController:
|
|
| 136 |
|
| 137 |
health_message = {"message": "", "type": ""}
|
| 138 |
if (
|
| 139 |
-
self.
|
| 140 |
-
or self.
|
| 141 |
):
|
| 142 |
health_message["message"] = (
|
| 143 |
"🚨 High pollution levels today. Avoid outdoor activities if possible, especially for vulnerable groups."
|
|
@@ -151,9 +151,9 @@ class UserController:
|
|
| 151 |
|
| 152 |
return random_fact, awareness_expanders, health_message
|
| 153 |
|
| 154 |
-
def
|
| 155 |
"""
|
| 156 |
-
Show a
|
| 157 |
|
| 158 |
Returns
|
| 159 |
-------
|
|
@@ -161,22 +161,22 @@ class UserController:
|
|
| 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.
|
| 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.
|
| 169 |
|
| 170 |
if submitted:
|
| 171 |
correct_answer = quiz_data["quiz"][question_number]["answer"]
|
| 172 |
if answer == correct_answer:
|
| 173 |
-
self.
|
| 174 |
else:
|
| 175 |
-
self.
|
| 176 |
f"Wrong answer! The correct answer was {correct_answer[0].lower() + correct_answer[1:]}."
|
| 177 |
)
|
| 178 |
|
| 179 |
-
def
|
| 180 |
self, ratio: float, left_function: Callable, right_function: Callable
|
| 181 |
) -> None:
|
| 182 |
"""
|
|
@@ -199,7 +199,7 @@ class UserController:
|
|
| 199 |
with right:
|
| 200 |
right_function()
|
| 201 |
|
| 202 |
-
def
|
| 203 |
"""
|
| 204 |
Prepare a line plot for the next three days' NO2 and O3 levels.
|
| 205 |
|
|
@@ -207,9 +207,9 @@ class UserController:
|
|
| 207 |
go.Figure: A plotly figure object.
|
| 208 |
"""
|
| 209 |
tomorrow, day_after_tomorrow, two_days_after_tomorrow = (
|
| 210 |
-
self.
|
| 211 |
)
|
| 212 |
-
self.
|
| 213 |
tomorrow,
|
| 214 |
day_after_tomorrow,
|
| 215 |
two_days_after_tomorrow,
|
|
@@ -217,8 +217,8 @@ class UserController:
|
|
| 217 |
fig = go.Figure()
|
| 218 |
fig.add_trace(
|
| 219 |
go.Scatter(
|
| 220 |
-
x=self.
|
| 221 |
-
y=self.
|
| 222 |
mode="lines+markers+text",
|
| 223 |
name="NO2",
|
| 224 |
line=dict(color="blue"),
|
|
@@ -226,8 +226,8 @@ class UserController:
|
|
| 226 |
)
|
| 227 |
fig.add_trace(
|
| 228 |
go.Scatter(
|
| 229 |
-
x=self.
|
| 230 |
-
y=self.
|
| 231 |
mode="lines+markers+text",
|
| 232 |
name="O3",
|
| 233 |
line=dict(color="lightblue"),
|
|
@@ -236,13 +236,13 @@ class UserController:
|
|
| 236 |
|
| 237 |
# WHO guideline as horizontal dotted lines
|
| 238 |
fig.add_hline(
|
| 239 |
-
y=self.
|
| 240 |
line_dash="dot",
|
| 241 |
line_color="blue",
|
| 242 |
annotation_text="WHO NO2 Guideline",
|
| 243 |
)
|
| 244 |
fig.add_hline(
|
| 245 |
-
y=self.
|
| 246 |
line_dash="dot",
|
| 247 |
line_color="lightblue",
|
| 248 |
annotation_text="WHO O3 Guideline",
|
|
@@ -256,7 +256,7 @@ class UserController:
|
|
| 256 |
)
|
| 257 |
return fig
|
| 258 |
|
| 259 |
-
def
|
| 260 |
"""
|
| 261 |
Get the next three days' dates.
|
| 262 |
|
|
@@ -268,7 +268,7 @@ class UserController:
|
|
| 268 |
two_days_after_tomorrow = date.today() + timedelta(days=3)
|
| 269 |
return tomorrow, day_after_tomorrow, two_days_after_tomorrow
|
| 270 |
|
| 271 |
-
def
|
| 272 |
"""
|
| 273 |
Compare the current pollutant levels to WHO guidelines.
|
| 274 |
|
|
@@ -277,7 +277,7 @@ class UserController:
|
|
| 277 |
"""
|
| 278 |
comparisons = []
|
| 279 |
for i, pollutant in enumerate(["NO2 (µg/m³)", "O3 (µg/m³)"]):
|
| 280 |
-
if self.
|
| 281 |
comparisons.append(
|
| 282 |
(
|
| 283 |
pollutant,
|
|
@@ -295,7 +295,7 @@ class UserController:
|
|
| 295 |
)
|
| 296 |
return comparisons
|
| 297 |
|
| 298 |
-
def
|
| 299 |
"""
|
| 300 |
Prepare gauge plots for the next three days' NO2 and O3 levels.
|
| 301 |
|
|
@@ -303,9 +303,9 @@ class UserController:
|
|
| 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.
|
| 307 |
)
|
| 308 |
-
self.
|
| 309 |
tomorrow,
|
| 310 |
day_after_tomorrow,
|
| 311 |
two_days_after_tomorrow,
|
|
@@ -315,18 +315,18 @@ class UserController:
|
|
| 315 |
for i, day in enumerate(
|
| 316 |
[tomorrow, day_after_tomorrow, two_days_after_tomorrow]
|
| 317 |
):
|
| 318 |
-
no2_value = self.
|
| 319 |
-
o3_value = self.
|
| 320 |
-
fig_no2 = self.
|
| 321 |
-
no2_value, self.
|
| 322 |
)
|
| 323 |
-
fig_o3 = self.
|
| 324 |
-
o3_value, self.
|
| 325 |
)
|
| 326 |
gauge_plots.append((i + 1, day.strftime("%B %d, %Y"), fig_no2, fig_o3))
|
| 327 |
return gauge_plots
|
| 328 |
|
| 329 |
-
def
|
| 330 |
self, value: float, guideline: float, title: str
|
| 331 |
) -> go.Figure:
|
| 332 |
"""
|
|
|
|
| 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 = [
|
|
|
|
| 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 |
|
|
|
|
| 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 |
"""
|
|
|
|
| 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()
|
|
|
|
| 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."
|
|
|
|
| 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 |
-------
|
|
|
|
| 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 |
"""
|
|
|
|
| 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 |
|
|
|
|
| 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,
|
|
|
|
| 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"),
|
|
|
|
| 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"),
|
|
|
|
| 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",
|
|
|
|
| 256 |
)
|
| 257 |
return fig
|
| 258 |
|
| 259 |
+
def _get_next_three_days_dates(self) -> tuple:
|
| 260 |
"""
|
| 261 |
Get the next three days' dates.
|
| 262 |
|
|
|
|
| 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 |
|
|
|
|
| 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,
|
|
|
|
| 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 |
|
|
|
|
| 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,
|
|
|
|
| 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 |
"""
|