-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.py
413 lines (328 loc) · 11.8 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
"""This program obtains a users name, and greets them.
It then provides a menu of options to choose from.
It fetches location, latitude, and longitude data based on zipcodes.
"""
import pgeocode
import math
import requests
import json
request_url = "https://archive-api.open-meteo.com/v1/archive"
def create_dataset():
"""Create a dataset of zip codes."""
zip_code = input("Please enter a zip code: ")
try:
dataset = HistoricalTemps(zip_code)
return dataset
except LookupError:
print("Error: The entered zip code is invalid. Please try again.")
return None
class HistoricalTemps:
"""
A class representing historical temperature data.
Attributes:
_zip_code (str): Zip code for which temperature data is fetched.
_start (str): Start date of range for temperature data.
_end (str): End date of range for historical temperature data.
_temp_list (list): List of tuples containing date and temp data.
"""
def __init__(self, zip_code, start="1950-08-13", end="2023-08-25"):
"""
Make all necessary attributes for the HistoricalTemps object.
Parameters:
zip_code (str): Zip code for which to fetch temp data.
start (str): Start date of the range.
end (str): End date of the range.
"""
self._zip_code = zip_code
self._start = start
self._end = end
lat, lon, loc_name = self.zip_to_loc_info(zip_code)
if math.isnan(lat) or math.isnan(lon):
raise LookupError("Invalid zip code: Location not found")
self._lat = lat
self._lon = lon
self._loc_name = loc_name
self._temp_list = None
self._load_temps()
def _load_temps(self):
"""
Load historical temperature data.
Currently, this method is hardcoded to provide sample data.
In the future, it will fetch real data from the internet.
This fake data is kept for now to ensure testing is smooth.
"""
params = {
"latitude": self._lat,
"longitude": self._lon,
"start_date": self._start,
"end_date": self._end,
"daily": "temperature_2m_max",
"timezone": "America/Los_Angeles"
}
response = requests.get(request_url, params=params)
json_data = response.text
self._temp_list = self._convert_json_to_list(json_data)
def average_temp(self):
"""
Calculate the average temperature from the loaded data.
Returns:
float: The average temperature.
"""
total_temp = sum(temp for date, temp in self._temp_list)
avg_temp = total_temp / len(self._temp_list)
return avg_temp
def is_data_loaded(self):
"""
Check if temperature data is loaded.
Returns:
bool: True if data is loaded, False otherwise.
"""
return self._temp_list is not None
@property
def zip_code(self):
"""
Gets the zip code.
Returns:
str: Zip code for which temperature data is fetched.
"""
return self._zip_code
@property
def start(self):
"""
Get the start value.
Returns:
The start value.
"""
return self._start
@start.setter
def start(self, value):
old_start = self._start
self._start = value
try:
self._load_temps()
except Exception as e:
self._start = old_start
raise LookupError(f"Can't load data for start date {value}: {e}")
@property
def end(self):
"""
Get the end value.
Returns:
The end value.
"""
return self._end
@end.setter
def end(self, value):
old_end = self._end
self._end = value
try:
self._load_temps()
except Exception as e:
self._end = old_end
raise LookupError(f"Can't load data for end date {value}: {e}")
@property
def loc_name(self):
"""
Get the location name.
Returns:
str: Name of the location based on zip code.
"""
return self._loc_name
@staticmethod
def zip_to_loc_info(zip_code):
"""
Return latitude, longitude, and location name for a zip code.
Parameters:
zip_code (str): Zip code to look up.
Returns:
tuple: (latitude, longitude, location name)
"""
nomi = pgeocode.Nominatim('us')
location = nomi.query_postal_code(zip_code)
if location.empty:
return None, None, None
lat = location.latitude
lon = location.longitude
loc_name = location.place_name
return lat, lon, loc_name
@staticmethod
def _convert_json_to_list(data):
"""
Convert JSON data to a list of tuples with dates and temps.
Parameters:
data (str): JSON string from open-meteo.com.
Returns:
list: List of tuples, each tuple has a date and a max temp.
"""
data_dict = json.loads(data)
dates = data_dict['daily']['time']
temps = data_dict['daily']['temperature_2m_max']
return list(zip(dates, temps))
def extreme_days(self, threshold: float):
"""
Find days when the temperature exceeds the given threshold.
Parameters:
threshold (float): The temp threshold to compare against.
Returns:
list: A list of tuples where the temp exceeds the threshold.
"""
return [(date, temp) for date, temp in self._temp_list if
temp > threshold]
def top_x_days(self, num_days=10):
"""
Return a list of tuples representing the days with the highest temps.
Parameters:
num_days (int): The number of days to return. Default is 10.
Returns:
list: A list of tuples (date, temp) with the highest temps.
"""
return sorted(self._temp_list, key=lambda x: x[1], reverse=True)[
:num_days]
def print_extreme_days(dataset: HistoricalTemps):
"""
Print days when the temp exceeds a threshold for a given dataset.
Parameters:
dataset (HistoricalTemps): The temperature dataset to analyze.
"""
if not dataset.is_data_loaded():
print("The dataset is not loaded.")
return
try:
threshold = float(input("Enter a threshold temperature: "))
except ValueError:
print("Please enter a valid number for the threshold temperature.")
return
extreme_days_list = dataset.extreme_days(threshold)
print(
f"There are {len(extreme_days_list)} days when"
f" the temperature exceeded {threshold:.1f} degrees.")
for date, temp in extreme_days_list:
print(f"Date: {date}, Temperature: {temp:.1f} degrees")
def print_top_five_days(dataset: HistoricalTemps):
"""
Print the top five days with the highest temps from the dataset.
Parameters:
dataset (HistoricalTemps): The dataset to analyze.
"""
if not dataset.is_data_loaded():
print("The temperature data is not loaded. Please check dataset.")
return
top_days = dataset.top_x_days(num_days=5)
print(f"Top 5 hottest days for {dataset.loc_name}:")
for date, temp in top_days:
print(f"Date: {date}, Temperature: {temp}°F")
def compare_average_temps(dataset_one: HistoricalTemps,
dataset_two: HistoricalTemps):
"""
Compare the average temperatures of two HistoricalTemps datasets.
Parameters:
dataset_one (HistoricalTemps): The first temperature dataset.
dataset_two (HistoricalTemps): The second temperature dataset.
"""
if not dataset_one.is_data_loaded() or not dataset_two.is_data_loaded():
print("One or both of the datasets are not loaded.")
return
avg_temp_one = dataset_one.average_temp()
avg_temp_two = dataset_two.average_temp()
print(f"The average temperature in {dataset_one.loc_name} "
f"is {avg_temp_one:.2f} degrees.")
print(f"The average temperature in {dataset_two.loc_name} "
f"is {avg_temp_two:.2f} degrees.")
def change_dates(dataset: HistoricalTemps):
"""
Change the start and end dates of the dataset.
Parameters:
dataset (HistoricalTemps): The dataset to modify.
"""
if not dataset.is_data_loaded():
print("The tempe data is not loaded. Please check dataset.")
return
try:
new_start = input("Enter the new start date (YYYY-MM-DD): ")
dataset.start = new_start
except LookupError as e:
print(f"Failed to change the start date: {e}")
return
try:
new_end = input("Enter the new end date (YYYY-MM-DD): ")
dataset.end = new_end
except LookupError as e:
print(f"Failed to change the end date: {e}")
return
print("Dates successfully changed.")
def main():
"""Ask for the user's name, and greet them.
Then run the function that asks them to choose an option.
"""
name = input("Please enter your name: ")
print(f"Hi {name}, let's explore some historical temperatures.")
print("")
menu()
def menu():
"""Accept a user's choice from a menu of options.
Run the function that displays a menu repeatedly until 9 is chosen.
"""
dataset_one = None
dataset_two = None
while True:
print_menu(dataset_one, dataset_two)
try:
option = int(input("What is your choice?"))
except ValueError:
print("Please enter a number only")
else:
match option:
case 1:
dataset_one = create_dataset()
case 2:
dataset_two = create_dataset()
case 3:
if dataset_one is not None and dataset_two is not None:
compare_average_temps(dataset_one, dataset_two)
else:
print("Both datasets must be loaded before comparing.")
case 4:
if dataset_one is not None:
print_extreme_days(dataset_one)
else:
print("Dataset one must be loaded first.")
case 5:
if dataset_one is not None:
print_top_five_days(dataset_one)
else:
print("Dataset one must be loaded first.")
case 6:
if dataset_one is not None:
change_dates(dataset_one)
else:
print("Dataset one must be loaded first.")
case 7:
if dataset_two is not None:
change_dates(dataset_two)
else:
print("Dataset two must be loaded first.")
case 9:
break
case _:
print("That wasn't a valid selection")
continue
print("Goodbye! Thank you for using the database")
def print_menu(dataset_one=None, dataset_two=None):
"""Display a menu of options based on loaded datasets."""
print("Main Menu")
if dataset_one is None:
print("1 - Load dataset one")
else:
print(f"1 - Replace {dataset_one.loc_name}")
if dataset_two is None:
print("2 - Load dataset two")
else:
print(f"2 - Replace {dataset_two.loc_name}")
print("3 - Compare average temperatures")
print("4 - Dates above threshold temperature")
print("5 - Highest historical dates")
print("6 - Change start and end dates for dataset one")
print("7 - Change start and end dates for dataset two")
print("9 - Quit")
if __name__ == "__main__":
main()