tariff_fetch 0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. tariff_fetch/__init__.py +3 -0
  2. tariff_fetch/_cli/__init__.py +33 -0
  3. tariff_fetch/_cli/genability.py +152 -0
  4. tariff_fetch/_cli/openei.py +89 -0
  5. tariff_fetch/_cli/rateacuity.py +164 -0
  6. tariff_fetch/_cli/rateacuity_gas_urdb.py +138 -0
  7. tariff_fetch/_cli/types.py +67 -0
  8. tariff_fetch/arcadiaurdb_.py +0 -0
  9. tariff_fetch/cli.py +179 -0
  10. tariff_fetch/cli_gas.py +72 -0
  11. tariff_fetch/foo.py +6 -0
  12. tariff_fetch/genability/__init__.py +1 -0
  13. tariff_fetch/genability/base.py +24 -0
  14. tariff_fetch/genability/converters.py +9 -0
  15. tariff_fetch/genability/lse.py +70 -0
  16. tariff_fetch/genability/pagination.py +12 -0
  17. tariff_fetch/genability/response.py +437 -0
  18. tariff_fetch/genability/search.py +42 -0
  19. tariff_fetch/genability/tariffs.py +126 -0
  20. tariff_fetch/openei/__init__.py +0 -0
  21. tariff_fetch/openei/base.py +26 -0
  22. tariff_fetch/openei/utility_rates.py +347 -0
  23. tariff_fetch/openeia.py +47 -0
  24. tariff_fetch/rateacuity/__init__.py +2 -0
  25. tariff_fetch/rateacuity/base.py +67 -0
  26. tariff_fetch/rateacuity/report_tables.py +81 -0
  27. tariff_fetch/rateacuity/schema.py +220 -0
  28. tariff_fetch/rateacuity/state.py +515 -0
  29. tariff_fetch/rateacuity/urdb.py +12 -0
  30. tariff_fetch/urdb/__init__.py +0 -0
  31. tariff_fetch/urdb/rateacuity_gas.py +53 -0
  32. tariff_fetch/urdb/rateacuity_history_gas/__init__.py +78 -0
  33. tariff_fetch/urdb/rateacuity_history_gas/exceptions.py +15 -0
  34. tariff_fetch/urdb/rateacuity_history_gas/history_data.py +162 -0
  35. tariff_fetch/urdb/rateacuity_history_gas/shared.py +19 -0
  36. tariff_fetch/urdb/rateacuity_history_gas/types.py +36 -0
  37. tariff_fetch/urdb/schema.py +179 -0
  38. tariff_fetch-0.1.dist-info/METADATA +151 -0
  39. tariff_fetch-0.1.dist-info/RECORD +42 -0
  40. tariff_fetch-0.1.dist-info/WHEEL +4 -0
  41. tariff_fetch-0.1.dist-info/entry_points.txt +3 -0
  42. tariff_fetch-0.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,3 @@
