diff --git a/.gitignore b/.gitignore index 8f91b31..ee730d8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,7 @@ +.DS_Store +Weatherman.pdf +rough/ +weatherfiles/ weatherfiles.zip -Weatherman.pdf \ No newline at end of file +FileTree.ini +.vscode/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d717ebf --- /dev/null +++ b/README.md @@ -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 diff --git a/code_files/constants.py b/code_files/constants.py new file mode 100644 index 0000000..8d12943 --- /dev/null +++ b/code_files/constants.py @@ -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", + } diff --git a/code_files/report_calculator.py b/code_files/report_calculator.py new file mode 100644 index 0000000..7405448 --- /dev/null +++ b/code_files/report_calculator.py @@ -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 diff --git a/code_files/weather_app.py b/code_files/weather_app.py new file mode 100644 index 0000000..3973b04 --- /dev/null +++ b/code_files/weather_app.py @@ -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'] \ No newline at end of file diff --git a/code_files/weather_parser.py b/code_files/weather_parser.py new file mode 100644 index 0000000..8466315 --- /dev/null +++ b/code_files/weather_parser.py @@ -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) diff --git a/code_files/weather_readings.py b/code_files/weather_readings.py new file mode 100644 index 0000000..f4f59d0 --- /dev/null +++ b/code_files/weather_readings.py @@ -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 diff --git a/code_files/weather_reporter.py b/code_files/weather_reporter.py new file mode 100644 index 0000000..8ef9060 --- /dev/null +++ b/code_files/weather_reporter.py @@ -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}") diff --git a/weatherman.py b/weatherman.py new file mode 100644 index 0000000..5307f9a --- /dev/null +++ b/weatherman.py @@ -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() \ No newline at end of file