Skip to content

Commit 61091d6

Browse files
committed
2 parents de1ad0d + 7ddf599 commit 61091d6

File tree

6 files changed

+448
-11
lines changed

6 files changed

+448
-11
lines changed

CONTRIBUTING.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Contributing to MISOReports
2+
3+
Thank you for considering contributing to **MISOReports**! We’re excited to have your support, and we welcome any feedback, bug reports, and improvements. This guide provides a quick overview of how you can contribute to the project.
4+
5+
## Ways to Contribute
6+
7+
There are several ways you can get involved:
8+
9+
1. [Report Bugs](#reporting-bugs)
10+
2. [Request Features](#requesting-features)
11+
3. [Open Issues](#opening-issues)
12+
4. [Submit Pull Requests](#submitting-pull-requests)
13+
14+
---
15+
16+
### Requesting Features
17+
18+
We’re always open to new ideas! If you have a feature in mind that would improve the project, please suggest it by:
19+
20+
1. [Opening a feature request issue](https://github.com/BrianWeiHaoMa/MISOReports/issues/new?template=feature_request.md)
21+
2. Describing the feature clearly, including why it would be beneficial
22+
3. Sharing any examples or references that can help us understand your idea
23+
24+
---
25+
26+
### Opening Issues
27+
28+
Issues are a great way to discuss ideas, report bugs, or request features. Before opening a new issue, please search to see if one already exists. If not, feel free to [open a new issue](https://github.com/[username]/[repository]/issues) and provide as much detail as possible.
29+
30+
If you encounter any bugs, please let us know! To make your bug report as effective as possible, please include:
31+
32+
- A clear and descriptive title for the issue
33+
- Steps to reproduce the bug
34+
- The expected vs. actual behavior
35+
- Screenshots, error messages, or code snippets if applicable
36+
- Environment details (e.g., operating system, browser, version)
37+
38+
You can report bugs by [opening an issue](https://github.com/BrianWeiHaoMa/MISOReports/issues/new?template=bug_report.md). We’ll do our best to address them as soon as possible
39+
40+
---
41+
42+
### Submitting Pull Requests
43+
44+
If you'd like to make changes to the code, please follow these steps:
45+
46+
1. **Fork the repository** and create a new branch.
47+
2. Make your changes, ensuring that they are focused and well-documented.
48+
3. **Test your changes** to make sure everything works as expected.
49+
4. **Submit a pull request** with a clear description of your changes and link to any related issues.
50+
51+
We welcome pull requests for:
52+
- Fixing bugs
53+
- Implementing new features
54+
- Improving documentation
55+
- Enhancing existing code for performance or readability
56+
57+
When submitting a pull request, make sure to:
58+
- Describe the problem your changes address or the feature they add
59+
- Reference any related issues by number (e.g., `#123`)
60+
- Keep changes focused on a single improvement or fix
61+
62+
---
63+
64+
Thank you for your interest in contributing! We’re excited to work with you to make **MISOReports** better for everyone.

DEV.md renamed to DEVELOPMENT.md

File renamed without changes.

LICENSE

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2024 Brian Wei Hao Ma, Ryan Green, William Sun
3+
Copyright (c) 2024 Brian Wei Hao Ma, Ryan B. Green, William Sun
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21-
SOFTWARE.
21+
SOFTWARE.

MISOReports/MISOReports.py

Lines changed: 237 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ def add_to_datetime(
177177
"""
178178
increment_mappings: dict[Callable[[datetime.datetime | None, str], str], relativedelta] = {
179179
MISOMarketReportsURLBuilder.url_generator_YYYY_current_month_name_to_two_months_later_name_first: relativedelta(months=3),
180+
MISOMarketReportsURLBuilder.url_generator_YYYY_underscore_current_month_name_to_two_months_later_name_first: relativedelta(months=3),
180181
MISOMarketReportsURLBuilder.url_generator_YYYYmmdd_first: relativedelta(days=1),
181182
MISOMarketReportsURLBuilder.url_generator_YYYYmm_first: relativedelta(months=1),
182183
MISOMarketReportsURLBuilder.url_generator_YYYY_first: relativedelta(years=1),
@@ -564,7 +565,77 @@ def parse_da_bcsf(
564565
def parse_rt_pr(
565566
res: requests.Response,
566567
) -> pd.DataFrame:
567-
raise NotImplementedError("Result has an atypical format.")
568+
df1 = pd.read_excel(
569+
io=io.BytesIO(res.content),
570+
skiprows=6,
571+
nrows=1,
572+
)
573+
df1.rename(columns={df1.columns[0]: "Type"}, inplace=True)
574+
df1.drop(labels=df1.columns[4:], axis=1, inplace=True)
575+
df1[["Type"]] = df1[["Type"]].astype(pandas.core.arrays.string_.StringDtype())
576+
df1[["Demand", "Supply", "Total"]] = df1[["Demand", "Supply", "Total"]].astype(numpy.dtypes.Float64DType())
577+
578+
df2 = pd.read_excel(
579+
io=io.BytesIO(res.content),
580+
skiprows=8,
581+
nrows=2,
582+
)
583+
df2.rename(columns={df2.columns[0]: "Type"}, inplace=True)
584+
df2.drop(labels=df2.columns[4:], axis=1, inplace=True)
585+
df2[["Type"]] = df2[["Type"]].astype(pandas.core.arrays.string_.StringDtype())
586+
df2[["Demand", "Supply", "Total"]] = df2[["Demand", "Supply", "Total"]].astype(numpy.dtypes.Float64DType())
587+
588+
df3 = pd.read_excel(
589+
io=io.BytesIO(res.content),
590+
skiprows=11,
591+
nrows=24,
592+
)
593+
shared_column_names = list(df3.columns)[1:]
594+
595+
df3.rename(columns={df3.columns[0]: "Hour"}, inplace=True)
596+
df3[["Hour"]] = df3[["Hour"]].replace('[^\\d]+', '', regex=True).astype(pandas.core.arrays.integer.Int64Dtype())
597+
df3[shared_column_names] = df3[shared_column_names].astype(numpy.dtypes.Float64DType())
598+
599+
df4 = pd.read_excel(
600+
io=io.BytesIO(res.content),
601+
skiprows=36,
602+
nrows=3,
603+
names=["Around the Clock"] + shared_column_names,
604+
)
605+
606+
df5 = pd.read_excel(
607+
io=io.BytesIO(res.content),
608+
skiprows=40,
609+
nrows=3,
610+
names=["On-Peak"] + shared_column_names,
611+
)
612+
613+
df6 = pd.read_excel(
614+
io=io.BytesIO(res.content),
615+
skiprows=44,
616+
nrows=3,
617+
names=["Off-Peak"] + shared_column_names,
618+
)
619+
620+
bottom_dfs = [df4, df5, df6]
621+
for i in range(len(bottom_dfs)):
622+
first_column = bottom_dfs[i].columns[0]
623+
bottom_dfs[i][[first_column]] = bottom_dfs[i][[first_column]].astype(pandas.core.arrays.string_.StringDtype())
624+
bottom_dfs[i][shared_column_names] = bottom_dfs[i][shared_column_names].astype(numpy.dtypes.Float64DType())
625+
626+
# No names written for any of the tables in the report.
627+
df = pd.DataFrame({
628+
MULTI_DF_NAMES_COLUMN: [
629+
f"Table {i}" for i in range(1, 7)
630+
],
631+
MULTI_DF_DFS_COLUMN: [
632+
df1,
633+
df2,
634+
df3,
635+
] + bottom_dfs,
636+
})
637+
638+
return df
568639

569640
@staticmethod
570641
def parse_rt_irsf(
@@ -750,7 +821,39 @@ def parse_Total_Uplift_by_Resource(
750821
def parse_ms_vlr_srw(
751822
res: requests.Response,
752823
) -> pd.DataFrame:
753-
raise NotImplementedError("Result contains 2 csv tables.")
824+
float_columns = ["DA VLR RSG MWP", "RT VLR RSG MWP", "DA+RT Total"]
825+
string_columns = ["Constraint"]
826+
column_names = string_columns + float_columns
827+
828+
df1 = pd.read_excel(
829+
io=io.BytesIO(res.content),
830+
skiprows=7,
831+
nrows=3,
832+
usecols=column_names,
833+
)
834+
df1[float_columns] = df1[float_columns].astype(numpy.dtypes.Float64DType())
835+
df1[string_columns] = df1[string_columns].astype(pandas.core.arrays.string_.StringDtype())
836+
837+
df2 = pd.read_excel(
838+
io=io.BytesIO(res.content),
839+
skiprows=15,
840+
nrows=7,
841+
usecols=column_names,
842+
)
843+
df2[float_columns] = df2[float_columns].astype(numpy.dtypes.Float64DType())
844+
df2[string_columns] = df2[string_columns].astype(pandas.core.arrays.string_.StringDtype())
845+
846+
df = pd.DataFrame({
847+
MULTI_DF_NAMES_COLUMN: [
848+
f"Table {i}" for i in range(1, 3)
849+
],
850+
MULTI_DF_DFS_COLUMN: [
851+
df1,
852+
df2,
853+
],
854+
})
855+
856+
return df
754857

755858
@staticmethod
756859
def parse_ms_rsg_srw(
@@ -900,7 +1003,63 @@ def parse_ms_vlr_HIST(
9001003
def parse_Daily_Uplift_by_Local_Resource_Zone(
9011004
res: requests.Response,
9021005
) -> pd.DataFrame:
903-
raise NotImplementedError("Result contains 10 csv tables.")
1006+
df0 = pd.read_excel(
1007+
io=io.BytesIO(res.content),
1008+
skiprows=9,
1009+
nrows=1,
1010+
)
1011+
1012+
month_string = df0.iloc[0, 0]
1013+
year_string = df0.iloc[0, 1]
1014+
1015+
if type(year_string) == str and type(month_string) == str:
1016+
year_string = year_string[-4:]
1017+
else:
1018+
raise ValueError("Unexpected: year_string or month_string is not a string.")
1019+
1020+
date_string = f"{month_string} {year_string}"
1021+
month_days = pd.Period(date_string, freq='M').days_in_month
1022+
n_rows = month_days + 1
1023+
1024+
def parse_report_part(skiprows: int) -> pd.DataFrame:
1025+
df = pd.read_excel(
1026+
io=io.BytesIO(res.content),
1027+
skiprows=skiprows,
1028+
nrows=n_rows,
1029+
)
1030+
df.rename(columns={df.columns[1]: "Date"}, inplace=True)
1031+
df.drop(labels=df.columns[0], axis=1, inplace=True)
1032+
1033+
df[["Date"]] = df[["Date"]].astype(pandas.core.arrays.string_.StringDtype())
1034+
df[["Day Ahead Capacity", "Day Ahead VLR", "Real Time Capacity", "Real Time VLR", "Real Time Transmission Reliability", "Price Volatility Make Whole Payments\n"]] = df[["Day Ahead Capacity", "Day Ahead VLR", "Real Time Capacity", "Real Time VLR", "Real Time Transmission Reliability", "Price Volatility Make Whole Payments\n"]].astype(numpy.dtypes.Float64DType())
1035+
1036+
return df
1037+
1038+
dfs = [] # There should be 10 dfs.
1039+
1040+
for i in range(10):
1041+
df = parse_report_part(9 + (4 + n_rows) * i)
1042+
dfs.append(df)
1043+
1044+
table_names = [
1045+
"LRZ 1",
1046+
"LRZ 10",
1047+
"LRZ 2",
1048+
"LRZ 3",
1049+
"LRZ 4",
1050+
"LRZ 5",
1051+
"LRZ 6",
1052+
"LRZ 7",
1053+
"LRZ 8",
1054+
"LRZ 9",
1055+
]
1056+
1057+
df = pd.DataFrame({
1058+
MULTI_DF_NAMES_COLUMN: table_names,
1059+
MULTI_DF_DFS_COLUMN: dfs,
1060+
})
1061+
1062+
return df
9041063

9051064
@staticmethod
9061065
def parse_fuelmix(
@@ -1317,7 +1476,48 @@ def parse_reservebindingconstraints(
13171476
def parse_totalload(
13181477
res: requests.Response,
13191478
) -> pd.DataFrame:
1320-
raise NotImplementedError("Result contains 3 csv tables.")
1479+
text = res.text
1480+
1481+
table_1 = "ClearedMW"
1482+
df1 = pd.read_csv(
1483+
filepath_or_buffer=io.StringIO(text),
1484+
skiprows=3,
1485+
nrows=24,
1486+
)
1487+
df1[["Load_Hour"]] = df1[["Load_Hour"]].astype(pandas.core.arrays.integer.Int64Dtype())
1488+
df1[["Load_Value"]] = df1[["Load_Value"]].astype(numpy.dtypes.Float64DType())
1489+
1490+
table_2 = "MediumTermLoadForecast"
1491+
df2 = pd.read_csv(
1492+
filepath_or_buffer=io.StringIO(text),
1493+
skiprows=29,
1494+
nrows=24,
1495+
)
1496+
df2[["Hour_End"]] = df2[["Hour_End"]].astype(pandas.core.arrays.integer.Int64Dtype())
1497+
df2[["Load_Forecast"]] = df2[["Load_Forecast"]].astype(numpy.dtypes.Float64DType())
1498+
1499+
table_3 = "FiveMinTotalLoad"
1500+
df3 = pd.read_csv(
1501+
filepath_or_buffer=io.StringIO(text),
1502+
skiprows=55,
1503+
)
1504+
df3[["Load_Time"]] = df3[["Load_Time"]].apply(pd.to_datetime, format="%H:%M")
1505+
df3[["Load_Value"]] = df3[["Load_Value"]].astype(numpy.dtypes.Float64DType())
1506+
1507+
df = pd.DataFrame({
1508+
MULTI_DF_NAMES_COLUMN: [
1509+
table_1,
1510+
table_2,
1511+
table_3,
1512+
],
1513+
MULTI_DF_DFS_COLUMN: [
1514+
df1,
1515+
df2,
1516+
df3,
1517+
],
1518+
})
1519+
1520+
return df
13211521

13221522
@staticmethod
13231523
def parse_RSG(
@@ -1546,7 +1746,39 @@ def parse_bids_cb(
15461746
def parse_asm_exante_damcp(
15471747
res: requests.Response,
15481748
) -> pd.DataFrame:
1549-
raise NotImplementedError("Result contains 2 csv tables.")
1749+
text = res.text
1750+
1751+
second_table_start_idx = text.index("Pnode,Zone,MCP Type")
1752+
1753+
table_1 = "Table 1"
1754+
df1 = pd.read_csv(
1755+
filepath_or_buffer=io.StringIO(text[:second_table_start_idx]),
1756+
skiprows=4,
1757+
nrows=24,
1758+
)
1759+
hours = [" HE 1"] + [f"HE {i}" for i in range(2, 25)]
1760+
df1[hours] = df1[hours].astype(numpy.dtypes.Float64DType())
1761+
df1[df1.columns[:3]] = df1[df1.columns[:3]].astype(pandas.core.arrays.string_.StringDtype())
1762+
1763+
table_2 = "Table 2"
1764+
df2 = pd.read_csv(
1765+
filepath_or_buffer=io.StringIO(text[second_table_start_idx:]),
1766+
)
1767+
df2[hours] = df2[hours].astype(numpy.dtypes.Float64DType())
1768+
df2[["Pnode", "Zone", "MCP Type"]] = df2[["Pnode", "Zone", "MCP Type"]].astype(pandas.core.arrays.string_.StringDtype())
1769+
1770+
df = pd.DataFrame({
1771+
MULTI_DF_NAMES_COLUMN: [
1772+
table_1,
1773+
table_2,
1774+
],
1775+
MULTI_DF_DFS_COLUMN: [
1776+
df1,
1777+
df2,
1778+
],
1779+
})
1780+
1781+
return df
15501782

15511783
@staticmethod
15521784
def parse_ftr_allocation_restoration(

MISOReports/checker.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -204,10 +204,10 @@ def string_format_split_df(
204204
if df.columns[0] == MULTI_DF_NAMES_COLUMN:
205205
for table_name, df in zip(df[MULTI_DF_NAMES_COLUMN], df[MULTI_DF_DFS_COLUMN]):
206206
new_string = f"""
207-
Report: {table_name}
208-
URL: {url}
209-
{string_format_split_df(df=df, top=i_top, bottom=i_bottom, auto_size=i_auto_size)}
210-
{string_format_suggestions(df=df, report_name=report_name)}
207+
Report: {table_name}
208+
URL: {url}
209+
{string_format_split_df(df=df, top=i_top, bottom=i_bottom, auto_size=i_auto_size)}
210+
{string_format_suggestions(df=df, report_name=report_name)}
211211
212212
213213
"""

0 commit comments

Comments
 (0)