def test_property_existing_in_multiple_cycles_can_have_meters_and_readings_associated_to_it( self): property_details = FakePropertyStateFactory( organization=self.org).get_details() property_details['organization_id'] = self.org.id # new state to be associated to new cycle using the same pm_property_id as state in old cycle property_details['pm_property_id'] = self.state_1.pm_property_id state = PropertyState(**property_details) state.save() new_property_state = PropertyState.objects.get(pk=state.id) new_cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) new_cycle = new_cycle_factory.get_cycle( start=datetime(2010, 10, 10, tzinfo=get_current_timezone())) # new state and cycle associated to old property PropertyView.objects.create(property=self.property_1, cycle=new_cycle, state=new_property_state) url = reverse("api:v3:import_files-start-save-data", args=[self.import_file.id]) url += f'?organization_id={self.org.pk}' post_params = { 'cycle_id': self.cycle.pk, } self.client.post(url, post_params) refreshed_property_1 = Property.objects.get(pk=self.property_1.id) self.assertEqual(refreshed_property_1.meters.all().count(), 2)
def setUp(self): user_details = { 'username': '******', 'password': '******', 'email': '*****@*****.**' } self.user = User.objects.create_superuser(**user_details) self.org, self.org_user, _ = create_organization(self.user) self.cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) self.property_factory = FakePropertyFactory(organization=self.org) self.property_state_factory = FakePropertyStateFactory(organization=self.org) self.cycle = self.cycle_factory.get_cycle( start=datetime(2010, 10, 10, tzinfo=get_current_timezone())) self.client.login(**user_details) self.state_1 = self.property_state_factory.get_property_state( address_line_1='1 property state', pm_property_id='5766973' # this allows the Property to be targetted for PM meter additions ) self.property_1 = self.property_factory.get_property() self.view_1 = PropertyView.objects.create( property=self.property_1, cycle=self.cycle, state=self.state_1 ) self.state_2 = self.property_state_factory.get_property_state(address_line_1='2 property state') self.property_2 = self.property_factory.get_property() self.view_2 = PropertyView.objects.create( property=self.property_2, cycle=self.cycle, state=self.state_2 ) self.import_record = ImportRecord.objects.create(owner=self.user, last_modified_by=self.user, super_organization=self.org)
def setUp(self): self.user_details = { 'username': '******', 'password': '******', } self.user = User.objects.create_superuser(email='*****@*****.**', **self.user_details) self.org, _, _ = create_organization(self.user) self.property_state_factory = FakePropertyStateFactory( organization=self.org) property_details = self.property_state_factory.get_details() self.pm_property_id = '12345' property_details['pm_property_id'] = self.pm_property_id property_details['organization_id'] = self.org.id state = PropertyState(**property_details) state.save() self.state = PropertyState.objects.get(pk=state.id) self.cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) self.cycle = self.cycle_factory.get_cycle( start=datetime(2010, 10, 10, tzinfo=get_current_timezone())) self.property_factory = FakePropertyFactory(organization=self.org) self.property = self.property_factory.get_property() self.property_view = PropertyView.objects.create( property=self.property, cycle=self.cycle, state=self.state) self.tz_obj = timezone(TIME_ZONE)
def setUp(self): user_details = { 'username': '******', 'password': '******', 'email': '*****@*****.**' } self.user = User.objects.create_superuser(**user_details) self.org, _, _ = create_organization(self.user) self.status_label = StatusLabel.objects.create( name='test', super_organization=self.org ) self.column_factory = FakeColumnFactory(organization=self.org) self.cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) self.property_factory = FakePropertyFactory(organization=self.org) self.property_state_factory = FakePropertyStateFactory(organization=self.org) self.taxlot_state_factory = FakeTaxLotStateFactory(organization=self.org) self.cycle = self.cycle_factory.get_cycle( start=datetime(2010, 10, 10, tzinfo=timezone.get_current_timezone()) ) self.default_bsync_profile = ColumnMappingProfile.objects.get(profile_type=ColumnMappingProfile.BUILDINGSYNC_DEFAULT) self.client.login(**user_details)
def setUp(self): user_details = { 'username': '******', 'password': '******', 'email': '*****@*****.**' } self.user = User.objects.create_superuser(**user_details) self.org, self.org_user, _ = create_organization(self.user) self.cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) self.taxlot_factory = FakeTaxLotFactory(organization=self.org) self.taxlot_state_factory = FakeTaxLotStateFactory( organization=self.org) self.cycle = self.cycle_factory.get_cycle( start=datetime(2010, 10, 10, tzinfo=get_current_timezone())) self.client.login(**user_details) self.state_1 = self.taxlot_state_factory.get_taxlot_state() self.taxlot_1 = self.taxlot_factory.get_taxlot() self.view_1 = TaxLotView.objects.create(taxlot=self.taxlot_1, cycle=self.cycle, state=self.state_1) self.state_2 = self.taxlot_state_factory.get_taxlot_state() self.taxlot_2 = self.taxlot_factory.get_taxlot() self.view_2 = TaxLotView.objects.create(taxlot=self.taxlot_2, cycle=self.cycle, state=self.state_2)
def setUp(self): self.maxDiff = None user_details = { 'username': '******', 'password': '******', } self.user = User.objects.create_superuser(email='*****@*****.**', **user_details) self.org = Organization.objects.create() OrganizationUser.objects.create(user=self.user, organization=self.org) self.audit_log_factory = FakePropertyAuditLogFactory( organization=self.org, user=self.user) self.cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) self.property_state_factory = FakePropertyStateFactory( organization=self.org) self.property_view_factory = FakePropertyViewFactory( organization=self.org, user=self.user) self.ga_factory = FakeGreenAssessmentFactory(organization=self.org) self.gap_factory = FakeGreenAssessmentPropertyFactory( organization=self.org, user=self.user) self.taxlot_property_factory = FakeTaxLotPropertyFactory( organization=self.org, user=self.user) self.taxlot_state_factory = FakeTaxLotStateFactory( organization=self.org) self.taxlot_view_factory = FakeTaxLotViewFactory(organization=self.org, user=self.user) self.assessment = self.ga_factory.get_green_assessment() self.cycle = self.cycle_factory.get_cycle() self.property_state = self.property_state_factory.get_property_state() self.property_view = self.property_view_factory.get_property_view( state=self.property_state, cycle=self.cycle) self.taxlot_state = self.taxlot_state_factory.get_taxlot_state() self.taxlot_view = self.taxlot_view_factory.get_taxlot_view( state=self.taxlot_state, cycle=self.cycle) self.audit_log = self.audit_log_factory.get_property_audit_log( state=self.property_state, view=self.property_view, record_type=AUDIT_USER_EDIT, description=json.dumps(['a', 'b'])) self.audit_log2 = self.audit_log_factory.get_property_audit_log( view=self.property_view) self.gap_data = { 'source': 'test', 'status': 'complete', 'status_date': datetime.date(2017, 0o1, 0o1), 'metric': 5, 'version': '0.1', 'date': datetime.date(2016, 0o1, 0o1), 'eligibility': True, 'assessment': self.assessment, 'view': self.property_view, } self.urls = ['http://example.com', 'http://example.org'] self.gap = self.gap_factory.get_green_assessment_property( **self.gap_data) self.serializer = PropertyViewAsStateSerializer( instance=self.property_view)
def setUp(self): user_details = { 'username': '******', 'password': '******', 'email': '*****@*****.**' } self.user = User.objects.create_superuser(**user_details) self.org, self.org_user, _ = create_organization(self.user) self.cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) self.property_factory = FakePropertyFactory(organization=self.org) self.property_state_factory = FakePropertyStateFactory(organization=self.org) self.cycle = self.cycle_factory.get_cycle( start=datetime(2010, 10, 10, tzinfo=get_current_timezone())) self.client.login(**user_details) self.state_1 = self.property_state_factory.get_property_state( address_line_1='1 property state', pm_property_id='5766973' # this allows the Property to be targetted for PM meter additions ) self.property_1 = self.property_factory.get_property() PropertyView.objects.create( property=self.property_1, cycle=self.cycle, state=self.state_1 ) self.state_2 = self.property_state_factory.get_property_state(address_line_1='2 property state') self.property_2 = self.property_factory.get_property() PropertyView.objects.create( property=self.property_2, cycle=self.cycle, state=self.state_2 ) self.import_record = ImportRecord.objects.create(owner=self.user, last_modified_by=self.user, super_organization=self.org) # Give 2 meters to one of the properties gb_filename = "example-GreenButton-data.xml" filepath = os.path.dirname(os.path.abspath(__file__)) + "/data/" + gb_filename gb_import_file = ImportFile.objects.create( import_record=self.import_record, source_type="GreenButton", uploaded_filename=gb_filename, file=SimpleUploadedFile(name=gb_filename, content=open(filepath, 'rb').read()), cycle=self.cycle, matching_results_data={"property_id": self.property_1.id} # this is how target property is specified ) gb_import_url = reverse("api:v2:import_files-save-raw-data", args=[gb_import_file.id]) gb_import_post_params = { 'cycle_id': self.cycle.pk, 'organization_id': self.org.pk, } self.client.post(gb_import_url, gb_import_post_params) # Merge the properties url = reverse('api:v2:properties-merge') + '?organization_id={}'.format(self.org.pk) post_params = json.dumps({ 'state_ids': [self.state_2.pk, self.state_1.pk] # priority given to state_1 }) self.client.post(url, post_params, content_type='application/json')
def setUp(self): selfvars = self.set_up(ASSESSED_RAW) self.user, self.org, self.import_file_1, self.import_record_1, self.cycle_1 = selfvars cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) self.cycle_2 = cycle_factory.get_cycle(name="Cycle 2") self.import_record_2, self.import_file_2 = self.create_import_file( self.user, self.org, self.cycle_2) self.property_state_factory = FakePropertyStateFactory( organization=self.org) self.taxlot_state_factory = FakeTaxLotStateFactory( organization=self.org)
def setUp(self): user_details = { 'username': '******', 'password': '******', 'email': '*****@*****.**', 'first_name': 'Test', 'last_name': 'User', } self.user = User.objects.create_user(**user_details) self.org_a, _, _ = create_organization(self.user) self.org_b, _, _ = create_organization(self.user) cycle_a = FakeCycleFactory(organization=self.org_a, user=self.user).get_cycle(name="Cycle Org A") cycle_b = FakeCycleFactory(organization=self.org_b, user=self.user).get_cycle(name="Cycle Org B") self.analysis_a = ( FakeAnalysisFactory(organization=self.org_a, user=self.user).get_analysis( name='Quite neat', service=Analysis.BSYNCR, ) ) view_factory_a = FakePropertyViewFactory(cycle=cycle_a, organization=self.org_a, user=self.user) self.property_views_a = [ view_factory_a.get_property_view( # override unitted fields so that hashes are correct site_eui=ureg.Quantity( float(view_factory_a.fake.random_int(min=50, max=600)), "kilobtu / foot ** 2 / year" ), gross_floor_area=ureg.Quantity( float(view_factory_a.fake.random_number(digits=6)), "foot ** 2" ), ) for i in range(2)] view_factory_b = FakePropertyViewFactory(cycle=cycle_b, organization=self.org_b, user=self.user) self.property_views_b = [ view_factory_b.get_property_view( # override unitted fields so that hashes are correct site_eui=ureg.Quantity( float(view_factory_b.fake.random_int(min=50, max=600)), "kilobtu / foot ** 2 / year" ), gross_floor_area=ureg.Quantity( float(view_factory_b.fake.random_number(digits=6)), "foot ** 2" ), ) for i in range(2)]
def setUp(self): self.api_view = UpdateInventoryLabelsAPIView() # Models can't be imported directly hence self self.PropertyViewLabels = self.api_view.models['property'] self.TaxlotViewLabels = self.api_view.models['taxlot'] self.user_details = { 'username': '******', 'password': '******', 'email': '*****@*****.**' } self.user = User.objects.create_superuser(**self.user_details) self.org, _, _ = create_organization(self.user) self.status_label = Label.objects.create( name='test', super_organization=self.org ) self.status_label_2 = Label.objects.create( name='test_2', super_organization=self.org ) self.client.login(**self.user_details) self.label_1 = Label.objects.all()[0] self.label_2 = Label.objects.all()[1] self.label_3 = Label.objects.all()[2] self.label_4 = Label.objects.all()[3] # Create some real PropertyViews, Properties, PropertyStates, and StatusLabels since validations happen cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) cycle = cycle_factory.get_cycle(start=datetime(2010, 10, 10, tzinfo=timezone.get_current_timezone())) property_state_factory = FakePropertyStateFactory(organization=self.org) for i in range(1, 11): ps = property_state_factory.get_property_state() p = Property.objects.create(organization=self.org) PropertyView.objects.create( cycle=cycle, state=ps, property=p ) self.propertyview_ids = PropertyView.objects.all().order_by('id').values_list('id', flat=True) self.mock_propertyview_label_qs = mock_queryset_factory( self.PropertyViewLabels, flatten=True, propertyview_id=self.propertyview_ids, statuslabel_id=[self.label_1.id] * 3 + [self.label_2.id] * 3 + [self.label_3.id] * 2 + [self.label_4.id] * 2 )
def setUp(self): self.user_details = { 'username': '******', 'password': '******', } self.user = User.objects.create_superuser(email='*****@*****.**', **self.user_details) self.org, _, _ = create_organization(self.user) self.client.login(**self.user_details) self.property_state_factory = FakePropertyStateFactory( organization=self.org) property_details = self.property_state_factory.get_details() property_details['organization_id'] = self.org.id state_1 = PropertyState(**property_details) state_1.save() self.state_1 = PropertyState.objects.get(pk=state_1.id) self.cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) self.cycle = self.cycle_factory.get_cycle( start=datetime(2010, 10, 10, tzinfo=get_current_timezone())) self.property_factory = FakePropertyFactory(organization=self.org) self.property_1 = self.property_factory.get_property() self.property_view_1 = PropertyView.objects.create( property=self.property_1, cycle=self.cycle, state=self.state_1) self.import_record = ImportRecord.objects.create( owner=self.user, last_modified_by=self.user, super_organization=self.org) filename = "example-GreenButton-data.xml" filepath = os.path.dirname( os.path.abspath(__file__)) + "/data/" + filename self.import_file = ImportFile.objects.create( import_record=self.import_record, source_type="GreenButton", uploaded_filename=filename, file=SimpleUploadedFile(name=filename, content=open(filepath, 'rb').read()), cycle=self.cycle, matching_results_data={"property_id": self.property_1.id}) self.tz_obj = timezone(TIME_ZONE)
def setUp(self): user_details = { 'username': '******', 'password': '******', 'email': '*****@*****.**' } self.user = User.objects.create_superuser(**user_details) self.org, self.org_user, _ = create_organization(self.user) self.column_factory = FakeColumnFactory(organization=self.org) self.cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) self.property_factory = FakePropertyFactory(organization=self.org) self.property_state_factory = FakePropertyStateFactory(organization=self.org) self.property_view_factory = FakePropertyViewFactory(organization=self.org) self.taxlot_state_factory = FakeTaxLotStateFactory(organization=self.org) self.cycle = self.cycle_factory.get_cycle( start=datetime(2010, 10, 10, tzinfo=timezone.get_current_timezone())) self.client.login(**user_details)
class InventoryViewTests(DeleteModelsTestCase): def setUp(self): user_details = { 'username': '******', 'password': '******', 'email': '*****@*****.**' } self.user = User.objects.create_superuser(**user_details) self.org, _, _ = create_organization(self.user) self.status_label = StatusLabel.objects.create( name='test', super_organization=self.org ) self.column_factory = FakeColumnFactory(organization=self.org) self.cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) self.property_factory = FakePropertyFactory(organization=self.org) self.property_state_factory = FakePropertyStateFactory(organization=self.org) self.taxlot_state_factory = FakeTaxLotStateFactory(organization=self.org) self.cycle = self.cycle_factory.get_cycle( start=datetime(2010, 10, 10, tzinfo=timezone.get_current_timezone()) ) self.client.login(**user_details) def test_get_building_sync(self): state = self.property_state_factory.get_property_state() prprty = self.property_factory.get_property() pv = PropertyView.objects.create( property=prprty, cycle=self.cycle, state=state ) # go to buildingsync endpoint params = { 'organization_id': self.org.pk } url = reverse('api:v2.1:properties-building-sync', args=[pv.id]) response = self.client.get(url, params) self.assertIn('<auc:FloorAreaValue>%s.0</auc:FloorAreaValue>' % state.gross_floor_area, response.content) def test_get_hpxml(self): state = self.property_state_factory.get_property_state() prprty = self.property_factory.get_property() pv = PropertyView.objects.create( property=prprty, cycle=self.cycle, state=state ) # go to buildingsync endpoint params = { 'organization_id': self.org.pk } url = reverse('api:v2.1:properties-hpxml', args=[pv.id]) response = self.client.get(url, params) self.assertIn('<GrossFloorArea>%s.0</GrossFloorArea>' % state.gross_floor_area, response.content)
def setUp(self): selfvars = self.set_up(ASSESSED_RAW) self.user, self.org, self.import_file_1, self.import_record_1, self.cycle_1 = selfvars cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) self.cycle_2 = cycle_factory.get_cycle(name="Cycle 2") self.import_record_2, self.import_file_2 = self.create_import_file( self.user, self.org, self.cycle_2 ) user_details = { 'username': '******', 'password': '******', 'email': '*****@*****.**' } self.client.login(**user_details) self.property_state_factory = FakePropertyStateFactory(organization=self.org) self.taxlot_state_factory = FakeTaxLotStateFactory(organization=self.org)
def setUp(self): self.maxDiff = None user_details = { 'username': '******', 'password': '******', } self.user = User.objects.create_superuser( email='*****@*****.**', **user_details) self.org, self.org_user, _ = create_organization(self.user) self.cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) self.property_factory = FakePropertyFactory(organization=self.org) self.property_state_factory = FakePropertyStateFactory(organization=self.org) self.column_list_factory = FakeColumnListSettingsFactory(organization=self.org) self.cycle = self.cycle_factory.get_cycle( start=datetime(2010, 10, 10, tzinfo=timezone.get_current_timezone()) ) class ProfileIdMixInclass(ProfileIdMixin): pass self.mixin_class = ProfileIdMixInclass()
def test_pm_property_id_existing_across_two_different_orgs_wont_lead_to_misassociated_meters( self): new_org, _, _ = create_organization(self.user) property_details = FakePropertyStateFactory( organization=new_org).get_details() property_details['organization_id'] = new_org.id # new state to be associated to property of different organization but has the same pm_property_id property_details['pm_property_id'] = self.state_1.pm_property_id state = PropertyState(**property_details) state.save() new_property_state = PropertyState.objects.get(pk=state.id) new_cycle_factory = FakeCycleFactory(organization=new_org, user=self.user) new_cycle = new_cycle_factory.get_cycle( start=datetime(2010, 10, 10, tzinfo=get_current_timezone())) new_property = self.property_factory.get_property() PropertyView.objects.create(property=new_property, cycle=new_cycle, state=new_property_state) url = reverse("api:v3:import_files-start-save-data", args=[self.import_file.id]) url += f'?organization_id={self.org.pk}' post_params = { 'cycle_id': self.cycle.pk, } self.client.post(url, post_params) # self.property_1 is associated to self.org, so according to post request, it should have 2 meters refreshed_property_1 = Property.objects.get( pk=self.property_1.id, organization_id__exact=self.org.pk) self.assertEqual(refreshed_property_1.meters.all().count(), 2) refreshed_new_property = Property.objects.get(pk=new_property.id) self.assertEqual(refreshed_new_property.meters.count(), 0)
def setUp(self): user_details = { 'username': '******', 'password': '******', 'email': '*****@*****.**' } self.user = User.objects.create_superuser(**user_details) self.org, self.org_user, _ = create_organization(self.user) self.cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) self.cycle = self.cycle_factory.get_cycle( start=datetime(2010, 10, 10, tzinfo=timezone.get_current_timezone()) ) self.property_factory = FakePropertyFactory(organization=self.org) self.property_state_factory = FakePropertyStateFactory(organization=self.org) self.property_view_factory = FakePropertyViewFactory(organization=self.org, cycle=self.cycle) self.taxlot_state_factory = FakeTaxLotStateFactory(organization=self.org) self.taxlot_view_factory = FakeTaxLotViewFactory(organization=self.org, cycle=self.cycle) # create 10 addresses that are exactly the same import_record = ImportRecord.objects.create(super_organization=self.org) self.import_file = ImportFile.objects.create( import_record=import_record, cycle=self.cycle, )
class GreenButtonImportTest(DataMappingBaseTestCase): def setUp(self): self.user_details = { 'username': '******', 'password': '******', } self.user = User.objects.create_superuser(email='*****@*****.**', **self.user_details) self.org, _, _ = create_organization(self.user) self.client.login(**self.user_details) self.property_state_factory = FakePropertyStateFactory( organization=self.org) property_details = self.property_state_factory.get_details() property_details['organization_id'] = self.org.id state_1 = PropertyState(**property_details) state_1.save() self.state_1 = PropertyState.objects.get(pk=state_1.id) self.cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) self.cycle = self.cycle_factory.get_cycle( start=datetime(2010, 10, 10, tzinfo=get_current_timezone())) self.property_factory = FakePropertyFactory(organization=self.org) self.property_1 = self.property_factory.get_property() self.property_view_1 = PropertyView.objects.create( property=self.property_1, cycle=self.cycle, state=self.state_1) self.import_record = ImportRecord.objects.create( owner=self.user, last_modified_by=self.user, super_organization=self.org) filename = "example-GreenButton-data.xml" filepath = os.path.dirname( os.path.abspath(__file__)) + "/data/" + filename self.import_file = ImportFile.objects.create( import_record=self.import_record, source_type="GreenButton", uploaded_filename=filename, file=SimpleUploadedFile(name=filename, content=open(filepath, 'rb').read()), cycle=self.cycle, matching_results_data={"property_id": self.property_1.id}) self.tz_obj = timezone(TIME_ZONE) def test_green_button_import_base_case(self): url = reverse("api:v2:import_files-save-raw-data", args=[self.import_file.id]) post_params = { 'cycle_id': self.cycle.pk, 'organization_id': self.org.pk, } self.client.post(url, post_params) refreshed_property_1 = Property.objects.get(pk=self.property_1.id) self.assertEqual(refreshed_property_1.meters.all().count(), 1) meter_1 = refreshed_property_1.meters.get(type=Meter.ELECTRICITY_GRID) self.assertEqual(meter_1.source, Meter.GREENBUTTON) self.assertEqual( meter_1.source_id, 'User/6150855/UsagePoint/409483/MeterReading/1/IntervalBlock/1') self.assertEqual(meter_1.is_virtual, False) self.assertEqual(meter_1.meter_readings.all().count(), 2) meter_reading_10, meter_reading_11 = list( meter_1.meter_readings.order_by('start_time').all()) self.assertEqual( meter_reading_10.start_time, make_aware(datetime(2011, 3, 5, 21, 0, 0), timezone=self.tz_obj)) self.assertEqual( meter_reading_10.end_time, make_aware(datetime(2011, 3, 5, 21, 15, 0), timezone=self.tz_obj)) self.assertEqual(meter_reading_10.reading, 1790 * 3.41 / 1000) self.assertEqual(meter_reading_10.source_unit, 'kWh (thousand Watt-hours)') self.assertEqual(meter_reading_10.conversion_factor, 3.41) self.assertEqual( meter_reading_11.start_time, make_aware(datetime(2011, 3, 5, 21, 15, 0), timezone=self.tz_obj)) self.assertEqual( meter_reading_11.end_time, make_aware(datetime(2011, 3, 5, 21, 30, 0), timezone=self.tz_obj)) self.assertEqual(meter_reading_11.reading, 1791 * 3.41 / 1000) self.assertEqual(meter_reading_11.source_unit, 'kWh (thousand Watt-hours)') self.assertEqual(meter_reading_11.conversion_factor, 3.41) # matching_results_data gets cleared out since the field wasn't meant for this refreshed_import_file = ImportFile.objects.get(pk=self.import_file.id) self.assertEqual(refreshed_import_file.matching_results_data, {}) # file should be disassociated from cycle too self.assertEqual(refreshed_import_file.cycle_id, None) def test_existing_meter_is_found_and_used_if_import_file_should_reference_it( self): property = Property.objects.get(pk=self.property_1.id) # Create a meter with the same details of the meter in the import file unsaved_meter = Meter( property=property, source=Meter.GREENBUTTON, source_id= 'User/6150855/UsagePoint/409483/MeterReading/1/IntervalBlock/1', type=Meter.ELECTRICITY_GRID, ) unsaved_meter.save() existing_meter = Meter.objects.get(pk=unsaved_meter.id) # Create a reading with a different date from those in the import file unsaved_meter_reading = MeterReading( meter=existing_meter, start_time=make_aware(datetime(2018, 1, 1, 0, 0, 0), timezone=self.tz_obj), end_time=make_aware(datetime(2018, 2, 1, 0, 0, 0), timezone=self.tz_obj), reading=12345, ) unsaved_meter_reading.save() existing_meter_reading = MeterReading.objects.get(reading=12345) url = reverse("api:v2:import_files-save-raw-data", args=[self.import_file.id]) post_params = { 'cycle_id': self.cycle.pk, 'organization_id': self.org.pk, } self.client.post(url, post_params) refreshed_property_1 = Property.objects.get(pk=self.property_1.id) self.assertEqual(refreshed_property_1.meters.all().count(), 1) refreshed_meter = refreshed_property_1.meters.get( type=Meter.ELECTRICITY_GRID) meter_reading_10, meter_reading_11, meter_reading_12 = list( refreshed_meter.meter_readings.order_by('start_time').all()) self.assertEqual(meter_reading_10.reading, 1790 * 3.41 / 1000) self.assertEqual(meter_reading_11.reading, 1791 * 3.41 / 1000) # Sanity check to be sure, nothing was changed with existing meter reading self.assertEqual(meter_reading_12, existing_meter_reading) def test_existing_meter_reading_has_reading_source_unit_and_conversion_factor_updated_if_import_file_references_previous_entry( self): property = Property.objects.get(pk=self.property_1.id) # Create a meter with the same details of one meter in the import file unsaved_meter = Meter( property=property, source=Meter.GREENBUTTON, source_id= 'User/6150855/UsagePoint/409483/MeterReading/1/IntervalBlock/1', type=Meter.ELECTRICITY_GRID, ) unsaved_meter.save() existing_meter = Meter.objects.get(pk=unsaved_meter.id) # Create a reading with the same date as one from the import file but different reading start_time = make_aware(datetime(2011, 3, 5, 21, 0, 0), timezone=self.tz_obj) end_time = make_aware(datetime(2011, 3, 5, 21, 15, 0), timezone=self.tz_obj) unsaved_meter_reading = MeterReading(meter=existing_meter, start_time=start_time, end_time=end_time, reading=1000, source_unit="GJ", conversion_factor=947.82) unsaved_meter_reading.save() url = reverse("api:v2:import_files-save-raw-data", args=[self.import_file.id]) post_params = { 'cycle_id': self.cycle.pk, 'organization_id': self.org.pk, } self.client.post(url, post_params) # Just as in the first test, 2 meter readings should exist self.assertEqual(MeterReading.objects.all().count(), 2) refreshed_property = Property.objects.get(pk=self.property_1.id) refreshed_meter = refreshed_property.meters.get( type=Meter.ELECTRICITY_GRID) meter_reading = refreshed_meter.meter_readings.get( start_time=start_time) self.assertEqual(meter_reading.end_time, end_time) self.assertEqual(meter_reading.reading, 1790 * 3.41 / 1000) self.assertEqual(meter_reading.source_unit, 'kWh (thousand Watt-hours)') self.assertEqual(meter_reading.conversion_factor, 3.41) def test_the_response_contains_expected_and_actual_reading_counts_for_pm_ids( self): url = reverse("api:v2:import_files-save-raw-data", args=[self.import_file.id]) post_params = { 'cycle_id': self.cycle.pk, 'organization_id': self.org.pk, } response = self.client.post(url, post_params) result = json.loads(response.content) expectation = [ { "source_id": "409483", "incoming": 2, "type": "Electric - Grid", "successfully_imported": 2, }, ] self.assertEqual(result['message'], expectation) def test_error_noted_in_response_if_meter_has_overlapping_readings_in_the_same_batch( self): filename = 'example-GreenButton-data-1002-1-dup.xml' filepath = os.path.dirname( os.path.abspath(__file__)) + "/data/" + filename one_dup_import_file = ImportFile.objects.create( import_record=self.import_record, source_type="GreenButton", uploaded_filename=filename, file=SimpleUploadedFile(name=filename, content=open(filepath, 'rb').read()), cycle=self.cycle, matching_results_data={"property_id": self.property_1.id}) url = reverse("api:v2:import_files-save-raw-data", args=[one_dup_import_file.id]) post_params = { 'cycle_id': self.cycle.pk, 'organization_id': self.org.pk, } response = self.client.post(url, post_params) result = json.loads(response.content) expectation = [ { "source_id": "409483", "type": "Electric - Grid", "incoming": 1002, "successfully_imported": 1000, "errors": 'Overlapping readings.', }, ] self.assertEqual(result['message'], expectation)
class TestPropertyViewAsStateSerializers(DeleteModelsTestCase): def setUp(self): self.maxDiff = None user_details = { 'username': '******', 'password': '******', } self.user = User.objects.create_superuser(email='*****@*****.**', **user_details) self.org, _, _ = create_organization(self.user) self.audit_log_factory = FakePropertyAuditLogFactory( organization=self.org, user=self.user) self.cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) self.property_state_factory = FakePropertyStateFactory( organization=self.org) self.property_view_factory = FakePropertyViewFactory( organization=self.org, user=self.user) self.ga_factory = FakeGreenAssessmentFactory(organization=self.org) self.gap_factory = FakeGreenAssessmentPropertyFactory( organization=self.org, user=self.user) self.taxlot_property_factory = FakeTaxLotPropertyFactory( organization=self.org, user=self.user) self.taxlot_state_factory = FakeTaxLotStateFactory( organization=self.org) self.taxlot_view_factory = FakeTaxLotViewFactory(organization=self.org, user=self.user) self.assessment = self.ga_factory.get_green_assessment() self.cycle = self.cycle_factory.get_cycle() self.property_state = self.property_state_factory.get_property_state() self.property_view = self.property_view_factory.get_property_view( state=self.property_state, cycle=self.cycle) self.taxlot_state = self.taxlot_state_factory.get_taxlot_state() self.taxlot_view = self.taxlot_view_factory.get_taxlot_view( state=self.taxlot_state, cycle=self.cycle) self.audit_log = self.audit_log_factory.get_property_audit_log( state=self.property_state, view=self.property_view, record_type=AUDIT_USER_EDIT, description=json.dumps(['a', 'b'])) self.audit_log2 = self.audit_log_factory.get_property_audit_log( view=self.property_view) self.gap_data = { 'source': 'test', 'status': 'complete', 'status_date': datetime.date(2017, 0o1, 0o1), 'metric': 5, 'version': '0.1', 'date': datetime.date(2016, 0o1, 0o1), 'eligibility': True, 'assessment': self.assessment, 'view': self.property_view, } self.urls = ['http://example.com', 'http://example.org'] self.gap = self.gap_factory.get_green_assessment_property( **self.gap_data) self.serializer = PropertyViewAsStateSerializer( instance=self.property_view) def test_init(self): """Test __init__.""" expected = PropertyAuditLogReadOnlySerializer(self.audit_log).data # for now convert the site_eui to a magnitude to get the test to pass # this really needs to be at another level data = self.serializer.current # data['state']['site_eui'] = data['state']['site_eui'].magnitude self.assertEqual(data, expected) def test_get_certifications(self): """Test get_certifications""" expected = [GreenAssessmentPropertyReadOnlySerializer(self.gap).data] self.assertEqual( self.serializer.get_certifications(self.property_view), expected) def test_get_changed_fields(self): """Test get_changed_fields""" expected = ['a', 'b'] self.assertEqual(self.serializer.get_changed_fields(None), expected) def test_get_date_edited(self): """Test get_date_edited""" expected = self.audit_log.created.ctime() self.assertEqual(self.serializer.get_date_edited(None), expected) def test_get_filename(self): """Test get_filename""" expected = self.audit_log.import_filename self.assertEqual(self.serializer.get_filename(None), expected) def test_get_history(self): """Test get_history""" obj = mock.MagicMock() obj.state = self.property_state data = self.serializer.get_history(obj) # Really need to figure out how to get the serializer to save the magnitude correctly. # data[0]['state']['site_eui'] = data[0]['state']['site_eui'].magnitude expected = [PropertyAuditLogReadOnlySerializer(self.audit_log2).data] self.assertEqual(data, expected) def test_get_state(self): obj = mock.MagicMock() obj.state = self.property_state def test_get_source(self): """Test get_source""" expected = self.audit_log.get_record_type_display() self.assertEqual(self.serializer.get_source(None), expected) def test_get_taxlots(self): """Test get_taxlots""" self.taxlot_property_factory.get_taxlot_property( cycle=self.cycle, property_view=self.property_view, taxlot_view=self.taxlot_view) result = self.serializer.get_taxlots(self.property_view) self.assertEqual(result[0]['state']['id'], self.taxlot_state.id) @mock.patch('seed.serializers.properties.PropertyView') @mock.patch('seed.serializers.properties.PropertyStateWritableSerializer') def test_create(self, mock_serializer, mock_pview): """Test create""" mock_serializer.return_value.is_valid.return_value = True mock_serializer.return_value.save.return_value = self.property_state mock_pview.objects.create.return_value = self.property_view data = {'org_id': 1, 'cycle': 2, 'state': {'test': 3}, 'property': 4} serializer = PropertyViewAsStateSerializer() serializer.create(data) mock_serializer.assert_called_with(data={'test': 3}) self.assertTrue(mock_serializer.return_value.save.called) mock_pview.objects.create.assert_called_with(state=self.property_state, cycle_id=2, property_id=4, org_id=1) @mock.patch('seed.serializers.properties.PropertyStateWritableSerializer') def test_update_put(self, mock_serializer): """Test update with PUT""" mock_serializer.return_value.is_valid.return_value = True mock_serializer.return_value.save.return_value = self.property_state mock_request = mock.MagicMock() data = {'org_id': 1, 'cycle': 2, 'state': {'test': 3}, 'property': 4} serializer = PropertyViewAsStateSerializer( context={'request': mock_request}) mock_request.METHOD = 'PUT' serializer.update(self.property_view, data) mock_serializer.assert_called_with(data={'test': 3}) self.assertTrue(mock_serializer.return_value.save.called) @mock.patch('seed.serializers.properties.PropertyStateWritableSerializer') def test_update_patch(self, mock_serializer): """Test update with PATCH""" mock_serializer.return_value.is_valid.return_value = True mock_serializer.return_value.save.return_value = self.property_state mock_request = mock.MagicMock() mock_request.method = 'PATCH' data = {'org_id': 1, 'cycle': 2, 'state': {'test': 3}, 'property': 4} serializer = PropertyViewAsStateSerializer( context={'request': mock_request}) serializer.update(self.property_view, data) mock_serializer.assert_called_with(self.property_state, data={'test': 3}) self.assertTrue(mock_serializer.return_value.save.called)
class TestMeterViewSet(DataMappingBaseTestCase): def setUp(self): self.user_details = { 'username': '******', 'password': '******', } self.user = User.objects.create_superuser(email='*****@*****.**', **self.user_details) self.org, _, _ = create_organization(self.user) # For some reason, defaults weren't established consistently for each test. self.org.display_meter_units = Organization._default_display_meter_units.copy( ) self.org.save() self.client.login(**self.user_details) self.property_state_factory = FakePropertyStateFactory( organization=self.org) property_details = self.property_state_factory.get_details() property_details['organization_id'] = self.org.id # pm_property_ids must match those within example-monthly-meter-usage.xlsx self.pm_property_id_1 = '5766973' self.pm_property_id_2 = '5766975' property_details['pm_property_id'] = self.pm_property_id_1 state_1 = PropertyState(**property_details) state_1.save() self.state_1 = PropertyState.objects.get(pk=state_1.id) property_details['pm_property_id'] = self.pm_property_id_2 state_2 = PropertyState(**property_details) state_2.save() self.state_2 = PropertyState.objects.get(pk=state_2.id) self.cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) self.cycle = self.cycle_factory.get_cycle( start=datetime(2010, 10, 10, tzinfo=get_current_timezone())) self.property_factory = FakePropertyFactory(organization=self.org) self.property_1 = self.property_factory.get_property() self.property_2 = self.property_factory.get_property() self.property_view_1 = PropertyView.objects.create( property=self.property_1, cycle=self.cycle, state=self.state_1) self.property_view_2 = PropertyView.objects.create( property=self.property_2, cycle=self.cycle, state=self.state_2) self.import_record = ImportRecord.objects.create( owner=self.user, last_modified_by=self.user, super_organization=self.org) # This file has multiple tabs filename = "example-pm-monthly-meter-usage.xlsx" filepath = os.path.dirname( os.path.abspath(__file__)) + "/data/" + filename self.import_file = ImportFile.objects.create( import_record=self.import_record, source_type="PM Meter Usage", uploaded_filename=filename, file=SimpleUploadedFile(name=filename, content=open(filepath, 'rb').read()), cycle=self.cycle) def test_parsed_meters_confirmation_verifies_energy_type_and_units(self): url = reverse('api:v2:meters-parsed-meters-confirmation') post_params = json.dumps({ 'file_id': self.import_file.id, 'organization_id': self.org.pk, }) result = self.client.post(url, post_params, content_type="application/json") result_dict = ast.literal_eval(result.content.decode("utf-8")) expectation = [ { "parsed_type": "Electric - Grid", "parsed_unit": "kBtu (thousand Btu)", }, { "parsed_type": "Natural Gas", "parsed_unit": "kBtu (thousand Btu)", }, ] self.assertCountEqual(result_dict.get("validated_type_units"), expectation) def test_parsed_meters_confirmation_verifies_energy_type_and_units_and_ignores_invalid_types_and_units( self): filename = "example-pm-monthly-meter-usage-with-unknown-types-and-units.xlsx" filepath = os.path.dirname( os.path.abspath(__file__)) + "/data/" + filename import_file_with_invalids = ImportFile.objects.create( import_record=self.import_record, source_type="PM Meter Usage", uploaded_filename=filename, file=SimpleUploadedFile(name=filename, content=open(filepath, 'rb').read()), cycle=self.cycle) url = reverse('api:v2:meters-parsed-meters-confirmation') post_params = json.dumps({ 'file_id': import_file_with_invalids.id, 'organization_id': self.org.pk, }) result = self.client.post(url, post_params, content_type="application/json") result_dict = ast.literal_eval(result.content.decode("utf-8")) expectation = [ { "parsed_type": "Electric - Grid", "parsed_unit": "kBtu (thousand Btu)", }, { "parsed_type": "Natural Gas", "parsed_unit": "kBtu (thousand Btu)", }, ] self.assertCountEqual(result_dict.get("validated_type_units"), expectation) def test_parsed_meters_confirmation_returns_pm_property_ids_and_corresponding_incoming_counts( self): url = reverse('api:v2:meters-parsed-meters-confirmation') post_params = json.dumps({ 'file_id': self.import_file.id, 'organization_id': self.org.pk, }) result = self.client.post(url, post_params, content_type="application/json") result_dict = ast.literal_eval(result.content.decode("utf-8")) expectation = [ { "pm_property_id": "5766973", "source_id": "5766973-0", "type": 'Electric - Grid', "incoming": 2, }, { "pm_property_id": "5766973", "source_id": "5766973-1", "type": 'Natural Gas', "incoming": 2, }, { "pm_property_id": "5766975", "source_id": "5766975-0", "type": 'Electric - Grid', "incoming": 2, }, { "pm_property_id": "5766975", "source_id": "5766975-1", "type": 'Natural Gas', "incoming": 2, }, ] self.assertCountEqual(result_dict.get("proposed_imports"), expectation) def test_parsed_meters_confirmation_also_verifies_cost_type_and_units_and_counts( self): filename = "example-pm-monthly-meter-usage-2-cost-meters.xlsx" filepath = os.path.dirname( os.path.abspath(__file__)) + "/data/" + filename cost_import_file = ImportFile.objects.create( import_record=self.import_record, source_type="PM Meter Usage", uploaded_filename=filename, file=SimpleUploadedFile(name=filename, content=open(filepath, 'rb').read()), cycle=self.cycle) url = reverse('api:v2:meters-parsed-meters-confirmation') post_params = json.dumps({ 'file_id': cost_import_file.id, 'organization_id': self.org.pk, }) result = self.client.post(url, post_params, content_type="application/json") result_dict = ast.literal_eval(result.content.decode("utf-8")) validated_type_units = [ { "parsed_type": "Electric - Grid", "parsed_unit": "kBtu (thousand Btu)", }, { "parsed_type": "Natural Gas", "parsed_unit": "kBtu (thousand Btu)", }, { "parsed_type": "Cost", "parsed_unit": "US Dollars", }, ] self.assertCountEqual(result_dict.get("validated_type_units"), validated_type_units) proposed_imports = [ { "pm_property_id": "5766973", "source_id": "5766973-0", "type": 'Electric - Grid', "incoming": 2, }, { "pm_property_id": "5766973", "source_id": "5766973-1", "type": 'Natural Gas', "incoming": 2, }, { "pm_property_id": "5766973", "source_id": "5766973-0", "type": 'Cost', "incoming": 2, }, { "pm_property_id": "5766973", "source_id": "5766973-1", "type": 'Cost', "incoming": 2, }, { "pm_property_id": "5766975", "source_id": "5766975-0", "type": 'Electric - Grid', "incoming": 2, }, { "pm_property_id": "5766975", "source_id": "5766975-1", "type": 'Natural Gas', "incoming": 2, }, ] self.assertCountEqual(result_dict.get("proposed_imports"), proposed_imports) # Verify this works for Org with CAN thermal conversions self.org.thermal_conversion_assumption = Organization.CAN self.org.save() can_result = self.client.post(url, post_params, content_type="application/json") can_result_dict = ast.literal_eval(can_result.content.decode("utf-8")) validated_type_units[2] = { "parsed_type": "Cost", "parsed_unit": "CAN Dollars", } self.assertCountEqual(can_result_dict.get("validated_type_units"), validated_type_units) def test_green_button_parsed_meters_confirmation_returns_a_green_button_id_incoming_counts_and_parsed_type_units_and_saves_property_id_to_file_cache( self): filename = "example-GreenButton-data.xml" filepath = os.path.dirname( os.path.abspath(__file__)) + "/data/" + filename xml_import_file = ImportFile.objects.create( import_record=self.import_record, source_type="GreenButton", uploaded_filename=filename, file=SimpleUploadedFile(name=filename, content=open(filepath, 'rb').read()), cycle=self.cycle) url = reverse('api:v2:meters-greenbutton-parsed-meters-confirmation') post_params = json.dumps({ 'file_id': xml_import_file.id, 'organization_id': self.org.pk, 'view_id': self.property_view_1.id, }) result = self.client.post(url, post_params, content_type="application/json") result_dict = ast.literal_eval(result.content.decode("utf-8")) proposed_imports = [ { "source_id": '409483', "type": 'Electric - Grid', "incoming": 2, }, ] validated_type_units = [ { "parsed_type": "Electric - Grid", "parsed_unit": "kWh (thousand Watt-hours)", }, ] self.assertEqual(result_dict['proposed_imports'], proposed_imports) self.assertEqual(result_dict['validated_type_units'], validated_type_units) refreshed_import_file = ImportFile.objects.get(pk=xml_import_file.id) self.assertEqual(refreshed_import_file.matching_results_data, {'property_id': self.property_view_1.property_id}) def test_parsed_meters_confirmation_returns_unlinkable_pm_property_ids( self): PropertyState.objects.all().delete() url = reverse('api:v2:meters-parsed-meters-confirmation') post_params = json.dumps({ 'file_id': self.import_file.id, 'organization_id': self.org.pk, }) result = self.client.post(url, post_params, content_type="application/json") result_dict = ast.literal_eval(result.content.decode("utf-8")) expectation = [ { "portfolio_manager_id": "5766973", }, { "portfolio_manager_id": "5766975", }, ] self.assertCountEqual(result_dict.get("unlinkable_pm_ids"), expectation) def test_property_meters_endpoint_returns_a_list_of_meters_of_a_view(self): # add meters and readings to property associated to property_view_1 save_raw_data(self.import_file.id) # create GB gas meter meter_details = { 'source': Meter.GREENBUTTON, 'source_id': '/v1/User/000/UsagePoint/123fakeID/MeterReading/000', 'type': Meter.NATURAL_GAS, 'property_id': self.property_view_1.property.id, } gb_gas_meter = Meter.objects.create(**meter_details) url = reverse('api:v2:meters-property-meters') post_params = json.dumps({ 'property_view_id': self.property_view_1.id, }) result = self.client.post(url, post_params, content_type="application/json") result_dict = ast.literal_eval(result.content.decode("utf-8")) electric_meter = Meter.objects.get( property_id=self.property_view_1.property_id, type=Meter.ELECTRICITY_GRID) gas_meter = Meter.objects.get( property_id=self.property_view_1.property_id, type=Meter.NATURAL_GAS, source=Meter.PORTFOLIO_MANAGER) expectation = [ { 'id': electric_meter.id, 'type': 'Electric - Grid', 'source': 'PM', 'source_id': '5766973-0', }, { 'id': gas_meter.id, 'type': 'Natural Gas', 'source': 'PM', 'source_id': '5766973-1', }, { 'id': gb_gas_meter.id, 'type': 'Natural Gas', 'source': 'GB', 'source_id': '123fakeID', }, ] self.assertCountEqual(result_dict, expectation) def test_property_meter_usage_returns_meter_readings_and_column_defs_given_property_view_and_nondefault_meter_display_org_settings( self): # Update settings for display meter units to change it from the default values. self.org.display_meter_units[ 'Electric - Grid'] = 'kWh (thousand Watt-hours)' self.org.display_meter_units[ 'Natural Gas'] = 'kcf (thousand cubic feet)' self.org.save() # add meters and readings to property associated to property_view_1 save_raw_data(self.import_file.id) meter_details = { 'source': Meter.GREENBUTTON, 'source_id': '/v1/User/000/UsagePoint/123fakeID/MeterReading/000', 'type': Meter.NATURAL_GAS, 'property_id': self.property_view_1.property.id, } gb_gas_meter = Meter.objects.create(**meter_details) tz_obj = timezone(TIME_ZONE) gb_gas_reading_details = { 'start_time': make_aware(datetime(2016, 1, 1, 0, 0, 0), timezone=tz_obj), 'end_time': make_aware(datetime(2016, 2, 1, 0, 0, 0), timezone=tz_obj), 'reading': 1000, 'source_unit': 'kBtu (thousand Btu)', 'conversion_factor': 1, 'meter_id': gb_gas_meter.id, } MeterReading.objects.create(**gb_gas_reading_details) url = reverse('api:v2:meters-property-meter-usage') post_params = json.dumps({ 'property_view_id': self.property_view_1.id, 'interval': 'Exact', 'excluded_meter_ids': [], }) result = self.client.post(url, post_params, content_type="application/json") result_dict = ast.literal_eval(result.content.decode("utf-8")) expectation = { 'readings': [ { 'start_time': '2016-01-01 00:00:00', 'end_time': '2016-02-01 00:00:00', 'Electric - Grid - PM - 5766973-0': (597478.9 / 3.41), 'Natural Gas - PM - 5766973-1': 576000.2 / 1026, 'Natural Gas - GB - 123fakeID': 1000 / 1026, }, { 'start_time': '2016-02-01 00:00:00', 'end_time': '2016-03-01 00:00:00', 'Electric - Grid - PM - 5766973-0': (548603.7 / 3.41), 'Natural Gas - PM - 5766973-1': 488000.1 / 1026, }, ], 'column_defs': [ { 'field': 'start_time', '_filter_type': 'datetime', }, { 'field': 'end_time', '_filter_type': 'datetime', }, { 'field': 'Electric - Grid - PM - 5766973-0', 'displayName': 'Electric - Grid - PM - 5766973-0 (kWh (thousand Watt-hours))', '_filter_type': 'reading', }, { 'field': 'Natural Gas - PM - 5766973-1', 'displayName': 'Natural Gas - PM - 5766973-1 (kcf (thousand cubic feet))', '_filter_type': 'reading', }, { 'field': 'Natural Gas - GB - 123fakeID', 'displayName': 'Natural Gas - GB - 123fakeID (kcf (thousand cubic feet))', '_filter_type': 'reading', }, ] } self.assertCountEqual(result_dict['readings'], expectation['readings']) self.assertCountEqual(result_dict['column_defs'], expectation['column_defs']) def test_property_meter_usage_returns_meter_readings_and_column_defs_when_cost_meter_included( self): filename = "example-pm-monthly-meter-usage-2-cost-meters.xlsx" filepath = os.path.dirname( os.path.abspath(__file__)) + "/data/" + filename cost_import_file = ImportFile.objects.create( import_record=self.import_record, source_type="PM Meter Usage", uploaded_filename=filename, file=SimpleUploadedFile(name=filename, content=open(filepath, 'rb').read()), cycle=self.cycle) # add meters and readings to property associated to property_view_1 save_raw_data(cost_import_file.id) url = reverse('api:v2:meters-property-meter-usage') post_params = json.dumps({ 'property_view_id': self.property_view_1.id, 'interval': 'Exact', 'excluded_meter_ids': [], }) result = self.client.post(url, post_params, content_type="application/json") result_dict = ast.literal_eval(result.content.decode("utf-8")) expectation = { 'readings': [ { 'start_time': '2016-01-01 00:00:00', 'end_time': '2016-02-01 00:00:00', 'Electric - Grid - PM - 5766973-0': 597478.9 / 3.41, 'Cost - PM - 5766973-0': 100, 'Natural Gas - PM - 5766973-1': 576000.2, 'Cost - PM - 5766973-1': 300, }, { 'start_time': '2016-02-01 00:00:00', 'end_time': '2016-03-01 00:00:00', 'Electric - Grid - PM - 5766973-0': 548603.7 / 3.41, 'Cost - PM - 5766973-0': 200, 'Natural Gas - PM - 5766973-1': 488000.1, 'Cost - PM - 5766973-1': 400, }, ], 'column_defs': [ { 'field': 'start_time', '_filter_type': 'datetime', }, { 'field': 'end_time', '_filter_type': 'datetime', }, { 'field': 'Electric - Grid - PM - 5766973-0', 'displayName': 'Electric - Grid - PM - 5766973-0 (kWh (thousand Watt-hours))', '_filter_type': 'reading', }, { 'field': 'Natural Gas - PM - 5766973-1', 'displayName': 'Natural Gas - PM - 5766973-1 (kBtu (thousand Btu))', '_filter_type': 'reading', }, { 'field': 'Cost - PM - 5766973-0', 'displayName': 'Cost - PM - 5766973-0 (US Dollars)', '_filter_type': 'reading', }, { 'field': 'Cost - PM - 5766973-1', 'displayName': 'Cost - PM - 5766973-1 (US Dollars)', '_filter_type': 'reading', }, ] } self.assertCountEqual(result_dict['readings'], expectation['readings']) self.assertCountEqual(result_dict['column_defs'], expectation['column_defs']) def test_property_meter_usage_returns_meter_readings_according_to_thermal_conversion_preferences_of_an_org_if_applicable_for_display_settings( self): # update the org settings thermal preference and display preference self.org.thermal_conversion_assumption = Organization.CAN self.org.display_meter_units["Diesel"] = "Liters" self.org.display_meter_units["Coke"] = "Lbs. (pounds)" self.org.save() # add meters and readings to property associated to property_view_1 meter_details = { 'source': Meter.PORTFOLIO_MANAGER, 'source_id': '123fakeID', 'type': Meter.DIESEL, 'property_id': self.property_view_1.property.id, } diesel_meter = Meter.objects.create(**meter_details) tz_obj = timezone(TIME_ZONE) diesel_reading_details = { 'start_time': make_aware(datetime(2016, 1, 1, 0, 0, 0), timezone=tz_obj), 'end_time': make_aware(datetime(2016, 2, 1, 0, 0, 0), timezone=tz_obj), 'reading': 10, 'source_unit': 'kBtu (thousand Btu)', 'conversion_factor': 1, 'meter_id': diesel_meter.id, } MeterReading.objects.create(**diesel_reading_details) meter_details['type'] = Meter.COKE meter_details['source_id'] = '456fakeID' coke_meter = Meter.objects.create(**meter_details) coke_reading_details = { 'start_time': make_aware(datetime(2016, 1, 1, 0, 0, 0), timezone=tz_obj), 'end_time': make_aware(datetime(2016, 2, 1, 0, 0, 0), timezone=tz_obj), 'reading': 100, 'source_unit': 'kBtu (thousand Btu)', 'conversion_factor': 1, 'meter_id': coke_meter.id, } MeterReading.objects.create(**coke_reading_details) post_params = json.dumps({ 'property_view_id': self.property_view_1.id, 'interval': 'Exact', 'excluded_meter_ids': [], }) url = reverse('api:v2:meters-property-meter-usage') result = self.client.post(url, post_params, content_type="application/json") result_dict = ast.literal_eval(result.content.decode("utf-8")) display_readings = [ { 'start_time': '2016-01-01 00:00:00', 'end_time': '2016-02-01 00:00:00', 'Diesel - PM - 123fakeID': 10 / 36.30, 'Coke - PM - 456fakeID': 100 / 12.39, }, ] self.assertCountEqual(result_dict['readings'], display_readings) def test_property_meter_usage_can_return_monthly_meter_readings_and_column_defs_with_nondefault_display_setting( self): # Update settings for display meter units to change it from the default values. self.org.display_meter_units[ 'Electric - Grid'] = 'kWh (thousand Watt-hours)' self.org.save() # add initial meters and readings save_raw_data(self.import_file.id) # add additional entries for each initial meter tz_obj = timezone(TIME_ZONE) for meter in Meter.objects.all(): # March 2016 reading reading_details = { 'meter_id': meter.id, 'start_time': make_aware(datetime(2016, 3, 1, 0, 0, 0), timezone=tz_obj), 'end_time': make_aware(datetime(2016, 4, 1, 0, 0, 0), timezone=tz_obj), 'reading': 100, 'source_unit': 'kBtu (thousand Btu)', 'conversion_factor': 1 } MeterReading.objects.create(**reading_details) # May 2016 reading reading_details['start_time'] = make_aware(datetime( 2016, 5, 1, 0, 0, 0), timezone=tz_obj) reading_details['end_time'] = make_aware(datetime( 2016, 6, 1, 0, 0, 0), timezone=tz_obj) reading_details['reading'] = 200 MeterReading.objects.create(**reading_details) url = reverse('api:v2:meters-property-meter-usage') post_params = json.dumps({ 'property_view_id': self.property_view_1.id, 'interval': 'Month', 'excluded_meter_ids': [], }) result = self.client.post(url, post_params, content_type="application/json") result_dict = ast.literal_eval(result.content.decode("utf-8")) expectation = { 'readings': [ { 'month': 'January 2016', 'Electric - Grid - PM - 5766973-0': 597478.9 / 3.41, 'Natural Gas - PM - 5766973-1': 576000.2, }, { 'month': 'February 2016', 'Electric - Grid - PM - 5766973-0': 548603.7 / 3.41, 'Natural Gas - PM - 5766973-1': 488000.1, }, { 'month': 'March 2016', 'Electric - Grid - PM - 5766973-0': 100 / 3.41, 'Natural Gas - PM - 5766973-1': 100, }, { 'month': 'May 2016', 'Electric - Grid - PM - 5766973-0': 200 / 3.41, 'Natural Gas - PM - 5766973-1': 200, }, ], 'column_defs': [ { 'field': 'month', '_filter_type': 'datetime', }, { 'field': 'Electric - Grid - PM - 5766973-0', 'displayName': 'Electric - Grid - PM - 5766973-0 (kWh (thousand Watt-hours))', '_filter_type': 'reading', }, { 'field': 'Natural Gas - PM - 5766973-1', 'displayName': 'Natural Gas - PM - 5766973-1 (kBtu (thousand Btu))', '_filter_type': 'reading', }, ] } self.assertCountEqual(result_dict['readings'], expectation['readings']) self.assertCountEqual(result_dict['column_defs'], expectation['column_defs']) def test_property_meter_usage_can_return_monthly_meter_readings_and_column_defs_for_submonthly_data_with_DST_transitions_and_specific_meters( self): # add initial meters and readings save_raw_data(self.import_file.id) property_1_electric_meter = Meter.objects.get(source_id='5766973-0') # add additional sub-montly entries for each initial meter tz_obj = timezone(TIME_ZONE) for meter in Meter.objects.all(): # November 2019 reading between DST transition reading_details = { 'meter_id': meter.id, 'start_time': make_aware(datetime(2019, 11, 3, 1, 59, 59), timezone=tz_obj, is_dst=True), 'end_time': make_aware(datetime(2019, 11, 3, 1, 59, 59), timezone=tz_obj, is_dst=False), 'reading': 100, 'source_unit': 'kBtu (thousand Btu)', 'conversion_factor': 1 } MeterReading.objects.create(**reading_details) # November 2019 reading after DST transition reading_details['start_time'] = make_aware(datetime( 2019, 11, 3, 2, 0, 0), timezone=tz_obj) reading_details['end_time'] = make_aware(datetime( 2019, 11, 3, 3, 0, 0), timezone=tz_obj) reading_details['reading'] = 200 MeterReading.objects.create(**reading_details) # Create a reading for only one of the meters that will be filtered out completely if meter.source_id == property_1_electric_meter.id: reading_details['start_time'] = make_aware(datetime( 2020, 11, 3, 2, 0, 0), timezone=tz_obj) reading_details['end_time'] = make_aware(datetime( 2020, 11, 3, 3, 0, 0), timezone=tz_obj) reading_details['reading'] = 10000000 MeterReading.objects.create(**reading_details) url = reverse('api:v2:meters-property-meter-usage') post_params = json.dumps({ 'property_view_id': self.property_view_1.id, 'interval': 'Month', 'excluded_meter_ids': [property_1_electric_meter.id], }) result = self.client.post(url, post_params, content_type="application/json") result_dict = ast.literal_eval(result.content.decode("utf-8")) expectation = { 'readings': [ { 'month': 'January 2016', 'Natural Gas - PM - 5766973-1': 576000.2, }, { 'month': 'February 2016', 'Natural Gas - PM - 5766973-1': 488000.1, }, { 'month': 'November 2019', 'Natural Gas - PM - 5766973-1': 300, }, ], 'column_defs': [ { 'field': 'month', '_filter_type': 'datetime', }, { 'field': 'Natural Gas - PM - 5766973-1', 'displayName': 'Natural Gas - PM - 5766973-1 (kBtu (thousand Btu))', '_filter_type': 'reading', }, ] } self.assertCountEqual(result_dict['readings'], expectation['readings']) self.assertCountEqual(result_dict['column_defs'], expectation['column_defs']) def test_property_meter_usage_can_return_monthly_meter_readings_and_column_defs_of_overlapping_submonthly_data_aggregating_monthly_data_to_maximize_total( self): # add initial meters and readings save_raw_data(self.import_file.id) # add additional entries for the Electricity meter tz_obj = timezone(TIME_ZONE) meter = Meter.objects.get(property_id=self.property_view_1.property.id, type=Meter.type_lookup['Electric - Grid']) # 2016 January reading that should override the existing reading reading_details = { 'meter_id': meter.id, 'start_time': make_aware(datetime(2016, 1, 1, 0, 0, 0), timezone=tz_obj), 'end_time': make_aware(datetime(2016, 1, 20, 23, 59, 59), timezone=tz_obj), 'reading': 100000000000000, 'source_unit': 'kBtu (thousand Btu)', 'conversion_factor': 1 } MeterReading.objects.create(**reading_details) # 2016 January reading that should be ignored reading_details['start_time'] = make_aware(datetime( 2016, 1, 1, 0, 0, 0), timezone=tz_obj) reading_details['end_time'] = make_aware(datetime( 2016, 3, 31, 23, 59, 59), timezone=tz_obj) reading_details['reading'] = 0.1 MeterReading.objects.create(**reading_details) # Create March 2016 entries having disregarded readings when finding monthly total # 1 week - not included in total reading_details['start_time'] = make_aware(datetime( 2016, 3, 1, 0, 0, 0), timezone=tz_obj) reading_details['end_time'] = make_aware(datetime( 2016, 3, 6, 23, 59, 59), timezone=tz_obj) reading_details['reading'] = 1 MeterReading.objects.create(**reading_details) # 1 week - not included in total reading_details['start_time'] = make_aware(datetime( 2016, 3, 7, 0, 0, 0), timezone=tz_obj) reading_details['end_time'] = make_aware(datetime( 2016, 3, 13, 23, 59, 59), timezone=tz_obj) reading_details['reading'] = 10 MeterReading.objects.create(**reading_details) # 10 days - included in total reading_details['start_time'] = make_aware(datetime( 2016, 3, 2, 0, 0, 0), timezone=tz_obj) reading_details['end_time'] = make_aware(datetime( 2016, 3, 11, 23, 59, 59), timezone=tz_obj) reading_details['reading'] = 100 MeterReading.objects.create(**reading_details) # 10 days - included in total reading_details['start_time'] = make_aware(datetime( 2016, 3, 12, 0, 0, 0), timezone=tz_obj) reading_details['end_time'] = make_aware(datetime( 2016, 3, 21, 23, 59, 59), timezone=tz_obj) reading_details['reading'] = 1000 MeterReading.objects.create(**reading_details) # Create April 2016 entries having disregarded readings when finding monthly total # 5 days - not included in total reading_details['start_time'] = make_aware(datetime( 2016, 4, 1, 0, 0, 0), timezone=tz_obj) reading_details['end_time'] = make_aware(datetime( 2016, 4, 4, 23, 59, 59), timezone=tz_obj) reading_details['reading'] = 2 MeterReading.objects.create(**reading_details) # 10 days - not included in total reading_details['start_time'] = make_aware(datetime( 2016, 4, 6, 0, 0, 0), timezone=tz_obj) reading_details['end_time'] = make_aware(datetime( 2016, 4, 15, 23, 59, 59), timezone=tz_obj) reading_details['reading'] = 20 MeterReading.objects.create(**reading_details) # 20 days - included in total reading_details['start_time'] = make_aware(datetime( 2016, 4, 2, 0, 0, 0), timezone=tz_obj) reading_details['end_time'] = make_aware(datetime( 2016, 4, 21, 23, 59, 59), timezone=tz_obj) reading_details['reading'] = 200 MeterReading.objects.create(**reading_details) url = reverse('api:v2:meters-property-meter-usage') post_params = json.dumps({ 'property_view_id': self.property_view_1.id, 'interval': 'Month', 'excluded_meter_ids': [], }) result = self.client.post(url, post_params, content_type="application/json") result_dict = ast.literal_eval(result.content.decode("utf-8")) expectation = { 'readings': [ { 'month': 'January 2016', 'Electric - Grid - PM - 5766973-0': 100000000000000 / 3.41, 'Natural Gas - PM - 5766973-1': 576000.2, }, { 'month': 'February 2016', 'Electric - Grid - PM - 5766973-0': 548603.7 / 3.41, 'Natural Gas - PM - 5766973-1': 488000.1, }, { 'month': 'March 2016', 'Electric - Grid - PM - 5766973-0': 1100 / 3.41, }, { 'month': 'April 2016', 'Electric - Grid - PM - 5766973-0': 200 / 3.41, }, ], 'column_defs': [ { 'field': 'month', '_filter_type': 'datetime', }, { 'field': 'Electric - Grid - PM - 5766973-0', 'displayName': 'Electric - Grid - PM - 5766973-0 (kWh (thousand Watt-hours))', '_filter_type': 'reading', }, { 'field': 'Natural Gas - PM - 5766973-1', 'displayName': 'Natural Gas - PM - 5766973-1 (kBtu (thousand Btu))', '_filter_type': 'reading', }, ] } self.assertCountEqual(result_dict['readings'], expectation['readings']) self.assertCountEqual(result_dict['column_defs'], expectation['column_defs']) def test_property_meter_usage_can_return_annual_meter_readings_and_column_defs_while_handling_a_nondefault_display_setting( self): # Update settings for display meter units to change it from the default values. self.org.display_meter_units[ 'Electric - Grid'] = 'kWh (thousand Watt-hours)' self.org.save() # add initial meters and readings save_raw_data(self.import_file.id) # add additional 2018 entries for each initial meter tz_obj = timezone(TIME_ZONE) for meter in Meter.objects.all(): # March 2018 reading reading_details = { 'meter_id': meter.id, 'start_time': make_aware(datetime(2018, 3, 1, 0, 0, 0), timezone=tz_obj), 'end_time': make_aware(datetime(2018, 4, 1, 0, 0, 0), timezone=tz_obj), 'reading': 100, 'source_unit': 'kBtu (thousand Btu)', 'conversion_factor': 1 } MeterReading.objects.create(**reading_details) # May 2018 reading reading_details['start_time'] = make_aware(datetime( 2018, 5, 1, 0, 0, 0), timezone=tz_obj) reading_details['end_time'] = make_aware(datetime( 2018, 6, 1, 0, 0, 0), timezone=tz_obj) reading_details['reading'] = 200 MeterReading.objects.create(**reading_details) url = reverse('api:v2:meters-property-meter-usage') post_params = json.dumps({ 'property_view_id': self.property_view_1.id, 'interval': 'Year', 'excluded_meter_ids': [], }) result = self.client.post(url, post_params, content_type="application/json") result_dict = ast.literal_eval(result.content.decode("utf-8")) expectation = { 'readings': [ { 'year': 2016, 'Electric - Grid - PM - 5766973-0': (597478.9 + 548603.7) / 3.41, 'Natural Gas - PM - 5766973-1': 576000.2 + 488000.1, }, { 'year': 2018, 'Electric - Grid - PM - 5766973-0': (100 + 200) / 3.41, 'Natural Gas - PM - 5766973-1': 100 + 200, }, ], 'column_defs': [ { 'field': 'year', '_filter_type': 'datetime', }, { 'field': 'Electric - Grid - PM - 5766973-0', 'displayName': 'Electric - Grid - PM - 5766973-0 (kWh (thousand Watt-hours))', '_filter_type': 'reading', }, { 'field': 'Natural Gas - PM - 5766973-1', 'displayName': 'Natural Gas - PM - 5766973-1 (kBtu (thousand Btu))', '_filter_type': 'reading', }, ] } self.assertCountEqual(result_dict['readings'], expectation['readings']) self.assertCountEqual(result_dict['column_defs'], expectation['column_defs'])
class MeterUtilTests(TestCase): def setUp(self): self.user_details = { 'username': '******', 'password': '******', } self.user = User.objects.create_superuser(email='*****@*****.**', **self.user_details) self.org, _, _ = create_organization(self.user) self.property_state_factory = FakePropertyStateFactory( organization=self.org) property_details = self.property_state_factory.get_details() self.pm_property_id = '12345' property_details['pm_property_id'] = self.pm_property_id property_details['organization_id'] = self.org.id state = PropertyState(**property_details) state.save() self.state = PropertyState.objects.get(pk=state.id) self.cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) self.cycle = self.cycle_factory.get_cycle( start=datetime(2010, 10, 10, tzinfo=get_current_timezone())) self.property_factory = FakePropertyFactory(organization=self.org) self.property = self.property_factory.get_property() self.property_view = PropertyView.objects.create( property=self.property, cycle=self.cycle, state=self.state) self.tz_obj = timezone(TIME_ZONE) def test_parse_meter_details_splits_monthly_info_into_meter_data_and_readings_even_with_DST_changing( self): raw_meters = [{ 'Portfolio Manager ID': self.pm_property_id, 'Portfolio Manager Meter ID': '123-PMMeterID', 'Start Date': '2016-03-01 00:00:00', 'End Date': '2016-04-01 00:00:00', 'Meter Type': 'Electric - Grid', 'Usage Units': 'kBtu (thousand Btu)', 'Usage/Quantity': 100, }, { 'Portfolio Manager ID': self.pm_property_id, 'Portfolio Manager Meter ID': '123-PMMeterID', 'Start Date': '2016-03-01 00:00:00', 'End Date': '2016-04-01 00:00:00', 'Meter Type': 'Natural Gas', 'Usage Units': 'kBtu (thousand Btu)', 'Usage/Quantity': 200, }] expected = [{ 'property_id': self.property.id, 'source': Meter.PORTFOLIO_MANAGER, 'source_id': '123-PMMeterID', 'type': Meter.ELECTRICITY_GRID, 'readings': [{ 'start_time': make_aware(datetime(2016, 3, 1, 0, 0, 0), timezone=self.tz_obj), 'end_time': make_aware(datetime(2016, 4, 1, 0, 0, 0), timezone=self.tz_obj), 'reading': 100, 'source_unit': 'kBtu (thousand Btu)', 'conversion_factor': 1 }] }, { 'property_id': self.property.id, 'source': Meter.PORTFOLIO_MANAGER, 'source_id': '123-PMMeterID', 'type': Meter.NATURAL_GAS, 'readings': [{ 'start_time': make_aware(datetime(2016, 3, 1, 0, 0, 0), timezone=self.tz_obj), 'end_time': make_aware(datetime(2016, 4, 1, 0, 0, 0), timezone=self.tz_obj), 'reading': 200, 'source_unit': 'kBtu (thousand Btu)', 'conversion_factor': 1 }] }] meters_parser = MetersParser(self.org.id, raw_meters) self.assertEqual(meters_parser.meter_and_reading_objs, expected) def test_parse_meter_details_splits_monthly_info_including_cost_into_meter_data_and_readings( self): raw_meters = [{ 'Portfolio Manager ID': self.pm_property_id, 'Portfolio Manager Meter ID': '123-PMMeterID-el', 'Start Date': '2016-03-01 00:00:00', 'End Date': '2016-04-01 00:00:00', 'Usage Units': 'kBtu (thousand Btu)', 'Meter Type': 'Electric - Grid', 'Usage/Quantity': 100, 'Cost ($)': 100, }, { 'Portfolio Manager ID': self.pm_property_id, 'Portfolio Manager Meter ID': '123-PMMeterID-gas', 'Start Date': '2016-03-01 00:00:00', 'End Date': '2016-04-01 00:00:00', 'Meter Type': 'Natural Gas', 'Usage Units': 'kBtu (thousand Btu)', 'Usage/Quantity': 200, 'Cost ($)': 50, }] expected = [ { 'property_id': self.property.id, 'source': Meter.PORTFOLIO_MANAGER, 'source_id': '123-PMMeterID-el', 'type': Meter.ELECTRICITY_GRID, 'readings': [{ 'start_time': make_aware(datetime(2016, 3, 1, 0, 0, 0), timezone=self.tz_obj), 'end_time': make_aware(datetime(2016, 4, 1, 0, 0, 0), timezone=self.tz_obj), 'reading': 100, 'source_unit': 'kBtu (thousand Btu)', 'conversion_factor': 1 }] }, { 'property_id': self.property.id, 'source': Meter.PORTFOLIO_MANAGER, 'source_id': '123-PMMeterID-el', 'type': Meter.COST, 'readings': [{ 'start_time': make_aware(datetime(2016, 3, 1, 0, 0, 0), timezone=self.tz_obj), 'end_time': make_aware(datetime(2016, 4, 1, 0, 0, 0), timezone=self.tz_obj), 'reading': 100, 'source_unit': 'US Dollars', 'conversion_factor': 1 }] }, { 'property_id': self.property.id, 'source': Meter.PORTFOLIO_MANAGER, 'source_id': '123-PMMeterID-gas', 'type': Meter.NATURAL_GAS, 'readings': [{ 'start_time': make_aware(datetime(2016, 3, 1, 0, 0, 0), timezone=self.tz_obj), 'end_time': make_aware(datetime(2016, 4, 1, 0, 0, 0), timezone=self.tz_obj), 'reading': 200, 'source_unit': 'kBtu (thousand Btu)', 'conversion_factor': 1 }] }, { 'property_id': self.property.id, 'source': Meter.PORTFOLIO_MANAGER, 'source_id': '123-PMMeterID-gas', 'type': Meter.COST, 'readings': [{ 'start_time': make_aware(datetime(2016, 3, 1, 0, 0, 0), timezone=self.tz_obj), 'end_time': make_aware(datetime(2016, 4, 1, 0, 0, 0), timezone=self.tz_obj), 'reading': 50, 'source_unit': 'US Dollars', 'conversion_factor': 1 }] }, ] meters_parser = MetersParser(self.org.id, raw_meters) self.assertEqual(meters_parser.meter_and_reading_objs, expected) def test_parser_uses_canadian_thermal_conversion_assumptions_if_org_specifies_it( self): self.org.thermal_conversion_assumption = Organization.CAN self.org.save() raw_meters = [{ 'Portfolio Manager ID': self.pm_property_id, 'Portfolio Manager Meter ID': '123-PMMeterID-gas', 'Start Date': '2016-03-01 00:00:00', 'End Date': '2016-04-01 00:00:00', 'Meter Type': 'Natural Gas', 'Usage Units': 'cm (cubic meters)', 'Usage/Quantity': 1000, 'Cost ($)': 100, }] expected = [ { 'property_id': self.property.id, 'source': Meter.PORTFOLIO_MANAGER, 'source_id': '123-PMMeterID-gas', 'type': Meter.NATURAL_GAS, 'readings': [{ 'start_time': make_aware(datetime(2016, 3, 1, 0, 0, 0), timezone=self.tz_obj), 'end_time': make_aware(datetime(2016, 4, 1, 0, 0, 0), timezone=self.tz_obj), 'reading': 36420.0, 'source_unit': 'cm (cubic meters)', 'conversion_factor': 36.42, }], }, { 'property_id': self.property.id, 'source': Meter.PORTFOLIO_MANAGER, 'source_id': '123-PMMeterID-gas', 'type': Meter.COST, 'readings': [{ 'start_time': make_aware(datetime(2016, 3, 1, 0, 0, 0), timezone=self.tz_obj), 'end_time': make_aware(datetime(2016, 4, 1, 0, 0, 0), timezone=self.tz_obj), 'reading': 100, 'source_unit': 'CAN Dollars', 'conversion_factor': 1, }], }, ] meters_parser = MetersParser(self.org.id, raw_meters) self.assertEqual(meters_parser.meter_and_reading_objs, expected) def test_parse_meter_details_works_with_multiple_meters_impacted_by_a_leap_year( self): raw_meters = [{ 'Property Id': self.pm_property_id, 'Month': 'Feb-16', 'Electricity Use (kBtu)': 111, 'Natural Gas Use (kBtu)': 333 }, { 'Property Id': self.pm_property_id, 'Month': 'Feb-17', 'Electricity Use (kBtu)': 222, 'Natural Gas Use (kBtu)': 444 }] raw_meters = [{ 'Portfolio Manager ID': self.pm_property_id, 'Portfolio Manager Meter ID': '123-PMMeterID', 'Start Date': '2016-02-01 00:00:00', 'End Date': '2016-03-01 00:00:00', 'Meter Type': 'Electric - Grid', 'Usage Units': 'kBtu (thousand Btu)', 'Usage/Quantity': 111, }, { 'Portfolio Manager ID': self.pm_property_id, 'Portfolio Manager Meter ID': '123-PMMeterID', 'Start Date': '2016-02-01 00:00:00', 'End Date': '2016-03-01 00:00:00', 'Meter Type': 'Natural Gas', 'Usage Units': 'kBtu (thousand Btu)', 'Usage/Quantity': 333, }, { 'Portfolio Manager ID': self.pm_property_id, 'Portfolio Manager Meter ID': '123-PMMeterID', 'Start Date': '2017-02-01 00:00:00', 'End Date': '2017-03-01 00:00:00', 'Meter Type': 'Electric - Grid', 'Usage Units': 'kBtu (thousand Btu)', 'Usage/Quantity': 222, }, { 'Portfolio Manager ID': self.pm_property_id, 'Portfolio Manager Meter ID': '123-PMMeterID', 'Start Date': '2017-02-01 00:00:00', 'End Date': '2017-03-01 00:00:00', 'Meter Type': 'Natural Gas', 'Usage Units': 'kBtu (thousand Btu)', 'Usage/Quantity': 444, }] expected = [{ 'property_id': self.property.id, 'source': Meter.PORTFOLIO_MANAGER, 'source_id': '123-PMMeterID', 'type': Meter.ELECTRICITY_GRID, 'readings': [{ 'start_time': make_aware(datetime(2016, 2, 1, 0, 0, 0), timezone=self.tz_obj), 'end_time': make_aware(datetime(2016, 3, 1, 0, 0, 0), timezone=self.tz_obj), 'reading': 111, 'source_unit': 'kBtu (thousand Btu)', 'conversion_factor': 1 }, { 'start_time': make_aware(datetime(2017, 2, 1, 0, 0, 0), timezone=self.tz_obj), 'end_time': make_aware(datetime(2017, 3, 1, 0, 0, 0), timezone=self.tz_obj), 'reading': 222, 'source_unit': 'kBtu (thousand Btu)', 'conversion_factor': 1 }] }, { 'property_id': self.property.id, 'source': Meter.PORTFOLIO_MANAGER, 'source_id': '123-PMMeterID', 'type': Meter.NATURAL_GAS, 'readings': [{ 'start_time': make_aware(datetime(2016, 2, 1, 0, 0, 0), timezone=self.tz_obj), 'end_time': make_aware(datetime(2016, 3, 1, 0, 0, 0), timezone=self.tz_obj), 'reading': 333, 'source_unit': 'kBtu (thousand Btu)', 'conversion_factor': 1 }, { 'start_time': make_aware(datetime(2017, 2, 1, 0, 0, 0), timezone=self.tz_obj), 'end_time': make_aware(datetime(2017, 3, 1, 0, 0, 0), timezone=self.tz_obj), 'reading': 444, 'source_unit': 'kBtu (thousand Btu)', 'conversion_factor': 1 }] }] meters_parser = MetersParser(self.org.id, raw_meters) self.assertEqual(meters_parser.meter_and_reading_objs, expected) def test_parse_meter_details_converts_energy_units_if_necessary(self): raw_meters = [{ 'Portfolio Manager ID': self.pm_property_id, 'Portfolio Manager Meter ID': '123-PMMeterID', 'Start Date': '2016-03-01 00:00:00', 'End Date': '2016-04-01 00:00:00', 'Meter Type': 'Natural Gas', 'Usage Units': 'ccf (hundred cubic feet)', 'Usage/Quantity': 1000, }, { 'Portfolio Manager ID': self.pm_property_id, 'Portfolio Manager Meter ID': '123-PMMeterID', 'Start Date': '2016-03-01 00:00:00', 'End Date': '2016-04-01 00:00:00', 'Meter Type': 'Fuel Oil (No. 1)', 'Usage Units': 'GJ', 'Usage/Quantity': 1000, }] meters_parser = MetersParser(self.org.id, raw_meters) result = meters_parser.meter_and_reading_objs if result[0]["type"] == Meter.FUEL_OIL_NO_1: fuel_oil_details = result[0] gas_details = result[1] else: fuel_oil_details = result[1] gas_details = result[0] self.assertEqual(fuel_oil_details["readings"][0]["reading"], 947820) self.assertEqual(fuel_oil_details["readings"][0]["source_unit"], "GJ") self.assertEqual(fuel_oil_details["readings"][0]["conversion_factor"], 947.82) self.assertEqual(gas_details["readings"][0]["reading"], 102600) self.assertEqual(gas_details["readings"][0]["source_unit"], "ccf (hundred cubic feet)") self.assertEqual(gas_details["readings"][0]["conversion_factor"], 102.6) def test_unlinked_properties_are_identified(self): raw_meters = [{ 'Portfolio Manager ID': "11111111", 'Portfolio Manager Meter ID': '123-PMMeterID', 'Start Date': '2016-03-01 00:00:00', 'End Date': '2016-04-01 00:00:00', 'Meter Type': 'Electric - Grid', 'Usage Units': 'kBtu (thousand Btu)', 'Usage/Quantity': 100, }, { 'Portfolio Manager ID': "22222222", 'Portfolio Manager Meter ID': '123-PMMeterID', 'Start Date': '2016-03-01 00:00:00', 'End Date': '2016-04-01 00:00:00', 'Meter Type': 'Electric - Grid', 'Usage Units': 'kBtu (thousand Btu)', 'Usage/Quantity': 100, }, { 'Portfolio Manager ID': "22222222", 'Portfolio Manager Meter ID': '123-PMMeterID', 'Start Date': '2016-04-01 00:00:00', 'End Date': '2016-05-01 00:00:00', 'Meter Type': 'Electric - Grid', 'Usage Units': 'kBtu (thousand Btu)', 'Usage/Quantity': 100, }] meters_parser = MetersParser(self.org.id, raw_meters) expected = [ { 'portfolio_manager_id': "11111111" }, { 'portfolio_manager_id': "22222222" }, ] self.assertCountEqual(expected, meters_parser.unlinkable_pm_ids) self.assertEqual([], meters_parser.meter_and_reading_objs) def test_meters_parser_can_handle_raw_meters_with_start_time_and_duration_involving_DST_change_and_a_leap_year( self): raw_meters = [ { 'start_time': 1552211999, # Mar. 10, 2019 01:59:59 (pre-DST change) 'source_id': 'ABCDEF', 'duration': 900, 'Meter Type': 'Natural Gas', 'Usage Units': 'GJ', 'Usage/Quantity': 100 }, { 'start_time': 1456732799, # Feb. 28, 2016 23:59:59 (leap year) 'source_id': 'ABCDEF', 'duration': 900, 'Meter Type': 'Natural Gas', 'Usage Units': 'GJ', 'Usage/Quantity': 1000 } ] expected = [{ 'property_id': self.property.id, 'source': Meter.GREENBUTTON, 'source_id': 'ABCDEF', 'type': Meter.NATURAL_GAS, 'readings': [ { 'start_time': make_aware(datetime(2019, 3, 10, 1, 59, 59), timezone=self.tz_obj), 'end_time': make_aware(datetime(2019, 3, 10, 3, 14, 59), timezone=self.tz_obj), 'reading': 94782.0, 'source_unit': 'GJ', 'conversion_factor': 947.82 }, { 'start_time': make_aware(datetime(2016, 2, 28, 23, 59, 59), timezone=self.tz_obj), 'end_time': make_aware(datetime(2016, 2, 29, 0, 14, 59), timezone=self.tz_obj), 'reading': 947820.0, 'source_unit': 'GJ', 'conversion_factor': 947.82 }, ] }] meters_parser = MetersParser(self.org.id, raw_meters, source_type=Meter.GREENBUTTON, property_id=self.property.id) self.assertEqual(meters_parser.meter_and_reading_objs, expected) def test_meters_parser_can_handle_delivered_PM_meters(self): raw_meters = [{ 'Portfolio Manager ID': self.pm_property_id, 'Portfolio Manager Meter ID': '123-PMMeterID', 'Start Date': 'Not Available', 'End Date': 'Not Available', 'Delivery Date': '2016-03-05 00:00:00', 'Meter Type': 'Electric - Grid', 'Usage Units': 'kBtu (thousand Btu)', 'Usage/Quantity': 100, }, { 'Portfolio Manager ID': self.pm_property_id, 'Portfolio Manager Meter ID': '123-PMMeterID', 'Start Date': 'Not Available', 'End Date': 'Not Available', 'Delivery Date': '2016-03-01 00:00:00', 'Meter Type': 'Natural Gas', 'Usage Units': 'kBtu (thousand Btu)', 'Usage/Quantity': 200, }] expected = [{ 'property_id': self.property.id, 'source': Meter.PORTFOLIO_MANAGER, 'source_id': '123-PMMeterID', 'type': Meter.ELECTRICITY_GRID, 'readings': [{ 'start_time': make_aware(datetime(2016, 3, 1, 0, 0, 0), timezone=self.tz_obj), 'end_time': make_aware(datetime(2016, 4, 1, 0, 0, 0), timezone=self.tz_obj), 'reading': 100, 'source_unit': 'kBtu (thousand Btu)', 'conversion_factor': 1 }] }, { 'property_id': self.property.id, 'source': Meter.PORTFOLIO_MANAGER, 'source_id': '123-PMMeterID', 'type': Meter.NATURAL_GAS, 'readings': [{ 'start_time': make_aware(datetime(2016, 3, 1, 0, 0, 0), timezone=self.tz_obj), 'end_time': make_aware(datetime(2016, 4, 1, 0, 0, 0), timezone=self.tz_obj), 'reading': 200, 'source_unit': 'kBtu (thousand Btu)', 'conversion_factor': 1 }] }] meters_parser = MetersParser(self.org.id, raw_meters) self.assertEqual(meters_parser.meter_and_reading_objs, expected)
class TaxLotMergeUnmergeViewTests(DataMappingBaseTestCase): def setUp(self): user_details = { 'username': '******', 'password': '******', 'email': '*****@*****.**' } self.user = User.objects.create_superuser(**user_details) self.org, self.org_user, _ = create_organization(self.user) self.cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) self.taxlot_factory = FakeTaxLotFactory(organization=self.org) self.taxlot_state_factory = FakeTaxLotStateFactory( organization=self.org) self.cycle = self.cycle_factory.get_cycle( start=datetime(2010, 10, 10, tzinfo=get_current_timezone())) self.client.login(**user_details) self.state_1 = self.taxlot_state_factory.get_taxlot_state() self.taxlot_1 = self.taxlot_factory.get_taxlot() self.view_1 = TaxLotView.objects.create(taxlot=self.taxlot_1, cycle=self.cycle, state=self.state_1) self.state_2 = self.taxlot_state_factory.get_taxlot_state() self.taxlot_2 = self.taxlot_factory.get_taxlot() self.view_2 = TaxLotView.objects.create(taxlot=self.taxlot_2, cycle=self.cycle, state=self.state_2) def test_taxlots_merge_without_losing_labels(self): # Create 3 Labels label_factory = FakeStatusLabelFactory(organization=self.org) label_1 = label_factory.get_statuslabel() label_2 = label_factory.get_statuslabel() label_3 = label_factory.get_statuslabel() self.view_1.labels.add(label_1, label_2) self.view_2.labels.add(label_2, label_3) # Merge the taxlots url = reverse('api:v2:taxlots-merge') + '?organization_id={}'.format( self.org.pk) post_params = json.dumps( {'state_ids': [self.state_2.pk, self.state_1.pk]}) self.client.post(url, post_params, content_type='application/json') # The resulting -View should have 3 notes view = TaxLotView.objects.first() self.assertEqual(view.labels.count(), 3) label_names = list(view.labels.values_list('name', flat=True)) self.assertCountEqual(label_names, [label_1.name, label_2.name, label_3.name]) def test_taxlots_merge_without_losing_notes(self): note_factory = FakeNoteFactory(organization=self.org, user=self.user) # Create 3 Notes and distribute them to the two -Views. note1 = note_factory.get_note(name='non_default_name_1') note2 = note_factory.get_note(name='non_default_name_2') self.view_1.notes.add(note1) self.view_1.notes.add(note2) note3 = note_factory.get_note(name='non_default_name_3') self.view_2.notes.add(note2) self.view_2.notes.add(note3) # Merge the taxlots url = reverse('api:v2:taxlots-merge') + '?organization_id={}'.format( self.org.pk) post_params = json.dumps( {'state_ids': [self.state_2.pk, self.state_1.pk]}) self.client.post(url, post_params, content_type='application/json') # The resulting -View should have 3 notes view = TaxLotView.objects.first() self.assertEqual(view.notes.count(), 3) note_names = list(view.notes.values_list('name', flat=True)) self.assertCountEqual(note_names, [note1.name, note2.name, note3.name]) def test_taxlots_merge_without_losing_pairings(self): # Create 2 pairings and distribute them to the two -Views. property_factory = FakePropertyFactory(organization=self.org) property_state_factory = FakePropertyStateFactory( organization=self.org) property_1 = property_factory.get_property() state_1 = property_state_factory.get_property_state() property_view_1 = PropertyView.objects.create(property=property_1, cycle=self.cycle, state=state_1) property_2 = property_factory.get_property() state_2 = property_state_factory.get_property_state() property_view_2 = PropertyView.objects.create(property=property_2, cycle=self.cycle, state=state_2) TaxLotProperty(primary=True, cycle_id=self.cycle.id, property_view_id=property_view_1.id, taxlot_view_id=self.view_1.id).save() TaxLotProperty(primary=True, cycle_id=self.cycle.id, property_view_id=property_view_2.id, taxlot_view_id=self.view_2.id).save() # Merge the taxlots url = reverse('api:v2:taxlots-merge') + '?organization_id={}'.format( self.org.pk) post_params = json.dumps({ 'state_ids': [self.state_2.pk, self.state_1.pk] # priority given to state_1 }) self.client.post(url, post_params, content_type='application/json') # There should still be 2 TaxLotProperties self.assertEqual(TaxLotProperty.objects.count(), 2) taxlot_view = TaxLotView.objects.first() paired_propertyview_ids = list( TaxLotProperty.objects.filter( taxlot_view_id=taxlot_view.id).values_list('property_view_id', flat=True)) self.assertCountEqual(paired_propertyview_ids, [property_view_1.id, property_view_2.id]) def test_merge_assigns_new_canonical_records_to_each_resulting_record_and_old_canonical_records_are_deleted_when_if_associated_to_views( self): # Capture old taxlot_ids persisting_taxlot_id = self.taxlot_1.id deleted_taxlot_id = self.taxlot_2.id new_cycle = self.cycle_factory.get_cycle( start=datetime(2011, 10, 10, tzinfo=get_current_timezone())) new_taxlot_state = self.taxlot_state_factory.get_taxlot_state() TaxLotView.objects.create(taxlot=self.taxlot_1, cycle=new_cycle, state=new_taxlot_state) # Merge the taxlots url = reverse('api:v2:taxlots-merge') + '?organization_id={}'.format( self.org.pk) post_params = json.dumps({ 'state_ids': [self.state_2.pk, self.state_1.pk] # priority given to state_1 }) self.client.post(url, post_params, content_type='application/json') self.assertFalse( TaxLotView.objects.filter(taxlot_id=deleted_taxlot_id).exists()) self.assertFalse(TaxLot.objects.filter(pk=deleted_taxlot_id).exists()) self.assertEqual( TaxLotView.objects.filter(taxlot_id=persisting_taxlot_id).count(), 1) def test_taxlots_unmerge_without_losing_labels(self): # Merge the taxlots url = reverse('api:v2:taxlots-merge') + '?organization_id={}'.format( self.org.pk) post_params = json.dumps({ 'state_ids': [self.state_2.pk, self.state_1.pk] # priority given to state_1 }) self.client.post(url, post_params, content_type='application/json') # Create 3 Labels - add 2 to view label_factory = FakeStatusLabelFactory(organization=self.org) label_1 = label_factory.get_statuslabel() label_2 = label_factory.get_statuslabel() view = TaxLotView.objects.first() # There's only one TaxLotView view.labels.add(label_1, label_2) # Unmerge the taxlots url = reverse('api:v2:taxlots-unmerge', args=[ view.id ]) + '?organization_id={}'.format(self.org.pk) self.client.post(url, content_type='application/json') for new_view in TaxLotView.objects.all(): self.assertEqual(new_view.labels.count(), 2) label_names = list(new_view.labels.values_list('name', flat=True)) self.assertCountEqual(label_names, [label_1.name, label_2.name]) def test_unmerge_results_in_the_use_of_new_canonical_taxlots_and_deletion_of_old_canonical_state_if_unrelated_to_any_views( self): # Merge the taxlots url = reverse('api:v2:taxlots-merge') + '?organization_id={}'.format( self.org.pk) post_params = json.dumps({ 'state_ids': [self.state_2.pk, self.state_1.pk] # priority given to state_1 }) self.client.post(url, post_params, content_type='application/json') # Capture "old" taxlot_id - there's only one TaxLotView view = TaxLotView.objects.first() taxlot_id = view.taxlot_id # Unmerge the taxlots url = reverse('api:v2:taxlots-unmerge', args=[ view.id ]) + '?organization_id={}'.format(self.org.pk) self.client.post(url, content_type='application/json') self.assertFalse(TaxLot.objects.filter(pk=taxlot_id).exists()) self.assertEqual(TaxLot.objects.count(), 2) def test_unmerge_results_in_the_persistence_of_old_canonical_state_if_related_to_any_views( self): # Merge the taxlots url = reverse('api:v2:taxlots-merge') + '?organization_id={}'.format( self.org.pk) post_params = json.dumps({ 'state_ids': [self.state_2.pk, self.state_1.pk] # priority given to state_1 }) self.client.post(url, post_params, content_type='application/json') # Associate only canonical taxlot with records across Cycle view = TaxLotView.objects.first() taxlot_id = view.taxlot_id new_cycle = self.cycle_factory.get_cycle( start=datetime(2011, 10, 10, tzinfo=get_current_timezone())) new_taxlot_state = self.taxlot_state_factory.get_taxlot_state() TaxLotView.objects.create(taxlot_id=taxlot_id, cycle=new_cycle, state=new_taxlot_state) # Unmerge the taxlots url = reverse('api:v2:taxlots-unmerge', args=[ view.id ]) + '?organization_id={}'.format(self.org.pk) self.client.post(url, content_type='application/json') self.assertTrue(TaxLot.objects.filter(pk=view.taxlot_id).exists()) self.assertEqual(TaxLot.objects.count(), 3)
class PropertyViewTests(DeleteModelsTestCase): def setUp(self): user_details = { 'username': '******', 'password': '******', 'email': '*****@*****.**' } self.user = User.objects.create_superuser(**user_details) self.org, self.org_user, _ = create_organization(self.user) self.column_factory = FakeColumnFactory(organization=self.org) self.cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) self.property_factory = FakePropertyFactory(organization=self.org) self.property_state_factory = FakePropertyStateFactory(organization=self.org) self.property_view_factory = FakePropertyViewFactory(organization=self.org) self.taxlot_state_factory = FakeTaxLotStateFactory(organization=self.org) self.cycle = self.cycle_factory.get_cycle( start=datetime(2010, 10, 10, tzinfo=timezone.get_current_timezone())) self.client.login(**user_details) def test_get_and_edit_properties(self): state = self.property_state_factory.get_property_state() prprty = self.property_factory.get_property() view = PropertyView.objects.create( property=prprty, cycle=self.cycle, state=state ) params = { 'organization_id': self.org.pk, 'page': 1, 'per_page': 999999999, 'columns': COLUMNS_TO_SEND, } url = reverse('api:v2.1:properties-list') + '?cycle_id={}'.format(self.cycle.pk) response = self.client.get(url, params) data = json.loads(response.content) self.assertEqual(len(data['properties']), 1) result = data['properties'][0] self.assertEqual(result['state']['address_line_1'], state.address_line_1) db_created_time = result['created'] db_updated_time = result['updated'] self.assertTrue(db_created_time is not None) self.assertTrue(db_updated_time is not None) # update the address new_data = { "state": { "address_line_1": "742 Evergreen Terrace" } } url = reverse('api:v2:properties-detail', args=[view.id]) + '?organization_id={}'.format(self.org.pk) response = self.client.put(url, json.dumps(new_data), content_type='application/json') data = json.loads(response.content) self.assertEqual(data['status'], 'success') # the above call returns data from the PropertyState, need to get the Property -- # call the get on the same API to retrieve it response = self.client.get(url, content_type='application/json') data = json.loads(response.content) # make sure the address was updated and that the datetimes were modified self.assertEqual(data['status'], 'success') self.assertEqual(data['state']['address_line_1'], '742 Evergreen Terrace') self.assertEqual(datetime.strptime(db_created_time, "%Y-%m-%dT%H:%M:%S.%fZ").replace(microsecond=0), datetime.strptime(data['property']['created'], "%Y-%m-%dT%H:%M:%S.%fZ").replace( microsecond=0)) self.assertGreater(datetime.strptime(data['property']['updated'], "%Y-%m-%dT%H:%M:%S.%fZ"), datetime.strptime(db_updated_time, "%Y-%m-%dT%H:%M:%S.%fZ")) def test_search_identifier(self): self.property_view_factory.get_property_view(cycle=self.cycle, custom_id_1='123456') self.property_view_factory.get_property_view(cycle=self.cycle, custom_id_1='987654 Long Street') self.property_view_factory.get_property_view(cycle=self.cycle, address_line_1='123 Main Street') self.property_view_factory.get_property_view(cycle=self.cycle, address_line_1='Hamilton Road', analysis_state=PropertyState.ANALYSIS_STATE_QUEUED) self.property_view_factory.get_property_view(cycle=self.cycle, custom_id_1='long road', analysis_state=PropertyState.ANALYSIS_STATE_QUEUED) # Typically looks like this # http://localhost:8000/api/v2.1/properties/?organization_id=265&cycle=219&identifier=09-IS # check for all items query_params = "?cycle={}&organization_id={}".format(self.cycle.pk, self.org.pk) url = reverse('api:v2.1:properties-list') + query_params response = self.client.get(url) result = json.loads(response.content) self.assertEqual(result['status'], 'success') results = result['properties'] self.assertEqual(len(results), 5) # check for 2 items with 123 query_params = "?cycle={}&organization_id={}&identifier={}".format(self.cycle.pk, self.org.pk, '123') url = reverse('api:v2.1:properties-list') + query_params response = self.client.get(url) result = json.loads(response.content) self.assertEqual(result['status'], 'success') results = result['properties'] # print out the result of this when there are more than two in an attempt to catch the # non-deterministic part of this test if len(results) > 2: print results self.assertEqual(len(results), 2) # check the analysis states query_params = "?cycle={}&organization_id={}&analysis_state={}".format(self.cycle.pk, self.org.pk, 'Completed') url = reverse('api:v2.1:properties-list') + query_params response = self.client.get(url) result = json.loads(response.content) self.assertEqual(result['status'], 'success') results = result['properties'] self.assertEqual(len(results), 0) query_params = "?cycle={}&organization_id={}&analysis_state={}".format( self.cycle.pk, self.org.pk, 'Not Started' ) url = reverse('api:v2.1:properties-list') + query_params response = self.client.get(url) result = json.loads(response.content) self.assertEqual(result['status'], 'success') results = result['properties'] self.assertEqual(len(results), 3) query_params = "?cycle={}&organization_id={}&analysis_state={}".format( self.cycle.pk, self.org.pk, 'Queued' ) url = reverse('api:v2.1:properties-list') + query_params response = self.client.get(url) result = json.loads(response.content) self.assertEqual(result['status'], 'success') results = result['properties'] self.assertEqual(len(results), 2) # check the combination of both the identifier and the analysis state query_params = "?cycle={}&organization_id={}&identifier={}&analysis_state={}".format( self.cycle.pk, self.org.pk, 'Long', 'Queued' ) url = reverse('api:v2.1:properties-list') + query_params response = self.client.get(url) result = json.loads(response.content) self.assertEqual(result['status'], 'success') results = result['properties'] self.assertEqual(len(results), 1)
def setUp(self): self.user_details = { 'username': '******', 'password': '******', } self.user = User.objects.create_superuser(email='*****@*****.**', **self.user_details) self.org, _, _ = create_organization(self.user) self.client.login(**self.user_details) self.property_state_factory = FakePropertyStateFactory( organization=self.org) property_details = self.property_state_factory.get_details() property_details['organization_id'] = self.org.id # pm_property_ids must match those within example-monthly-meter-usage.xlsx self.pm_property_id_1 = '5766973' self.pm_property_id_2 = '5766975' property_details['pm_property_id'] = self.pm_property_id_1 state_1 = PropertyState(**property_details) state_1.save() self.state_1 = PropertyState.objects.get(pk=state_1.id) property_details['pm_property_id'] = self.pm_property_id_2 state_2 = PropertyState(**property_details) state_2.save() self.state_2 = PropertyState.objects.get(pk=state_2.id) self.cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) self.cycle = self.cycle_factory.get_cycle( start=datetime(2010, 10, 10, tzinfo=get_current_timezone())) self.property_factory = FakePropertyFactory(organization=self.org) self.property_1 = self.property_factory.get_property() self.property_2 = self.property_factory.get_property() self.property_view_1 = PropertyView.objects.create( property=self.property_1, cycle=self.cycle, state=self.state_1) self.property_view_2 = PropertyView.objects.create( property=self.property_2, cycle=self.cycle, state=self.state_2) self.import_record = ImportRecord.objects.create( owner=self.user, last_modified_by=self.user, super_organization=self.org) # This file has multiple tabs filename = "example-pm-monthly-meter-usage.xlsx" filepath = os.path.dirname( os.path.abspath(__file__)) + "/data/" + filename self.import_file = ImportFile.objects.create( import_record=self.import_record, source_type="PM Meter Usage", uploaded_filename=filename, file=SimpleUploadedFile(name=filename, content=open(filepath, 'rb').read()), cycle=self.cycle) self.tz_obj = timezone(TIME_ZONE)
class TestProfileIdMixin(TestCase): """Test OrgMixin -- provides get_organization_id method""" def setUp(self): self.maxDiff = None user_details = { 'username': '******', 'password': '******', } self.user = User.objects.create_superuser( email='*****@*****.**', **user_details) self.org, self.org_user, _ = create_organization(self.user) self.cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) self.property_factory = FakePropertyFactory(organization=self.org) self.property_state_factory = FakePropertyStateFactory(organization=self.org) self.column_list_factory = FakeColumnListSettingsFactory(organization=self.org) self.cycle = self.cycle_factory.get_cycle( start=datetime(2010, 10, 10, tzinfo=timezone.get_current_timezone()) ) class ProfileIdMixInclass(ProfileIdMixin): pass self.mixin_class = ProfileIdMixInclass() def tearDown(self): PropertyView.objects.all().delete() self.user.delete() self.org.delete() self.org_user.delete() def test_get_profile_id(self): """test get_organization method""" state = self.property_state_factory.get_property_state(extra_data={"field_1": "value_1"}) prprty = self.property_factory.get_property() PropertyView.objects.create( property=prprty, cycle=self.cycle, state=state ) # save all the columns in the state to the database so we can setup column list settings Column.save_column_names(state) columns = self.mixin_class.get_show_columns(self.org.id, None) self.assertGreater(len(columns['fields']), 10) self.assertListEqual(columns['extra_data'], ['field_1']) columns = self.mixin_class.get_show_columns(self.org.id, -1) self.assertGreater(len(columns['fields']), 10) self.assertListEqual(columns['extra_data'], ['field_1']) columns = self.mixin_class.get_show_columns(self.org.id, 1000000) self.assertGreater(len(columns['fields']), 10) self.assertListEqual(columns['extra_data'], ['field_1']) # no extra data columnlistsetting = self.column_list_factory.get_columnlistsettings( columns=['address_line_1', 'site_eui'] ) columns = self.mixin_class.get_show_columns(self.org.id, columnlistsetting.id) self.assertListEqual(columns['fields'], ['extra_data', 'id', 'address_line_1', 'site_eui']) self.assertListEqual(columns['extra_data'], []) # with extra data columnlistsetting = self.column_list_factory.get_columnlistsettings( columns=['address_line_1', 'site_eui', 'field_1'] ) columns = self.mixin_class.get_show_columns(self.org.id, columnlistsetting.id) self.assertListEqual(columns['fields'], ['extra_data', 'id', 'address_line_1', 'site_eui']) self.assertListEqual(columns['extra_data'], ['field_1'])
class TaxLotViewTests(DataMappingBaseTestCase): def setUp(self): user_details = { 'username': '******', 'password': '******', 'email': '*****@*****.**' } self.user = User.objects.create_superuser(**user_details) self.org, self.org_user, _ = create_organization(self.user) self.client.login(**user_details) self.cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) self.cycle = self.cycle_factory.get_cycle( start=datetime(2010, 10, 10, tzinfo=get_current_timezone())) self.taxlot_factory = FakeTaxLotFactory(organization=self.org) self.taxlot_state_factory = FakeTaxLotStateFactory( organization=self.org) self.column_list_factory = FakeColumnListSettingsFactory( organization=self.org) def test_get_links_for_a_single_property(self): # Create 2 linked property sets state = self.taxlot_state_factory.get_taxlot_state( extra_data={"field_1": "value_1"}) taxlot = self.taxlot_factory.get_taxlot() view_1 = TaxLotView.objects.create(taxlot=taxlot, cycle=self.cycle, state=state) later_cycle = self.cycle_factory.get_cycle( start=datetime(2100, 10, 10, tzinfo=get_current_timezone())) state_2 = self.taxlot_state_factory.get_taxlot_state( extra_data={"field_1": "value_2"}) view_2 = TaxLotView.objects.create(taxlot=taxlot, cycle=later_cycle, state=state_2) # save all the columns in the state to the database Column.save_column_names(state) url = reverse('api:v2:taxlots-links', args=[view_1.id]) post_params = json.dumps({'organization_id': self.org.pk}) response = self.client.post(url, post_params, content_type='application/json') data = response.json()['data'] self.assertEqual(len(data), 2) # results should be ordered by descending cycle start date result_1 = data[1] self.assertEqual(result_1['address_line_1'], state.address_line_1) self.assertEqual(result_1['extra_data']['field_1'], 'value_1') self.assertEqual(result_1['cycle_id'], self.cycle.id) self.assertEqual(result_1['view_id'], view_1.id) result_2 = data[0] self.assertEqual(result_2['address_line_1'], state_2.address_line_1) self.assertEqual(result_2['extra_data']['field_1'], 'value_2') self.assertEqual(result_2['cycle_id'], later_cycle.id) self.assertEqual(result_2['view_id'], view_2.id) def test_first_lat_long_edit(self): state = self.taxlot_state_factory.get_taxlot_state() taxlot = self.taxlot_factory.get_taxlot() view = TaxLotView.objects.create(taxlot=taxlot, cycle=self.cycle, state=state) # update the address new_data = { "state": { "latitude": 39.765251, "longitude": -104.986138, } } url = reverse('api:v2:taxlots-detail', args=[ view.id ]) + '?organization_id={}'.format(self.org.pk) response = self.client.put(url, json.dumps(new_data), content_type='application/json') data = json.loads(response.content) self.assertEqual(data['status'], 'success') response = self.client.get(url, content_type='application/json') data = json.loads(response.content) self.assertEqual(data['status'], 'success') self.assertIsNotNone(data['state']['long_lat']) self.assertIsNotNone(data['state']['geocoding_confidence']) def test_merged_indicators_provided_on_filter_endpoint(self): _import_record, import_file_1 = self.create_import_file( self.user, self.org, self.cycle) base_details = { 'address_line_1': '123 Match Street', 'import_file_id': import_file_1.id, 'data_state': DATA_STATE_MAPPING, 'no_default_data': False, } self.taxlot_state_factory.get_taxlot_state(**base_details) # set import_file_1 mapping done so that record is "created for users to view". import_file_1.mapping_done = True import_file_1.save() match_buildings(import_file_1.id) _import_record_2, import_file_2 = self.create_import_file( self.user, self.org, self.cycle) url = reverse( 'api:v2:taxlots-filter' ) + '?cycle_id={}&organization_id={}&page=1&per_page=999999999'.format( self.cycle.pk, self.org.pk) response = self.client.post(url) data = json.loads(response.content) self.assertFalse(data['results'][0]['merged_indicator']) # make sure merged_indicator is True when merge occurs base_details['city'] = 'Denver' base_details['import_file_id'] = import_file_2.id self.taxlot_state_factory.get_taxlot_state(**base_details) # set import_file_2 mapping done so that match merging can occur. import_file_2.mapping_done = True import_file_2.save() match_buildings(import_file_2.id) url = reverse( 'api:v2:taxlots-filter' ) + '?cycle_id={}&organization_id={}&page=1&per_page=999999999'.format( self.cycle.pk, self.org.pk) response = self.client.post(url) data = json.loads(response.content) self.assertTrue(data['results'][0]['merged_indicator']) # Create pairings and check if paired object has indicator as well property_factory = FakePropertyFactory(organization=self.org) property_state_factory = FakePropertyStateFactory( organization=self.org) property = property_factory.get_property() property_state = property_state_factory.get_property_state() property_view = PropertyView.objects.create(property=property, cycle=self.cycle, state=property_state) # attach pairing to one and only taxlot_view TaxLotProperty(primary=True, cycle_id=self.cycle.id, property_view_id=property_view.id, taxlot_view_id=TaxLotView.objects.get().id).save() url = reverse( 'api:v2:taxlots-filter' ) + '?cycle_id={}&organization_id={}&page=1&per_page=999999999'.format( self.cycle.pk, self.org.pk) response = self.client.post(url) data = json.loads(response.content) related = data['results'][0]['related'][0] self.assertTrue('merged_indicator' in related) self.assertFalse(related['merged_indicator']) def test_taxlot_match_merge_link(self): base_details = { 'jurisdiction_tax_lot_id': '123MatchID', 'no_default_data': False, } tls_1 = self.taxlot_state_factory.get_taxlot_state(**base_details) taxlot = self.taxlot_factory.get_taxlot() view_1 = TaxLotView.objects.create(taxlot=taxlot, cycle=self.cycle, state=tls_1) cycle_2 = self.cycle_factory.get_cycle( start=datetime(2018, 10, 10, tzinfo=get_current_timezone())) tls_2 = self.taxlot_state_factory.get_taxlot_state(**base_details) taxlot_2 = self.taxlot_factory.get_taxlot() TaxLotView.objects.create(taxlot=taxlot_2, cycle=cycle_2, state=tls_2) url = reverse('api:v2:taxlots-match-merge-link', args=[view_1.id]) response = self.client.post(url, content_type='application/json') summary = response.json() expected_summary = { 'view_id': None, 'match_merged_count': 0, 'match_link_count': 1, } self.assertEqual(expected_summary, summary) refreshed_view_1 = TaxLotView.objects.get(state_id=tls_1.id) view_2 = TaxLotView.objects.get(state_id=tls_2.id) self.assertEqual(refreshed_view_1.taxlot_id, view_2.taxlot_id) def test_taxlots_cycles_list(self): # Create TaxLot set in cycle 1 state = self.taxlot_state_factory.get_taxlot_state( extra_data={"field_1": "value_1"}) taxlot = self.taxlot_factory.get_taxlot() TaxLotView.objects.create(taxlot=taxlot, cycle=self.cycle, state=state) cycle_2 = self.cycle_factory.get_cycle( start=datetime(2018, 10, 10, tzinfo=get_current_timezone())) state_2 = self.taxlot_state_factory.get_taxlot_state( extra_data={"field_1": "value_2"}) taxlot_2 = self.taxlot_factory.get_taxlot() TaxLotView.objects.create(taxlot=taxlot_2, cycle=cycle_2, state=state_2) # save all the columns in the state to the database so we can setup column list settings Column.save_column_names(state) # get the columnlistsetting (default) for all columns columnlistsetting = self.column_list_factory.get_columnlistsettings( inventory_type=VIEW_LIST_TAXLOT, columns=['address_line_1', 'field_1'], table_name='TaxLotState') post_params = json.dumps({ 'organization_id': self.org.pk, 'profile_id': columnlistsetting.id, 'cycle_ids': [self.cycle.id, cycle_2.id] }) url = reverse('api:v2:taxlots-cycles') response = self.client.post(url, post_params, content_type='application/json') data = response.json() address_line_1_key = 'address_line_1_' + str( columnlistsetting.columns.get(column_name='address_line_1').id) field_1_key = 'field_1_' + str( columnlistsetting.columns.get(column_name='field_1').id) self.assertEqual(len(data), 2) result_1 = data[str(self.cycle.id)] self.assertEqual(result_1[0][address_line_1_key], state.address_line_1) self.assertEqual(result_1[0][field_1_key], 'value_1') self.assertEqual(result_1[0]['id'], taxlot.id) result_2 = data[str(cycle_2.id)] self.assertEqual(result_2[0][address_line_1_key], state_2.address_line_1) self.assertEqual(result_2[0][field_1_key], 'value_2') self.assertEqual(result_2[0]['id'], taxlot_2.id)
def setUp(self): user_details = { 'username': '******', 'password': '******', 'email': '*****@*****.**', 'first_name': 'Test', 'last_name': 'User', } self.user = User.objects.create_user(**user_details) self.org, self.org_user, _ = create_organization(self.user) self.org_b, self.org_user, _ = create_organization(self.user) self.client.login(**user_details) cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) cycle_a = cycle_factory.get_cycle(name="Cycle A") cycle_b = cycle_factory.get_cycle(name="Cycle B") property_factory = FakePropertyFactory(organization=self.org) self.property_a = property_factory.get_property() property_b = property_factory.get_property() property_state_factory = FakePropertyStateFactory( organization=self.org) property_state_a = property_state_factory.get_property_state() property_state_b = property_state_factory.get_property_state() property_state_c = property_state_factory.get_property_state() property_state_d = property_state_factory.get_property_state() # create an analysis with two property views, each with the same property but a different cycle self.analysis_a = Analysis.objects.create(name='test a', service=Analysis.BSYNCR, status=Analysis.CREATING, user=self.user, organization=self.org) self.analysis_property_view_a = AnalysisPropertyView.objects.create( analysis=self.analysis_a, property=self.property_a, cycle=cycle_a, property_state=property_state_a) self.analysis_property_view_b = AnalysisPropertyView.objects.create( analysis=self.analysis_a, property=self.property_a, cycle=cycle_b, property_state=property_state_b) # create an analysis with two property views, each with the same cycle but a different property self.analysis_b = Analysis.objects.create(name='test b', service=Analysis.BSYNCR, status=Analysis.READY, user=self.user, organization=self.org) self.analysis_property_view_c = AnalysisPropertyView.objects.create( analysis=self.analysis_b, property=self.property_a, cycle=cycle_a, property_state=property_state_c) self.analysis_property_view_d = AnalysisPropertyView.objects.create( analysis=self.analysis_b, property=property_b, cycle=cycle_a, property_state=property_state_d) # create an analysis with no property views self.analysis_c = Analysis.objects.create(name='test c', service=Analysis.BSYNCR, status=Analysis.QUEUED, user=self.user, organization=self.org) # create an analysis with a different organization self.analysis_d = Analysis.objects.create(name='test d', service=Analysis.BSYNCR, status=Analysis.RUNNING, user=self.user, organization=self.org_b) # create an output file and add to 3 analysis property views self.analysis_output_file_a = AnalysisOutputFile.objects.create( file=SimpleUploadedFile('test file a', b'test file a contents'), content_type=AnalysisOutputFile.BUILDINGSYNC) self.analysis_output_file_a.analysis_property_views.add( self.analysis_property_view_a) self.analysis_output_file_a.analysis_property_views.add( self.analysis_property_view_b) self.analysis_output_file_a.analysis_property_views.add( self.analysis_property_view_c) # create an output file and add to 1 analysis property view self.analysis_output_file_b = AnalysisOutputFile.objects.create( file=SimpleUploadedFile('test file b', b'test file b contents'), content_type=AnalysisOutputFile.BUILDINGSYNC) self.analysis_output_file_b.analysis_property_views.add( self.analysis_property_view_a)
class MeterUsageImportTest(TestCase): def setUp(self): self.user_details = { 'username': '******', 'password': '******', } self.user = User.objects.create_superuser(email='*****@*****.**', **self.user_details) self.org, _, _ = create_organization(self.user) self.client.login(**self.user_details) self.property_state_factory = FakePropertyStateFactory( organization=self.org) property_details = self.property_state_factory.get_details() property_details['organization_id'] = self.org.id # pm_property_ids must match those within example-monthly-meter-usage.xlsx self.pm_property_id_1 = '5766973' self.pm_property_id_2 = '5766975' property_details['pm_property_id'] = self.pm_property_id_1 state_1 = PropertyState(**property_details) state_1.save() self.state_1 = PropertyState.objects.get(pk=state_1.id) property_details['pm_property_id'] = self.pm_property_id_2 state_2 = PropertyState(**property_details) state_2.save() self.state_2 = PropertyState.objects.get(pk=state_2.id) self.cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) self.cycle = self.cycle_factory.get_cycle( start=datetime(2010, 10, 10, tzinfo=get_current_timezone())) self.property_factory = FakePropertyFactory(organization=self.org) self.property_1 = self.property_factory.get_property() self.property_2 = self.property_factory.get_property() self.property_view_1 = PropertyView.objects.create( property=self.property_1, cycle=self.cycle, state=self.state_1) self.property_view_2 = PropertyView.objects.create( property=self.property_2, cycle=self.cycle, state=self.state_2) self.import_record = ImportRecord.objects.create( owner=self.user, last_modified_by=self.user, super_organization=self.org) # This file has multiple tabs filename = "example-pm-monthly-meter-usage.xlsx" filepath = os.path.dirname( os.path.abspath(__file__)) + "/data/" + filename self.import_file = ImportFile.objects.create( import_record=self.import_record, source_type="PM Meter Usage", uploaded_filename=filename, file=SimpleUploadedFile(name=filename, content=open(filepath, 'rb').read()), cycle=self.cycle) self.tz_obj = timezone(TIME_ZONE) def test_import_meter_usage_file_base_case(self): """ Expect to have 4 meters - 2 for each property - 1 for gas and 1 for electricity. Each meter will have 2 readings, for a total of 8 readings. These come from 8 meter usage rows in the .xlsx file - 1 per reading. """ url = reverse("api:v3:import_files-start-save-data", args=[self.import_file.id]) url += f'?organization_id={self.org.pk}' post_params = { 'cycle_id': self.cycle.pk, } self.client.post(url, post_params) refreshed_property_1 = Property.objects.get(pk=self.property_1.id) self.assertEqual(refreshed_property_1.meters.all().count(), 2) meter_1 = refreshed_property_1.meters.get(type=Meter.ELECTRICITY_GRID) self.assertEqual(meter_1.source, Meter.PORTFOLIO_MANAGER) self.assertEqual(meter_1.source_id, '5766973-0') self.assertEqual(meter_1.is_virtual, False) self.assertEqual(meter_1.meter_readings.all().count(), 2) meter_reading_10, meter_reading_11 = list( meter_1.meter_readings.order_by('start_time').all()) self.assertEqual( meter_reading_10.start_time, make_aware(datetime(2016, 1, 1, 0, 0, 0), timezone=self.tz_obj)) self.assertEqual( meter_reading_10.end_time, make_aware(datetime(2016, 2, 1, 0, 0, 0), timezone=self.tz_obj)) self.assertEqual(meter_reading_10.reading, 597478.9) self.assertEqual(meter_reading_10.source_unit, "kBtu (thousand Btu)") # spot check self.assertEqual(meter_reading_10.conversion_factor, 1) # spot check self.assertEqual( meter_reading_11.start_time, make_aware(datetime(2016, 2, 1, 0, 0, 0), timezone=self.tz_obj)) self.assertEqual( meter_reading_11.end_time, make_aware(datetime(2016, 3, 1, 0, 0, 0), timezone=self.tz_obj)) self.assertEqual(meter_reading_11.reading, 548603.7) meter_2 = refreshed_property_1.meters.get(type=Meter.NATURAL_GAS) self.assertEqual(meter_2.source, Meter.PORTFOLIO_MANAGER) self.assertEqual(meter_2.source_id, '5766973-1') self.assertEqual(meter_2.meter_readings.all().count(), 2) meter_reading_20, meter_reading_21 = list( meter_2.meter_readings.order_by('start_time').all()) self.assertEqual( meter_reading_20.start_time, make_aware(datetime(2016, 1, 1, 0, 0, 0), timezone=self.tz_obj)) self.assertEqual( meter_reading_20.end_time, make_aware(datetime(2016, 2, 1, 0, 0, 0), timezone=self.tz_obj)) self.assertEqual(meter_reading_20.reading, 576000.2) self.assertEqual( meter_reading_21.start_time, make_aware(datetime(2016, 2, 1, 0, 0, 0), timezone=self.tz_obj)) self.assertEqual( meter_reading_21.end_time, make_aware(datetime(2016, 3, 1, 0, 0, 0), timezone=self.tz_obj)) self.assertEqual(meter_reading_21.reading, 488000.1) refreshed_property_2 = Property.objects.get(pk=self.property_2.id) self.assertEqual(refreshed_property_2.meters.all().count(), 2) meter_3 = refreshed_property_2.meters.get(type=Meter.ELECTRICITY_GRID) self.assertEqual(meter_3.source, Meter.PORTFOLIO_MANAGER) self.assertEqual(meter_3.source_id, '5766975-0') self.assertEqual(meter_3.meter_readings.all().count(), 2) meter_reading_30, meter_reading_40 = list( meter_3.meter_readings.order_by('start_time').all()) self.assertEqual( meter_reading_30.start_time, make_aware(datetime(2016, 1, 1, 0, 0, 0), timezone=self.tz_obj)) self.assertEqual( meter_reading_30.end_time, make_aware(datetime(2016, 2, 1, 0, 0, 0), timezone=self.tz_obj)) self.assertEqual(meter_reading_30.reading, 154572.2) self.assertEqual(meter_reading_30.source_unit, "kBtu (thousand Btu)") # spot check self.assertEqual(meter_reading_30.conversion_factor, 1) # spot check self.assertEqual( meter_reading_40.start_time, make_aware(datetime(2016, 2, 1, 0, 0, 0), timezone=self.tz_obj)) self.assertEqual( meter_reading_40.end_time, make_aware(datetime(2016, 3, 1, 0, 0, 0), timezone=self.tz_obj)) self.assertEqual(meter_reading_40.reading, 141437.5) meter_4 = refreshed_property_2.meters.get(type=Meter.NATURAL_GAS) self.assertEqual(meter_4.source, Meter.PORTFOLIO_MANAGER) self.assertEqual(meter_4.source_id, '5766975-1') self.assertEqual(meter_4.meter_readings.all().count(), 2) meter_reading_40, meter_reading_41 = list( meter_4.meter_readings.order_by('start_time').all()) self.assertEqual( meter_reading_40.start_time, make_aware(datetime(2016, 1, 1, 0, 0, 0), timezone=self.tz_obj)) self.assertEqual( meter_reading_40.end_time, make_aware(datetime(2016, 2, 1, 0, 0, 0), timezone=self.tz_obj)) self.assertEqual(meter_reading_40.reading, 299915) self.assertEqual( meter_reading_41.start_time, make_aware(datetime(2016, 2, 1, 0, 0, 0), timezone=self.tz_obj)) self.assertEqual( meter_reading_41.end_time, make_aware(datetime(2016, 3, 1, 0, 0, 0), timezone=self.tz_obj)) self.assertEqual(meter_reading_41.reading, 496310.9) # file should be disassociated from cycle too refreshed_import_file = ImportFile.objects.get(pk=self.import_file.id) self.assertEqual(refreshed_import_file.cycle_id, None) def test_import_meter_usage_file_ignores_unknown_types_or_units(self): """ Expect to have 3 meters. The first meter belongs to the first property and should have 2 readings. The second meter belongs to the second property and should have 1 reading. The last meter belongs to the second property and should have 1 reading. These come from 8 meter usage rows in the .xlsx file (1 per reading) where 4 of them have either an invalid type or unit. """ filename = "example-pm-monthly-meter-usage-with-unknown-types-and-units.xlsx" filepath = os.path.dirname( os.path.abspath(__file__)) + "/data/" + filename import_file_with_invalids = ImportFile.objects.create( import_record=self.import_record, source_type="PM Meter Usage", uploaded_filename=filename, file=SimpleUploadedFile(name=filename, content=open(filepath, 'rb').read()), cycle=self.cycle) url = reverse("api:v3:import_files-start-save-data", args=[import_file_with_invalids.id]) url += f'?organization_id={self.org.pk}' post_params = { 'cycle_id': self.cycle.pk, } self.client.post(url, post_params) self.assertEqual(3, Meter.objects.count()) self.assertEqual(4, MeterReading.objects.count()) refreshed_property_1 = Property.objects.get(pk=self.property_1.id) self.assertEqual(refreshed_property_1.meters.all().count(), 1) meter_1 = refreshed_property_1.meters.first() self.assertEqual(meter_1.meter_readings.all().count(), 2) refreshed_property_2 = Property.objects.get(pk=self.property_2.id) self.assertEqual(refreshed_property_2.meters.all().count(), 2) meter_2 = refreshed_property_2.meters.get(type=Meter.ELECTRICITY_GRID) self.assertEqual(meter_2.meter_readings.all().count(), 1) meter_3 = refreshed_property_2.meters.get(type=Meter.NATURAL_GAS) self.assertEqual(meter_3.meter_readings.all().count(), 1) def test_import_meter_usage_file_including_2_cost_meters(self): filename = "example-pm-monthly-meter-usage-2-cost-meters.xlsx" filepath = os.path.dirname( os.path.abspath(__file__)) + "/data/" + filename cost_meter_import_file = ImportFile.objects.create( import_record=self.import_record, source_type="PM Meter Usage", uploaded_filename=filename, file=SimpleUploadedFile(name=filename, content=open(filepath, 'rb').read()), cycle=self.cycle) url = reverse("api:v3:import_files-start-save-data", args=[cost_meter_import_file.id]) url += f'?organization_id={self.org.pk}' post_params = { 'cycle_id': self.cycle.pk, } self.client.post(url, post_params) cost_meters = Meter.objects.filter(type=Meter.COST) self.assertEqual(2, cost_meters.count()) electric_cost_meter = cost_meters.get(source_id='5766973-0') gas_cost_meter = cost_meters.get(source_id='5766973-1') self.assertEqual(2, electric_cost_meter.meter_readings.count()) self.assertEqual(2, gas_cost_meter.meter_readings.count()) electric_reading_values = electric_cost_meter.meter_readings.values_list( 'reading', flat=True) self.assertCountEqual([100, 200], electric_reading_values) gas_reading_values = gas_cost_meter.meter_readings.values_list( 'reading', flat=True) self.assertCountEqual([300, 400], gas_reading_values) def test_existing_meter_is_found_and_used_if_import_file_should_reference_it( self): property = Property.objects.get(pk=self.property_1.id) # Create a meter with the same details of one meter in the import file unsaved_meter = Meter( property=property, source=Meter.PORTFOLIO_MANAGER, source_id='5766973-0', type=Meter.ELECTRICITY_GRID, ) unsaved_meter.save() existing_meter = Meter.objects.get(pk=unsaved_meter.id) # Create a reading with a different date from those in the import file unsaved_meter_reading = MeterReading( meter=existing_meter, start_time=make_aware(datetime(2018, 1, 1, 0, 0, 0), timezone=self.tz_obj), end_time=make_aware(datetime(2018, 2, 1, 0, 0, 0), timezone=self.tz_obj), reading=12345, conversion_factor=1.0) unsaved_meter_reading.save() existing_meter_reading = MeterReading.objects.get(reading=12345) url = reverse("api:v3:import_files-start-save-data", args=[self.import_file.id]) url += f'?organization_id={self.org.pk}' post_params = { 'cycle_id': self.cycle.pk, } self.client.post(url, post_params) refreshed_property_1 = Property.objects.get(pk=self.property_1.id) self.assertEqual(refreshed_property_1.meters.all().count(), 2) refreshed_meter = refreshed_property_1.meters.get( type=Meter.ELECTRICITY_GRID) meter_reading_10, meter_reading_11, meter_reading_12 = list( refreshed_meter.meter_readings.order_by('start_time').all()) self.assertEqual(meter_reading_10.reading, 597478.9) self.assertEqual(meter_reading_11.reading, 548603.7) # Sanity check to be sure, nothing was changed with existing meter reading self.assertEqual(meter_reading_12, existing_meter_reading) def test_existing_meter_reading_has_reading_source_unit_and_conversion_factor_updated_if_import_file_references_previous_entry( self): property = Property.objects.get(pk=self.property_1.id) # Create a meter with the same details of one meter in the import file unsaved_meter = Meter( property=property, source=Meter.PORTFOLIO_MANAGER, source_id='5766973-0', type=Meter.ELECTRICITY_GRID, ) unsaved_meter.save() existing_meter = Meter.objects.get(pk=unsaved_meter.id) # Create a reading with the same date as one from the import file but different reading start_time = make_aware(datetime(2016, 1, 1, 0, 0, 0), timezone=self.tz_obj) end_time = make_aware(datetime(2016, 2, 1, 0, 0, 0), timezone=self.tz_obj) unsaved_meter_reading = MeterReading(meter=existing_meter, start_time=start_time, end_time=end_time, reading=12345, source_unit="GJ", conversion_factor=947.82) unsaved_meter_reading.save() url = reverse("api:v3:import_files-start-save-data", args=[self.import_file.id]) url += f'?organization_id={self.org.pk}' post_params = { 'cycle_id': self.cycle.pk, } self.client.post(url, post_params) # Just as in the first test, 8 meter readings should exist self.assertEqual(MeterReading.objects.all().count(), 8) refreshed_property = Property.objects.get(pk=self.property_1.id) refreshed_meter = refreshed_property.meters.get( type=Meter.ELECTRICITY_GRID) meter_reading = refreshed_meter.meter_readings.get( start_time=start_time) self.assertEqual(meter_reading.end_time, end_time) self.assertEqual(meter_reading.reading, 597478.9) self.assertEqual(meter_reading.source_unit, "kBtu (thousand Btu)") self.assertEqual(meter_reading.conversion_factor, 1) def test_property_existing_in_multiple_cycles_can_have_meters_and_readings_associated_to_it( self): property_details = FakePropertyStateFactory( organization=self.org).get_details() property_details['organization_id'] = self.org.id # new state to be associated to new cycle using the same pm_property_id as state in old cycle property_details['pm_property_id'] = self.state_1.pm_property_id state = PropertyState(**property_details) state.save() new_property_state = PropertyState.objects.get(pk=state.id) new_cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) new_cycle = new_cycle_factory.get_cycle( start=datetime(2010, 10, 10, tzinfo=get_current_timezone())) # new state and cycle associated to old property PropertyView.objects.create(property=self.property_1, cycle=new_cycle, state=new_property_state) url = reverse("api:v3:import_files-start-save-data", args=[self.import_file.id]) url += f'?organization_id={self.org.pk}' post_params = { 'cycle_id': self.cycle.pk, } self.client.post(url, post_params) refreshed_property_1 = Property.objects.get(pk=self.property_1.id) self.assertEqual(refreshed_property_1.meters.all().count(), 2) def test_meters_and_readings_are_associated_to_every_record_across_all_cycles_with_a_given_pm_property_id( self): # new, in-cycle state NOT associated to existing record but has same PM Property ID property_details_1 = FakePropertyStateFactory( organization=self.org).get_details() property_details_1['organization_id'] = self.org.id property_details_1['pm_property_id'] = self.state_1.pm_property_id property_details_1['custom_id_1'] = "values that forces non-match" new_property_1 = PropertyState(**property_details_1) new_property_1.save() property_3 = self.property_factory.get_property() PropertyView.objects.create(property=property_3, cycle=self.cycle, state=new_property_1) # new, out-cycle state NOT associated to existing record but has same PM Property ID property_details_2 = FakePropertyStateFactory( organization=self.org).get_details() property_details_2['organization_id'] = self.org.id property_details_2['pm_property_id'] = self.state_1.pm_property_id property_details_2[ 'custom_id_1'] = "second value that forces non-match" new_property_2 = PropertyState(**property_details_2) new_property_2.save() new_cycle = self.cycle_factory.get_cycle( start=datetime(2011, 10, 10, tzinfo=get_current_timezone())) property_4 = self.property_factory.get_property() PropertyView.objects.create(property=property_4, cycle=new_cycle, state=new_property_2) url = reverse("api:v3:import_files-start-save-data", args=[self.import_file.id]) url += f'?organization_id={self.org.pk}' post_params = { 'cycle_id': self.cycle.pk, } self.client.post(url, post_params) refreshed_property_1 = Property.objects.get(pk=self.property_1.id) self.assertEqual(refreshed_property_1.meters.all().count(), 2) refreshed_property_3 = Property.objects.get(pk=property_3.id) self.assertEqual(refreshed_property_3.meters.all().count(), 2) refreshed_property_4 = Property.objects.get(pk=property_4.id) self.assertEqual(refreshed_property_4.meters.all().count(), 2) def test_pm_property_id_existing_across_two_different_orgs_wont_lead_to_misassociated_meters( self): new_org, _, _ = create_organization(self.user) property_details = FakePropertyStateFactory( organization=new_org).get_details() property_details['organization_id'] = new_org.id # new state to be associated to property of different organization but has the same pm_property_id property_details['pm_property_id'] = self.state_1.pm_property_id state = PropertyState(**property_details) state.save() new_property_state = PropertyState.objects.get(pk=state.id) new_cycle_factory = FakeCycleFactory(organization=new_org, user=self.user) new_cycle = new_cycle_factory.get_cycle( start=datetime(2010, 10, 10, tzinfo=get_current_timezone())) new_property = self.property_factory.get_property() PropertyView.objects.create(property=new_property, cycle=new_cycle, state=new_property_state) url = reverse("api:v3:import_files-start-save-data", args=[self.import_file.id]) url += f'?organization_id={self.org.pk}' post_params = { 'cycle_id': self.cycle.pk, } self.client.post(url, post_params) # self.property_1 is associated to self.org, so according to post request, it should have 2 meters refreshed_property_1 = Property.objects.get( pk=self.property_1.id, organization_id__exact=self.org.pk) self.assertEqual(refreshed_property_1.meters.all().count(), 2) refreshed_new_property = Property.objects.get(pk=new_property.id) self.assertEqual(refreshed_new_property.meters.count(), 0) def test_the_response_contains_expected_and_actual_reading_counts_single_cycle( self): url = reverse("api:v3:import_files-start-save-data", args=[self.import_file.id]) url += f'?organization_id={self.org.pk}' post_params = { 'cycle_id': self.cycle.pk, } response = self.client.post(url, post_params) result = json.loads(response.content) expectation = [ { "property_id": self.property_1.id, "cycles": self.cycle.name, "pm_property_id": "5766973", "source_id": "5766973-0", "type": "Electric - Grid", "incoming": 2, "successfully_imported": 2, }, { "property_id": self.property_1.id, "cycles": self.cycle.name, "pm_property_id": "5766973", "source_id": "5766973-1", "type": "Natural Gas", "incoming": 2, "successfully_imported": 2, }, { "property_id": self.property_2.id, "cycles": self.cycle.name, "pm_property_id": "5766975", "source_id": "5766975-0", "type": "Electric - Grid", "incoming": 2, "successfully_imported": 2, }, { "property_id": self.property_2.id, "cycles": self.cycle.name, "pm_property_id": "5766975", "source_id": "5766975-1", "type": "Natural Gas", "incoming": 2, "successfully_imported": 2, }, ] self.assertCountEqual(result['message'], expectation) def test_the_response_contains_expected_and_actual_reading_counts_across_cycles_for_linked_properties( self): property_details = FakePropertyStateFactory( organization=self.org).get_details() property_details['organization_id'] = self.org.id # new state will be linked to existing record and has same PM Property ID property_details['pm_property_id'] = self.state_1.pm_property_id state = PropertyState(**property_details) state.save() new_property_state = PropertyState.objects.get(pk=state.id) new_cycle = self.cycle_factory.get_cycle( start=datetime(2011, 10, 10, tzinfo=get_current_timezone())) PropertyView.objects.create(property=self.property_1, cycle=new_cycle, state=new_property_state) url = reverse("api:v3:import_files-start-save-data", args=[self.import_file.id]) url += f'?organization_id={self.org.pk}' post_params = { 'cycle_id': self.cycle.pk, } response = self.client.post(url, post_params) result = json.loads(response.content) expectation = [ { "property_id": self.property_1.id, "cycles": self.cycle.name + ", " + new_cycle.name, "pm_property_id": "5766973", "source_id": "5766973-0", "type": "Electric - Grid", "incoming": 2, "successfully_imported": 2, }, { "property_id": self.property_1.id, "cycles": self.cycle.name + ", " + new_cycle.name, "pm_property_id": "5766973", "source_id": "5766973-1", "type": "Natural Gas", "incoming": 2, "successfully_imported": 2, }, { "property_id": self.property_2.id, "cycles": self.cycle.name, "pm_property_id": "5766975", "source_id": "5766975-0", "type": "Electric - Grid", "incoming": 2, "successfully_imported": 2, }, { "property_id": self.property_2.id, "cycles": self.cycle.name, "pm_property_id": "5766975", "source_id": "5766975-1", "type": "Natural Gas", "incoming": 2, "successfully_imported": 2, }, ] self.assertCountEqual(result['message'], expectation) def test_the_response_contains_expected_and_actual_reading_counts_by_property_id_even_in_the_same_cycle( self): property_details = FakePropertyStateFactory( organization=self.org).get_details() property_details['organization_id'] = self.org.id # Create new state NOT associated to existing record but has same PM Property ID property_details['pm_property_id'] = self.state_1.pm_property_id property_details['custom_id_1'] = "values that forces non-match" state = PropertyState(**property_details) state.save() new_property_state = PropertyState.objects.get(pk=state.id) # new state in cycle associated to old property property_3 = self.property_factory.get_property() PropertyView.objects.create(property=property_3, cycle=self.cycle, state=new_property_state) url = reverse("api:v3:import_files-start-save-data", args=[self.import_file.id]) url += f'?organization_id={self.org.pk}' post_params = { 'cycle_id': self.cycle.pk, } response = self.client.post(url, post_params) result = json.loads(response.content) expectation = [ { "property_id": self.property_1.id, "cycles": self.cycle.name, "pm_property_id": "5766973", "source_id": "5766973-0", "type": "Electric - Grid", "incoming": 2, "successfully_imported": 2, }, { "property_id": property_3.id, "cycles": self.cycle.name, "pm_property_id": "5766973", "source_id": "5766973-0", "type": "Electric - Grid", "incoming": 2, "successfully_imported": 2, }, { "property_id": self.property_1.id, "cycles": self.cycle.name, "pm_property_id": "5766973", "source_id": "5766973-1", "type": "Natural Gas", "incoming": 2, "successfully_imported": 2, }, { "property_id": property_3.id, "cycles": self.cycle.name, "pm_property_id": "5766973", "source_id": "5766973-1", "type": "Natural Gas", "incoming": 2, "successfully_imported": 2, }, { "property_id": self.property_2.id, "cycles": self.cycle.name, "pm_property_id": "5766975", "source_id": "5766975-0", "type": "Electric - Grid", "incoming": 2, "successfully_imported": 2, }, { "property_id": self.property_2.id, "cycles": self.cycle.name, "pm_property_id": "5766975", "source_id": "5766975-1", "type": "Natural Gas", "incoming": 2, "successfully_imported": 2, }, ] self.assertCountEqual(result['message'], expectation) def test_the_response_contains_expected_and_actual_reading_counts_for_pm_ids_with_costs( self): filename = "example-pm-monthly-meter-usage-2-cost-meters.xlsx" filepath = os.path.dirname( os.path.abspath(__file__)) + "/data/" + filename cost_meter_import_file = ImportFile.objects.create( import_record=self.import_record, source_type="PM Meter Usage", uploaded_filename=filename, file=SimpleUploadedFile(name=filename, content=open(filepath, 'rb').read()), cycle=self.cycle) url = reverse("api:v3:import_files-start-save-data", args=[cost_meter_import_file.id]) url += f'?organization_id={self.org.pk}' post_params = { 'cycle_id': self.cycle.pk, } response = self.client.post(url, post_params) result = json.loads(response.content) expectation = [ { "property_id": self.property_1.id, "cycles": self.cycle.name, "pm_property_id": "5766973", "source_id": "5766973-0", "type": "Electric - Grid", "incoming": 2, "successfully_imported": 2, }, { "property_id": self.property_1.id, "cycles": self.cycle.name, "pm_property_id": "5766973", "source_id": "5766973-1", "type": "Natural Gas", "incoming": 2, "successfully_imported": 2, }, { "property_id": self.property_1.id, "cycles": self.cycle.name, "pm_property_id": "5766973", "source_id": "5766973-0", "type": "Cost", "incoming": 2, "successfully_imported": 2, }, { "property_id": self.property_1.id, "cycles": self.cycle.name, "pm_property_id": "5766973", "source_id": "5766973-1", "type": "Cost", "incoming": 2, "successfully_imported": 2, }, { "property_id": self.property_2.id, "cycles": self.cycle.name, "pm_property_id": "5766975", "source_id": "5766975-0", "type": "Electric - Grid", "incoming": 2, "successfully_imported": 2, }, { "property_id": self.property_2.id, "cycles": self.cycle.name, "pm_property_id": "5766975", "source_id": "5766975-1", "type": "Natural Gas", "incoming": 2, "successfully_imported": 2, }, ] self.assertCountEqual(result['message'], expectation) def test_error_noted_in_response_if_meter_has_overlapping_readings(self): """ If a meter has overlapping readings, the process of upserting a reading will encounter the issue of not knowing which reading should take precedence over the other. In this case, neither the meter (if applicable) nor any of its readings are created. """ dup_import_record = ImportRecord.objects.create( owner=self.user, last_modified_by=self.user, super_organization=self.org) dup_filename = "example-pm-monthly-meter-usage-1-dup.xlsx" dup_filepath = os.path.dirname(os.path.abspath( __file__)) + "/../data_importer/tests/data/" + dup_filename dup_file = ImportFile.objects.create( import_record=dup_import_record, source_type="PM Meter Usage", uploaded_filename=dup_filename, file=SimpleUploadedFile(name=dup_filename, content=open(dup_filepath, 'rb').read()), cycle=self.cycle) url = reverse("api:v3:import_files-start-save-data", args=[dup_file.id]) url += f'?organization_id={self.org.pk}' post_params = { 'cycle_id': self.cycle.pk, } response = self.client.post(url, post_params) total_meters_count = Meter.objects.count() result_summary = json.loads(response.content) expected_import_summary = [ { "property_id": self.property_1.id, "cycles": self.cycle.name, "pm_property_id": "5766973", "source_id": "5766973-0", "type": "Electric - Grid", "incoming": 2, "successfully_imported": 2, "errors": "", }, { "property_id": self.property_1.id, "cycles": self.cycle.name, "pm_property_id": "5766973", "source_id": "5766973-1", "type": "Natural Gas", "incoming": 2, "successfully_imported": 2, "errors": "", }, { "property_id": self.property_2.id, "cycles": self.cycle.name, "pm_property_id": "5766975", "source_id": "5766975-0", "type": "Electric - Grid", "incoming": 4, "successfully_imported": 0, "errors": "Overlapping readings.", }, { "property_id": self.property_2.id, "cycles": self.cycle.name, "pm_property_id": "5766975", "source_id": "5766975-1", "type": "Natural Gas", "incoming": 4, "successfully_imported": 0, "errors": "Overlapping readings.", }, ] self.assertCountEqual(result_summary['message'], expected_import_summary) self.assertEqual(total_meters_count, 2)
class InventoryViewTests(DeleteModelsTestCase): def setUp(self): user_details = { 'username': '******', 'password': '******', 'email': '*****@*****.**' } self.user = User.objects.create_superuser(**user_details) self.org, _, _ = create_organization(self.user) self.status_label = StatusLabel.objects.create( name='test', super_organization=self.org) self.column_factory = FakeColumnFactory(organization=self.org) self.cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) self.property_factory = FakePropertyFactory(organization=self.org) self.property_state_factory = FakePropertyStateFactory( organization=self.org) self.taxlot_state_factory = FakeTaxLotStateFactory( organization=self.org) self.cycle = self.cycle_factory.get_cycle(start=datetime( 2010, 10, 10, tzinfo=timezone.get_current_timezone())) self.client.login(**user_details) def test_get_building_sync(self): state = self.property_state_factory.get_property_state() prprty = self.property_factory.get_property() pv = PropertyView.objects.create(property=prprty, cycle=self.cycle, state=state) # go to buildingsync endpoint params = {'organization_id': self.org.pk} url = reverse('api:v2.1:properties-building-sync', args=[pv.id]) response = self.client.get(url, params) self.assertIn( '<auc:FloorAreaValue>%s.0</auc:FloorAreaValue>' % state.gross_floor_area, response.content.decode("utf-8")) def test_upload_and_get_building_sync(self): # import_record = filename = path.join(BASE_DIR, 'seed', 'building_sync', 'tests', 'data', 'ex_1.xml') url = reverse('api:v2:building_file-list') fsysparams = { 'file': open(filename, 'rb'), 'file_type': 'BuildingSync', 'organization_id': self.org.id, 'cycle_id': self.cycle.id } response = self.client.post(url, fsysparams) self.assertEqual(response.status_code, 200) result = json.loads(response.content) self.assertEqual(result['status'], 'success') self.assertEqual(result['message'], 'successfully imported file') self.assertEqual( result['data']['property_view']['state']['year_built'], 1967) self.assertEqual( result['data']['property_view']['state']['postal_code'], '94111') # now get the building sync that was just uploaded property_id = result['data']['property_view']['id'] url = reverse('api:v2.1:properties-building-sync', args=[property_id]) response = self.client.get(url) self.assertIn('<auc:YearOfConstruction>1967</auc:YearOfConstruction>', response.content.decode("utf-8")) def test_upload_with_measure_duplicates(self): # import_record = filename = path.join(BASE_DIR, 'seed', 'building_sync', 'tests', 'data', 'buildingsync_ex01_measures.xml') url = reverse('api:v2:building_file-list') fsysparams = { 'file': open(filename, 'rb'), 'file_type': 'BuildingSync', 'organization_id': self.org.id, 'cycle_id': self.cycle.id } response = self.client.post(url, fsysparams) self.assertEqual(response.status_code, 200) result = json.loads(response.content) self.assertEqual(result['status'], 'success') expected_message = "successfully imported file with warnings ['Measure category and name is not valid other_electric_motors_and_drives:replace_with_higher_efficiency', 'Measure category and name is not valid other_hvac:install_demand_control_ventilation', 'Measure associated with scenario not found. Scenario: Replace with higher efficiency Only, Measure name: Measure22', 'Measure associated with scenario not found. Scenario: Install demand control ventilation Only, Measure name: Measure24']" self.assertEqual(result['message'], expected_message) self.assertEqual( len(result['data']['property_view']['state']['measures']), 28) self.assertEqual( len(result['data']['property_view']['state']['scenarios']), 31) self.assertEqual( result['data']['property_view']['state']['year_built'], 1967) self.assertEqual( result['data']['property_view']['state']['postal_code'], '94111') # upload the same file again url = reverse('api:v2:building_file-list') fsysparams = { 'file': open(filename, 'rb'), 'file_type': 'BuildingSync', 'organization_id': self.org.id, 'cycle_id': self.cycle.id } response = self.client.post(url, fsysparams) self.assertEqual(response.status_code, 200) result = json.loads(response.content) self.assertEqual( len(result['data']['property_view']['state']['measures']), 28) self.assertEqual( len(result['data']['property_view']['state']['scenarios']), 31) def test_upload_and_get_building_sync_diff_ns(self): # import_record = filename = path.join(BASE_DIR, 'seed', 'building_sync', 'tests', 'data', 'ex_1_different_namespace.xml') url = reverse('api:v2:building_file-list') fsysparams = { 'file': open(filename, 'rb'), 'file_type': 'BuildingSync', 'organization_id': self.org.id, 'cycle_id': self.cycle.id } response = self.client.post(url, fsysparams) self.assertEqual(response.status_code, 200) result = json.loads(response.content) self.assertEqual(result['status'], 'success') self.assertEqual(result['message'], 'successfully imported file') self.assertEqual( result['data']['property_view']['state']['year_built'], 1889) # now get the building sync that was just uploaded property_id = result['data']['property_view']['id'] url = reverse('api:v2.1:properties-building-sync', args=[property_id]) response = self.client.get(url) self.assertIn('<auc:YearOfConstruction>1889</auc:YearOfConstruction>', response.content.decode('utf-8')) def test_get_hpxml(self): state = self.property_state_factory.get_property_state() prprty = self.property_factory.get_property() pv = PropertyView.objects.create(property=prprty, cycle=self.cycle, state=state) # go to buildingsync endpoint params = {'organization_id': self.org.pk} url = reverse('api:v2.1:properties-hpxml', args=[pv.id]) response = self.client.get(url, params) self.assertIn( '<GrossFloorArea>%s.0</GrossFloorArea>' % state.gross_floor_area, response.content.decode('utf-8'))
class PropertyViewTests(DeleteModelsTestCase): def setUp(self): user_details = { 'username': '******', 'password': '******', 'email': '*****@*****.**' } self.user = User.objects.create_superuser(**user_details) self.org, self.org_user, _ = create_organization(self.user) self.column_factory = FakeColumnFactory(organization=self.org) self.cycle_factory = FakeCycleFactory(organization=self.org, user=self.user) self.property_factory = FakePropertyFactory(organization=self.org) self.property_state_factory = FakePropertyStateFactory( organization=self.org) self.property_view_factory = FakePropertyViewFactory( organization=self.org) self.taxlot_state_factory = FakeTaxLotStateFactory( organization=self.org) self.cycle = self.cycle_factory.get_cycle( start=datetime(2010, 10, 10, tzinfo=get_current_timezone())) self.column_list_factory = FakeColumnListSettingsFactory( organization=self.org) self.client.login(**user_details) def test_get_and_edit_properties(self): state = self.property_state_factory.get_property_state() prprty = self.property_factory.get_property() view = PropertyView.objects.create(property=prprty, cycle=self.cycle, state=state) params = { 'organization_id': self.org.pk, 'page': 1, 'per_page': 999999999, 'columns': COLUMNS_TO_SEND, } url = reverse('api:v2.1:properties-list') + '?cycle_id={}'.format( self.cycle.pk) response = self.client.get(url, params) data = json.loads(response.content) self.assertEqual(len(data['properties']), 1) result = data['properties'][0] self.assertEqual(result['state']['address_line_1'], state.address_line_1) db_created_time = result['created'] db_updated_time = result['updated'] self.assertTrue(db_created_time is not None) self.assertTrue(db_updated_time is not None) # update the address new_data = {"state": {"address_line_1": "742 Evergreen Terrace"}} url = reverse('api:v2:properties-detail', args=[ view.id ]) + '?organization_id={}'.format(self.org.pk) response = self.client.put(url, json.dumps(new_data), content_type='application/json') data = json.loads(response.content) self.assertEqual(data['status'], 'success') # the above call returns data from the PropertyState, need to get the Property -- # call the get on the same API to retrieve it response = self.client.get(url, content_type='application/json') data = json.loads(response.content) # make sure the address was updated and that the datetimes were modified self.assertEqual(data['status'], 'success') self.assertEqual(data['state']['address_line_1'], '742 Evergreen Terrace') self.assertEqual( datetime.strptime(db_created_time, "%Y-%m-%dT%H:%M:%S.%fZ").replace(microsecond=0), datetime.strptime(data['property']['created'], "%Y-%m-%dT%H:%M:%S.%fZ").replace(microsecond=0)) self.assertGreater( datetime.strptime(data['property']['updated'], "%Y-%m-%dT%H:%M:%S.%fZ"), datetime.strptime(db_updated_time, "%Y-%m-%dT%H:%M:%S.%fZ")) def test_list_properties_with_profile_id(self): state = self.property_state_factory.get_property_state( extra_data={"field_1": "value_1"}) prprty = self.property_factory.get_property() PropertyView.objects.create(property=prprty, cycle=self.cycle, state=state) # save all the columns in the state to the database so we can setup column list settings Column.save_column_names(state) # get the columnlistsetting (default) for all columns columnlistsetting = self.column_list_factory.get_columnlistsettings( columns=['address_line_1', 'field_1']) params = { 'organization_id': self.org.pk, 'profile_id': columnlistsetting.id, } url = reverse('api:v2.1:properties-list') + '?cycle_id={}'.format( self.cycle.pk) response = self.client.get(url, params) data = response.json() self.assertEqual(len(data['properties']), 1) result = data['properties'][0] self.assertEqual(result['state']['address_line_1'], state.address_line_1) self.assertEqual(result['state']['extra_data']['field_1'], 'value_1') self.assertFalse(result['state'].get('city', None)) def test_search_identifier(self): self.property_view_factory.get_property_view(cycle=self.cycle, custom_id_1='123456') self.property_view_factory.get_property_view( cycle=self.cycle, custom_id_1='987654 Long Street') self.property_view_factory.get_property_view( cycle=self.cycle, address_line_1='123 Main Street') self.property_view_factory.get_property_view( cycle=self.cycle, address_line_1='Hamilton Road', analysis_state=PropertyState.ANALYSIS_STATE_QUEUED) self.property_view_factory.get_property_view( cycle=self.cycle, custom_id_1='long road', analysis_state=PropertyState.ANALYSIS_STATE_QUEUED) # Typically looks like this # http://localhost:8000/api/v2.1/properties/?organization_id=265&cycle=219&identifier=09-IS # check for all items query_params = "?cycle={}&organization_id={}".format( self.cycle.pk, self.org.pk) url = reverse('api:v2.1:properties-list') + query_params response = self.client.get(url) result = json.loads(response.content) self.assertEqual(result['status'], 'success') results = result['properties'] self.assertEqual(len(results), 5) # check for 2 items with 123 query_params = "?cycle={}&organization_id={}&identifier={}".format( self.cycle.pk, self.org.pk, '123') url = reverse('api:v2.1:properties-list') + query_params response = self.client.get(url) result = json.loads(response.content) self.assertEqual(result['status'], 'success') results = result['properties'] # print out the result of this when there are more than two in an attempt to catch the # non-deterministic part of this test if len(results) > 2: print(results) self.assertEqual(len(results), 2) # check the analysis states query_params = "?cycle={}&organization_id={}&analysis_state={}".format( self.cycle.pk, self.org.pk, 'Completed') url = reverse('api:v2.1:properties-list') + query_params response = self.client.get(url) result = json.loads(response.content) self.assertEqual(result['status'], 'success') results = result['properties'] self.assertEqual(len(results), 0) query_params = "?cycle={}&organization_id={}&analysis_state={}".format( self.cycle.pk, self.org.pk, 'Not Started') url = reverse('api:v2.1:properties-list') + query_params response = self.client.get(url) result = json.loads(response.content) self.assertEqual(result['status'], 'success') results = result['properties'] self.assertEqual(len(results), 3) query_params = "?cycle={}&organization_id={}&analysis_state={}".format( self.cycle.pk, self.org.pk, 'Queued') url = reverse('api:v2.1:properties-list') + query_params response = self.client.get(url) result = json.loads(response.content) self.assertEqual(result['status'], 'success') results = result['properties'] self.assertEqual(len(results), 2) # check the combination of both the identifier and the analysis state query_params = "?cycle={}&organization_id={}&identifier={}&analysis_state={}".format( self.cycle.pk, self.org.pk, 'Long', 'Queued') url = reverse('api:v2.1:properties-list') + query_params response = self.client.get(url) result = json.loads(response.content) self.assertEqual(result['status'], 'success') results = result['properties'] self.assertEqual(len(results), 1) def test_meters_exist(self): # Create a property set with meters state_1 = self.property_state_factory.get_property_state() property_1 = self.property_factory.get_property() PropertyView.objects.create(property=property_1, cycle=self.cycle, state=state_1) import_record = ImportRecord.objects.create( owner=self.user, last_modified_by=self.user, super_organization=self.org) filename = "example-GreenButton-data.xml" filepath = os.path.dirname( os.path.abspath(__file__)) + "/data/" + filename import_file = ImportFile.objects.create( import_record=import_record, source_type="GreenButton", uploaded_filename=filename, file=SimpleUploadedFile(name=filename, content=open(filepath, 'rb').read()), cycle=self.cycle, matching_results_data={ "property_id": property_1.id } # this is how target property is specified ) gb_import_url = reverse("api:v2:import_files-save-raw-data", args=[import_file.id]) gb_import_post_params = { 'cycle_id': self.cycle.pk, 'organization_id': self.org.pk, } self.client.post(gb_import_url, gb_import_post_params) # Create a property set without meters state_2 = self.property_state_factory.get_property_state() property_2 = self.property_factory.get_property() PropertyView.objects.create(property=property_2, cycle=self.cycle, state=state_2) url = reverse('api:v2:properties-meters-exist') true_post_params = json.dumps( {'inventory_ids': [property_2.pk, property_1.pk]}) true_result = self.client.post(url, true_post_params, content_type='application/json') self.assertEqual(b'true', true_result.content) false_post_params = json.dumps({'inventory_ids': [property_2.pk]}) false_result = self.client.post(url, false_post_params, content_type='application/json') self.assertEqual(b'false', false_result.content)