Skip to content

Commit

Permalink
Merge branch 'meters-merge-update' into develop
Browse files Browse the repository at this point in the history
# Conflicts:
#	seed/tests/test_property_views.py
  • Loading branch information
axelstudios committed Jun 19, 2019
2 parents 49837d1 + f24ee6c commit ab48d0d
Show file tree
Hide file tree
Showing 9 changed files with 637 additions and 21 deletions.
46 changes: 44 additions & 2 deletions seed/models/meters.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
:copyright (c) 2014 - 2019, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Department of Energy) and contributors. All rights reserved. # NOQA
"""

from django.db import models

from django.db import (
connection,
models,
)
from seed.models import Property

from quantityfield.fields import QuantityField
Expand Down Expand Up @@ -79,6 +81,46 @@ class Meter(models.Model):

type = models.IntegerField(choices=ENERGY_TYPES, default=None, null=True)

def copy_readings(self, source_meter, overlaps_possible=True):
"""
Copies MeterReadings of another Meter. By default, overlapping readings
are considered possible so a SQL bulk upsert is used. But if overlapping
readings are explicitly specified as not possible, a more efficient
bulk_create is used.
"""
if overlaps_possible:
reading_strings = [
f"({self.id}, '{reading.start_time}', '{reading.end_time}', {reading.reading.magnitude}, '{reading.source_unit}', {reading.conversion_factor})"
for reading
in source_meter.meter_readings.all()
]

sql = (
"INSERT INTO seed_meterreading(meter_id, start_time, end_time, reading, source_unit, conversion_factor)" +
" VALUES " + ", ".join(reading_strings) +
" ON CONFLICT (meter_id, start_time, end_time)" +
" DO UPDATE SET reading = EXCLUDED.reading, source_unit = EXCLUDED.source_unit, conversion_factor = EXCLUDED.conversion_factor" +
" RETURNING reading;"
)

with connection.cursor() as cursor:
cursor.execute(sql)
else:
readings = {
MeterReading(
start_time=reading.start_time,
end_time=reading.end_time,
reading=reading.reading,
source_unit=reading.source_unit,
conversion_factor=reading.conversion_factor,
meter_id=self.id,
)
for reading
in source_meter.meter_readings.all()
}

MeterReading.objects.bulk_create(readings)


class MeterReading(models.Model):
meter = models.ForeignKey(
Expand Down
49 changes: 47 additions & 2 deletions seed/models/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
from django.apps import apps
from django.contrib.postgres.fields import JSONField
from django.contrib.gis.db import models as geomodels
from django.db import IntegrityError
from django.db import models
from django.db import (
models,
transaction,
IntegrityError,
)
from django.db.models.signals import pre_delete, pre_save, post_save, m2m_changed
from django.dispatch import receiver
from django.forms.models import model_to_dict
Expand Down Expand Up @@ -78,6 +81,48 @@ class Meta:
def __str__(self):
return 'Property - %s' % (self.pk)

def copy_meters(self, source_state_id, source_persists=True):
"""
Copies meters from a source Property to the current Property.
It's most efficient if the persistence of the source Property's readings
aren't needed as bulk reassignments can then be used.
The cases and logic are described in comments throughout.
"""
source_property = Property.objects.get(pk=source_state_id)

# If the source property has no meters to copy, there's nothing to do.
if not source_property.meters.exists():
return

if self.meters.exists() is False and source_persists is False:
# In this case, simply copy over the meters and readings from source in bulk.
source_property.meters.update(property_id=self.id)
else:
# In any other case, copy over the readings from source one meter at
# a time, checking to see if self has a similar meter each time.
for source_meter in source_property.meters.all():
with transaction.atomic():
target_meter, created = self.meters.get_or_create(
is_virtual=source_meter.is_virtual,
source=source_meter.source,
source_id=source_meter.source_id,
type=source_meter.type
)

if created:
# If self didn't have a similar meter and a new one was created,
# decide what to do depending on whether source meters need to persist.
if source_persists:
# Note, overlaps aren't possible since a new meter was created.
target_meter.copy_readings(source_meter, overlaps_possible=False)
else:
source_meter.meter_readings.update(meter=target_meter)
else:
# If self did have a similar meter, copy readings assuming overlaps are possible.
target_meter.copy_readings(source_meter, overlaps_possible=True)


class PropertyState(models.Model):
"""Store a single property. This contains all the state information about the property"""
Expand Down
12 changes: 11 additions & 1 deletion seed/static/seed/js/controllers/inventory_list_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,17 @@ angular.module('BE.seed.controller.inventory_list', [])
},
inventory_type: function () {
return $scope.inventory_type;
}
},
has_meters: function() {
if ($scope.inventory_type === 'properties') {
var inventory_ids = $scope.selectedOrder.slice().reverse();
return inventory_service.properties_meters_exist(inventory_ids).then(function (has_meters) {
return has_meters;
});
} else {
return false;
}
},
}
});
modalInstance.result.then(function () {
Expand Down
4 changes: 3 additions & 1 deletion seed/static/seed/js/controllers/merge_modal_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ angular.module('BE.seed.controller.merge_modal', [])
'columns',
'data',
'inventory_type',
function ($scope, $log, matching_service, $uibModalInstance, Notification, spinner_utility, uiGridConstants, naturalSort, columns, data, inventory_type) {
'has_meters',
function ($scope, $log, matching_service, $uibModalInstance, Notification, spinner_utility, uiGridConstants, naturalSort, columns, data, inventory_type, has_meters) {
spinner_utility.hide();

$scope.inventory_type = inventory_type;
$scope.data = data;
$scope.result = [{}];
$scope.processing = false;
$scope.has_meters = has_meters;

// Columns
$scope.columns = columns;
Expand Down
8 changes: 8 additions & 0 deletions seed/static/seed/js/services/inventory_service.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,14 @@ angular.module('BE.seed.service.inventory', []).factory('inventory_service', [
*
*/

inventory_service.properties_meters_exist = function (inventory_ids) {
return $http.post('/api/v2/properties/meters_exist/', {
inventory_ids: inventory_ids,
}).then(function (response) {
return response.data;
});
}

inventory_service.get_property = function (view_id) {
// Error checks
if (_.isNil(view_id)) {
Expand Down
5 changes: 5 additions & 0 deletions seed/static/seed/partials/merge_modal.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ <h4 class="modal-title">Merge Multiple {$:: inventory_type === 'properties' ? 'P
<div ui-grid="gridOptions" class="merge-grid" ui-grid-draggable-rows ui-grid-resize-columns style="margin-top: 5px;"></div>
</div>
</div>
<div class="modal-body row">
<div class="alert alert-warning" ng-show="has_meters">
These records have energy readings that will be merged together. Readings of records with higher priority are used for overlapping readings.
</div>
</div>
<div class="modal-footer" ng-class="{'no-click': processing}">
<button type="button" class="btn btn-default" ng-click="cancel()">Cancel</button>
<button type="button" class="btn btn-info" ng-click="merge()" autofocus>Merge</button>
Expand Down
80 changes: 80 additions & 0 deletions seed/tests/data/example-GreenButton-data-1-overlapping.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://naesb.org/espi espi.xsd">
<id>urn:uuid:5762c9e8-4e65-3b0c-83b3-7874683f3dbe</id>
<link href="/v1/espi_third_party_batch_feed" rel="self">
</link>
<title type="text">Opower ESPI Third Party Batch Feed v1</title>
<updated>2012-04-06T20:04:14.983Z</updated>
<entry>
<id>urn:uuid:7c0ea8fe-646f-32fa-b415-37b370e8edee</id>
<link href="/v1/User/6150855/UsagePoint/409483" rel="self">
</link>
<link href="/v1/User/6150855/UsagePoint/409483/MeterReading/1" rel="related">
</link>
<title type="text">A10S Large Est Values</title>
<updated>2012-04-06T20:04:14.983Z</updated>
<published>2011-11-30T12:00:00.000Z</published>
<content type="xml">
<UsagePoint xmlns="http://naesb.org/espi">
<ServiceCategory>
<kind>0</kind>
</ServiceCategory>
</UsagePoint>
</content>
</entry>
<entry>
<id>urn:uuid:64cfa7a1-aae7-305a-8d73-19f29f52a0b0</id>
<link href="/v1/User/6150855/UsagePoint/409483/MeterReading/1" rel="self">
</link>
<link href="/v1/ReadingType/1" rel="related">
</link>
<link href="/v1/User/6150855/UsagePoint/409483/MeterReading/1/IntervalBlock/1" rel="related">
</link>
<updated>2012-04-06T20:04:14.983Z</updated>
<published>2011-11-30T12:00:00.000Z</published>
<content type="xml">
<MeterReading xmlns="http://naesb.org/espi">
</MeterReading>
</content>
</entry>
<entry>
<id>urn:uuid:4e1226d5-5172-3fdf-adf6-4001aee94849</id>
<link href="/v1/ReadingType/1" rel="self">
</link>
<updated>2012-04-06T20:04:14.983Z</updated>
<published>2011-11-30T12:00:00.000Z</published>
<content type="xml">
<ReadingType xmlns="http://naesb.org/espi">
<powerOfTenMultiplier>0</powerOfTenMultiplier>
<uom>72</uom>
</ReadingType>
</content>
</entry>
<entry>
<id>urn:uuid:e50a62c2-aa08-3348-9f17-fe7893558949</id>
<link href="/v1/User/6150855/UsagePoint/409483/MeterReading/1/IntervalBlock/1" rel="self">
</link>
<content type="xml">
<IntervalBlock xmlns="http://naesb.org/espi">
<interval>
<duration>34383600</duration>
<start>1299387600</start>
</interval>
<IntervalReading>
<timePeriod>
<duration>900</duration>
<start>1299388500</start>
</timePeriod>
<value>100</value>
</IntervalReading>
<IntervalReading>
<timePeriod>
<duration>900</duration>
<start>1299389400</start>
</timePeriod>
<value>200</value>
</IntervalReading>
</IntervalBlock>
</content>
</entry>
</feed>

0 comments on commit ab48d0d

Please sign in to comment.