Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
1204b86
Implemented parser to parse files and save weather readings
ttqureshi Jun 11, 2024
e8c4281
UPDATE
ttqureshi Jun 11, 2024
e0bd13b
weather report for a particular year
ttqureshi Jun 11, 2024
30130f1
weather report generation, given a month
ttqureshi Jun 12, 2024
e0b5443
Bar charts for daily temperatures of a month
ttqureshi Jun 12, 2024
eb2e336
One line bar chart
ttqureshi Jun 12, 2024
28434c5
Add README
ttqureshi Jun 12, 2024
b2c6ec3
Update README
ttqureshi Jun 12, 2024
8df84b2
Update README.md
ttqureshi Jun 12, 2024
efbce2b
Update .gitignore
ttqureshi Jun 12, 2024
55c418a
Refactoring: Parser class implemented
ttqureshi Jun 13, 2024
e93578b
Refactoring: Parser class implemented
ttqureshi Jun 13, 2024
5b854ba
Refactoring completed: Made use of classes for calculations and repor…
ttqureshi Jun 13, 2024
a9bfba7
Making suggested changes
ttqureshi Jun 20, 2024
599a07a
NoneType exception handled
ttqureshi Jun 24, 2024
b1f61ff
Using datetime instead of int
ttqureshi Jun 24, 2024
90150ed
Help messages
ttqureshi Jun 24, 2024
b6d9353
Meaningful argument names
ttqureshi Jun 24, 2024
6f2c4b5
Allowing multiple dates to be passed
ttqureshi Jun 24, 2024
4b32012
Refactor to use OOP for ReportCalculator and WeatherReporter classes
ttqureshi Jun 24, 2024
29f7b39
Formatting
ttqureshi Jun 24, 2024
f57eab6
complying with pep8 import order
ttqureshi Jun 24, 2024
bf1e860
fix argparse error
ttqureshi Jun 24, 2024
f6bfc70
Refactored to reduce redundancy by instantiating ReportCalculator and…
ttqureshi Jun 24, 2024
a0136e7
perf: Add __eq__() method
ttqureshi Jun 25, 2024
a335bc9
fix: validate_reading(value) in WeatherReading
ttqureshi Jun 25, 2024
d424b15
Update .gitignore
ttqureshi Jun 25, 2024
06bb420
minor change: Set the default value of extract_to to None
ttqureshi Jun 25, 2024
e643841
Organizing imports
ttqureshi Jun 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
.DS_Store
Weatherman.pdf
rough/
weatherfiles/
weatherfiles.zip
Weatherman.pdf
FileTree.ini
.vscode/
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# WEATHER MAN


1. To get the highest temperature, lowest temperature and humidity for a given year, run:

```weatherman.py dir/path/for/files/extraction -e 2006```

2. To get the average highest temperature​, average lowest temperature​,​ average mean humidity​ for a month, run:

```weatherman.py dir/path/for/files/extraction -a 2006/5```

3. To draw horizontal bar charts on the console for highest and lowest temperature on each day, run:

```weatherman.py dir/path/for/files/extraction -c 2006/5```

4. Multiple reports can be generated by passing multiple arguments, like:

```weatherman.py dir/path/for/files/extraction -c 2011/03 -a 2011/3 -e 2011```

5. For a given month, to get one horizontal bar chart per day, add --inline flag to the command:

```weatherman.py dir/path/for/files/extraction -c 2011/3 --inline```

## Note:
Enter `./` in place of `dir/path/for/files/extraction` for current working directory
21 changes: 21 additions & 0 deletions code_files/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
class Colors:
RED = "\033[91m"
BLUE = "\033[94m"
RESET = "\033[0m"


class MonthsMapping:
MONTHS = {
1: "January",
2: "February",
3: "March",
4: "April",
5: "May",
6: "June",
7: "July",
8: "August",
9: "September",
10: "October",
11: "November",
12: "December",
}
49 changes: 49 additions & 0 deletions code_files/report_calculator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from code_files.weather_readings import WeatherExtremes


class ReportCalculator:
def __init__(self, readings=None):
self.readings = readings

def compute_extreme_stats(self):
"""Computes the extreme weather statistics for a particular year"""

highest_temp_reading = max(
self.readings,
key=lambda r: r.max_temp if r.max_temp is not None else float("-inf"),
)
lowest_temp_reading = min(
self.readings,
key=lambda r: r.min_temp if r.min_temp is not None else float("inf"),
)
highest_humidity_reading = max(
self.readings,
key=lambda r: (
r.max_humidity if r.max_humidity is not None else float("-inf")
),
)

weather_extremes = WeatherExtremes(
highest_temp=highest_temp_reading.max_temp,
lowest_temp=lowest_temp_reading.min_temp,
highest_humidity=highest_humidity_reading.max_humidity,
date_htemp=highest_temp_reading.date.split("-"),
date_ltemp=lowest_temp_reading.date.split("-"),
date_hhumid=highest_humidity_reading.date.split("-"),
)

