We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Namibia parser should read out data timestamp
Current time in Namibia is 2 April 14:41 but the [Nampower dashboard](http://www.nampower.com.na/Scada.aspx) is stuck on 1 April 11:45:40.

Timestamp of the data is available in http://www.nampower.com.na/gis/images/File_Info.png

We could OCR that file. A quick attempt using the current tesseract methods and upscaling from 600x20 to 1200x40 seems to have worked alright, I got `SCADA File Dene: 01/04/2018 11:45:40` (it should have been "File Date" but otherwise correct). There is always the danger of OCR misreading, so maybe only ignore the datapoint if the timestamp can be read and is too far in the past.

Screenshot from our page:
![na](https://user-images.githubusercontent.com/47415/38196680-b4fa6000-3684-11e8-95a4-9c03b6a19576.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/NA.py`
Content:
```
1 #!/usr/bin/env python3
2 
3 """Parser for Namibia."""
4 
5 import arrow
6 from bs4 import BeautifulSoup
7 from .lib.validation import validate
8 from logging import getLogger
9 from PIL import Image
10 from pytesseract import image_to_string
11 import re
12 import requests
13 
14 
15 generation_link = 'http://www.nampower.com.na/gis/images/Gx.png'
16 exchanges_link = 'http://www.nampower.com.na/gis/images/Imports_Exports.png'
17 
18 plant_mapping = {"Ruacana": "hydro",
19                  "Van Eck": "coal",
20                  "Paratus": "oil",
21                  "Anixas": "oil",
22                  "Solar": "solar",
23                  "Wind": "wind"
24                  }
25 
26 exchange_mapping = {"NA->ZA": "ESKOM",
27                     "NA->ZM": "ZESCO"
28                     }
29 
30 
31 def get_text_from_image(link, expected_size, new_size, logger, session=None):
32     """
33     Gets image from link and checks expected size vs actual.
34     Converts to black & white and enlarges to improve OCR accuracy.
35     Performs OCR using tesseract and returns a str.
36     """
37 
38     s = session or requests.Session()
39     img = Image.open(s.get(link, stream=True).raw)
40 
41     if img.size != expected_size:
42         if (logger):
43             logger.warning("Check Namibia Scada dashboard for {} changes.".format(link),
44                 extras={'key': 'NA'})
45         else:
46             print("Check Namibia Scada dashboard for {} changes.".format(link))
47 
48     gray = img.convert('L')
49     gray_enlarged = gray.resize(new_size, Image.LANCZOS)
50     text = image_to_string(gray_enlarged, lang='eng')
51 
52     return text
53 
54 
55 def data_processor(text):
56     """
57     Takes text produced from OCR and extracts production.
58     Returns a dictionary.
59     """
60 
61     production = {}
62     for k in plant_mapping.keys():
63         pattern = re.escape(k) + r": (\d+\.\d\d)"
64         try:
65             val = re.search(pattern, text).group(1)
66             production[plant_mapping[k]] = production.get(plant_mapping[k], 0.0)+ float(val)
67         except (AttributeError, ValueError) as e:
68             production[plant_mapping[k]] = None
69 
70     return production
71 
72 
73 def fetch_production(zone_key = 'NA', session=None, target_datetime=None, logger=getLogger(__name__)):
74     """
75     Requests the last known production mix (in MW) of a given country
76     Arguments:
77     zone_key (optional) -- used in case a parser is able to fetch multiple countries
78     session (optional)      -- request session passed in order to re-use an existing session
79     Return:
80     A dictionary in the form:
81     {
82       'zoneKey': 'FR',
83       'datetime': '2017-01-01T00:00:00Z',
84       'production': {
85           'biomass': 0.0,
86           'coal': 0.0,
87           'gas': 0.0,
88           'hydro': 0.0,
89           'nuclear': null,
90           'oil': 0.0,
91           'solar': 0.0,
92           'wind': 0.0,
93           'geothermal': 0.0,
94           'unknown': 0.0
95       },
96       'storage': {
97           'hydro': -10.0,
98       },
99       'source': 'mysource.com'
100     }
101     """
102     if target_datetime:
103         raise NotImplementedError('This parser is not yet able to parse past dates')
104 
105     raw_text = get_text_from_image(session=session, link=generation_link, \
106                                    expected_size=(400, 245), new_size=(1000,612), \
107                                    logger=logger)
108 
109     production = data_processor(raw_text)
110 
111     data = {
112           'zoneKey': zone_key,
113           'datetime': arrow.now('Africa/Windhoek').datetime,
114           'production': production,
115           'storage': {},
116           'source': 'nampower.com.na'
117           }
118 
119     data = validate(data, required=['hydro'])
120 
121     return data
122 
123 
124 def exchange_processor(text, exchange, logger):
125     """
126     Takes text produced from OCR and extracts exchange flow.
127     Returns a float or None.
128     """
129 
130     utility = exchange_mapping[exchange]
131 
132     try:
133         pattern = re.escape(utility) + r"([\D]*?)([-+]?\d+\.\d\d)"
134         val = re.search(pattern, text).group(2)
135         flow = float(val)
136     except (AttributeError, ValueError) as e:
137         logger.warning("""{} regex match failed on the following text.
138                           {}""".format(exchange, text))
139         raise Exception("Exchange {} cannot be read.".format(exchange)) from e
140 
141     return flow
142 
143 
144 def fetch_exchange(zone_key1, zone_key2, session=None, target_datetime=None, logger=getLogger(__name__)):
145     """Requests the last known power exchange (in MW) between two zones
146     Arguments:
147     zone_key1           -- the first country code
148     zone_key2           -- the second country code; order of the two codes in params doesn't matter
149     session (optional)      -- request session passed in order to re-use an existing session
150     Return:
151     A dictionary in the form:
152     {
153       'sortedZoneKeys': 'DK->NO',
154       'datetime': '2017-01-01T00:00:00Z',
155       'netFlow': 0.0,
156       'source': 'mysource.com'
157     }
158     where net flow is from DK into NO
159     """
160     if target_datetime:
161         raise NotImplementedError('This parser is not yet able to parse past dates')
162 
163     sorted_codes = "->".join(sorted([zone_key1, zone_key2]))
164 
165     raw_text = get_text_from_image(session=session, link=exchanges_link,
166                                    expected_size=(400, 195), new_size=(1120, 546), \
167                                    logger=logger)
168 
169     if sorted_codes == 'NA->ZA':
170         flow = exchange_processor(raw_text, 'NA->ZA', logger=logger)
171     elif sorted_codes == 'NA->ZM':
172         flow = exchange_processor(raw_text, 'NA->ZM', logger=logger)
173     else:
174         raise NotImplementedError('This exchange pair is not implemented')
175 
176     #Import considered positive in data source.
177     if flow is not None:
178         flow = -1 * flow
179 
180     exchange = {'sortedZoneKeys': sorted_codes,
181                 'datetime': arrow.now('Africa/Windhoek').datetime,
182                 'netFlow': flow,
183                 'source': 'nampower.com.na'
184                 }
185 
186     return exchange
187 
188 
189 if __name__ == '__main__':
190     print('fetch_production() ->')
191     print(fetch_production())
192     print('fetch_exchange(NA, ZA)')
193     print(fetch_exchange('NA', 'ZA'))
194     print('fetch_exchange(NA, ZM)')
195     print(fetch_exchange('NA', 'ZM'))
196
```
--- 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/NA.py b/parsers/NA.py
--- a/parsers/NA.py
+++ b/parsers/NA.py
@@ -12,6 +12,7 @@
 import requests
 
 
+timestamp_link = 'http://www.nampower.com.na/gis/images/File_Info.png'
 generation_link = 'http://www.nampower.com.na/gis/images/Gx.png'
 exchanges_link = 'http://www.nampower.com.na/gis/images/Imports_Exports.png'
 
@@ -39,11 +40,8 @@
     img = Image.open(s.get(link, stream=True).raw)
 
     if img.size != expected_size:
-        if (logger):
-            logger.warning("Check Namibia Scada dashboard for {} changes.".format(link),
-                extras={'key': 'NA'})
-        else:
-            print("Check Namibia Scada dashboard for {} changes.".format(link))
+        logger.warning("Check Namibia Scada dashboard for {} changes.".format(link),
+            extras={'key': 'NA'})
 
     gray = img.convert('L')
     gray_enlarged = gray.resize(new_size, Image.LANCZOS)
@@ -52,6 +50,35 @@
     return text
 
 
+def check_timestamp(session=None, logger=None):
+    """
+    Sometimes the Scada Dashboard image stops updating for a while.
+    This function tries to ensure that only data younger than 1 hour
+    is accepted.
+    """
+
+    scada_info = get_text_from_image(session=session, link=timestamp_link,
+                                     expected_size=(600,20), new_size=(1200,40),
+                                     logger=logger)
+
+    timestamp = scada_info.split(':', 1)[1]
+
+    try:
+        scada_time = arrow.get(timestamp, ' DD/MM/YYYY HH:mm:ss')
+    except arrow.parser.ParserError as e:
+        logger.warning('Namibia scada timestamp cannot be read, got {}.'.format(timestamp))
+        # The OCR of the Scada dashboard is not very reliable, on failure safer to assume data is good.
+        return
+
+    data_time = scada_time.replace(tzinfo='Africa/Windhoek')
+    current_time = arrow.now('Africa/Windhoek')
+    diff = current_time - data_time
+
+    # Need to be sure we don't get old data if image stops updating.
+    if diff.seconds > 3600:
+        raise ValueError('Namibia scada data is too old to use, data is {} hours old.'.format(diff.seconds/3600))
+
+
 def data_processor(text):
     """
     Takes text produced from OCR and extracts production.
@@ -107,6 +134,7 @@
                                    logger=logger)
 
     production = data_processor(raw_text)
+    check_timestamp(session=session, logger=logger)
 
     data = {
           'zoneKey': zone_key,
@@ -177,6 +205,8 @@
     if flow is not None:
         flow = -1 * flow
 
+    check_timestamp(session=session, logger=logger)
+
     exchange = {'sortedZoneKeys': sorted_codes,
                 'datetime': arrow.now('Africa/Windhoek').datetime,
                 'netFlow': flow,

{"golden_diff": "diff --git a/parsers/NA.py b/parsers/NA.py\n--- a/parsers/NA.py\n+++ b/parsers/NA.py\n@@ -12,6 +12,7 @@\n import requests\n \n \n+timestamp_link = 'http://www.nampower.com.na/gis/images/File_Info.png'\n generation_link = 'http://www.nampower.com.na/gis/images/Gx.png'\n exchanges_link = 'http://www.nampower.com.na/gis/images/Imports_Exports.png'\n \n@@ -39,11 +40,8 @@\n     img = Image.open(s.get(link, stream=True).raw)\n \n     if img.size != expected_size:\n-        if (logger):\n-            logger.warning(\"Check Namibia Scada dashboard for {} changes.\".format(link),\n-                extras={'key': 'NA'})\n-        else:\n-            print(\"Check Namibia Scada dashboard for {} changes.\".format(link))\n+        logger.warning(\"Check Namibia Scada dashboard for {} changes.\".format(link),\n+            extras={'key': 'NA'})\n \n     gray = img.convert('L')\n     gray_enlarged = gray.resize(new_size, Image.LANCZOS)\n@@ -52,6 +50,35 @@\n     return text\n \n \n+def check_timestamp(session=None, logger=None):\n+    \"\"\"\n+    Sometimes the Scada Dashboard image stops updating for a while.\n+    This function tries to ensure that only data younger than 1 hour\n+    is accepted.\n+    \"\"\"\n+\n+    scada_info = get_text_from_image(session=session, link=timestamp_link,\n+                                     expected_size=(600,20), new_size=(1200,40),\n+                                     logger=logger)\n+\n+    timestamp = scada_info.split(':', 1)[1]\n+\n+    try:\n+        scada_time = arrow.get(timestamp, ' DD/MM/YYYY HH:mm:ss')\n+    except arrow.parser.ParserError as e:\n+        logger.warning('Namibia scada timestamp cannot be read, got {}.'.format(timestamp))\n+        # The OCR of the Scada dashboard is not very reliable, on failure safer to assume data is good.\n+        return\n+\n+    data_time = scada_time.replace(tzinfo='Africa/Windhoek')\n+    current_time = arrow.now('Africa/Windhoek')\n+    diff = current_time - data_time\n+\n+    # Need to be sure we don't get old data if image stops updating.\n+    if diff.seconds > 3600:\n+        raise ValueError('Namibia scada data is too old to use, data is {} hours old.'.format(diff.seconds/3600))\n+\n+\n def data_processor(text):\n     \"\"\"\n     Takes text produced from OCR and extracts production.\n@@ -107,6 +134,7 @@\n                                    logger=logger)\n \n     production = data_processor(raw_text)\n+    check_timestamp(session=session, logger=logger)\n \n     data = {\n           'zoneKey': zone_key,\n@@ -177,6 +205,8 @@\n     if flow is not None:\n         flow = -1 * flow\n \n+    check_timestamp(session=session, logger=logger)\n+\n     exchange = {'sortedZoneKeys': sorted_codes,\n                 'datetime': arrow.now('Africa/Windhoek').datetime,\n                 'netFlow': flow,\n", "issue": "Namibia parser should read out data timestamp\nCurrent time in Namibia is 2 April 14:41 but the [Nampower dashboard](http://www.nampower.com.na/Scada.aspx) is stuck on 1 April 11:45:40.\r\n\r\nTimestamp of the data is available in http://www.nampower.com.na/gis/images/File_Info.png\r\n\r\nWe could OCR that file. A quick attempt using the current tesseract methods and upscaling from 600x20 to 1200x40 seems to have worked alright, I got `SCADA File Dene: 01/04/2018 11:45:40` (it should have been \"File Date\" but otherwise correct). There is always the danger of OCR misreading, so maybe only ignore the datapoint if the timestamp can be read and is too far in the past.\r\n\r\nScreenshot from our page:\r\n![na](https://user-images.githubusercontent.com/47415/38196680-b4fa6000-3684-11e8-95a4-9c03b6a19576.png)\r\n\r\n\n", "before_files": [{"content": "#!/usr/bin/env python3\n\n\"\"\"Parser for Namibia.\"\"\"\n\nimport arrow\nfrom bs4 import BeautifulSoup\nfrom .lib.validation import validate\nfrom logging import getLogger\nfrom PIL import Image\nfrom pytesseract import image_to_string\nimport re\nimport requests\n\n\ngeneration_link = 'http://www.nampower.com.na/gis/images/Gx.png'\nexchanges_link = 'http://www.nampower.com.na/gis/images/Imports_Exports.png'\n\nplant_mapping = {\"Ruacana\": \"hydro\",\n                 \"Van Eck\": \"coal\",\n                 \"Paratus\": \"oil\",\n                 \"Anixas\": \"oil\",\n                 \"Solar\": \"solar\",\n                 \"Wind\": \"wind\"\n                 }\n\nexchange_mapping = {\"NA->ZA\": \"ESKOM\",\n                    \"NA->ZM\": \"ZESCO\"\n                    }\n\n\ndef get_text_from_image(link, expected_size, new_size, logger, session=None):\n    \"\"\"\n    Gets image from link and checks expected size vs actual.\n    Converts to black & white and enlarges to improve OCR accuracy.\n    Performs OCR using tesseract and returns a str.\n    \"\"\"\n\n    s = session or requests.Session()\n    img = Image.open(s.get(link, stream=True).raw)\n\n    if img.size != expected_size:\n        if (logger):\n            logger.warning(\"Check Namibia Scada dashboard for {} changes.\".format(link),\n                extras={'key': 'NA'})\n        else:\n            print(\"Check Namibia Scada dashboard for {} changes.\".format(link))\n\n    gray = img.convert('L')\n    gray_enlarged = gray.resize(new_size, Image.LANCZOS)\n    text = image_to_string(gray_enlarged, lang='eng')\n\n    return text\n\n\ndef data_processor(text):\n    \"\"\"\n    Takes text produced from OCR and extracts production.\n    Returns a dictionary.\n    \"\"\"\n\n    production = {}\n    for k in plant_mapping.keys():\n        pattern = re.escape(k) + r\": (\\d+\\.\\d\\d)\"\n        try:\n            val = re.search(pattern, text).group(1)\n            production[plant_mapping[k]] = production.get(plant_mapping[k], 0.0)+ float(val)\n        except (AttributeError, ValueError) as e:\n            production[plant_mapping[k]] = None\n\n    return production\n\n\ndef fetch_production(zone_key = 'NA', session=None, target_datetime=None, logger=getLogger(__name__)):\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    session (optional)      -- request session passed in order to re-use an existing session\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        raise NotImplementedError('This parser is not yet able to parse past dates')\n\n    raw_text = get_text_from_image(session=session, link=generation_link, \\\n                                   expected_size=(400, 245), new_size=(1000,612), \\\n                                   logger=logger)\n\n    production = data_processor(raw_text)\n\n    data = {\n          'zoneKey': zone_key,\n          'datetime': arrow.now('Africa/Windhoek').datetime,\n          'production': production,\n          'storage': {},\n          'source': 'nampower.com.na'\n          }\n\n    data = validate(data, required=['hydro'])\n\n    return data\n\n\ndef exchange_processor(text, exchange, logger):\n    \"\"\"\n    Takes text produced from OCR and extracts exchange flow.\n    Returns a float or None.\n    \"\"\"\n\n    utility = exchange_mapping[exchange]\n\n    try:\n        pattern = re.escape(utility) + r\"([\\D]*?)([-+]?\\d+\\.\\d\\d)\"\n        val = re.search(pattern, text).group(2)\n        flow = float(val)\n    except (AttributeError, ValueError) as e:\n        logger.warning(\"\"\"{} regex match failed on the following text.\n                          {}\"\"\".format(exchange, text))\n        raise Exception(\"Exchange {} cannot be read.\".format(exchange)) from e\n\n    return flow\n\n\ndef fetch_exchange(zone_key1, zone_key2, session=None, target_datetime=None, logger=getLogger(__name__)):\n    \"\"\"Requests the last known power exchange (in MW) between two zones\n    Arguments:\n    zone_key1           -- the first country code\n    zone_key2           -- the second country code; order of the two codes in params doesn't matter\n    session (optional)      -- request session passed in order to re-use an existing session\n    Return:\n    A dictionary in the form:\n    {\n      'sortedZoneKeys': 'DK->NO',\n      'datetime': '2017-01-01T00:00:00Z',\n      'netFlow': 0.0,\n      'source': 'mysource.com'\n    }\n    where net flow is from DK into NO\n    \"\"\"\n    if target_datetime:\n        raise NotImplementedError('This parser is not yet able to parse past dates')\n\n    sorted_codes = \"->\".join(sorted([zone_key1, zone_key2]))\n\n    raw_text = get_text_from_image(session=session, link=exchanges_link,\n                                   expected_size=(400, 195), new_size=(1120, 546), \\\n                                   logger=logger)\n\n    if sorted_codes == 'NA->ZA':\n        flow = exchange_processor(raw_text, 'NA->ZA', logger=logger)\n    elif sorted_codes == 'NA->ZM':\n        flow = exchange_processor(raw_text, 'NA->ZM', logger=logger)\n    else:\n        raise NotImplementedError('This exchange pair is not implemented')\n\n    #Import considered positive in data source.\n    if flow is not None:\n        flow = -1 * flow\n\n    exchange = {'sortedZoneKeys': sorted_codes,\n                'datetime': arrow.now('Africa/Windhoek').datetime,\n                'netFlow': flow,\n                'source': 'nampower.com.na'\n                }\n\n    return exchange\n\n\nif __name__ == '__main__':\n    print('fetch_production() ->')\n    print(fetch_production())\n    print('fetch_exchange(NA, ZA)')\n    print(fetch_exchange('NA', 'ZA'))\n    print('fetch_exchange(NA, ZM)')\n    print(fetch_exchange('NA', 'ZM'))\n", "path": "parsers/NA.py"}], "after_files": [{"content": "#!/usr/bin/env python3\n\n\"\"\"Parser for Namibia.\"\"\"\n\nimport arrow\nfrom bs4 import BeautifulSoup\nfrom .lib.validation import validate\nfrom logging import getLogger\nfrom PIL import Image\nfrom pytesseract import image_to_string\nimport re\nimport requests\n\n\ntimestamp_link = 'http://www.nampower.com.na/gis/images/File_Info.png'\ngeneration_link = 'http://www.nampower.com.na/gis/images/Gx.png'\nexchanges_link = 'http://www.nampower.com.na/gis/images/Imports_Exports.png'\n\nplant_mapping = {\"Ruacana\": \"hydro\",\n                 \"Van Eck\": \"coal\",\n                 \"Paratus\": \"oil\",\n                 \"Anixas\": \"oil\",\n                 \"Solar\": \"solar\",\n                 \"Wind\": \"wind\"\n                 }\n\nexchange_mapping = {\"NA->ZA\": \"ESKOM\",\n                    \"NA->ZM\": \"ZESCO\"\n                    }\n\n\ndef get_text_from_image(link, expected_size, new_size, logger, session=None):\n    \"\"\"\n    Gets image from link and checks expected size vs actual.\n    Converts to black & white and enlarges to improve OCR accuracy.\n    Performs OCR using tesseract and returns a str.\n    \"\"\"\n\n    s = session or requests.Session()\n    img = Image.open(s.get(link, stream=True).raw)\n\n    if img.size != expected_size:\n        logger.warning(\"Check Namibia Scada dashboard for {} changes.\".format(link),\n            extras={'key': 'NA'})\n\n    gray = img.convert('L')\n    gray_enlarged = gray.resize(new_size, Image.LANCZOS)\n    text = image_to_string(gray_enlarged, lang='eng')\n\n    return text\n\n\ndef check_timestamp(session=None, logger=None):\n    \"\"\"\n    Sometimes the Scada Dashboard image stops updating for a while.\n    This function tries to ensure that only data younger than 1 hour\n    is accepted.\n    \"\"\"\n\n    scada_info = get_text_from_image(session=session, link=timestamp_link,\n                                     expected_size=(600,20), new_size=(1200,40),\n                                     logger=logger)\n\n    timestamp = scada_info.split(':', 1)[1]\n\n    try:\n        scada_time = arrow.get(timestamp, ' DD/MM/YYYY HH:mm:ss')\n    except arrow.parser.ParserError as e:\n        logger.warning('Namibia scada timestamp cannot be read, got {}.'.format(timestamp))\n        # The OCR of the Scada dashboard is not very reliable, on failure safer to assume data is good.\n        return\n\n    data_time = scada_time.replace(tzinfo='Africa/Windhoek')\n    current_time = arrow.now('Africa/Windhoek')\n    diff = current_time - data_time\n\n    # Need to be sure we don't get old data if image stops updating.\n    if diff.seconds > 3600:\n        raise ValueError('Namibia scada data is too old to use, data is {} hours old.'.format(diff.seconds/3600))\n\n\ndef data_processor(text):\n    \"\"\"\n    Takes text produced from OCR and extracts production.\n    Returns a dictionary.\n    \"\"\"\n\n    production = {}\n    for k in plant_mapping.keys():\n        pattern = re.escape(k) + r\": (\\d+\\.\\d\\d)\"\n        try:\n            val = re.search(pattern, text).group(1)\n            production[plant_mapping[k]] = production.get(plant_mapping[k], 0.0)+ float(val)\n        except (AttributeError, ValueError) as e:\n            production[plant_mapping[k]] = None\n\n    return production\n\n\ndef fetch_production(zone_key = 'NA', session=None, target_datetime=None, logger=getLogger(__name__)):\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    session (optional)      -- request session passed in order to re-use an existing session\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        raise NotImplementedError('This parser is not yet able to parse past dates')\n\n    raw_text = get_text_from_image(session=session, link=generation_link, \\\n                                   expected_size=(400, 245), new_size=(1000,612), \\\n                                   logger=logger)\n\n    production = data_processor(raw_text)\n    check_timestamp(session=session, logger=logger)\n\n    data = {\n          'zoneKey': zone_key,\n          'datetime': arrow.now('Africa/Windhoek').datetime,\n          'production': production,\n          'storage': {},\n          'source': 'nampower.com.na'\n          }\n\n    data = validate(data, required=['hydro'])\n\n    return data\n\n\ndef exchange_processor(text, exchange, logger):\n    \"\"\"\n    Takes text produced from OCR and extracts exchange flow.\n    Returns a float or None.\n    \"\"\"\n\n    utility = exchange_mapping[exchange]\n\n    try:\n        pattern = re.escape(utility) + r\"([\\D]*?)([-+]?\\d+\\.\\d\\d)\"\n        val = re.search(pattern, text).group(2)\n        flow = float(val)\n    except (AttributeError, ValueError) as e:\n        logger.warning(\"\"\"{} regex match failed on the following text.\n                          {}\"\"\".format(exchange, text))\n        raise Exception(\"Exchange {} cannot be read.\".format(exchange)) from e\n\n    return flow\n\n\ndef fetch_exchange(zone_key1, zone_key2, session=None, target_datetime=None, logger=getLogger(__name__)):\n    \"\"\"Requests the last known power exchange (in MW) between two zones\n    Arguments:\n    zone_key1           -- the first country code\n    zone_key2           -- the second country code; order of the two codes in params doesn't matter\n    session (optional)      -- request session passed in order to re-use an existing session\n    Return:\n    A dictionary in the form:\n    {\n      'sortedZoneKeys': 'DK->NO',\n      'datetime': '2017-01-01T00:00:00Z',\n      'netFlow': 0.0,\n      'source': 'mysource.com'\n    }\n    where net flow is from DK into NO\n    \"\"\"\n    if target_datetime:\n        raise NotImplementedError('This parser is not yet able to parse past dates')\n\n    sorted_codes = \"->\".join(sorted([zone_key1, zone_key2]))\n\n    raw_text = get_text_from_image(session=session, link=exchanges_link,\n                                   expected_size=(400, 195), new_size=(1120, 546), \\\n                                   logger=logger)\n\n    if sorted_codes == 'NA->ZA':\n        flow = exchange_processor(raw_text, 'NA->ZA', logger=logger)\n    elif sorted_codes == 'NA->ZM':\n        flow = exchange_processor(raw_text, 'NA->ZM', logger=logger)\n    else:\n        raise NotImplementedError('This exchange pair is not implemented')\n\n    #Import considered positive in data source.\n    if flow is not None:\n        flow = -1 * flow\n\n    check_timestamp(session=session, logger=logger)\n\n    exchange = {'sortedZoneKeys': sorted_codes,\n                'datetime': arrow.now('Africa/Windhoek').datetime,\n                'netFlow': flow,\n                'source': 'nampower.com.na'\n                }\n\n    return exchange\n\n\nif __name__ == '__main__':\n    print('fetch_production() ->')\n    print(fetch_production())\n    print('fetch_exchange(NA, ZA)')\n    print(fetch_exchange('NA', 'ZA'))\n    print('fetch_exchange(NA, ZM)')\n    print(fetch_exchange('NA', 'ZM'))\n", "path": "parsers/NA.py"}]}