1
+ """This is a repository for modern data science projects.."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,33 @@
1
+ import os
2
+ from datetime import datetime
3
+ from pathlib import Path
4
+
5
+ import questionary
6
+ from pathvalidate import sanitize_filename
7
+ from rich.console import Console
8
+
9
+ console = Console()
10
+
11
+
12
+ def prompt_filename(output_folder: Path, suggested_filename: str, extension: str) -> Path:
13
+ date_str = datetime.now().strftime("%Y-%m-%d")
14
+ suggested_filename = sanitize_filename(f"{suggested_filename}_{date_str}")
15
+ if output_folder.exists():
16
+ existing_filenames = set(output_folder.iterdir())
17
+ filepath = next(
18
+ _
19
+ for i in range(0xFFFFFF)
20
+ if (_ := output_folder.joinpath(f"{suggested_filename}-{i}{os.extsep}{extension}"))
21
+ not in existing_filenames
22
+ )
23
+ else:
24
+ filepath = output_folder.joinpath(f"{suggested_filename}-0{os.extsep}{extension}")
25
+
26
+ return Path(
27
+ questionary.path(
28
+ message="Path to save the results",
29
+ default=filepath.as_posix(),
30
+ file_filter=lambda _: Path(_).suffix == extension,
31
+ validate=lambda _: (not os.path.exists(_)) or "A file with that name already exists",
32
+ ).ask()
33
+ )
@@ -0,0 +1,152 @@
1
+ import json
2
+ import os
3
+ from datetime import datetime, timezone
4
+ from pathlib import Path
5
+
6
+ import questionary
7
+ from dotenv import load_dotenv
8
+
9
+ from tariff_fetch.genability.lse import get_lses_page
10
+ from tariff_fetch.genability.tariffs import CustomerClass, TariffType, tariffs_paginate
11
+
12
+ from . import console, prompt_filename
13
+ from .types import Utility
14
+
15
+
16
+ def _find_utility_lse_id(utility: Utility) -> int | None:
17
+ with console.status("Fetching lses..."):
18
+ lses = get_lses_page(
19
+ fields="min",
20
+ searchOn=["code"],
21
+ search=str(utility.eia_id),
22
+ startsWith=True,
23
+ endsWith=True,
24
+ )["results"]
25
+ if len(lses) == 0:
26
+ # No utilities found with this eia id
27
+ console.print(
28
+ f'Utility "{utility.name}" with EIA Id {utility.eia_id} not found in arcadia database', style="bold red"
29
+ )
30
+ return None
31
+ if len(lses) == 1:
32
+ # Found one utility
33
+ utility_lse_id = lses[0]["lseId"]
34
+ return utility_lse_id
35
+ else:
36
+ # Nothing found; this should *theoretically* never happen but let's keep it just in case
37
+ choices = [questionary.Choice(title=_["name"], value=_["lseId"]) for _ in lses]
38
+ choices.append(questionary.Separator())
39
+ choices.append(questionary.Choice(title="None of these", value=None))
40
+ utility_lse_id: int | None = questionary.select(
41
+ message=f"Found multiple utilities with lse id = {utility.eia_id}. Select one.", choices=choices
42
+ ).ask()
43
+ if utility_lse_id is None:
44
+ console.print("No utility chosen")
45
+ return None
46
+ return utility_lse_id
47
+
48
+
49
+ def _select_tariffs(
50
+ lse_id: int, customer_classes: list[CustomerClass], tariff_types: list[TariffType]
51
+ ) -> list[tuple[str, int]]:
52
+ with console.status("Fetching tariffs..."):
53
+ tariffs = list(
54
+ tariffs_paginate(
55
+ lseId=lse_id,
56
+ fields="min",
57
+ effectiveOn=datetime.now(timezone.utc),
58
+ customerClasses=customer_classes,
59
+ tariffTypes=tariff_types,
60
+ )
61
+ )
62
+ if not tariffs:
63
+ return []
64
+ return questionary.checkbox(
65
+ message="Select tariffs",
66
+ choices=[
67
+ questionary.Choice(title=_["tariffName"], value=(_["tariffName"], _["masterTariffId"]), checked=True)
68
+ for _ in tariffs
69
+ ],
70
+ use_search_filter=True,
71
+ use_jk_keys=False,
72
+ ).ask()
73
+
74
+
75
+ def _select_customer_classes() -> list[CustomerClass]:
76
+ return questionary.checkbox(
77
+ message="Select customer classes",
78
+ choices=[
79
+ questionary.Choice(title="Residential", value="RESIDENTIAL"),
80
+ questionary.Choice(title="General", value="GENERAL"),
81
+ questionary.Choice(title="Special Use", value="SPECIAL_USE"),
82
+ ],
83
+ validate=lambda _: True if _ else "Select at least one customer class",
84
+ ).ask()
85
+
86
+
87
+ def _select_tariff_types() -> list[TariffType]:
88
+ return questionary.checkbox(
89
+ message="Select tariff types",
90
+ choices=[
91
+ questionary.Choice(title="Default", value="DEFAULT"),
92
+ questionary.Choice(title="Alternative", value="ALTERNATIVE"),
93
+ questionary.Choice(title="Optional extra", value="OPTIONAL_EXTRA"),
94
+ questionary.Choice(title="Rider", value="RIDER"),
95
+ ],
96
+ validate=lambda _: bool(_) or "Select at least one tariff type",
97
+ ).ask()
98
+
99
+
100
+ def _fetch_tariffs(tariffs: list[tuple[str, int]]):
101
+ result = []
102
+ with console.status("Fetching tariffs..."):
103
+ for name, id_ in tariffs:
104
+ console.print(f"Tariff id: {name}")
105
+ page = list(
106
+ tariffs_paginate(
107
+ masterTariffId=id_,
108
+ effectiveOn=datetime.now(timezone.utc),
109
+ fields="ext",
110
+ populateProperties=True,
111
+ populateRates=True,
112
+ )
113
+ )
114
+ result.extend(page)
115
+ return result
116
+
117
+
118
+ def process_genability(utility: Utility, output_folder: Path):
119
+ load_dotenv()
120
+ if not os.getenv("ARCADIA_APP_ID"):
121
+ console.print("[b]ARCADIA_APP_ID[/] environment variable is not set.")
122
+ if not os.getenv("ARCADIA_APP_KEY"):
123
+ console.print("[b]ARCADIA_APP_KEY[/] environment variable is not set.")
124
+ if not (os.getenv("ARCADIA_APP_ID") and os.getenv("ARCADIA_APP_KEY")):
125
+ console.print("Cannot use Arcadia API due to missing credentials")
126
+ console.input("Press enter to proceed...")
127
+ return
128
+
129
+ lse_id = _find_utility_lse_id(utility)
130
+ if lse_id is None:
131
+ return
132
+
133
+ if not (customer_classes := _select_customer_classes()):
134
+ return
135
+
136
+ if not (tariff_types := _select_tariff_types()):
137
+ return
138
+
139
+ if not (tariffs := _select_tariffs(lse_id, customer_classes, tariff_types)):
140
+ console.print("[red]No tariffs found[/]")
141
+ console.input("Press enter to proceed...")
142
+ return
143
+
144
+ results = _fetch_tariffs(tariffs)
145
+ suggested_filename = f"arcadia_{utility.name}"
146
+
147
+ if not (filename := prompt_filename(output_folder, suggested_filename, "json")):
148
+ return
149
+
150
+ filename.parent.mkdir(exist_ok=True)
151
+ filename.write_text(json.dumps(results, indent=2))
152
+ console.print(f"Wrote [blue]{len(results)}[/] records to {filename}")
@@ -0,0 +1,89 @@
1
+ import json
2
+ import os
3
+ from datetime import datetime, timezone
4
+ from pathlib import Path
5
+ from typing import Literal
6
+
7
+ import questionary
8
+ from dotenv import load_dotenv
9
+
10
+ from tariff_fetch.openei.utility_rates import UtilityRateSector, UtilityRatesResponseItem, iter_utility_rates
11
+
12
+ from . import console, prompt_filename
13
+ from .types import Utility
14
+
15
+
16
+ def _prompt_sector() -> UtilityRateSector:
17
+ return questionary.select(
18
+ message="Select sector",
19
+ choices=[
20
+ "Residential",
21
+ "Commercial",
22
+ "Industrial",
23
+ "Lighting",
24
+ ],
25
+ ).ask()
26
+
27
+
28
+ def _prompt_detail_level() -> Literal["full", "minimal"]:
29
+ return questionary.select(
30
+ message="Select level of detail",
31
+ choices=["full", "minimal"],
32
+ ).ask()
33
+
34
+
35
+ def _get_tariffs(
36
+ eia_id: int, sector: UtilityRateSector, detail: Literal["full", "minimal"]
37
+ ) -> list[UtilityRatesResponseItem]:
38
+ api_key = os.getenv("OPENEI_API_KEY")
39
+ if not api_key:
40
+ raise ValueError("API Key is not set (via OPENEI_API_KEY variable)")
41
+ with console.status("Fetching rates..."):
42
+ iterator = iter_utility_rates(
43
+ api_key,
44
+ effective_on_date=datetime.now(timezone.utc),
45
+ sector=sector,
46
+ detail=detail,
47
+ eia=eia_id,
48
+ )
49
+ return list(iterator)
50
+
51
+
52
+ def _prompt_tariffs(tariffs: list[UtilityRatesResponseItem]) -> list[UtilityRatesResponseItem]:
53
+ return questionary.checkbox(
54
+ message="Select tariffs to include",
55
+ choices=[questionary.Choice(title=_["name"], value=_, checked=True) for _ in tariffs],
56
+ ).ask()
57
+
58
+
59
+ def process_openei(utility: Utility, output_folder: Path):
60
+ load_dotenv()
61
+ if not os.getenv("OPENEI_API_KEY"):
62
+ console.print("[b]OPENEI_API_KEY[/] environment variable is not set")
63
+ console.print("Cannot use OpenEI API due to missing credentials")
64
+ console.input("Press enter to proceed...")
65
+ return
66
+
67
+ if not (sector := _prompt_sector()):
68
+ return
69
+ if not (detail_level := _prompt_detail_level()):
70
+ return
71
+ tariffs = _get_tariffs(utility.eia_id, sector, detail_level)
72
+ if not tariffs:
73
+ console.print("[red]No tariffs found[/]")
74
+ console.input("Press enter to proceed...")
75
+ return
76
+ tariffs = _prompt_tariffs(tariffs)
77
+ if not tariffs:
78
+ console.print("[red]No tariffs selected[/]")
79
+ console.input("Press enter to proceed...")
80
+ return
81
+
82
+ suggested_filename = f"openei_{utility.name}_{sector}_{detail_level}"
83
+ if not (filepath := prompt_filename(output_folder, suggested_filename, "json")):
84
+ return
85
+
86
+ filepath.parent.mkdir(exist_ok=True)
87
+ print(filepath)
88
+ filepath.write_text(json.dumps(tariffs, indent=2))
89
+ console.print(f"Wrote [blue]{len(tariffs)}[/] items to {filepath}")
@@ -0,0 +1,164 @@
1
+ import json
2
+ import os
3
+ from pathlib import Path
4
+
5
+ import questionary
6
+ import tenacity
7
+ from dotenv import load_dotenv
8
+ from fuzzywuzzy import fuzz
9
+ from selenium.common.exceptions import WebDriverException
10
+
11
+ from tariff_fetch._cli.types import Utility
12
+ from tariff_fetch.rateacuity import LoginState, create_context
13
+
14
+ from . import console, prompt_filename
15
+
16
+
17
+ def process_rateacuity_gas(output_folder: Path, state: str):
18
+ load_dotenv()
19
+ if not (username := os.getenv("RATEACUITY_USERNAME")):
20
+ console.print("[b]RATEACUITY_USERNAME[/] environment variable is not set")
21
+ if not (password := os.getenv("RATEACUITY_PASSWORD")):
22
+ console.print("[b]RATEACUITY_PASSWORD[/] environment variable is not set")
23
+ if not (username and password):
24
+ console.print("Cannot use RateAcuity due to missing credentials")
25
+ console.input("Press enter to proceed...")
26
+ return
27
+
28
+ selected_utility = None
29
+ tariffs_to_include = None
30
+ results = []
31
+
32
+ for attempt in tenacity.Retrying(
33
+ stop=tenacity.stop_after_attempt(3), retry=tenacity.retry_if_exception_type(WebDriverException)
34
+ ):
35
+ with attempt, create_context() as context:
36
+ with console.status("Fetching list of utilities..."):
37
+ scraping_state = (
38
+ LoginState(context).login(username, password).gas().benchmark_all().select_state(state.upper())
39
+ )
40
+ utilities = [_ for _ in scraping_state.get_utilities() if _]
41
+
42
+ if not utilities:
43
+ raise RuntimeError(f"Something's wrong: rateacuity shows no utilities for this state ({state})")
44
+
45
+ if selected_utility is None:
46
+ selected_utility = questionary.select(
47
+ message="Select a utility from available choices",
48
+ choices=utilities,
49
+ use_jk_keys=False,
50
+ use_search_filter=True,
51
+ use_shortcuts=False,
52
+ ).ask()
53
+ if not selected_utility:
54
+ return
55
+
56
+ with console.status("Fetching list of tariffs..."):
57
+ scraping_state = scraping_state.select_utility(selected_utility)
58
+ tariffs = [_ for _ in scraping_state.get_schedules() if _]
59
+
60
+ if tariffs_to_include is None:
61
+ tariffs_to_include = questionary.checkbox(
62
+ message="Select tariffs to include",
63
+ choices=tariffs,
64
+ use_jk_keys=False,
65
+ use_search_filter=True,
66
+ validate=lambda _: bool(_) or "Select at least one tariff",
67
+ ).ask()
68
+
69
+ if not tariffs_to_include:
70
+ console.print("[red]No tariffs selected[/]")
71
+ console.input("Press enter to proceed...")
72
+ return
73
+
74
+ with console.status("Fetching tariffs..."):
75
+ while tariffs_to_include:
76
+ tariff = tariffs_to_include.pop(0)
77
+ console.log(f"Fetching {tariff}")
78
+ scraping_state = scraping_state.select_schedule(tariff)
79
+ sections = scraping_state.as_sections()
80
+ results.append({"schedule": tariff, "sections": sections})
81
+ scraping_state = scraping_state.back_to_selections()
82
+
83
+ assert selected_utility
84
+ suggested_filename = f"gas_rateacuity_{selected_utility}"
85
+ filename = prompt_filename(output_folder, suggested_filename, "json")
86
+ filename.parent.mkdir(exist_ok=True)
87
+ filename.write_text(json.dumps(results, indent=2))
88
+
89
+
90
+ def process_rateacuity(output_folder: Path, state: str, utility: Utility):
91
+ load_dotenv()
92
+ if not (username := os.getenv("RATEACUITY_USERNAME")):
93
+ console.print("[b]RATEACUITY_USERNAME[/] environment variable is not set")
94
+ if not (password := os.getenv("RATEACUITY_PASSWORD")):
95
+ console.print("[b]RATEACUITY_PASSWORD[/] environment variable is not set")
96
+ if not (username and password):
97
+ console.print("Cannot use RateAcuity due to missing credentials")
98
+ console.input("Press enter to proceed...")
99
+ return
100
+
101
+ selected_utility = None
102
+ tariffs_to_include = None
103
+ results = []
104
+
105
+ for attempt in tenacity.Retrying(
106
+ stop=tenacity.stop_after_attempt(3), retry=tenacity.retry_if_exception_type(WebDriverException)
107
+ ):
108
+ with attempt, create_context() as context:
109
+ with console.status("Fetching list of utilities..."):
110
+ scraping_state = (
111
+ LoginState(context).login(username, password).electric().benchmark_all().select_state(state.upper())
112
+ )
113
+ utilities = [_ for _ in scraping_state.get_utilities() if _]
114
+
115
+ if not utilities:
116
+ raise RuntimeError(f"Something's wrong: rateacuity shows no utilities for this state ({state})")
117
+
118
+ if selected_utility is None:
119
+ utilities_scored = sorted(utilities, key=lambda _: fuzz.ratio(utility.name, _), reverse=True)
120
+ selected_utility = utilities_scored.pop(0)
121
+ if not questionary.confirm(f"Is this the correct utility: {selected_utility} ?").ask():
122
+ selected_utility = questionary.select(
123
+ message="Select a utility from available choices",
124
+ choices=utilities_scored,
125
+ use_jk_keys=False,
126
+ use_search_filter=True,
127
+ use_shortcuts=False,
128
+ ).ask()
129
+ if not selected_utility:
130
+ return
131
+
132
+ with console.status("Fetching list of tariffs..."):
133
+ scraping_state = scraping_state.select_utility(selected_utility)
134
+ tariffs = [_ for _ in scraping_state.get_schedules() if _]
135
+
136
+ if tariffs_to_include is None:
137
+ tariffs_to_include = questionary.checkbox(
138
+ message="Select tariffs to include",
139
+ choices=tariffs,
140
+ use_jk_keys=False,
141
+ use_search_filter=True,
142
+ validate=lambda _: bool(_) or "Select at least one tariff",
143
+ ).ask()
144
+
145
+ if not tariffs_to_include:
146
+ console.print("[red]No tariffs selected[/]")
147
+ console.input("Press enter to proceed...")
148
+ return
149
+
150
+ with console.status("Fetching tariffs..."):
151
+ while tariffs_to_include:
152
+ tariff = tariffs_to_include.pop(0)
153
+ console.log(f"Fetching {tariff}")
154
+ scraping_state = scraping_state.select_schedule(tariff)
155
+ sections = scraping_state.as_sections()
156
+ results.append({"schedule": tariff, "sections": sections})
157
+ scraping_state = scraping_state.back_to_selections()
158
+
159
+ assert selected_utility
160
+ suggested_filename = f"rateacuity_{selected_utility}"
161
+ if not (filename := prompt_filename(output_folder, suggested_filename, "json")):
162
+ return
163
+ filename.parent.mkdir(exist_ok=True)
164
+ filename.write_text(json.dumps(results, indent=2))
@@ -0,0 +1,138 @@
1
+ import json
2
+ import os
3
+ from collections.abc import Collection
4
+ from datetime import date
5
+ from pathlib import Path
6
+ from statistics import mean
7
+ from typing import cast
8
+
9
+ import questionary
10
+ import tenacity
11
+ from dotenv import load_dotenv
12
+ from rich.prompt import Confirm
13
+ from selenium.common.exceptions import WebDriverException
14
+
15
+ from tariff_fetch.rateacuity import LoginState, create_context
16
+ from tariff_fetch.urdb.rateacuity_history_gas import (
17
+ build_urdb,
18
+ )
19
+ from tariff_fetch.urdb.rateacuity_history_gas.history_data import HistoryData, PercentageRow, Row
20
+ from tariff_fetch.urdb.schema import URDBRate
21
+
22
+ from . import console, prompt_filename
23
+
24
+ # TODO: This is ungodly ugly but it works
25
+
26
+
27
+ def process_rateacuity_gas_urdb(output_folder: Path, state: str, year: int):
28
+ _ = load_dotenv()
29
+ if not (username := os.getenv("RATEACUITY_USERNAME")):
30
+ console.print("[b]RATEACUITY_USERNAME[/] environment variable is not set")
31
+ if not (password := os.getenv("RATEACUITY_PASSWORD")):
32
+ console.print("[b]RATEACUITY_PASSWORD[/] environment variable is not set")
33
+ if not (username and password):
34
+ console.print("Cannot use RateAcuity due to missing credentials")
35
+ _ = console.input("Press enter to proceed...")
36
+ return
37
+
38
+ selected_utility = None
39
+ tariffs_to_include = None
40
+ result: list[URDBRate] = []
41
+ for attempt in tenacity.Retrying(
42
+ stop=tenacity.stop_after_attempt(3), retry=tenacity.retry_if_exception_type(WebDriverException)
43
+ ):
44
+ with attempt, create_context() as context:
45
+ with console.status("Fetching list of utilities..."):
46
+ scraping_state = (
47
+ LoginState(context).login(username, password).gas().history().select_state(state.upper())
48
+ )
49
+ utilities = [_ for _ in scraping_state.get_utilities() if _]
50
+ if selected_utility is None:
51
+ selected_utility = cast(
52
+ str,
53
+ questionary.select(
54
+ message="Select a utility from available choices",
55
+ choices=utilities,
56
+ use_jk_keys=False,
57
+ use_search_filter=True,
58
+ use_shortcuts=False,
59
+ ).ask(),
60
+ )
61
+ if not selected_utility:
62
+ return
63
+ with console.status("Fetching list of tariffs..."):
64
+ scraping_state = scraping_state.select_utility(selected_utility)
65
+ tariffs = [_ for _ in scraping_state.get_schedules() if _]
66
+ if tariffs_to_include is None:
67
+ tariffs_to_include = cast(
68
+ list[str],
69
+ questionary.checkbox(
70
+ message="Select tariffs to include",
71
+ choices=tariffs,
72
+ use_jk_keys=False,
73
+ use_search_filter=True,
74
+ validate=lambda _: bool(_) or "Select at least one tariff",
75
+ ).ask(),
76
+ )
77
+
78
+ if not tariffs_to_include:
79
+ console.print("[red]No tariffs selected[/]")
80
+ _ = console.input("Press enter to proceed...")
81
+ return
82
+ console.print("Fetching tariffs")
83
+ while tariffs_to_include:
84
+ tariff = tariffs_to_include.pop(0)
85
+ console.log(f"Fetching {tariff}")
86
+ scraping_state = (
87
+ scraping_state.select_schedule(tariff)
88
+ .set_enddate(date(year, 12, 1))
89
+ .set_number_of_comparisons(12)
90
+ .set_frequency(1)
91
+ )
92
+ df = scraping_state.as_dataframe()
93
+ hd = HistoryData(df)
94
+ validation_errors = hd.validate_rows()
95
+ proceed = True
96
+ if validation_errors:
97
+ console.print("Following rows cannot be processed and will be ignored:")
98
+ for error in validation_errors:
99
+ console.print(f" - {error.row}")
100
+ proceed = Confirm.ask("Proceed?", console=console)
101
+
102
+ if proceed:
103
+ apply_percentages = False
104
+ rows = list(hd.rows())
105
+ if percentage_columns := _get_percentage_columns(rows):
106
+ percentage_columns_strings = [
107
+ f"- {c[0]} ({c[1]}): {c[2]}" if c[1] else f"- {c[0]}: {c[2]}" for c in percentage_columns
108
+ ]
109
+ console.print("Found following percentage columns (values are averages over 12 months):")
110
+ console.print("\n".join(percentage_columns_strings))
111
+ console.print("It is impossible to tell which percentages apply to which specific rates.")
112
+ console.print("Percentages will be applied to the final result as is")
113
+ apply_percentages = Confirm.ask("Apply percentages? (otherwise percentages will be ignored)")
114
+
115
+ urdb = build_urdb(rows, apply_percentages)
116
+ urdb["utility"] = selected_utility
117
+ urdb["name"] = tariff
118
+ result.append(urdb)
119
+
120
+ scraping_state = (
121
+ scraping_state.back_to_selections()
122
+ .history()
123
+ .select_state(state.upper())
124
+ .select_utility(selected_utility)
125
+ )
126
+ suggested_filename = f"rateacuity_{selected_utility}.urdb.{year}."
127
+ if not (filename := prompt_filename(output_folder, suggested_filename, "json")):
128
+ return
129
+ filename.parent.mkdir(exist_ok=True)
130
+ _ = filename.write_text(json.dumps(result, indent=2))
131
+
132
+
133
+ def _get_percentage_columns(rows: Collection[Row]) -> list[tuple[str, str | None, float]]:
134
+ return [
135
+ (row.rate, row.location, mean(row.month_value_float(month) for month in range(0, 12)))
136
+ for row in rows
137
+ if isinstance(row, PercentageRow)
138
+ ]
@@ -0,0 +1,67 @@
1
+ from enum import Enum
2
+ from typing import NamedTuple
3
+
4
+
5
+ class Provider(str, Enum):
6
+ GENABILITY = "Genability"
7
+ OPENEI = "OpenEI"
8
+ RATEACUITY = "RateAcuity"
9
+
10
+
11
+ class StateCode(str, Enum):
12
+ AL = "al"
13
+ AK = "ak"
14
+ AZ = "az"
15
+ AR = "ar"
16
+ CA = "ca"
17
+ CO = "co"
18
+ CT = "ct"
19
+ DE = "de"
20
+ FL = "fl"
21
+ GA = "ga"
22
+ HI = "hi"
23
+ ID = "id"
24
+ IL = "il"
25
+ IN = "in"
26
+ IA = "ia"
27
+ KS = "ks"
28
+ KY = "ky"
29
+ LA = "la"
30
+ ME = "me"
31
+ MD = "md"
32
+ MA = "ma"
33
+ MI = "mi"
34
+ MN = "mn"
35
+ MS = "ms"
36
+ MO = "mo"
37
+ MT = "mt"
38
+ NE = "ne"
39
+ NV = "nv"
40
+ NH = "nh"
41
+ NJ = "nj"
42
+ NM = "nm"
43
+ NY = "ny"
44
+ NC = "nc"
45
+ ND = "nd"
46
+ OH = "oh"
47
+ OK = "ok"
48
+ OR = "or"
49
+ PA = "pa"
50
+ RI = "ri"
51
+ SC = "sc"
52
+ SD = "sd"
53
+ TN = "tn"
54
+ TX = "tx"
55
+ UT = "ut"
56
+ VT = "vt"
57
+ VA = "va"
58
+ WA = "wa"
59
+ WV = "wv"
60
+ WI = "wi"
61
+ WY = "wy"
62
+ DC = "dc"
63
+
64
+
65
+ class Utility(NamedTuple):
66
+ eia_id: int
67
+ name: str
File without changes