return weather_extremes

def compute_average_stats(self):
"""Computes the average weather statistics for a particular month of the year"""

total_max_temp = sum(r.max_temp for r in self.readings if r.max_temp)
total_min_temp = sum(r.min_temp for r in self.readings if r.min_temp)
total_mean_humidity = sum(r.mean_humidity for r in self.readings if r.mean_humidity)

count = len(self.readings)
avg_max_temp = total_max_temp / count
avg_min_temp = total_min_temp / count
avg_mean_humidity = total_mean_humidity / count

return avg_max_temp, avg_min_temp, avg_mean_humidity
5 changes: 5 additions & 0 deletions code_files/weather_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from code_files.weather_parser import WeatherParser
from code_files.weather_reporter import WeatherReporter
from code_files.report_calculator import ReportCalculator

__all__ = ['WeatherParser', 'WeatherReporter', 'ReportCalculator']
49 changes: 49 additions & 0 deletions code_files/weather_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import csv
import os
import zipfile

from code_files.weather_readings import WeatherReading


class WeatherParser:
def __init__(self, extract_to=None):
self.extract_to = extract_to
self.weather_readings = []

def extract_zip(self):
"""Extracts files from a weatherfiles archive.

Args:
extract_to (str): The path to the directory where the extracted files will be placed.
"""
with zipfile.ZipFile("weatherfiles.zip", "r") as zip_ref:
zip_ref.extractall(self.extract_to)

def parse_weather_file(self, path):
"""Parses a TXT file containing weather data.

This method reads a TXT file at the specified path and populates the
weather_readings list of the class with WeatherReading objects. Each
object represents a day's weather data.
"""
with open(path, "r") as file:
reader = csv.DictReader(file)
for row in reader:
date = row[list(row.keys())[0]]
max_temp = row["Max TemperatureC"]
min_temp = row["Min TemperatureC"]
max_humidity = row["Max Humidity"]
mean_humidity = row[" Mean Humidity"]

reading = WeatherReading(
date, max_temp, min_temp, max_humidity, mean_humidity
)
self.weather_readings.append(reading)

def parse_all_files(self):
"""Parses all weather data files in the 'weatherfiles' directory."""

dir_path = os.path.join(self.extract_to, "weatherfiles")
for filename in os.listdir(dir_path):
file_path = os.path.join(dir_path, filename)
self.parse_weather_file(file_path)
34 changes: 34 additions & 0 deletions code_files/weather_readings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from dataclasses import dataclass


class WeatherReading:
def __init__(self, date, max_temp, min_temp, max_humidity, mean_humidity):
self.date = date
self.max_temp = self.validate_reading(max_temp)
self.min_temp = self.validate_reading(min_temp)
self.max_humidity = self.validate_reading(max_humidity)
self.mean_humidity = self.validate_reading(mean_humidity)

def __eq__(self, other):
return self.date == other.date and \
self.max_temp == other.max_temp and \
self.min_temp == other.min_temp and \
self.max_humidity == other.max_humidity and \
self.mean_humidity == other.mean_humidity

@staticmethod
def validate_reading(value):
if value: # if value is empty ''
return int(value)
elif value == 0: # if value is 0
return value


@dataclass
class WeatherExtremes:
highest_temp: int
lowest_temp: int
highest_humidity: int
date_htemp: list
date_ltemp: list
date_hhumid: list
55 changes: 55 additions & 0 deletions code_files/weather_reporter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from datetime import datetime
from code_files.constants import Colors, MonthsMapping


class WeatherReporter:
def __init__(self, weather_extremes=None, date=None, avg_max_temp=None, avg_min_temp=None, avg_mean_humidity=None, high_bar=None, low_bar=None, isInline=None):
self.weather_extremes = weather_extremes
self.date = date
self.avg_max_temp = avg_max_temp
self.avg_min_temp = avg_min_temp
self.avg_mean_humidity = avg_mean_humidity
self.high_bar = high_bar
self.low_bar = low_bar
self.isInline = isInline

def generate_report_extremes(self):
"""Generates a report summarizing weather extremes for a year."""

print(f"\n<==== Report for the year {self.weather_extremes.date_htemp[0]}: ====>")
print(
f"Highest: {self.weather_extremes.highest_temp}C on "
f"{MonthsMapping.MONTHS[int(self.weather_extremes.date_htemp[1])]} "
f"{self.weather_extremes.date_htemp[2]}"
)
print(
f"Lowest: {self.weather_extremes.lowest_temp}C on "
f"{MonthsMapping.MONTHS[int(self.weather_extremes.date_ltemp[1])]} "
f"{self.weather_extremes.date_ltemp[2]}"
)
print(
f"Humidity: {self.weather_extremes.highest_humidity}% on "
f"{MonthsMapping.MONTHS[int(self.weather_extremes.date_hhumid[1])]} "
f"{self.weather_extremes.date_hhumid[2]}"
)
print("-------------------------------------")

