We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Chile (CL-SING): total generation stuck at fixed value
The total generation of the CL-SING zone is stuck at 1 939 MW. The renewable part varies, but the "unknown" category always fills the gap. The issue is on the data provider's side.

![image](https://user-images.githubusercontent.com/25743609/39404467-282d9276-4b94-11e8-9472-7cb3a589d39d.png)

![image](https://user-images.githubusercontent.com/25743609/39404501-a6889878-4b94-11e8-9aca-3e25307c52ee.png)
Chile (CL-SING): total generation stuck at fixed value
The total generation of the CL-SING zone is stuck at 1 939 MW. The renewable part varies, but the "unknown" category always fills the gap. The issue is on the data provider's side.

![image](https://user-images.githubusercontent.com/25743609/39404467-282d9276-4b94-11e8-9472-7cb3a589d39d.png)

![image](https://user-images.githubusercontent.com/25743609/39404501-a6889878-4b94-11e8-9aca-3e25307c52ee.png)

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `parsers/CL_SING.py`
Content:
```
1 #!/usr/bin/env python3
2 
3 # Parser for the Chile-SING region.
4 # There are plans to unify the Chilean grid.
5 # https://www.engerati.com/article/chile%E2%80%99s-grid-be-unified-new-transmission-highway
6 
7 from __future__ import print_function
8 import arrow
9 from bs4 import BeautifulSoup
10 from collections import defaultdict
11 import datetime
12 import json
13 from pytz import timezone
14 import pandas as pd
15 import re
16 import requests
17 
18 url = 'https://sger.coordinadorelectrico.cl/Charts/AmChartCurvaCompuesta?showinfo=True'
19 
20 plant_map = {
21              u"Generacion_Neta": "unknown",
22              u"Real Andes Solar": "solar",
23              u"Real Bolero": "solar",
24              u"Real Cerro Dominador PV": "solar",
25              u"Real Finis Terrae": "solar",
26              u"Real La Huayca 2": "solar",
27              u"Real Mar\xeda Elena FV": "solar",
28              u"Real PAS2": "solar",
29              u"Real PAS3": "solar",
30              u"Real Pampa Camarones": "solar",
31              u"Real Parque Eolico Sierra Gorda Este": "wind",
32              u"Real Puerto Seco Solar": "solar",
33              u"Real Solar El Aguila 1": "solar",
34              u"Real Solar Jama": "solar",
35              u"Real Solar Jama 2": "solar",
36              u"Real Uribe": "solar",
37              u"Real Valle de los Vientos": "wind",
38              u"date": "datetime"
39              }
40 
41 generation_mapping = {'Gas Natural': 'gas',
42                       'Eólico': 'wind',
43                       'Otro (Compensación Activa)': 'unknown',
44                       'Solar': 'solar',
45                       'Cogeneración': 'biomass',
46                       'Geotérmica': 'geothermal',
47                       'Hidro': 'hydro'}
48 
49 
50 def get_old_data(lookup_time, session=None):
51     """Fetches data for past days, returns a list of tuples."""
52 
53     s=session or requests.Session()
54     data_url = "http://cdec2.cdec-sing.cl/pls/portal/cdec.pck_oper_real_2_pub.qry_gen_real_2?p_fecha_ini={0}&p_fecha_fin={0}&p_informe=E".format(lookup_time)
55 
56     data_req = s.get(data_url)
57     df = pd.read_html(data_req.text, decimal=',', thousands = '', header=0, index_col=0)
58     df = df[-2]
59 
60     if df.shape != (14, 26):
61         raise AttributeError('Format for Cl-SING historical data has changed, check for new tables or generation types.')
62 
63     data = []
64     for col in df:
65         if col not in ['25', 'Total']:
66             hour = int(col)
67             production = df[col].to_dict()
68             production['coal'] = production['Carbón + Petcoke'] + production['Carbón'] + production['Petcoke']
69             production['oil'] = production['Diesel + Fuel Oil'] + production['Diesel'] + production['Fuel Oil Nro. 6']
70 
71             keys_to_remove = ['Carbón + Petcoke', 'Carbón', 'Petcoke', 'Diesel + Fuel Oil', 'Diesel', 'Fuel Oil Nro. 6', 'Total']
72 
73             for k in keys_to_remove:
74                 production.pop(k)
75 
76             production = {generation_mapping.get(k,k):v for k,v in production.items()}
77 
78             try:
79                 dt = arrow.get(lookup_time).replace(hour=hour, tzinfo='Chile/Continental').datetime
80             except ValueError:
81                 # Midnight is defined as 24 not 0 at end of day.
82                 dt = arrow.get(lookup_time).shift(days=+1)
83                 dt = dt.replace(tzinfo='Chile/Continental').datetime
84 
85             data.append((dt, production))
86 
87     return data
88 
89 
90 def get_data(session=None):
91     """
92     Makes a GET request to the data url.  Parses the returned page to find the
93     data which is contained in a javascript variable.
94     Returns a list of dictionaries.
95     """
96 
97     s = session or requests.Session()
98     datareq = s.get(url)
99     soup = BeautifulSoup(datareq.text, 'html.parser')
100     chartdata = soup.find('script', type="text/javascript").text
101 
102     # regex that finds js var called chartData, list can be variable length.
103     pattern = r'chartData = \[(.*)\]'
104     match = re.search(pattern, chartdata)
105     rawdata = match.group(0)
106 
107     # Cut down to just the list.
108     start = rawdata.index('[')
109     sliced = rawdata[start:]
110     loaded_data = json.loads(sliced)
111 
112     return loaded_data
113 
114 
115 def convert_time_str(ts):
116     """Takes a unicode time string and converts into an aware datetime object."""
117 
118     dt_naive = datetime.datetime.strptime(ts, '%Y-%m-%d %H:%M')
119     localtz = timezone('Chile/Continental')
120     dt_aware = localtz.localize(dt_naive)
121 
122     return dt_aware
123 
124 
125 def data_processer(data, logger):
126     """
127     Takes raw production data and converts it into a usable form.
128     Removes unneeded keys and sums generation types.
129     Returns a list.
130     """
131 
132     clean_data = []
133     for datapoint in data:
134         bad_keys = (u'Total_ERNC', u'Generacion Total')
135         for bad in bad_keys:
136             datapoint.pop(bad, None)
137 
138         for key in datapoint.keys():
139             if key not in plant_map.keys():
140                 logger.warning('{} is missing from the CL_SING plant mapping.'.format(key))
141 
142         mapped_plants = [(plant_map.get(plant, 'unknown'), val) for plant, val
143                          in datapoint.items()]
144         datapoint = defaultdict(lambda: 0.0)
145 
146         # Sum values for duplicate keys.
147         for key, val in mapped_plants:
148             try:
149                 datapoint[key] += val
150             except TypeError:
151                 # datetime key is a string!
152                 datapoint[key] = val
153 
154         dt = convert_time_str(datapoint['datetime'])
155         datapoint.pop('datetime')
156         clean_data.append((dt, dict(datapoint)))
157 
158     return clean_data
159 
160 
161 def fetch_production(zone_key='CL-SING', session=None, target_datetime=None, logger=None):
162     """
163     Requests the last known production mix (in MW) of a given country
164     Arguments:
165     zone_key (optional) -- used in case a parser is able to fetch multiple countries
166     Return:
167     A dictionary in the form:
168     {
169       'zoneKey': 'FR',
170       'datetime': '2017-01-01T00:00:00Z',
171       'production': {
172           'biomass': 0.0,
173           'coal': 0.0,
174           'gas': 0.0,
175           'hydro': 0.0,
176           'nuclear': null,
177           'oil': 0.0,
178           'solar': 0.0,
179           'wind': 0.0,
180           'geothermal': 0.0,
181           'unknown': 0.0
182       },
183       'storage': {
184           'hydro': -10.0,
185       },
186       'source': 'mysource.com'
187     }
188     """
189     if target_datetime:
190         lookup_time = arrow.get(target_datetime).floor('day').format('DD-MM-YYYY')
191         dp = get_old_data(lookup_time, session)
192     else:
193         gd = get_data(session)
194         dp = data_processer(gd, logger)
195 
196     production_mix_by_hour = []
197     for point in dp:
198         production_mix = {
199           'zoneKey': zone_key,
200           'datetime': point[0],
201           'production': point[1],
202           'source': 'sger.coordinadorelectrico.cl'
203         }
204         production_mix_by_hour.append(production_mix)
205 
206     return production_mix_by_hour
207 
208 
209 if __name__ == '__main__':
210     """Main method, never used by the Electricity Map backend, but handy for testing."""
211 
212     print('fetch_production() ->')
213     print(fetch_production())
214     print('fetch_production(target_datetime=01/01/2018)')
215     print(fetch_production(target_datetime='01/01/2018'))
216
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/parsers/CL_SING.py b/parsers/CL_SING.py
--- a/parsers/CL_SING.py
+++ b/parsers/CL_SING.py
@@ -7,7 +7,7 @@
 from __future__ import print_function
 import arrow
 from bs4 import BeautifulSoup
-from collections import defaultdict
+from collections import defaultdict, Counter
 import datetime
 import json
 from pytz import timezone
@@ -203,6 +203,16 @@
         }
         production_mix_by_hour.append(production_mix)
 
+    # if production is the same for at least 3 in the last 48 hours, it's
+    # considered constant. Using round because of floating-point precision.
+    c = Counter([round(sum(v for _, v in e['production'].items()), 3)
+                 for e in production_mix_by_hour])
+    most_common = c.most_common(1)[0]
+    if most_common[1] > 3:
+        raise ValueError(
+            'Detected constant prod in CL-SING. Value {} occured {} times in '
+            'the last 48 hours.'.format(most_common[0], most_common[1]))
+
     return production_mix_by_hour

{"golden_diff": "diff --git a/parsers/CL_SING.py b/parsers/CL_SING.py\n--- a/parsers/CL_SING.py\n+++ b/parsers/CL_SING.py\n@@ -7,7 +7,7 @@\n from __future__ import print_function\n import arrow\n from bs4 import BeautifulSoup\n-from collections import defaultdict\n+from collections import defaultdict, Counter\n import datetime\n import json\n from pytz import timezone\n@@ -203,6 +203,16 @@\n         }\n         production_mix_by_hour.append(production_mix)\n \n+    # if production is the same for at least 3 in the last 48 hours, it's\n+    # considered constant. Using round because of floating-point precision.\n+    c = Counter([round(sum(v for _, v in e['production'].items()), 3)\n+                 for e in production_mix_by_hour])\n+    most_common = c.most_common(1)[0]\n+    if most_common[1] > 3:\n+        raise ValueError(\n+            'Detected constant prod in CL-SING. Value {} occured {} times in '\n+            'the last 48 hours.'.format(most_common[0], most_common[1]))\n+\n     return production_mix_by_hour\n", "issue": "Chile (CL-SING): total generation stuck at fixed value\nThe total generation of the CL-SING zone is stuck at 1 939 MW. The renewable part varies, but the \"unknown\" category always fills the gap. The issue is on the data provider's side.\r\n\r\n![image](https://user-images.githubusercontent.com/25743609/39404467-282d9276-4b94-11e8-9472-7cb3a589d39d.png)\r\n\r\n![image](https://user-images.githubusercontent.com/25743609/39404501-a6889878-4b94-11e8-9aca-3e25307c52ee.png)\nChile (CL-SING): total generation stuck at fixed value\nThe total generation of the CL-SING zone is stuck at 1 939 MW. The renewable part varies, but the \"unknown\" category always fills the gap. The issue is on the data provider's side.\r\n\r\n![image](https://user-images.githubusercontent.com/25743609/39404467-282d9276-4b94-11e8-9472-7cb3a589d39d.png)\r\n\r\n![image](https://user-images.githubusercontent.com/25743609/39404501-a6889878-4b94-11e8-9aca-3e25307c52ee.png)\n", "before_files": [{"content": "#!/usr/bin/env python3\n\n# Parser for the Chile-SING region.\n# There are plans to unify the Chilean grid.\n# https://www.engerati.com/article/chile%E2%80%99s-grid-be-unified-new-transmission-highway\n\nfrom __future__ import print_function\nimport arrow\nfrom bs4 import BeautifulSoup\nfrom collections import defaultdict\nimport datetime\nimport json\nfrom pytz import timezone\nimport pandas as pd\nimport re\nimport requests\n\nurl = 'https://sger.coordinadorelectrico.cl/Charts/AmChartCurvaCompuesta?showinfo=True'\n\nplant_map = {\n             u\"Generacion_Neta\": \"unknown\",\n             u\"Real Andes Solar\": \"solar\",\n             u\"Real Bolero\": \"solar\",\n             u\"Real Cerro Dominador PV\": \"solar\",\n             u\"Real Finis Terrae\": \"solar\",\n             u\"Real La Huayca 2\": \"solar\",\n             u\"Real Mar\\xeda Elena FV\": \"solar\",\n             u\"Real PAS2\": \"solar\",\n             u\"Real PAS3\": \"solar\",\n             u\"Real Pampa Camarones\": \"solar\",\n             u\"Real Parque Eolico Sierra Gorda Este\": \"wind\",\n             u\"Real Puerto Seco Solar\": \"solar\",\n             u\"Real Solar El Aguila 1\": \"solar\",\n             u\"Real Solar Jama\": \"solar\",\n             u\"Real Solar Jama 2\": \"solar\",\n             u\"Real Uribe\": \"solar\",\n             u\"Real Valle de los Vientos\": \"wind\",\n             u\"date\": \"datetime\"\n             }\n\ngeneration_mapping = {'Gas Natural': 'gas',\n                      'E\u00f3lico': 'wind',\n                      'Otro (Compensaci\u00f3n Activa)': 'unknown',\n                      'Solar': 'solar',\n                      'Cogeneraci\u00f3n': 'biomass',\n                      'Geot\u00e9rmica': 'geothermal',\n                      'Hidro': 'hydro'}\n\n\ndef get_old_data(lookup_time, session=None):\n    \"\"\"Fetches data for past days, returns a list of tuples.\"\"\"\n\n    s=session or requests.Session()\n    data_url = \"http://cdec2.cdec-sing.cl/pls/portal/cdec.pck_oper_real_2_pub.qry_gen_real_2?p_fecha_ini={0}&p_fecha_fin={0}&p_informe=E\".format(lookup_time)\n\n    data_req = s.get(data_url)\n    df = pd.read_html(data_req.text, decimal=',', thousands = '', header=0, index_col=0)\n    df = df[-2]\n\n    if df.shape != (14, 26):\n        raise AttributeError('Format for Cl-SING historical data has changed, check for new tables or generation types.')\n\n    data = []\n    for col in df:\n        if col not in ['25', 'Total']:\n            hour = int(col)\n            production = df[col].to_dict()\n            production['coal'] = production['Carb\u00f3n + Petcoke'] + production['Carb\u00f3n'] + production['Petcoke']\n            production['oil'] = production['Diesel + Fuel Oil'] + production['Diesel'] + production['Fuel Oil Nro. 6']\n\n            keys_to_remove = ['Carb\u00f3n + Petcoke', 'Carb\u00f3n', 'Petcoke', 'Diesel + Fuel Oil', 'Diesel', 'Fuel Oil Nro. 6', 'Total']\n\n            for k in keys_to_remove:\n                production.pop(k)\n\n            production = {generation_mapping.get(k,k):v for k,v in production.items()}\n\n            try:\n                dt = arrow.get(lookup_time).replace(hour=hour, tzinfo='Chile/Continental').datetime\n            except ValueError:\n                # Midnight is defined as 24 not 0 at end of day.\n                dt = arrow.get(lookup_time).shift(days=+1)\n                dt = dt.replace(tzinfo='Chile/Continental').datetime\n\n            data.append((dt, production))\n\n    return data\n\n\ndef get_data(session=None):\n    \"\"\"\n    Makes a GET request to the data url.  Parses the returned page to find the\n    data which is contained in a javascript variable.\n    Returns a list of dictionaries.\n    \"\"\"\n\n    s = session or requests.Session()\n    datareq = s.get(url)\n    soup = BeautifulSoup(datareq.text, 'html.parser')\n    chartdata = soup.find('script', type=\"text/javascript\").text\n\n    # regex that finds js var called chartData, list can be variable length.\n    pattern = r'chartData = \\[(.*)\\]'\n    match = re.search(pattern, chartdata)\n    rawdata = match.group(0)\n\n    # Cut down to just the list.\n    start = rawdata.index('[')\n    sliced = rawdata[start:]\n    loaded_data = json.loads(sliced)\n\n    return loaded_data\n\n\ndef convert_time_str(ts):\n    \"\"\"Takes a unicode time string and converts into an aware datetime object.\"\"\"\n\n    dt_naive = datetime.datetime.strptime(ts, '%Y-%m-%d %H:%M')\n    localtz = timezone('Chile/Continental')\n    dt_aware = localtz.localize(dt_naive)\n\n    return dt_aware\n\n\ndef data_processer(data, logger):\n    \"\"\"\n    Takes raw production data and converts it into a usable form.\n    Removes unneeded keys and sums generation types.\n    Returns a list.\n    \"\"\"\n\n    clean_data = []\n    for datapoint in data:\n        bad_keys = (u'Total_ERNC', u'Generacion Total')\n        for bad in bad_keys:\n            datapoint.pop(bad, None)\n\n        for key in datapoint.keys():\n            if key not in plant_map.keys():\n                logger.warning('{} is missing from the CL_SING plant mapping.'.format(key))\n\n        mapped_plants = [(plant_map.get(plant, 'unknown'), val) for plant, val\n                         in datapoint.items()]\n        datapoint = defaultdict(lambda: 0.0)\n\n        # Sum values for duplicate keys.\n        for key, val in mapped_plants:\n            try:\n                datapoint[key] += val\n            except TypeError:\n                # datetime key is a string!\n                datapoint[key] = val\n\n        dt = convert_time_str(datapoint['datetime'])\n        datapoint.pop('datetime')\n        clean_data.append((dt, dict(datapoint)))\n\n    return clean_data\n\n\ndef fetch_production(zone_key='CL-SING', session=None, target_datetime=None, logger=None):\n    \"\"\"\n    Requests the last known production mix (in MW) of a given country\n    Arguments:\n    zone_key (optional) -- used in case a parser is able to fetch multiple countries\n    Return:\n    A dictionary in the form:\n    {\n      'zoneKey': 'FR',\n      'datetime': '2017-01-01T00:00:00Z',\n      'production': {\n          'biomass': 0.0,\n          'coal': 0.0,\n          'gas': 0.0,\n          'hydro': 0.0,\n          'nuclear': null,\n          'oil': 0.0,\n          'solar': 0.0,\n          'wind': 0.0,\n          'geothermal': 0.0,\n          'unknown': 0.0\n      },\n      'storage': {\n          'hydro': -10.0,\n      },\n      'source': 'mysource.com'\n    }\n    \"\"\"\n    if target_datetime:\n        lookup_time = arrow.get(target_datetime).floor('day').format('DD-MM-YYYY')\n        dp = get_old_data(lookup_time, session)\n    else:\n        gd = get_data(session)\n        dp = data_processer(gd, logger)\n\n    production_mix_by_hour = []\n    for point in dp:\n        production_mix = {\n          'zoneKey': zone_key,\n          'datetime': point[0],\n          'production': point[1],\n          'source': 'sger.coordinadorelectrico.cl'\n        }\n        production_mix_by_hour.append(production_mix)\n\n    return production_mix_by_hour\n\n\nif __name__ == '__main__':\n    \"\"\"Main method, never used by the Electricity Map backend, but handy for testing.\"\"\"\n\n    print('fetch_production() ->')\n    print(fetch_production())\n    print('fetch_production(target_datetime=01/01/2018)')\n    print(fetch_production(target_datetime='01/01/2018'))\n", "path": "parsers/CL_SING.py"}], "after_files": [{"content": "#!/usr/bin/env python3\n\n# Parser for the Chile-SING region.\n# There are plans to unify the Chilean grid.\n# https://www.engerati.com/article/chile%E2%80%99s-grid-be-unified-new-transmission-highway\n\nfrom __future__ import print_function\nimport arrow\nfrom bs4 import BeautifulSoup\nfrom collections import defaultdict, Counter\nimport datetime\nimport json\nfrom pytz import timezone\nimport pandas as pd\nimport re\nimport requests\n\nurl = 'https://sger.coordinadorelectrico.cl/Charts/AmChartCurvaCompuesta?showinfo=True'\n\nplant_map = {\n             u\"Generacion_Neta\": \"unknown\",\n             u\"Real Andes Solar\": \"solar\",\n             u\"Real Bolero\": \"solar\",\n             u\"Real Cerro Dominador PV\": \"solar\",\n             u\"Real Finis Terrae\": \"solar\",\n             u\"Real La Huayca 2\": \"solar\",\n             u\"Real Mar\\xeda Elena FV\": \"solar\",\n             u\"Real PAS2\": \"solar\",\n             u\"Real PAS3\": \"solar\",\n             u\"Real Pampa Camarones\": \"solar\",\n             u\"Real Parque Eolico Sierra Gorda Este\": \"wind\",\n             u\"Real Puerto Seco Solar\": \"solar\",\n             u\"Real Solar El Aguila 1\": \"solar\",\n             u\"Real Solar Jama\": \"solar\",\n             u\"Real Solar Jama 2\": \"solar\",\n             u\"Real Uribe\": \"solar\",\n             u\"Real Valle de los Vientos\": \"wind\",\n             u\"date\": \"datetime\"\n             }\n\ngeneration_mapping = {'Gas Natural': 'gas',\n                      'E\u00f3lico': 'wind',\n                      'Otro (Compensaci\u00f3n Activa)': 'unknown',\n                      'Solar': 'solar',\n                      'Cogeneraci\u00f3n': 'biomass',\n                      'Geot\u00e9rmica': 'geothermal',\n                      'Hidro': 'hydro'}\n\n\ndef get_old_data(lookup_time, session=None):\n    \"\"\"Fetches data for past days, returns a list of tuples.\"\"\"\n\n    s=session or requests.Session()\n    data_url = \"http://cdec2.cdec-sing.cl/pls/portal/cdec.pck_oper_real_2_pub.qry_gen_real_2?p_fecha_ini={0}&p_fecha_fin={0}&p_informe=E\".format(lookup_time)\n\n    data_req = s.get(data_url)\n    df = pd.read_html(data_req.text, decimal=',', thousands = '', header=0, index_col=0)\n    df = df[-2]\n\n    if df.shape != (14, 26):\n        raise AttributeError('Format for Cl-SING historical data has changed, check for new tables or generation types.')\n\n    data = []\n    for col in df:\n        if col not in ['25', 'Total']:\n            hour = int(col)\n            production = df[col].to_dict()\n            production['coal'] = production['Carb\u00f3n + Petcoke'] + production['Carb\u00f3n'] + production['Petcoke']\n            production['oil'] = production['Diesel + Fuel Oil'] + production['Diesel'] + production['Fuel Oil Nro. 6']\n\n            keys_to_remove = ['Carb\u00f3n + Petcoke', 'Carb\u00f3n', 'Petcoke', 'Diesel + Fuel Oil', 'Diesel', 'Fuel Oil Nro. 6', 'Total']\n\n            for k in keys_to_remove:\n                production.pop(k)\n\n            production = {generation_mapping.get(k,k):v for k,v in production.items()}\n\n            try:\n                dt = arrow.get(lookup_time).replace(hour=hour, tzinfo='Chile/Continental').datetime\n            except ValueError:\n                # Midnight is defined as 24 not 0 at end of day.\n                dt = arrow.get(lookup_time).shift(days=+1)\n                dt = dt.replace(tzinfo='Chile/Continental').datetime\n\n            data.append((dt, production))\n\n    return data\n\n\ndef get_data(session=None):\n    \"\"\"\n    Makes a GET request to the data url.  Parses the returned page to find the\n    data which is contained in a javascript variable.\n    Returns a list of dictionaries.\n    \"\"\"\n\n    s = session or requests.Session()\n    datareq = s.get(url)\n    soup = BeautifulSoup(datareq.text, 'html.parser')\n    chartdata = soup.find('script', type=\"text/javascript\").text\n\n    # regex that finds js var called chartData, list can be variable length.\n    pattern = r'chartData = \\[(.*)\\]'\n    match = re.search(pattern, chartdata)\n    rawdata = match.group(0)\n\n    # Cut down to just the list.\n    start = rawdata.index('[')\n    sliced = rawdata[start:]\n    loaded_data = json.loads(sliced)\n\n    return loaded_data\n\n\ndef convert_time_str(ts):\n    \"\"\"Takes a unicode time string and converts into an aware datetime object.\"\"\"\n\n    dt_naive = datetime.datetime.strptime(ts, '%Y-%m-%d %H:%M')\n    localtz = timezone('Chile/Continental')\n    dt_aware = localtz.localize(dt_naive)\n\n    return dt_aware\n\n\ndef data_processer(data, logger):\n    \"\"\"\n    Takes raw production data and converts it into a usable form.\n    Removes unneeded keys and sums generation types.\n    Returns a list.\n    \"\"\"\n\n    clean_data = []\n    for datapoint in data:\n        bad_keys = (u'Total_ERNC', u'Generacion Total')\n        for bad in bad_keys:\n            datapoint.pop(bad, None)\n\n        for key in datapoint.keys():\n            if key not in plant_map.keys():\n                logger.warning('{} is missing from the CL_SING plant mapping.'.format(key))\n\n        mapped_plants = [(plant_map.get(plant, 'unknown'), val) for plant, val\n                         in datapoint.items()]\n        datapoint = defaultdict(lambda: 0.0)\n\n        # Sum values for duplicate keys.\n        for key, val in mapped_plants:\n            try:\n                datapoint[key] += val\n            except TypeError:\n                # datetime key is a string!\n                datapoint[key] = val\n\n        dt = convert_time_str(datapoint['datetime'])\n        datapoint.pop('datetime')\n        clean_data.append((dt, dict(datapoint)))\n\n    return clean_data\n\n\ndef fetch_production(zone_key='CL-SING', session=None, target_datetime=None, logger=None):\n    \"\"\"\n    Requests the last known production mix (in MW) of a given country\n    Arguments:\n    zone_key (optional) -- used in case a parser is able to fetch multiple countries\n    Return:\n    A dictionary in the form:\n    {\n      'zoneKey': 'FR',\n      'datetime': '2017-01-01T00:00:00Z',\n      'production': {\n          'biomass': 0.0,\n          'coal': 0.0,\n          'gas': 0.0,\n          'hydro': 0.0,\n          'nuclear': null,\n          'oil': 0.0,\n          'solar': 0.0,\n          'wind': 0.0,\n          'geothermal': 0.0,\n          'unknown': 0.0\n      },\n      'storage': {\n          'hydro': -10.0,\n      },\n      'source': 'mysource.com'\n    }\n    \"\"\"\n    if target_datetime:\n        lookup_time = arrow.get(target_datetime).floor('day').format('DD-MM-YYYY')\n        dp = get_old_data(lookup_time, session)\n    else:\n        gd = get_data(session)\n        dp = data_processer(gd, logger)\n\n    production_mix_by_hour = []\n    for point in dp:\n        production_mix = {\n          'zoneKey': zone_key,\n          'datetime': point[0],\n          'production': point[1],\n          'source': 'sger.coordinadorelectrico.cl'\n        }\n        production_mix_by_hour.append(production_mix)\n\n    # if production is the same for at least 3 in the last 48 hours, it's\n    # considered constant. Using round because of floating-point precision.\n    c = Counter([round(sum(v for _, v in e['production'].items()), 3)\n                 for e in production_mix_by_hour])\n    most_common = c.most_common(1)[0]\n    if most_common[1] > 3:\n        raise ValueError(\n            'Detected constant prod in CL-SING. Value {} occured {} times in '\n            'the last 48 hours.'.format(most_common[0], most_common[1]))\n\n    return production_mix_by_hour\n\n\nif __name__ == '__main__':\n    \"\"\"Main method, never used by the Electricity Map backend, but handy for testing.\"\"\"\n\n    print('fetch_production() ->')\n    print(fetch_production())\n    print('fetch_production(target_datetime=01/01/2018)')\n    print(fetch_production(target_datetime='01/01/2018'))\n", "path": "parsers/CL_SING.py"}]}