From 96d4e681d8a09dfe0569fe3022969f88e1947956 Mon Sep 17 00:00:00 2001 From: Tom Keffer Date: Sat, 12 Oct 2019 12:00:12 -0700 Subject: [PATCH] Moved get_aggregate() to aggregate.py. Added heat/cool to aggregate --- TODO.md | 23 ++------ bin/weewx/aggregate.py | 97 ++++++++++++++++++++++++++++++- bin/weewx/tests/test_aggregate.py | 25 +++++++- bin/weewx/xtypes.py | 1 - 4 files changed, 121 insertions(+), 25 deletions(-) diff --git a/TODO.md b/TODO.md index f0d0a50b..c07edc10 100644 --- a/TODO.md +++ b/TODO.md @@ -1,10 +1,13 @@ +### Extensible types +Need some way of initializing the daily summaries for wind. + ### Logging -Refactor wee_import to use the new logging facility. +Refactor `wee_import` to use the new logging facility. Update *Upgrading Guide*. -### Issue 435 (Update WU uploader) +### Issue [#435](https://github.com/weewx/weewx/issues/435) (Update WU uploader) Need to test some of the new types, in particular `windSpeed2`, `windGust10`, `windGustDir10`. @@ -15,22 +18,6 @@ from `weeutil.weeutil`. Document the suffixes `.exists` and `.has_data`. -Explore introducing extendable observation types. They would be registered -with `Manager`. - -The following drivers have been checked under Python 3: - -``` -vantage.py -wmr100.py -``` - -In general, we should expect the user to run the command `python`, whatever that might -point to. On some systems, it will be Python 2, others Python 3. - -Nevertheless, we should probably include a section on how to run explicitly under -Python 2 vs 3. - Update macos.htm for how to install using Python 3. Update redhat.htm for how to install using Python 3. diff --git a/bin/weewx/aggregate.py b/bin/weewx/aggregate.py index 95235900..107a36be 100644 --- a/bin/weewx/aggregate.py +++ b/bin/weewx/aggregate.py @@ -39,8 +39,9 @@ simple_sql = "SELECT %(aggregate_type)s(%(obs_type)s) FROM %(table_name)s " \ "WHERE dateTime > %(start)s AND dateTime <= %(stop)s AND %(obs_type)s IS NOT NULL" -def get_aggregate(obs_type, timespan, aggregate_type, db_manager, **option_dict): - """Returns an aggregation of an observation type over a given time period. +def get_aggregate_archive(obs_type, timespan, aggregate_type, db_manager, **option_dict): + """Returns an aggregation of an observation type over a given time period, using the + main archive table. obs_type: The type over which aggregation is to be done (e.g., 'barometer', 'outTemp', 'rain', ...) @@ -195,7 +196,7 @@ def get_aggregate_daily(obs_type, timespan, aggregate_type, db_manager, **option # boundaries, and are not the first or last records in the database. if not (isStartOfDay(timespan.start) or timespan.start == db_manager.first_timestamp) \ or not (isStartOfDay(timespan.stop) or timespan.stop == db_manager.last_timestamp): - raise weewx.ViolatedPrecondition("Invalid timespan for using daily summaries: %s" % timespan) + raise weewx.UnknownAggregation(aggregate_type) if 'val' in option_dict: val = option_dict['val'] @@ -392,3 +393,93 @@ def get_aggregate_wind(obs_type, timespan, aggregate_type, db_manager, **option_ t, g = weewx.units.getStandardUnitType(std_unit_system, obs_type, aggregate_type) # Form the ValueTuple and return it: return weewx.units.ValueTuple(value, t, g) + + +def get_aggregate_heatcool(obs_type, timespan, aggregate_type, db_manager, **option_dict): + """Returns an aggregation of a wind type over a timespan by using the main archive table. + + obs_type: The type over which aggregation is to be done (e.g., 'barometer', + 'outTemp', 'rain', ...) + + timespan: An instance of weeutil.Timespan with the time period over which + aggregation is to be done. + + aggregate_type: The type of aggregation to be done. + + db_manager: An instance of weewx.manager.Manager or subclass. + + option_dict: Not used in this version. + + returns: A ValueTuple containing the result. Note that the value contained in the ValueTuple + will be a complex number for aggregation_types of 'avg', 'sum', 'first', 'last', 'min', and 'max'. + """ + # Default base temperature and unit type for heating and cooling degree days, + # as a value tuple + default_heatbase = (65.0, "degree_F", "group_temperature") + default_coolbase = (65.0, "degree_F", "group_temperature") + default_growbase = (50.0, "degree_F", "group_temperature") + + # Check to see whether heating or cooling degree days are being asked for: + if obs_type not in ['heatdeg', 'cooldeg', 'growdeg']: + raise weewx.UnknownType(obs_type) + + # Only summation (total) or average heating or cooling degree days is supported: + if aggregate_type not in ['sum', 'avg']: + raise weewx.UnknownAggregation(aggregate_type) + + # Get the base for heating and cooling degree-days + units_dict = option_dict.get('skin_dict', {}).get('Units', {}) + dd_dict = units_dict.get('DegreeDays', {}) + heatbase = dd_dict.get('heating_base', default_heatbase) + coolbase = dd_dict.get('cooling_base', default_coolbase) + growbase = dd_dict.get('growing_base', default_growbase) + heatbase_t = (float(heatbase[0]), heatbase[1], "group_temperature") + coolbase_t = (float(coolbase[0]), coolbase[1], "group_temperature") + growbase_t = (float(growbase[0]), growbase[1], "group_temperature") + + total = 0.0 + count = 0 + for daySpan in weeutil.weeutil.genDaySpans(timespan.start, timespan.stop): + # Get the average temperature for the day as a value tuple: + Tavg_t = get_aggregate_daily('outTemp', daySpan, 'avg', db_manager) + # Make sure it's valid before including it in the aggregation: + if Tavg_t is not None and Tavg_t[0] is not None: + if obs_type == 'heatdeg': + # Convert average temperature to the same units as heatbase: + Tavg_target_t = weewx.units.convert(Tavg_t, heatbase_t[1]) + total += weewx.wxformulas.heating_degrees(Tavg_target_t[0], heatbase_t[0]) + elif obs_type == 'cooldeg': + # Convert average temperature to the same units as coolbase: + Tavg_target_t = weewx.units.convert(Tavg_t, coolbase_t[1]) + total += weewx.wxformulas.cooling_degrees(Tavg_target_t[0], coolbase_t[0]) + else: + # Must be 'growdeg'. Convert average temperature to the same units as growbase: + Tavg_target_t = weewx.units.convert(Tavg_t, growbase_t[1]) + total += weewx.wxformulas.cooling_degrees(Tavg_target_t[0], growbase_t[0]) + + count += 1 + + if aggregate_type == 'sum': + result = total + else: + result = total / count if count else None + + # Look up the unit type and group of the result: + (t, g) = weewx.units.getStandardUnitType(db_manager.std_unit_system, obs_type, aggregate_type) + # Return as a value tuple + return weewx.units.ValueTuple(result, t, g) + + +aggregate_fns = [get_aggregate_heatcool, + get_aggregate_wind, + get_aggregate_daily, + get_aggregate_archive] + + +def get_aggregate(obs_type, timespan, aggregate_type, db_manager, **option_dict): + for agg_fn in aggregate_fns: + try: + return agg_fn(obs_type, timespan, aggregate_type, db_manager, **option_dict) + except (weewx.UnknownAggregation, weewx.UnknownType): + pass + raise weewx.UnknownAggregation(aggregate_type) diff --git a/bin/weewx/tests/test_aggregate.py b/bin/weewx/tests/test_aggregate.py index 825595cd..43cda8c8 100644 --- a/bin/weewx/tests/test_aggregate.py +++ b/bin/weewx/tests/test_aggregate.py @@ -19,6 +19,7 @@ import gen_fake_data import weeutil.logger import weewx import weewx.aggregate +import weewx.manager from weeutil.weeutil import TimeSpan from weewx.units import ValueTuple @@ -56,7 +57,7 @@ class TestAggregate(unittest.TestCase): def test_get_aggregate(self): # Use the same function to test calculating aggregations from the main archive file, as well # as from the daily summaries: - self.get_aggregate_fn(weewx.aggregate.get_aggregate) + self.get_aggregate_fn(weewx.aggregate.get_aggregate_archive) self.get_aggregate_fn(weewx.aggregate.get_aggregate_daily) def get_aggregate_fn(self, aggregate_fn): @@ -88,7 +89,7 @@ class TestAggregate(unittest.TestCase): self.assertAlmostEqual(sum_vt[0], 7.68, 2) # get_aggregate() has a few extra aggregate types: - if aggregate_fn == weewx.aggregate.get_aggregate: + if aggregate_fn == weewx.aggregate.get_aggregate_archive: first_vt = aggregate_fn('outTemp', TimeSpan(start_ts, stop_ts), 'first', db_manager) # Get the timestamp of the first record inside the month ts = start_ts + gen_fake_data.interval @@ -134,7 +135,7 @@ class TestAggregate(unittest.TestCase): avg_wind_vt = weewx.aggregate.get_aggregate_daily('wind', TimeSpan(start_ts, stop_ts), 'avg', db_manager) self.assertAlmostEqual(avg_wind_vt[0], 10.21, 2) # Double check this last one against the average calculated from the archive - avg_wind_vt = weewx.aggregate.get_aggregate('windSpeed', TimeSpan(start_ts, stop_ts), 'avg', db_manager) + avg_wind_vt = weewx.aggregate.get_aggregate_archive('windSpeed', TimeSpan(start_ts, stop_ts), 'avg', db_manager) self.assertAlmostEqual(avg_wind_vt[0], 10.21, 2) vecavg_wind_vt = weewx.aggregate.get_aggregate_daily('wind', TimeSpan(start_ts, stop_ts), 'vecavg', @@ -145,6 +146,24 @@ class TestAggregate(unittest.TestCase): db_manager) self.assertAlmostEqual(vecdir_wind_vt[0], 88.74, 2) + def test_get_aggregate_heatcool(self): + with weewx.manager.open_manager_with_config(self.config_dict, 'wx_binding') as db_manager: + month_start_tt = (2010, 3, 1, 0, 0, 0, 0, 0, -1) + month_stop_tt = (2010, 4, 1, 0, 0, 0, 0, 0, -1) + start_ts = time.mktime(month_start_tt) + stop_ts = time.mktime(month_stop_tt) + + # First, with the default heating base: + heatdeg = weewx.aggregate.get_aggregate_heatcool('heatdeg', TimeSpan(start_ts, stop_ts), 'sum', db_manager) + self.assertAlmostEqual(heatdeg[0], 1123.99, 2) + # Now with an explicit heating base: + heatdeg = weewx.aggregate.get_aggregate_heatcool('heatdeg', TimeSpan(start_ts, stop_ts), 'sum', + db_manager, + skin_dict={'Units': {'DegreeDays': { + 'heating_base': (60.0, "degree_F", "group_temperature") + }}}) + self.assertAlmostEqual(heatdeg[0], 968.99, 2) + if __name__ == '__main__': unittest.main() diff --git a/bin/weewx/xtypes.py b/bin/weewx/xtypes.py index 3d7d63c9..9b88d43e 100644 --- a/bin/weewx/xtypes.py +++ b/bin/weewx/xtypes.py @@ -9,7 +9,6 @@ from __future__ import print_function import weewx scalar_types = [] -series_types = [] def get_scalar(key, record, db_manager=None):