Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,9 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

watttime/api_do_not_commit.py
tests/test_do_not_commit.py
analysis/*

!analysis/.gitkeep
Empty file added analysis/.gitkeep
Empty file.
4 changes: 3 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
setup(
name="watttime",
description="An officially maintained python client for WattTime's API providing access to electricity grid emissions data.",
long_description=open('README.md').read(),
long_description=open("README.md").read(),
long_description_content_type="text/markdown",
version="v1.3.2",
packages=["watttime"],
python_requires=">=3.8",
install_requires=["requests", "pandas>1.0.0", "holidays", "python-dateutil"],
extras_require={"report": ["papermill", "nbconvert", "plotly", "scipy"]},
scripts=["watttime/report.py"],
)
94 changes: 94 additions & 0 deletions tests/test_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import unittest
from datetime import datetime, date
from watttime import report
from pathlib import Path
from typing import List, Union
from itertools import product

BA_LIST: List[str] = ["WEM"]
MODEL_DATE_LIST: List[str] = ["2022-10-01", "2024-10-16"]
SIGNAL_TYPE: str = "co2_moer"
EVAL_START: Union[str, datetime, date] = "2024-01-01T00:00Z"
EVAL_END: Union[str, datetime, date] = "2024-03-01T00:00Z"
FORECAST_SAMPLE_DAYS = 3


class TestReport(unittest.TestCase):
def setUp(self):
self.eval_days = report.parse_eval_days(
eval_start=EVAL_START, eval_end=EVAL_END
)
self.sample_days = report.parse_forecast_sample_days(
eval_days=self.eval_days, forecast_sample_days=FORECAST_SAMPLE_DAYS
)

self.jobs = list(
Copy link
Contributor

@jcofield jcofield Mar 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest not using job(s) here and instead aligning ModelAnalysis with this variable. The natural alignment for the current draft would be to just name this something more like what ModelAnalysis dataset is: self.analysis_data, self.analyses, etc...

I would reserve the use of the word job for (i) uses of a parallelization package (ii) deeper down in this package (see comment there)

report.ModelAnalysis(
ba=i[0],
model_date=i[1],
signal_type=SIGNAL_TYPE,
eval_start=EVAL_START,
eval_end=EVAL_END,
eval_days=self.eval_days,
sample_days=self.sample_days,
)
for i in product(BA_LIST, MODEL_DATE_LIST)
)

# Compile forecast_v_moer for each job
for job in self.jobs:
job.compile_forecast_v_moer()

def test_compile_forecast_v_moer(self):
for job in self.jobs:
self.assertFalse(job.moers.empty)
self.assertFalse(job.forecasts.empty)
self.assertFalse(job.forecasts_v_moers.empty)

def test_plot_sample_moers(self):
fig = report.plot_sample_moers(self.jobs)
self.assertGreater(len(fig.data), 0)

def test_plot_distribution_moers(self):
fig = report.plot_distribution_moers(self.jobs)
self.assertGreater(len(fig.data), 0)

def test_plot_ba_heatmaps(self):
fig = report.plot_ba_heatmaps(self.jobs)
self.assertGreater(len(fig.data), 0)

def test_plot_norm_mae(self):
fig = report.plot_norm_mae(self.jobs)
self.assertGreater(len(fig.data), 0)

def test_plot_rank_correlation(self):
fig = report.plot_rank_correlation(self.jobs)
self.assertGreater(len(fig.data), 0)

def test_plot_impact_forecast_metrics(self):
fig = report.plot_impact_forecast_metrics(self.jobs)
self.assertGreater(len(fig.data), 0)

def test_plot_sample_moers_single_job(self):
fig = report.plot_sample_moers([self.jobs[0]])
self.assertGreater(len(fig.data), 0)

def test_plot_distribution_moers_single_job(self):
fig = report.plot_distribution_moers([self.jobs[0]])
self.assertGreater(len(fig.data), 0)

def test_plot_ba_heatmaps_single_job(self):
fig = report.plot_ba_heatmaps([self.jobs[0]])
self.assertGreater(len(fig.data), 0)

def test_plot_norm_mae_single_job(self):
fig = report.plot_norm_mae([self.jobs[0]])
self.assertGreater(len(fig.data), 0)

def test_plot_rank_correlation_single_job(self):
fig = report.plot_rank_correlation([self.jobs[0]])
self.assertGreater(len(fig.data), 0)

def test_plot_impact_forecast_metrics_single_job(self):
fig = report.plot_impact_forecast_metrics([self.jobs[0]])
self.assertGreater(len(fig.data), 0)
3 changes: 2 additions & 1 deletion watttime/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from watttime.api import *
from watttime.tcy import TCYCalculator
from watttime.tcy import TCYCalculator
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This puts TCYCalculator in this file's namespace, permitting import wattttime.TCYCalculator. One disadvantage to doing this is that it broadens the surface area of imports anytime there is a watttime namespace import. What is the advantage to re-namespacing it here?

from watttime.report import ModelAnalysis
Copy link
Contributor

@jcofield jcofield Mar 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My suspicion is that this a workaround to load this into the jupyter notebook. If the jupyter notebook said from watttime import ModelAnalysis then this would just be a convenience feature but instead it says from watttime import report. Is this a workaround? If so, let's discuss a more direct approach and if not, what is the advantage to this re-namespacing?

Loading
Loading