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.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,16 +76,16 @@ class UserController:
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,23 +97,23 @@ class UserController:
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,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.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,8 +136,8 @@ class UserController:
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,9 +151,9 @@ class UserController:
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,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.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,7 +199,7 @@ class UserController:
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,9 +207,9 @@ class UserController:
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,8 +217,8 @@ class UserController:
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,8 +226,8 @@ class UserController:
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,13 +236,13 @@ class UserController:
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,7 +256,7 @@ class UserController:
256
  )
257
  return fig
258
 
259
- def get_next_three_days_dates(self) -> tuple:
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 compare_to_who(self) -> list:
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.today_data[pollutant] > self.who_guidelines["WHO Guideline"][i]:
281
  comparisons.append(
282
  (
283
  pollutant,
@@ -295,7 +295,7 @@ class UserController:
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,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.get_next_three_days_dates()
307
  )
308
- self.next_three_days["Date"] = [
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.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
  """
 
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
  """