Skip to content

Commit

Permalink
Create Mexican exchanges (#1512)
Browse files Browse the repository at this point in the history
* Create function for exchange with Mexico

* Update exchanges with MX->US-CA

* Rename exchange to MX-BC->US-CA

* Separate Baja California from Mexico

* Update mockserver state

* Renable US->US-CA exchange

* Rename exchange in EIA.py

* Split Mexico into control areas

* Generate new world.json

* Create new parser for Mexican exchanges

* Tidy up MX flow directions

* Add all MX exchanges to config

* Update mockserver state

* Update en translation with new MX zones
  • Loading branch information
systemcatch authored and brunolajoie committed Aug 10, 2018
1 parent 417a219 commit 227fa56
Show file tree
Hide file tree
Showing 9 changed files with 333 additions and 17 deletions.
134 changes: 132 additions & 2 deletions config/exchanges.json
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,16 @@
},
"rotation": 90
},
"BZ->MX-PN":{
"lonlat": [
-88.627,
18.240
],
"parsers": {
"exchange": "MX.fetch_exchange"
},
"rotation": 0
},
"CA-AB->CA-BC": {
"lonlat": [
-119.811359,
Expand Down Expand Up @@ -1426,16 +1436,136 @@
},
"rotation": 0
},
"MX->US-CA": {
"MX-BC->US-CA": {
"lonlat": [
-116.027,
32.607
],
"parsers": {
"exchange": "EIA.fetch_exchange"
"exchange": "US_CA.fetch_exchange"
},
"rotation": -10
},
"MX-CE->MX-OC":{
"lonlat": [
-99.102,
19.041
],
"parsers": {
"exchange": "MX.fetch_exchange"
},
"rotation": -35
},
"MX-CE->MX-OR":{
"lonlat": [
-97.328,
18.094
],
"parsers": {
"exchange": "MX.fetch_exchange"
},
"rotation": 150
},
"MX-NE->MX-NO":{
"lonlat": [
-101.151,
26.323
],
"parsers": {
"exchange": "MX.fetch_exchange"
},
"rotation": -90
},
"MX-NE->MX-OC":{
"lonlat": [
-100.371,
23.221
],
"parsers": {
"exchange": "MX.fetch_exchange"
},
"rotation": -160
},
"MX-NE->MX-OR":{
"lonlat": [
-98.130,
22.345
],
"parsers": {
"exchange": "MX.fetch_exchange"
},
"rotation": 170
},
"MX-NO->MX-NW":{
"lonlat": [
-108.721,
29.325
],
"parsers": {
"exchange": "MX.fetch_exchange"
},
"rotation": -90
},
"MX-NW->MX-OC":{
"lonlat": [
-105.557,
22.675
],
"parsers": {
"exchange": "MX.fetch_exchange"
},
"rotation": 145
},
"MX-NO->MX-OC":{
"lonlat": [
-103.755,
23.946
],
"parsers": {
"exchange": "MX.fetch_exchange"
},
"rotation": 150
},
"MX-NE->US":{
"lonlat": [
-98.042,
26.057
],
"parsers": {
"exchange": "MX.fetch_exchange"
},
"rotation": 0
},
"MX-NO->US":{
"lonlat": [
-107.292,
31.709
],
"parsers": {
"exchange": "MX.fetch_exchange"
},
"rotation": 0
},
"MX-OC->MX-OR":{
"lonlat": [
-101.272,
18.480
],
"parsers": {
"exchange": "MX.fetch_exchange"
},
"rotation": 145
},
"MX-OR->MX-PN":{
"lonlat": [
-92.175,
18.250
],
"parsers": {
"exchange": "MX.fetch_exchange"
},
"rotation": 90
},
"NA->ZA":{
"lonlat": [
18.512,
Expand Down
2 changes: 1 addition & 1 deletion mockserver/public/v3/state

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions parsers/EIA.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
}

EXCHANGES = {
'MX->US-CA': 'EBA.CISO-CFE.ID.H',
'MX-BC->US-CA': 'EBA.CISO-CFE.ID.H',
'US-BPA->US-IPC': 'EBA.BPAT-IPCO.ID.H',
'US-ERCOT->US-SPP': 'EBA.ERCO-SWPP.ID.H',
'US-MISO->US-PJM': 'EBA.MISO-PJM.ID.H',
Expand Down Expand Up @@ -85,7 +85,7 @@ def fetch_exchange(zone_key1, zone_key2, session=None, target_datetime=None, log

data = []
for datapoint in raw_data:
if sortedcodes == 'MX->US-CA':
if sortedcodes == 'MX-BC->US-CA':
datapoint[1] = -1*datapoint[1]

exchange = {'sortedZoneKeys': sortedcodes,
Expand All @@ -102,4 +102,4 @@ def fetch_exchange(zone_key1, zone_key2, session=None, target_datetime=None, log
"Main method, never used by the Electricity Map backend, but handy for testing."

print(fetch_consumption_forecast('US-NY'))
print(fetch_exchange('MX', 'US-CA'))
print(fetch_exchange('MX-BC', 'US-CA'))
112 changes: 112 additions & 0 deletions parsers/MX.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#!/usr/bin/env python3

import arrow
import pandas
import requests
from bs4 import BeautifulSoup
from collections import defaultdict

MX_EXCHANGE_URL = 'http://www.cenace.gob.mx/Paginas/Publicas/Info/DemandaRegional.aspx'

EXCHANGES = {"MX-NO->MX-NW": "IntercambioNTE-NOR",
"MX-NE->MX-NO": "IntercambioNES-NTE",
"MX-NE->MX-OR": "IntercambioNES-ORI",
"MX-OR->MX-PN": "IntercambioORI-PEN",
"MX-CE->MX-OR": "IntercambioORI-CEL",
"MX-OC->MX-OR": "IntercambioOCC-ORI",
"MX-CE->MX-OC": "IntercambioOCC-CEL",
"MX-NE->MX-OC": "IntercambioNES-OCC",
"MX-NO->MX-OC": "IntercambioNTE-OCC",
"MX-NW->MX-OC": "IntercambioNOR-OCC",
"MX-NO->US": "IntercambioUSA-NTE",
"MX-NE->US": "IntercambioUSA-NES",
"BZ->MX-PN": "IntercambioPEN-BEL"
}


def fetch_MX_exchange(sorted_zone_keys, s):
"""
Finds current flow between two Mexican control areas.
Returns a float.
"""

req = s.get(MX_EXCHANGE_URL)
soup = BeautifulSoup(req.text, 'html.parser')
exchange_div = soup.find("div", attrs={'id': EXCHANGES[sorted_zone_keys]})
val = exchange_div.text

# cenace html uses unicode hyphens instead of minus signs and , as thousand separator
trantab = str.maketrans({chr(8208): chr(45), ",": ""})

val = val.translate(trantab)
flow = float(val)

if sorted_zone_keys in ["BZ->MX-PN", "MX-CE->MX-OR", "MX-CE->MX-OC"]:
# reversal needed for these zones due to EM ordering
flow = -1*flow

return flow


def fetch_exchange(zone_key1, zone_key2, session=None, target_datetime=None,
logger=None):
"""Requests the last known power exchange (in MW) between two zones
Arguments:
zone_key1: the first country code
zone_key2: the second country code; order of the two codes in params
doesn't matter
session: request session passed in order to re-use an existing session
Return:
A dictionary in the form:
{
'sortedZoneKeys': 'DK->NO',
'datetime': '2017-01-01T00:00:00Z',
'netFlow': 0.0,
'source': 'mysource.com'
}
where net flow is from DK into NO
"""
sorted_zone_keys = '->'.join(sorted([zone_key1, zone_key2]))

if sorted_zone_keys not in EXCHANGES:
raise NotImplementedError('Exchange pair not supported: {}'.format(sorted_zone_keys))

s = session or requests.Session()

netflow = fetch_MX_exchange(sorted_zone_keys, s)

data = {
'sortedZoneKeys': sorted_zone_keys,
'datetime': arrow.now('America/Tijuana').datetime,
'netFlow': netflow,
'source': 'cenace.gob.mx'
}

return data


if __name__ == '__main__':
print("fetch_exchange(MX-NO, MX-NW)")
print(fetch_exchange("MX-NO", "MX-NW"))
print("fetch_exchange(MX-OR, MX-PN)")
print(fetch_exchange("MX-OR", "MX-PN"))
print("fetch_exchange(MX-NE, MX-OC)")
print(fetch_exchange("MX-NE", "MX-OC"))
print("fetch_exchange(MX-NE, MX-NO)")
print(fetch_exchange("MX-NE", "MX-NO"))
print("fetch_exchange(MX-OC, MX-OR)")
print(fetch_exchange("MX-OC", "MX-OR"))
print("fetch_exchange(MX-NE, US)")
print(fetch_exchange("MX-NE", "US"))
print("fetch_exchange(MX-CE, MX-OC)")
print(fetch_exchange("MX-CE", "MX-OC"))
print("fetch_exchange(MX-PN, BZ)")
print(fetch_exchange("MX-PN", "BZ"))
print("fetch_exchange(MX-NO, MX-OC)")
print(fetch_exchange("MX-NO", "MX-OC"))
print("fetch_exchange(MX-NO, US)")
print(fetch_exchange("MX-NO", "US"))
print("fetch_exchange(MX-NE, MX-OR)")
print(fetch_exchange("MX-NE", "MX-OR"))
print("fetch_exchange(MX-CE, MX-OR)")
print(fetch_exchange("MX-CE", "MX-OR"))
45 changes: 37 additions & 8 deletions parsers/US_CA.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#!/usr/bin/env python3

from collections import defaultdict

# The arrow library is used to handle datetimes
import arrow
# The pandas library is used to parse content through HTTP
import pandas
import requests
from bs4 import BeautifulSoup
from collections import defaultdict

FUEL_SOURCE_CSV = 'http://www.caiso.com/outlook/SP/fuelsource.csv'

MX_EXCHANGE_URL = 'http://www.cenace.gob.mx/Paginas/Publicas/Info/DemandaRegional.aspx'

def fetch_production(zone_key='US-CA', session=None, target_datetime=None,
logger=None):
Expand Down Expand Up @@ -156,6 +156,23 @@ def fetch_historical_data(target_datetime):
return daily_data, import_data


def fetch_MX_exchange(s):
req = s.get(MX_EXCHANGE_URL)
soup = BeautifulSoup(req.text, 'html.parser')
exchange_div = soup.find("div", attrs={'id': 'IntercambioUSA-BCA'})
val = exchange_div.text

# cenace html uses unicode hyphens instead of minus signs
try:
val = val.replace(chr(8208), chr(45))
except ValueError:
pass

# negative value indicates flow from CA to MX

return float(val)


def fetch_exchange(zone_key1, zone_key2, session=None, target_datetime=None,
logger=None):
"""Requests the last known power exchange (in MW) between two zones
Expand All @@ -175,9 +192,18 @@ def fetch_exchange(zone_key1, zone_key2, session=None, target_datetime=None,
where net flow is from DK into NO
"""
sorted_zone_keys = '->'.join(sorted([zone_key1, zone_key2]))
if sorted_zone_keys != 'US->US-CA':
raise NotImplementedError(
'Exchange pair not supported: {}'.format(sorted_zone_keys))

s = session or requests.Session()

if sorted_zone_keys == 'MX-BC->US-CA':
netflow = fetch_MX_exchange(s)
exchange = {
'sortedZoneKeys': sorted_zone_keys,
'datetime': arrow.now('America/Tijuana').datetime,
'netFlow': netflow,
'source': 'cenace.gob.mx'
}
return exchange

if target_datetime:
return fetch_historical_exchange(target_datetime)
Expand Down Expand Up @@ -214,4 +240,7 @@ def fetch_exchange(zone_key1, zone_key2, session=None, target_datetime=None,
pprint(fetch_production())

print('fetch_exchange("US-CA", "US") ->')
pprint(fetch_exchange("US-CA", "US"))
#pprint(fetch_exchange("US-CA", "US"))

print('fetch_exchange("MX-BC", "US-CA")')
pprint(fetch_exchange("MX-BC", "US-CA"))
15 changes: 14 additions & 1 deletion web/generate-geometries.js
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,20 @@ const zoneDefinitions = [
{zoneName: 'MR', type: 'country', id: 'MRT'},
{zoneName: 'MS', type: 'country', id: 'MSR'},
{zoneName: 'MU', type: 'country', id: 'MUS'},
{zoneName: 'MX', type: 'country', id: 'MEX'},
// {zoneName: 'MX', type: 'country', id: 'MEX'},
// {zoneName: 'MX', type: 'administrations', administrations: [
// 'MEX-2714', 'MEX-2716', 'MEX-2713', 'MEX-2715',
// 'MEX-2734', 'MEX-2721', 'MEX-2719', 'MEX-2717', 'MEX-2728', 'MEX-2728', 'MEX-2733','MEX-2730',
// 'MEX-2724', 'MEX-2726', 'MEX-2731', 'MEX-2718', 'MEX-2720', 'MEX-2727', 'MEX-2732', 'MEX-2724',
// 'MEX-2729', 'MEX-2723', 'MEX-2735', 'MEX-2725', 'MEX-2722', 'MEX-2737', 'MEX-2736']},
{zoneName: 'MX-BC', type: 'administrations', administrations: ['MEX-2706', 'MEX-2707']},
{zoneName: 'MX-CE', type: 'administrations', administrations: ['MEX-2724', 'MEX-2726', 'MEX-2727', 'MEX-2732']},
{zoneName: 'MX-NW', type: 'administrations', administrations: ['MEX-2711', 'MEX-2712']},
{zoneName: 'MX-NO', type: 'administrations', administrations: ['MEX-2708', 'MEX-2709', 'MEX-2710']},
{zoneName: 'MX-NE', type: 'administrations', administrations: ['MEX-2714', 'MEX-2716']},
{zoneName: 'MX-OC', type: 'administrations', administrations: ['MEX-2713', 'MEX-2715', 'MEX-2717', 'MEX-2718', 'MEX-2719', 'MEX-2720', 'MEX-2721', 'MEX-2728', 'MEX-2730', 'MEX-2731', 'MEX-2733']},
{zoneName: 'MX-OR', type: 'administrations', administrations: ['MEX-2723', 'MEX-2725', 'MEX-2729', 'MEX-2734', 'MEX-2735']},
{zoneName: 'MX-PN', type: 'administrations', administrations: ['MEX-2722', 'MEX-2736', 'MEX-2737']},
// {zoneName: 'MY', type: 'country', id: 'MYS'},
{zoneName: 'MW', type: 'country', id: 'MWI'},
{zoneName: 'MY-EM', type: 'administrations', administrations: ['MYS-1186', 'MYS-1187']},
Expand Down
Loading

0 comments on commit 227fa56

Please sign in to comment.