From e087d38dd702cc325957384e768c1187ebe33475 Mon Sep 17 00:00:00 2001 From: Alireza Aghamohammadi Date: Wed, 13 Dec 2023 16:57:24 +0330 Subject: [PATCH] Add ReportGenerator and Logging - Added support for terminal coverage reports in pytest configuration. - Included ReportGenerator class to create CSV reports for optimal solar panel orientation. - Integrated a logger decorator to add logging capability to classes. Adjustments in `setup.cfg` now include terminal coverage reports along with existing xml coverage report. Introduced `ReportGenerator` class to `src/pysolorie/__init__.py` for CSV report generation capability. A new `logger.py` with a decorator pattern has been added to enable logging throughout the PySolorie package. Developed test case to validate the `ReportGenerator`'s functionality in generating accurate CSV reports in `tests/test_pysolorie.py`. --- setup.cfg | 2 +- src/pysolorie/__init__.py | 2 ++ src/pysolorie/logger.py | 30 ++++++++++++++++++++ src/pysolorie/report.py | 58 +++++++++++++++++++++++++++++++++++++++ tests/test_pysolorie.py | 35 ++++++++++++++++++++++- 5 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 src/pysolorie/logger.py create mode 100644 src/pysolorie/report.py diff --git a/setup.cfg b/setup.cfg index b61eb89..223bfbe 100644 --- a/setup.cfg +++ b/setup.cfg @@ -45,7 +45,7 @@ max-line-length = 88 [tool:pytest] testpaths = tests -addopts = --cov --strict-markers --cov-report=xml +addopts = --cov --strict-markers --cov-report=term --cov-report=xml xfail_strict = True [coverage:run] diff --git a/src/pysolorie/__init__.py b/src/pysolorie/__init__.py index aad9c11..b62556d 100644 --- a/src/pysolorie/__init__.py +++ b/src/pysolorie/__init__.py @@ -17,6 +17,7 @@ from .model import HottelModel from .numerical_integration import IrradiationCalculator from .observer import Observer +from .report import ReportGenerator from .sun_position import SunPosition __all__ = [ @@ -26,4 +27,5 @@ "Observer", "AtmosphericTransmission", "IrradiationCalculator", + "ReportGenerator", ] diff --git a/src/pysolorie/logger.py b/src/pysolorie/logger.py new file mode 100644 index 0000000..ece1148 --- /dev/null +++ b/src/pysolorie/logger.py @@ -0,0 +1,30 @@ +# Copyright 2023 Alireza Aghamohammadi + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging + + +def logger_decorator(cls): + class WithLogging(cls): # type: ignore + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.logger = logging.getLogger(cls.__name__) + self.logger.setLevel(logging.INFO) + self.logger.propagate = False + logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + ) + + return WithLogging diff --git a/src/pysolorie/report.py b/src/pysolorie/report.py new file mode 100644 index 0000000..5a047a4 --- /dev/null +++ b/src/pysolorie/report.py @@ -0,0 +1,58 @@ +# Copyright 2023 Alireza Aghamohammadi + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import csv +from pathlib import Path + +from .logger import logger_decorator +from .numerical_integration import IrradiationCalculator + + +@logger_decorator +class ReportGenerator: + def generate_optimal_orientation_csv_report( + self, + path: Path, + irradiation_calculator: IrradiationCalculator, + from_day: int, + to_day: int, + ) -> None: + r""" + This method generates a report of + optimal solar panel orientation in CSV format. + + :param path: A Path object that points to the CSV file + where the report will be written. + :type path: Path + :param irradiation_calculator: An instance of the IrradiationCalculator class. + :type irradiation_calculator: pysolorie.IrradiationCalculator + :param from_day: The starting day of the report. + :type from_day: int + :param to_day: The ending day of the report. + :type to_day: int + """ + with open(path, "w", newline="") as file: + writer = csv.writer(file) + writer.writerow(["Day", "Beta (degrees)"]) + + for day in range(from_day, to_day): + beta = irradiation_calculator.find_optimal_orientation(day) + logger_name = "logger" + getattr(self, logger_name).info( + f"On day {day}," + + "the solar panel's optimal orientation is {beta} degrees." + ) + + # Write the result to the CSV file + writer.writerow([day, beta]) diff --git a/tests/test_pysolorie.py b/tests/test_pysolorie.py index b8a2142..7d0a2bc 100644 --- a/tests/test_pysolorie.py +++ b/tests/test_pysolorie.py @@ -11,8 +11,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - +import csv import math +from pathlib import Path from typing import Tuple import pytest @@ -22,6 +23,7 @@ HottelModel, IrradiationCalculator, Observer, + ReportGenerator, SolarIrradiance, SunPosition, ) @@ -270,3 +272,34 @@ def test_find_optimal_orientation( ) result = irradiation_calculator.find_optimal_orientation(day_of_year) assert pytest.approx(result, abs=1e-3) == expected_result + + +def test_generate_optimal_orientation_csv_report(tmpdir): + # Create a temporary directory for the test + temp_dir = Path(tmpdir) + + # Initialize the ReportGenerator + report_generator = ReportGenerator() + + # Initialize the IrradiationCalculator for Tehran + irradiation_calculator = IrradiationCalculator("MIDLATITUDE SUMMER", 1200, 35.6892) + + # Define the path for the CSV file + csv_path = temp_dir / "report.csv" + from_day = 60 + to_day = 70 + # Call the method to generate the report + report_generator.generate_optimal_orientation_csv_report( + csv_path, irradiation_calculator, from_day, to_day + ) + + # Check the CSV file + with open(csv_path, "r") as file: + reader = csv.reader(file) + header = next(reader) + assert header == ["Day", "Beta (degrees)"] + for i, row in enumerate(reader, start=from_day): + day, beta = row + assert int(day) == i + expected_beta = irradiation_calculator.find_optimal_orientation(i) + assert pytest.approx(float(beta), abs=1e-3) == expected_beta