-
Notifications
You must be signed in to change notification settings - Fork 924
/
IN_HP.py
207 lines (181 loc) · 7.44 KB
/
IN_HP.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
#!/usr/bin/env python3
"""Parser for Himachal Pradesh (Indian State)."""
from datetime import datetime
from enum import Enum
from logging import Logger, getLogger
from zoneinfo import ZoneInfo
from bs4 import BeautifulSoup
from requests import Session
# This URL is called from within the
# https://hpsldc.com/intra-state-power-transaction/
# page to load the data.
DATA_URL = "https://hpsldc.com/wp-admin/admin-ajax.php"
ZONE_KEY = "IN-HP"
TZ = ZoneInfo("Asia/Kolkata")
class GenType(Enum):
"""
Enum for plant generation types found in the data.
Enum values are the keys for the production dictionary returned from fetch_production().
"""
HYDRO = "hydro"
UNKNOWN = "unknown"
# Map of plant names (as given in data source) to their type.
# Source for types is http://meritindia.in/state-data/himachal-pradesh
# (click 'Show details' at the bottom) or the link above/next to the
# relevant entry if there is no record in meritindia.
# Corroborating source: https://hpaldc.org/index.asp?pg=powStn
#
# Total plant capacity as manually calculated from the data source:
# GENERATION OF HP(Z): 992.45 MW Hydro, 608.26 MW Unknown.
# (Unknown capacity value not given in data, estimated as 135% of actual generation
# as this is the average ratio for plants with known capacity).
# (B1)ISGS(HPSLDC WEB PORTAL): 4483.02 MW Hydro.
# Total (for zones/IN_HP.yaml): 5475.47 MW Hydro, 608.26 MW Unknown.
PLANT_NAMES_TO_TYPES = {
### Plants in GENERATION OF HP(Z) table ###
# Listed as ISGS in type source but state in data source
"BASPA(3X100MW)": GenType.HYDRO,
"BHABA(3X40MW)": GenType.HYDRO,
"GIRI(2X30MW)": GenType.HYDRO,
"LARJI(3X42MW)": GenType.HYDRO,
"BASSI(4X16.5MW)": GenType.HYDRO,
# http://globalenergyobservatory.org/geoid/44638
"MALANA(2X43MW)": GenType.HYDRO,
"ANDHRA(3X5.65MW)": GenType.HYDRO,
"GHANVI(2X11.25MW)": GenType.HYDRO, # GANVI in type source
# https://www.ejatlas.org/conflict/kashang-hydroelectricity-project
"KASHANG(3X65MW)": GenType.HYDRO,
# https://cdm.unfccc.int/Projects/DB/RWTUV1354641854.75/view
"Sawra Kuddu (3x37MW)": GenType.HYDRO,
"MICROP/HMONITORED(HPSEBL)": GenType.UNKNOWN, # No type source
"IPPsP/HMONITORED": GenType.UNKNOWN, # No type source
"MICROP/HUNMONITORED": GenType.UNKNOWN, # No type source
### Plants in (B1)ISGS(HPSLDC WEB PORTAL) table ###
"BSIUL": GenType.HYDRO, # BAIRASIUL HEP in type source.
"CHAMERA1": GenType.HYDRO,
"CHAMERA2": GenType.HYDRO,
"CHAMERA3": GenType.HYDRO,
"PARBATI3": GenType.HYDRO,
"NJPC": GenType.HYDRO, # NATHPA JHAKRI HEP in type source.
"RAMPUR": GenType.HYDRO,
"KOLDAM": GenType.HYDRO,
}
def fetch_production(
zone_key: str = ZONE_KEY,
session: Session | None = None,
target_datetime: datetime | None = None,
logger: Logger = getLogger(__name__),
) -> dict:
"""Requests the last known production mix (in MW) of Himachal Pradesh (India)"""
r = session or Session()
if target_datetime is None:
url = DATA_URL
else:
raise NotImplementedError("This parser is not yet able to parse past dates")
res = r.post(url, {"action": "intra_state_power_transaction"})
assert res.status_code == 200, (
"Exception when fetching production for "
"{zone_key}: {res.status_code} error when calling url={url}"
)
soup = BeautifulSoup(res.text, "html.parser")
return {
"zoneKey": ZONE_KEY,
"datetime": datetime.now(tz=TZ),
"production": combine_gen(
get_state_gen(soup, logger), get_isgs_gen(soup, logger)
),
"source": "hpsldc.com",
}
def get_state_gen(soup, logger: Logger):
"""
Gets the total generation by type from state powerplants (MW).
Data is from the table titled GENERATION OF HP(Z).
"""
table_name = "GENERATION OF HP(Z)"
gen = {GenType.HYDRO.value: 0.0, GenType.UNKNOWN.value: 0.0}
# Read all rows except the last (Total).
for row in get_table_rows(soup, "table_5", table_name)[:-1]:
try:
cols = row.find_all("td")
# Column 1: plant name, column 2: current generation.
gen_type = PLANT_NAMES_TO_TYPES[cols[0].text]
gen[gen_type.value] += float(cols[1].text)
except (AttributeError, KeyError, IndexError, ValueError):
logger.error(
f"Error importing data from row: {row}", extra={"key": ZONE_KEY}
)
return gen
def get_isgs_gen(soup, logger: Logger):
"""
Gets the total generation by type from ISGS powerplants (MW).
ISGS means Inter-State Generating Station: one owned by multiple states.
Data is from the table titled (B1)ISGS(HPSLDC WEB PORTAL).
"""
table_name = "(B1)ISGS(HPSLDC WEB PORTAL)"
gen = {GenType.HYDRO.value: 0.0, GenType.UNKNOWN.value: 0.0}
# Read all rows except the first (headers) and last two (OTHERISGS and
# Total).
for row in get_table_rows(soup, "table_4", table_name)[1:-2]:
try:
cols = row.find_all("td")
if not cols[0].has_attr("class"):
# Ignore first column (COMPANY), which only has cells for some
# rows (using rowspan).
del cols[0]
# Once COMPANY column is excluded, we have
# column 1: plant name, column 3: current generation.
gen_type = PLANT_NAMES_TO_TYPES[cols[0].text]
gen[gen_type.value] += float(cols[2].text)
except (AttributeError, KeyError, IndexError, ValueError):
logger.error(
f"Error importing data from row: {row}", extra={"key": ZONE_KEY}
)
return gen
def get_table_rows(soup, container_class, table_name):
"""Gets the table rows in the div identified by the provided class."""
try:
rows = soup.find("div", class_=container_class).find("tbody").find_all("tr")
if len(rows) == 0:
raise ValueError
return rows
except (AttributeError, ValueError) as err:
raise Exception(f"Error reading table {table_name}: {err}") from err
def combine_gen(gen1, gen2):
"""
Combines two dictionaries of generation data.
Currently only does Hydro and Unknown as there are no other types in the
data source.
"""
return {
GenType.HYDRO.value: round(
gen1[GenType.HYDRO.value] + gen2[GenType.HYDRO.value], 2
),
GenType.UNKNOWN.value: round(
gen1[GenType.UNKNOWN.value] + gen2[GenType.UNKNOWN.value], 2
),
}
def fetch_consumption(
zone_key: str = ZONE_KEY,
session: Session | None = None,
target_datetime: datetime | None = None,
logger=getLogger(__name__),
):
# Not currently implemented as this function is not used by the map,
# but if required the data is available as 'Demand Met' on
# https://hpsldc.com/
raise NotImplementedError("Fetch production not implemented")
def fetch_price(
zone_key: str = ZONE_KEY,
session: Session | None = None,
target_datetime: datetime | None = None,
logger=getLogger(__name__),
):
# The only price data available in the source is 'DSM Rate'.
# I.e. Demand side management rate.
# I believe (though I'm not sure) that this refers to a payment
# made by the grid to consumers who reduce their consumption at times
# of high demand, therefore it is not appropriate for this function.
raise NotImplementedError("No price data available")
if __name__ == "__main__":
print("fetch_production() ->")
print(fetch_production())