Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions seed/data_importer/meters_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@
from pytz import AmbiguousTimeError, NonExistentTimeError, timezone

from config.settings.common import TIME_ZONE
from seed.data_importer.utils import kbtu_thermal_conversion_factors, kgal_water_conversion_factors, usage_point_id
from seed.data_importer.utils import (
fahrenheit_temperature_conversion_factors,
kbtu_thermal_conversion_factors,
kgal_water_conversion_factors,
usage_point_id,
)
from seed.lib.mcm import reader
from seed.lib.superperms.orgs.models import Organization
from seed.models import Meter, Property, PropertyView
Expand Down Expand Up @@ -63,6 +68,7 @@ def __init__(self, org_id, meters_and_readings_details, source_type=Meter.PORTFO
self._cache_org_country = None
self._cache_kbtu_thermal_conversion_factors = None
self._cache_kgal_water_conversion_factors = None
self._cache_fahrenheit_temperature_conversion_factors = None

self._meters_and_readings_details = meters_and_readings_details
self._org_id = org_id
Expand Down Expand Up @@ -120,6 +126,13 @@ def _kgal_water_conversion_factors(self):

return self._cache_kgal_water_conversion_factors

@property
def _fahrenheit_temperature_conversion_factors(self):
if self._cache_fahrenheit_temperature_conversion_factors is None:
self._cache_fahrenheit_temperature_conversion_factors = fahrenheit_temperature_conversion_factors(self._org_country)

return self._cache_fahrenheit_temperature_conversion_factors

@property
def _org_country(self):
if self._cache_org_country is None:
Expand Down Expand Up @@ -351,9 +364,11 @@ def _parse_meter_readings(self, raw_details, meter_details, start_time, end_time
unit = raw_details["Usage Units"]
thermal_conversion_factor = self._kbtu_thermal_conversion_factors.get(type_name, {}).get(unit, None)
water_conversion_factor = self._kgal_water_conversion_factors.get(type_name, {}).get(unit, None)
if thermal_conversion_factor is None and water_conversion_factor is None:
temperature_conversion_factor = self._fahrenheit_temperature_conversion_factors.get(type_name, {}).get(unit, None)

conversion_factor = thermal_conversion_factor or water_conversion_factor or temperature_conversion_factor
if conversion_factor is None:
return False
conversion_factor = thermal_conversion_factor or water_conversion_factor

meter_details["type"] = Meter.type_lookup[type_name]

Expand Down
65 changes: 65 additions & 0 deletions seed/data_importer/tests/test_meters_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,39 @@ def test_meters_parser_can_handle_meter_water_units(self):
for readings in zipped_readings:
self.assertEqual(round(readings[0], 5), round(readings[1], 5))

def test_meters_parser_can_handle_meter_temperature_units(self):
raw_meters = []
raw_meter = {
"Portfolio Manager ID": self.pm_property_id,
"Portfolio Manager Meter ID": "1-PMMeterID",
"Start Date": "2016-02-01 00:00:00",
"End Date": "2016-03-01 00:00:00",
"Meter Type": "Average Temperature",
"Usage Units": "",
"Usage/Quantity": 1,
}

usage_units = [
"F (Fahrenheit)",
]

for usage_unit in usage_units:
meter = raw_meter.copy()
meter["Usage Units"] = usage_unit
raw_meters.append(meter)

meters_parser = MetersParser(self.org.id, raw_meters)

expected_readings = [1]
actual_readings = [reading["reading"] for reading in meters_parser.meter_and_reading_objs[0]["readings"]]

self.assertEqual(len(actual_readings), 1)

zipped_readings = zip(actual_readings, expected_readings)
# round to account for binary nature of floating point
for readings in zipped_readings:
self.assertEqual(round(readings[0], 5), round(readings[1], 5))

def test_meters_parser_ignores_unexpected_meter_water_types(self):
raw_meters = []
raw_meter = {
Expand Down Expand Up @@ -899,3 +932,35 @@ def test_meters_parser_ignores_unexpected_meter_water_types(self):
actual_readings = meters_parser.meter_and_reading_objs

self.assertEqual(len(actual_readings), 3)

def test_meters_parser_ignores_unexpected_meter_temperature_types(self):
raw_meters = []
raw_meter = {
"Portfolio Manager ID": self.pm_property_id,
"Portfolio Manager Meter ID": "1-PMMeterID",
"Start Date": "2016-02-01 00:00:00",
"End Date": "2016-03-01 00:00:00",
"Meter Type": "",
"Usage Units": "F (Fahrenheit)",
"Usage/Quantity": 1,
}

meter_types = [
"Heating Degree Days",
"Cooling Degree Days",
"Average Temperature",
# invalid types
"Potable Invalid",
"Other Indoor",
"Well Water: Indoor",
]

for type in meter_types:
meter = raw_meter.copy()
meter["Meter Type"] = type
raw_meters.append(meter)

meters_parser = MetersParser(self.org.id, raw_meters)
actual_readings = meters_parser.meter_and_reading_objs

self.assertEqual(len(actual_readings), 3)
12 changes: 12 additions & 0 deletions seed/data_importer/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,18 @@ def kgal_water_conversion_factors(coutry):
return factors


def fahrenheit_temperature_conversion_factors(coutry):
"""
Returns water conversion factor provided by Portfolio Manager.
Conversion factors taken from: https://www.kylesconverter.com/temperature/

# should country be considered?
"""
meter_types = ["Heating Degree Days", "Cooling Degree Days", "Average Temperature"]

return {meter_type: {"F (Fahrenheit)": 1.00} for meter_type in meter_types}


def usage_point_id(raw_source_id):
"""
Extracts and returns the usage point ID of a GreenButton full uri ID.
Expand Down
55 changes: 55 additions & 0 deletions seed/migrations/0242_add_meter_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Generated by Django 3.2.25 on 2025-01-02 19:26

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("seed", "0241_alter_meter_type"),
]

operations = [
migrations.AlterField(
model_name="meter",
name="type",
field=models.IntegerField(
choices=[
(1, "Coal (anthracite)"),
(2, "Coal (bituminous)"),
(3, "Coke"),
(4, "Diesel"),
(25, "District Chilled Water"),
(5, "District Chilled Water - Absorption"),
(6, "District Chilled Water - Electric"),
(7, "District Chilled Water - Engine"),
(8, "District Chilled Water - Other"),
(9, "District Hot Water"),
(10, "District Steam"),
(26, "Electric"),
(11, "Electric - Grid"),
(12, "Electric - Solar"),
(13, "Electric - Wind"),
(14, "Fuel Oil (No. 1)"),
(15, "Fuel Oil (No. 2)"),
(16, "Fuel Oil (No. 4)"),
(17, "Fuel Oil (No. 5 and No. 6)"),
(18, "Kerosene"),
(19, "Natural Gas"),
(20, "Other:"),
(21, "Propane"),
(22, "Wood"),
(23, "Cost"),
(24, "Electric - Unknown"),
(99, "Custom Meter"),
(27, "Potable Indoor"),
(29, "Potable Outdoor"),
(28, "Potable: Mixed Indoor/Outdoor"),
(30, "Heating Degree Days"),
(31, "Cooling Degree Days"),
(32, "Average Temperature"),
],
default=None,
null=True,
),
),
]
6 changes: 6 additions & 0 deletions seed/models/meters.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ class Meter(models.Model):
POTABLE_INDOOR = 27
POTABLE_MIXED = 28
POTABLE_OUTDOOR = 29
HEATING_DEGREE_DAYS = 30
COOLING_DEGREE_DAYS = 31
AVERAGE_TEMPERATURE = 32
CUSTOM_METER = 99

# Taken from EnergyStar Portfolio Manager
Expand Down Expand Up @@ -98,6 +101,9 @@ class Meter(models.Model):
(POTABLE_INDOOR, "Potable Indoor"),
(POTABLE_OUTDOOR, "Potable Outdoor"),
(POTABLE_MIXED, "Potable: Mixed Indoor/Outdoor"),
(HEATING_DEGREE_DAYS, "Heating Degree Days"),
(COOLING_DEGREE_DAYS, "Cooling Degree Days"),
(AVERAGE_TEMPERATURE, "Average Temperature"),
)
ENERGY_TYPE_BY_METER_TYPE = dict(ENERGY_TYPES)

Expand Down