From 3906e7f45d4cd2a4c5876b9449d80d19d3d4fbfa Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Mon, 2 Sep 2013 18:42:47 +0000 Subject: [PATCH] fix units and trending bugs in zambretti forecaster --- bin/user/forecast.py | 45 ++++++++++++--- bin/user/test_forecast.py | 115 ++++++++++++++++++++++++++++---------- 2 files changed, 124 insertions(+), 36 deletions(-) diff --git a/bin/user/forecast.py b/bin/user/forecast.py index c697f187..3be7a247 100644 --- a/bin/user/forecast.py +++ b/bin/user/forecast.py @@ -64,6 +64,15 @@ Configuration # hemisphere can be NORTH or SOUTH #hemisphere = NORTH + # The interval determines how often the trend is calculated + #interval = 600 + + # The lower and upper pressure define the range to which the forecaster + # should be calibrated, in units of millibar (hPa). The 'barometer' + # pressure (not station pressure) is used to calculate the forecast. + #lower_pressure = 950.0 + #upper_pressure = 1050.0 + [[NWS]] # First figure out your forecast office identifier (foid), then request # a point forecast using a url of this form in a web browser: @@ -106,6 +115,8 @@ Configuration [Databases] ... + # a typical installation will use either sqlite or mysql + [[forecast_sqlite]] root = %(WEEWX_ROOT)s database = archive/forecast.sdb @@ -120,6 +131,7 @@ Configuration [Engines] [[WxEngine]] + # append only the forecasting service(s) that you need service_list = ... , user.forecast.ZambrettiForecast, user.forecast.NWSForecast, user.forecast.WUForecast, user.forecast.XTideForecast @@ -439,7 +451,7 @@ def get_int(config_dict, label, default_value): windSpeed WIND SPD avewind.mph windGust WIND GUST maxwind.mph windChar WIND CHAR - clouds CLOUDS | AVG CLOUNDS + clouds CLOUDS | AVG CLOUNDS skyicon pop POP 12HR pop qpf QPF 12HR qpf_allday.in qsf SNOW 12HR snow_allday.in @@ -652,6 +664,8 @@ class Forecast(StdService): record - dictionary with keys corresponding to database fields """ + if record is None: + return archive.addRecord(record) @staticmethod @@ -703,11 +717,16 @@ class ZambrettiForecast(Forecast): """calculate zambretti code""" def __init__(self, engine, config_dict): - super(ZambrettiForecast, self).__init__(engine, config_dict, Z_KEY) + super(ZambrettiForecast, self).__init__(engine, config_dict, Z_KEY, + interval=600) d = config_dict['Forecast'].get(Z_KEY, {}) self.hemisphere = d.get('hemisphere', 'NORTH') - loginf('%s: interval=%s max_age=%s hemisphere=%s' % - (Z_KEY, self.interval, self.max_age, self.hemisphere)) + self.lower_pressure = d.get('lower_pressure', 950.0) + self.upper_pressure = d.get('upper_pressure', 1050.0) + self.last_pressure = None + loginf('%s: interval=%s max_age=%s hemisphere=%s lower_pressure=%s upper_pressure=%s' % + (Z_KEY, self.interval, self.max_age, self.hemisphere, + self.lower_pressure, self.upper_pressure)) def get_forecast(self, event): logdbg('%s: generating zambretti forecast' % Z_KEY) @@ -715,12 +734,22 @@ class ZambrettiForecast(Forecast): ts = rec['dateTime'] tt = time.gmtime(ts) pressure = rec['barometer'] + if rec['usUnits'] == weewx.US: + vt = (float(pressure), "inHg", "group_pressure") + pressure = weewx.units.convert(vt, 'mbar')[0] month = tt.tm_mon - 1 # month is [0-11] wind = int(rec['windDir'] / 22.5) # wind dir is [0-15] + if self.last_pressure is not None and pressure is not None: + trend = self.last_pressure - pressure + else: + trend = None + self.last_pressure = pressure north = self.hemisphere.lower() != 'south' - logdbg('%s: pressure=%s month=%s wind=%s north=%s' % - (Z_KEY, pressure, month, wind, north)) - code = ZambrettiCode(pressure, month, wind, north) + logdbg('%s: pressure=%s month=%s wind=%s trend=%s north=%s' % + (Z_KEY, pressure, month, wind, trend, north)) + code = ZambrettiCode(pressure, month, wind, trend, north, + baro_bottom=self.lower_pressure, + baro_top=self.upper_pressure) logdbg('%s: code is %s' % (Z_KEY, code)) if code is None: return None @@ -785,6 +814,8 @@ def ZambrettiCode(pressure, month, wind, trend, if pressure is None: return None + if trend is None: + return None if month < 0 or month > 11: return None if wind < 0 or wind > 15: diff --git a/bin/user/test_forecast.py b/bin/user/test_forecast.py index 6571d4ab..5f739426 100644 --- a/bin/user/test_forecast.py +++ b/bin/user/test_forecast.py @@ -2663,6 +2663,15 @@ WU_TENANTS_HARBOR = ''' # generic templates for combinations of summary and period # should work with each forecast source +PERIODS_TEMPLATE = ''' + +#for $f in $forecast.weather_periods('SOURCE', from_ts=TS, max_events=20) +$f.event_ts $f.duration $f.tempMin $f.temp $f.tempMax $f.humidity $f.dewpoint $f.windSpeed $f.windGust $f.windDir $f.windChar $f.pop +#end for + + +''' + SUMMARY_TEMPLATE = ''' #set $summary = $forecast.weather_summary('SOURCE', ts=TS) @@ -2974,6 +2983,61 @@ class ForecastTest(unittest.TestCase): self.assertEqual(forecast.ZambrettiText('Y'), 'Stormy, may improve') self.assertEqual(forecast.ZambrettiText('Z'), 'Stormy, much rain') + def test_zambretti_generator(self): + tname = 'test_zambretti_generator' + tdir = get_testdir(tname) + rmtree(tdir) + cd = create_config(tdir, 'user.forecast.ZambrettiForecast') + eng = weewx.wxengine.StdEngine(cd) + zf = forecast.ZambrettiForecast(eng, cd) + + # first record, no trend, so no zambretti + event = weewx.Event(weewx.NEW_ARCHIVE_RECORD) + event.record = {'interval': 5, 'outHumidity': 60.0, 'rainRate': 0.0, 'heatindex': 62.779999999999994, 'radiation': None, 'inTemp': 66.230000000000004, 'windGustDir': 112.5, 'status': 0.0, 'barometer': 29.838662744470582, 'windchill': 62.779999999999994, 'dewpoint': 48.684961368525371, 'rain': 0.0, 'pressure': 29.806556408741884, 'rainTotal': 0.68999999999999995, 'altimeter': 29.830054257884523, 'usUnits': 1, 'UV': None, 'dateTime': 1378143300, 'windDir': 90.0, 'outTemp': 62.779999999999994, 'windSpeed': 0.0, 'inHumidity': 78.0, 'windGust': 0.0} + record = zf.get_forecast(event) + self.assertEqual(record, None) + + # next record gives us a trend + event.record = {'barometer': 29.834685721179159, 'usUnits': 1, 'dateTime': 1378143900, 'windDir': 90.0} + record = zf.get_forecast(event) + self.assertEqual(record, {'event_ts': 1378143900, 'dateTime': 1378143900, 'zcode': 'C', 'issued_ts': 1378143900, 'method': 'Zambretti', 'usUnits': 1}) + + # now the pressure goes up slightly + event.record = {'barometer': 29.835649151484603, 'usUnits': 1, 'dateTime': 1378144200, 'windDir': 90.0} + record = zf.get_forecast(event) + self.assertEqual(record, {'event_ts': 1378144200, 'dateTime': 1378144200, 'zcode': 'K', 'issued_ts': 1378144200, 'method': 'Zambretti', 'usUnits': 1}) + + # now the pressure drops + event.record = {'barometer': 29.0, 'usUnits': 1, 'dateTime': 1378144500, 'windDir': 90.0} + record = zf.get_forecast(event) + self.assertEqual(record, {'event_ts': 1378144500, 'dateTime': 1378144500, 'zcode': 'L', 'issued_ts': 1378144500, 'method': 'Zambretti', 'usUnits': 1}) + + def test_zambretti_units(self): + '''ensure that zambretti works with both US and METRIC''' + + tname = 'test_zambretti_units' + tdir = get_testdir(tname) + rmtree(tdir) + cd = create_config(tdir, 'user.forecast.ZambrettiForecast') + eng = weewx.wxengine.StdEngine(cd) + zf = forecast.ZambrettiForecast(eng, cd) + + # first record, no trend, so no zambretti + event = weewx.Event(weewx.NEW_ARCHIVE_RECORD) + event.record = {'barometer': 1010.33712053, 'usUnits': weewx.METRIC, 'dateTime': 1378143300, 'windDir': 90.0} + record = zf.get_forecast(event) + self.assertEqual(record, None) + + # next record gives us a trend + event.record = {'barometer': 1010.20245852, 'usUnits': weewx.METRIC, 'dateTime': 1378143900, 'windDir': 90.0} + record = zf.get_forecast(event) + self.assertEqual(record, {'event_ts': 1378143900, 'dateTime': 1378143900, 'zcode': 'C', 'issued_ts': 1378143900, 'method': 'Zambretti', 'usUnits': 1}) + + # now the pressure goes up slightly + event.record = {'barometer': 1010.23508027, 'usUnits': weewx.METRIC, 'dateTime': 1378144200, 'windDir': 90.0} + record = zf.get_forecast(event) + self.assertEqual(record, {'event_ts': 1378144200, 'dateTime': 1378144200, 'zcode': 'K', 'issued_ts': 1378144200, 'method': 'Zambretti', 'usUnits': 1}) + def test_zambretti_bogus_values(self): self.assertEqual(forecast.ZambrettiCode(0, 0, 0, 0), 'Z') self.assertEqual(forecast.ZambrettiCode(None, 0, 0, 0), None) @@ -3131,17 +3195,12 @@ $forecast.zambretti.code def test_nws_template_periods(self): matrix = forecast.ParseNWSForecast(PFM_BOS_SINGLE, 'MAZ014') records = forecast.ProcessNWSForecast('BOX', 'MAZ014', matrix) + template = PERIODS_TEMPLATE.replace('SOURCE', 'NWS') + template = template.replace('TS', '1368328140') self.runTemplateTest('test_nws_template_periods', 'user.forecast.NWSForecast', records, - ''' - -#for $f in $forecast.weather_periods('NWS', from_ts=1368328140, max_events=20) -$f.event_ts $f.duration $f.tempMin $f.temp $f.tempMax $f.humidity $f.dewpoint $f.windSpeed $f.windGust $f.windDir $f.windChar $f.pop -#end for - - -''', + template, ''' 12-May-2013 02:00 10800 - 61.0F - 87% 57.0F 8.0 mph - S - @@ -3309,37 +3368,31 @@ SW self.assertEqual(matrix, None) def test_wu_template_periods(self): - matrix = forecast.CreateWUForecastMatrix(WU_BOS) + matrix = forecast.CreateWUForecastMatrix(WU_TENANTS_HARBOR, + issued_ts=1378090800) records = forecast.ProcessWUForecast(matrix) + template = PERIODS_TEMPLATE.replace('SOURCE', 'WU') + template = template.replace('TS', '1378090800') self.runTemplateTest('test_wu_template_periods', 'user.forecast.WUForecast', records, + template, ''' -#for $f in $forecast.weather_periods('WU', from_ts=1368328140, max_events=20) -$f.event_ts $f.duration $f.tempMin $f.temp $f.tempMax $f.humidity $f.dewpoint $f.windSpeed $f.windGust $f.windDir $f.windChar $f.pop -#end for - - -''', - ''' - -15-May-2013 23:00 86400 55.0F - 68.0F 69% - 15.0 mph 19.0 mph SSW 50% -16-May-2013 23:00 86400 54.0F - 77.0F 42% - 19.0 mph 23.0 mph W 10% -17-May-2013 23:00 86400 54.0F - 72.0F 51% - 5.0 mph 11.0 mph NW 10% -18-May-2013 23:00 86400 48.0F - 70.0F 59% - 7.0 mph 9.0 mph SE 0% -19-May-2013 23:00 86400 48.0F - 66.0F 70% - 8.0 mph 10.0 mph SE 0% -20-May-2013 23:00 86400 52.0F - 68.0F 85% - 11.0 mph 13.0 mph S 0% -21-May-2013 23:00 86400 54.0F - 73.0F 72% - 8.0 mph 10.0 mph E 0% -22-May-2013 23:00 86400 55.0F - 77.0F 76% - 6.0 mph 8.0 mph ESE 0% -23-May-2013 23:00 86400 54.0F - 75.0F 92% - 3.0 mph 4.0 mph SE 0% -24-May-2013 23:00 86400 57.0F - 75.0F 90% - 3.0 mph 5.0 mph SE 40% +01-Sep-2013 23:00 86400 73.0F - 86.0F 83% - 10.0 mph 11.0 mph SSW 40% +02-Sep-2013 23:00 86400 72.0F - 81.0F 91% - 8.0 mph 10.0 mph S 60% +03-Sep-2013 23:00 86400 61.0F - 81.0F 70% - 8.0 mph 9.0 mph SW 50% +04-Sep-2013 23:00 86400 59.0F - 79.0F 78% - 10.0 mph 11.0 mph NW 0% +05-Sep-2013 23:00 86400 57.0F - 75.0F 90% - 8.0 mph 10.0 mph W 0% +06-Sep-2013 23:00 86400 54.0F - 72.0F 74% - 4.0 mph 6.0 mph ESE 0% +07-Sep-2013 23:00 86400 63.0F - 79.0F 93% - 11.0 mph 14.0 mph SW 0% +08-Sep-2013 23:00 86400 61.0F - 77.0F 65% - 7.0 mph 9.0 mph E 0% +09-Sep-2013 23:00 86400 61.0F - 77.0F 75% - 3.0 mph 4.0 mph SW 0% +10-Sep-2013 23:00 86400 61.0F - 79.0F 86% - 2.0 mph 3.0 mph SSW 0% ''') -#FIXME: wrong ts on these - def test_wu_template_summary(self): matrix = forecast.CreateWUForecastMatrix(WU_TENANTS_HARBOR, issued_ts=1378090800) @@ -3647,11 +3700,15 @@ $a.moon_fullness e = wxengine.StdEngine(config_dict) f = forecast.ZambrettiForecast(e, config_dict) record = {} + record['usUnits'] = weewx.METRIC record['barometer'] = 1030 record['windDir'] = 180 event = weewx.Event(weewx.NEW_ARCHIVE_RECORD) event.record = record event.record['dateTime'] = int(time.time()) + f.get_forecast(event) # first zambretti is None to set trend + time.sleep(1) + event.record['dateTime'] = int(time.time()) forecast.Forecast.save_forecast(archive, f.get_forecast(event)) time.sleep(1) event.record['dateTime'] = int(time.time())