def generate_report_averages(self):
"""Generates a report summarizing average weather statistics for a given year and month."""

print(f"\n<==== Report for the month: {MonthsMapping.MONTHS[int(self.date.strftime('%m'))]} {self.date.strftime('%Y')} ====>")
print(f"Highest Average: {self.avg_max_temp:.1f}C")
print(f"Lowest Average: {self.avg_min_temp:.1f}C")
print(f"Average Mean Humidity: {self.avg_mean_humidity:.1f}%")
print("-------------------------------------")

def generate_report_barchart(self):
"""Generates bar chart of daily highest and lowest temperatures for a given month of the year"""

if self.isInline:
print(f"{self.date.strftime('%d')}{Colors.BLUE} {self.low_bar}{Colors.RED} {self.high_bar} "
f"{Colors.BLUE}{len(self.low_bar)}C{Colors.RESET} - "
f"{Colors.RED}{len(self.high_bar)}C{Colors.RESET}")
else:
print(f"{self.date.strftime('%d') + Colors.RED} {self.high_bar} {len(self.high_bar)}C{Colors.RESET}")
print(f"{self.date.strftime('%d') + Colors.BLUE} {self.low_bar} {len(self.low_bar)}C{Colors.RESET}")
78 changes: 78 additions & 0 deletions weatherman.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import argparse
from datetime import datetime

from code_files.weather_app import WeatherParser, WeatherReporter, ReportCalculator

def main():
parser = argparse.ArgumentParser()
parser.add_argument("extract_to", type=str, help='Directory to extract the results to.')
parser.add_argument("-e", "--yearly_report", type=lambda e: datetime.strptime(e, '%Y'), nargs='*', help='Extreme weather stats for a year (format: YYYY).')
parser.add_argument("-a", "--monthly_report", type=lambda a: datetime.strptime(a, '%Y/%m'), nargs='*', help='Year & Month for average weather stats (format: YYYY/MM)')
parser.add_argument("-c", "--temp_chart", type=lambda c: datetime.strptime(c, '%Y/%m'), nargs='*', help='Year & Month for daily temperature chart (format: YYYY/MM)')
parser.add_argument("--inline", action="store_true", help="One bar chart for highest and lowest temp on each day")

args = parser.parse_args()

parser = WeatherParser(args.extract_to)
parser.extract_zip()
parser.parse_all_files()

report_calculator = ReportCalculator()
weather_reporter = WeatherReporter()

if args.yearly_report:
for yearly_report in args.yearly_report:
yearly_readings = [r for r in parser.weather_readings if r.date.startswith(yearly_report.strftime('%Y'))]

if yearly_readings:
report_calculator.readings = yearly_readings
weather_extremes = report_calculator.compute_extreme_stats()

weather_reporter.weather_extremes = weather_extremes
weather_reporter.generate_report_extremes()
else:
print(f"No record found to show WEATHER EXTREMES agaisnt your input")

if args.monthly_report:
for date in args.monthly_report:
year = date.strftime('%Y')
month = int(date.strftime('%m'))
monthly_readings = [r for r in parser.weather_readings if r.date.startswith(f"{year}-{month}-")]

if monthly_readings:
report_calculator.readings = monthly_readings
avg_max_temp, avg_min_temp, avg_mean_humidity = report_calculator.compute_average_stats()

weather_reporter.date = date
weather_reporter.avg_max_temp = avg_max_temp
weather_reporter.avg_min_temp = avg_min_temp
weather_reporter.avg_mean_humidity = avg_mean_humidity
weather_reporter.generate_report_averages()
else:
print(f"No record found to show AVERAGE STATS against your input")

if args.temp_chart:
for temp_chart in args.temp_chart:
year = temp_chart.strftime('%Y')
month = int(temp_chart.strftime('%m'))
monthly_readings = [r for r in parser.weather_readings if r.date.startswith(f"{year}-{month}-")]

if monthly_readings:
for reading in monthly_readings:
if reading.max_temp and reading.min_temp:
high_bar = "+" * reading.max_temp
low_bar = "+" * reading.min_temp
date = datetime.strptime(reading.date, "%Y-%m-%d")

weather_reporter.date = date
weather_reporter.high_bar = high_bar
weather_reporter.low_bar = low_bar
weather_reporter.isInline = args.inline
weather_reporter.generate_report_barchart()
else:
print(f"No record found to plot CHARTS against your input")
print("-------------------------------------")


if __name__ == "__main__":
main()