예제 #1
0
class WktTests(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)

    def test_long_lat_wkt_takes_a_state_and_returns_the_WKT_string_or_None(self):
        property_details = self.property_state_factory.get_details()
        property_details['organization_id'] = self.org.id

        no_long_lat_property = PropertyState(**property_details)
        no_long_lat_property.save()

        property_details['long_lat'] = 'POINT (-104.985765 39.764984)'

        geocoded_property = PropertyState(**property_details)
        geocoded_property.save()

        no_long_lat_record = PropertyState.objects.get(pk=no_long_lat_property.id)
        geocoded_record = PropertyState.objects.get(pk=geocoded_property.id)

        self.assertIsNone(no_long_lat_record.long_lat)
        self.assertIsNone(long_lat_wkt(no_long_lat_record))

        self.assertIsInstance(geocoded_record.long_lat, Point)
        self.assertEqual('POINT (-104.985765 39.764984)', long_lat_wkt(geocoded_property))

    def test_bounding_box_wkt_takes_a_state_and_returns_the_WKT_string_or_None(self):
        property_details = self.property_state_factory.get_details()
        property_details['organization_id'] = self.org.id

        no_bounding_box_property = PropertyState(**property_details)
        no_bounding_box_property.save()

        property_details['bounding_box'] = 'POLYGON ((0 0, 4 0, 4 4, 0 4, 0 0))'

        bounding_box_property = PropertyState(**property_details)
        bounding_box_property.save()

        no_bounding_box_record = PropertyState.objects.get(pk=no_bounding_box_property.id)
        geocoded_record = PropertyState.objects.get(pk=bounding_box_property.id)

        self.assertIsNone(no_bounding_box_record.bounding_box)
        self.assertIsNone(bounding_box_wkt(no_bounding_box_record))

        self.assertIsInstance(geocoded_record.bounding_box, Polygon)
        self.assertEqual('POLYGON ((0 0, 4 0, 4 4, 0 4, 0 0))', bounding_box_wkt(bounding_box_property))
예제 #2
0
class UbidSpecificWktMethods(TestCase):
    def setUp(self):
        user_details = {
            'username': '******',
            'password': '******',
        }
        self.user = User.objects.create_superuser(email='*****@*****.**',
                                                  **user_details)
        self.org, _, _ = create_organization(self.user)
        self.org.save()

        self.property_state_factory = FakePropertyStateFactory(
            organization=self.org)

    def test_centroid_wkt_takes_a_state_and_returns_the_WKT_string_or_None(
            self):
        property_details = self.property_state_factory.get_details()
        property_details['organization_id'] = self.org.id

        no_centroid_property = PropertyState(**property_details)
        no_centroid_property.save()

        property_details['centroid'] = 'POLYGON ((0 0, 4 0, 4 4, 0 4, 0 0))'

        centroid_property = PropertyState(**property_details)
        centroid_property.save()

        no_centroid_record = PropertyState.objects.get(
            pk=no_centroid_property.id)
        geocoded_record = PropertyState.objects.get(pk=centroid_property.id)

        self.assertIsNone(no_centroid_record.centroid)
        self.assertIsNone(centroid_wkt(no_centroid_record))

        self.assertIsInstance(geocoded_record.centroid, Polygon)
        self.assertEqual('POLYGON ((0 0, 4 0, 4 4, 0 4, 0 0))',
                         centroid_wkt(centroid_property))
예제 #3
0
class UbidViewTests(TestCase):
    def setUp(self):
        user_details = {
            'username': '******',
            'password': '******',
        }
        self.user = User.objects.create_superuser(email='*****@*****.**',
                                                  **user_details)
        self.org, _, _ = create_organization(self.user)
        self.client.login(**user_details)

        self.property_state_factory = FakePropertyStateFactory(
            organization=self.org)
        self.taxlot_state_factory = FakeTaxLotStateFactory(
            organization=self.org)
        self.property_view_factory = FakePropertyViewFactory(
            organization=self.org)
        self.taxlot_view_factory = FakeTaxLotViewFactory(organization=self.org)

    def test_ubid_decode_by_id_endpoint_base(self):
        property_details = self.property_state_factory.get_details()
        property_details['organization_id'] = self.org.id
        property_details['ubid'] = '86HJPCWQ+2VV-1-3-2-3'

        property = PropertyState(**property_details)
        property.save()

        property_view = self.property_view_factory.get_property_view(
            state=property)

        url = reverse(
            'api:v3:ubid-decode-by-ids') + '?organization_id=%s' % self.org.pk
        post_params = {'property_view_ids': [property_view.id]}

        self.client.post(url, post_params)

        refreshed_property = PropertyState.objects.get(pk=property.id)

        property_bounding_box_wkt = (
            "POLYGON ((-87.56021875000002 41.74504999999999, "
            "-87.56021875000002 41.74514999999997, "
            "-87.56043749999996 41.74514999999997, "
            "-87.56043749999996 41.74504999999999, "
            "-87.56021875000002 41.74504999999999))")
        property_centroid_wkt = (
            "POLYGON ((-87.56031249999999 41.74509999999998, "
            "-87.56031249999999 41.74512499999997, "
            "-87.56034374999999 41.74512499999997, "
            "-87.56034374999999 41.74509999999998, "
            "-87.56031249999999 41.74509999999998))")
        self.assertEqual(property_bounding_box_wkt,
                         bounding_box_wkt(refreshed_property))
        self.assertEqual(property_centroid_wkt,
                         centroid_wkt(refreshed_property))

    def test_decode_ubid_results_returns_a_summary_dictionary(self):
        property_none_details = self.property_state_factory.get_details()
        property_none_details["organization_id"] = self.org.id
        property_none = PropertyState(**property_none_details)
        property_none.save()

        property_none_view = self.property_view_factory.get_property_view(
            state=property_none)

        property_correctly_populated_details = self.property_state_factory.get_details(
        )
        property_correctly_populated_details["organization_id"] = self.org.id
        property_correctly_populated_details['ubid'] = '86HJPCWQ+2VV-1-3-2-3'
        property_correctly_populated_details['bounding_box'] = (
            "POLYGON ((-87.56021875000002 41.74504999999999, "
            "-87.56021875000002 41.74514999999997, "
            "-87.56043749999996 41.74514999999997, "
            "-87.56043749999996 41.74504999999999, "
            "-87.56021875000002 41.74504999999999))")
        property_correctly_populated_details['centroid'] = (
            "POLYGON ((-87.56031249999999 41.74509999999998, "
            "-87.56031249999999 41.74512499999997, "
            "-87.56034374999999 41.74512499999997, "
            "-87.56034374999999 41.74509999999998, "
            "-87.56031249999999 41.74509999999998))")
        property_correctly_populated = PropertyState(
            **property_correctly_populated_details)
        property_correctly_populated.save()

        property_correctly_populated_view = self.property_view_factory.get_property_view(
            state=property_correctly_populated)

        property_not_decoded_details = self.property_state_factory.get_details(
        )
        property_not_decoded_details["organization_id"] = self.org.id
        property_not_decoded_details['ubid'] = '86HJPCWQ+2VV-1-3-2-3'
        # bounding_box could be populated from a GeoJSON import
        property_not_decoded_details['bounding_box'] = (
            "POLYGON ((-87.56021875000002 41.74504999999999, "
            "-87.56021875000002 41.74514999999997, "
            "-87.56043749999996 41.74514999999997, "
            "-87.56043749999996 41.74504999999999, "
            "-87.56021875000002 41.74504999999999))")
        property_not_decoded = PropertyState(**property_not_decoded_details)
        property_not_decoded.save()

        property_not_decoded_view = self.property_view_factory.get_property_view(
            state=property_not_decoded)

        url = reverse(
            'api:v3:ubid-decode-results') + '?organization_id=%s' % self.org.pk
        post_params = {
            'property_view_ids': [
                property_none_view.id, property_correctly_populated_view.id,
                property_not_decoded_view.id
            ]
        }

        result = self.client.post(url, post_params)
        result_dict = ast.literal_eval(result.content.decode("utf-8"))

        expectation = {
            "ubid_unpopulated": 1,
            "ubid_successfully_decoded": 1,
            "ubid_not_decoded": 1,
            "ulid_unpopulated": 0,
            "ulid_successfully_decoded": 0,
            "ulid_not_decoded": 0
        }

        self.assertEqual(result_dict, expectation)

    def test_decode_ulid_results_returns_a_summary_dictionary(self):
        taxlot_none_details = self.taxlot_state_factory.get_details()
        taxlot_none_details["organization_id"] = self.org.id
        taxlot_none = TaxLotState(**taxlot_none_details)
        taxlot_none.save()

        taxlot_none_view = self.taxlot_view_factory.get_taxlot_view(
            state=taxlot_none)

        taxlot_correctly_populated_details = self.taxlot_state_factory.get_details(
        )
        taxlot_correctly_populated_details["organization_id"] = self.org.id
        taxlot_correctly_populated_details['ulid'] = '86HJPCWQ+2VV-1-3-2-3'
        taxlot_correctly_populated_details['bounding_box'] = (
            "POLYGON ((-87.56021875000002 41.74504999999999, "
            "-87.56021875000002 41.74514999999997, "
            "-87.56043749999996 41.74514999999997, "
            "-87.56043749999996 41.74504999999999, "
            "-87.56021875000002 41.74504999999999))")
        taxlot_correctly_populated_details['centroid'] = (
            "POLYGON ((-87.56031249999999 41.74509999999998, "
            "-87.56031249999999 41.74512499999997, "
            "-87.56034374999999 41.74512499999997, "
            "-87.56034374999999 41.74509999999998, "
            "-87.56031249999999 41.74509999999998))")
        taxlot_correctly_populated = TaxLotState(
            **taxlot_correctly_populated_details)
        taxlot_correctly_populated.save()

        taxlot_correctly_populated_view = self.taxlot_view_factory.get_taxlot_view(
            state=taxlot_correctly_populated)

        taxlot_not_decoded_details = self.taxlot_state_factory.get_details()
        taxlot_not_decoded_details["organization_id"] = self.org.id
        taxlot_not_decoded_details['ulid'] = '86HJPCWQ+2VV-1-3-2-3'
        # bounding_box could be populated from a GeoJSON import
        taxlot_not_decoded_details['bounding_box'] = (
            "POLYGON ((-87.56021875000002 41.74504999999999, "
            "-87.56021875000002 41.74514999999997, "
            "-87.56043749999996 41.74514999999997, "
            "-87.56043749999996 41.74504999999999, "
            "-87.56021875000002 41.74504999999999))")
        taxlot_not_decoded = TaxLotState(**taxlot_not_decoded_details)
        taxlot_not_decoded.save()

        taxlot_not_decoded_view = self.taxlot_view_factory.get_taxlot_view(
            state=taxlot_not_decoded)

        url = reverse(
            'api:v3:ubid-decode-results') + '?organization_id=%s' % self.org.pk
        post_params = {
            'taxlot_view_ids': [
                taxlot_none_view.id, taxlot_correctly_populated_view.id,
                taxlot_not_decoded_view.id
            ]
        }

        result = self.client.post(url, post_params)
        result_dict = ast.literal_eval(result.content.decode("utf-8"))

        expectation = {
            "ubid_unpopulated": 0,
            "ubid_successfully_decoded": 0,
            "ubid_not_decoded": 0,
            "ulid_unpopulated": 1,
            "ulid_successfully_decoded": 1,
            "ulid_not_decoded": 1
        }

        self.assertEqual(result_dict, expectation)
예제 #4
0
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)
예제 #5
0
class UbidUtilMethods(TestCase):
    def setUp(self):
        user_details = {
            'username': '******',
            'password': '******',
        }
        self.user = User.objects.create_superuser(email='*****@*****.**',
                                                  **user_details)
        self.org, _, _ = create_organization(self.user)
        self.org.save()

        self.property_state_factory = FakePropertyStateFactory(
            organization=self.org)
        self.taxlot_state_factory = FakeTaxLotStateFactory(
            organization=self.org)

    def test_decode_ubids_is_successful_when_valid_UBID_provided(self):
        property_details = self.property_state_factory.get_details()
        property_details['organization_id'] = self.org.id
        property_details['ubid'] = '86HJPCWQ+2VV-1-3-2-3'

        property = PropertyState(**property_details)
        property.save()
        properties = PropertyState.objects.filter(pk=property.id)

        decode_unique_ids(properties)
        refreshed_property = PropertyState.objects.get(pk=property.id)
        property_bounding_box_wkt = (
            "POLYGON ((-87.56021875000002 41.74504999999999, "
            "-87.56021875000002 41.74514999999997, "
            "-87.56043749999996 41.74514999999997, "
            "-87.56043749999996 41.74504999999999, "
            "-87.56021875000002 41.74504999999999))")
        property_centroid_wkt = (
            "POLYGON ((-87.56031249999999 41.74509999999998, "
            "-87.56031249999999 41.74512499999997, "
            "-87.56034374999999 41.74512499999997, "
            "-87.56034374999999 41.74509999999998, "
            "-87.56031249999999 41.74509999999998))")
        self.assertEqual(property_bounding_box_wkt,
                         bounding_box_wkt(refreshed_property))
        self.assertEqual(property_centroid_wkt,
                         centroid_wkt(refreshed_property))
        self.assertEqual(refreshed_property.latitude, 41.7451)
        self.assertEqual(refreshed_property.longitude, -87.560328125)

    def test_decode_ulids_is_successful_when_valid_ULID_provided(self):
        taxlot_details = self.taxlot_state_factory.get_details()
        taxlot_details['organization_id'] = self.org.id
        taxlot_details['ulid'] = '86HJPCWQ+2VV-1-3-2-3'

        taxlot = TaxLotState(**taxlot_details)
        taxlot.save()
        taxlots = TaxLotState.objects.filter(pk=taxlot.id)

        decode_unique_ids(taxlots)
        refreshed_taxlot = TaxLotState.objects.get(pk=taxlot.id)
        taxlot_bounding_box_wkt = (
            "POLYGON ((-87.56021875000002 41.74504999999999, "
            "-87.56021875000002 41.74514999999997, "
            "-87.56043749999996 41.74514999999997, "
            "-87.56043749999996 41.74504999999999, "
            "-87.56021875000002 41.74504999999999))")
        taxlot_centroid_wkt = (
            "POLYGON ((-87.56031249999999 41.74509999999998, "
            "-87.56031249999999 41.74512499999997, "
            "-87.56034374999999 41.74512499999997, "
            "-87.56034374999999 41.74509999999998, "
            "-87.56031249999999 41.74509999999998))")
        self.assertEqual(taxlot_bounding_box_wkt,
                         bounding_box_wkt(refreshed_taxlot))
        self.assertEqual(taxlot_centroid_wkt, centroid_wkt(refreshed_taxlot))
        self.assertEqual(refreshed_taxlot.latitude, 41.7451)
        self.assertEqual(refreshed_taxlot.longitude, -87.560328125)

    def test_decode_ubids_does_nothing_if_no_UBID_provided(self):
        property_details = self.property_state_factory.get_details()
        property_details['organization_id'] = self.org.id

        property = PropertyState(**property_details)
        property.save()
        properties = PropertyState.objects.filter(pk=property.id)

        decode_unique_ids(properties)
        refreshed_property = PropertyState.objects.get(pk=property.id)

        self.assertIsNone(bounding_box_wkt(refreshed_property))
        self.assertIsNone(centroid_wkt(refreshed_property))

    def test_decode_ulids_does_nothing_if_no_ULID_provided(self):
        taxlot_details = self.taxlot_state_factory.get_details()
        taxlot_details['organization_id'] = self.org.id

        taxlot = TaxLotState(**taxlot_details)
        taxlot.save()
        taxlots = PropertyState.objects.filter(pk=taxlot.id)

        decode_unique_ids(taxlots)
        refreshed_taxlot = TaxLotState.objects.get(pk=taxlot.id)

        self.assertIsNone(bounding_box_wkt(refreshed_taxlot))
        self.assertIsNone(centroid_wkt(refreshed_taxlot))

    def test_decode_ubids_doesnt_throw_an_error_if_an_invalid_ubid_is_provided(
            self):
        property_details = self.property_state_factory.get_details()
        property_details['organization_id'] = self.org.id
        property_details['ubid'] = 'invalidubid'

        property = PropertyState(**property_details)
        property.save()
        properties = PropertyState.objects.filter(pk=property.id)

        decode_unique_ids(properties)

        refreshed_property = PropertyState.objects.get(pk=property.id)

        self.assertIsNone(bounding_box_wkt(refreshed_property))
        self.assertIsNone(centroid_wkt(refreshed_property))

    def test_decode_ulids_doesnt_throw_an_error_if_an_invalid_ulid_is_provided(
            self):
        taxlot_details = self.taxlot_state_factory.get_details()
        taxlot_details['organization_id'] = self.org.id
        taxlot_details['ulid'] = 'invalidulid'

        taxlot = TaxLotState(**taxlot_details)
        taxlot.save()
        taxlots = TaxLotState.objects.filter(pk=taxlot.id)

        decode_unique_ids(taxlots)

        refreshed_taxlot = TaxLotState.objects.get(pk=taxlot.id)

        self.assertIsNone(bounding_box_wkt(refreshed_taxlot))
        self.assertIsNone(centroid_wkt(refreshed_taxlot))
예제 #6
0
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)
예제 #7
0
class GeocodeViewTests(TestCase):
    def setUp(self):
        user_details = {
            'username': '******',
            'password': '******',
        }
        self.user = User.objects.create_superuser(email='*****@*****.**',
                                                  **user_details)
        self.org, _, _ = create_organization(self.user)
        self.client.login(**user_details)

        self.property_state_factory = FakePropertyStateFactory(
            organization=self.org)
        self.tax_lot_state_factory = FakeTaxLotStateFactory(
            organization=self.org)

    def test_geocode_endpoint_base_with_prepopulated_lat_long_no_api_request(
            self):
        property_details = self.property_state_factory.get_details()
        property_details['organization_id'] = self.org.id
        property_details['latitude'] = 39.765251
        property_details['longitude'] = -104.986138

        property = PropertyState(**property_details)
        property.save()

        url = reverse('api:v2:geocode-geocode-by-ids')
        post_params = {
            'organization_id': self.org.pk,
            'property_ids': [property.id],
            'taxlot_ids': []
        }

        self.client.post(url, post_params)

        refreshed_property = PropertyState.objects.get(pk=property.id)

        self.assertEqual('POINT (-104.986138 39.765251)',
                         long_lat_wkt(refreshed_property))

    def test_geocode_confidence_summary_returns_summary_dictionary(self):
        property_none_details = self.property_state_factory.get_details()
        property_none_details["organization_id"] = self.org.id
        property_none = PropertyState(**property_none_details)
        property_none.save()

        property_high_details = self.property_state_factory.get_details()
        property_high_details["organization_id"] = self.org.id
        property_high_details["geocoding_confidence"] = "High (P1AAA)"
        property_high = PropertyState(**property_high_details)
        property_high.save()

        property_low_details = self.property_state_factory.get_details()
        property_low_details["organization_id"] = self.org.id
        property_low_details["geocoding_confidence"] = "Low (P1CCC)"
        property_low = PropertyState(**property_low_details)
        property_low.save()

        property_manual_details = self.property_state_factory.get_details()
        property_manual_details["organization_id"] = self.org.id
        property_manual_details[
            "geocoding_confidence"] = "Manually geocoded (N/A)"
        property_manual = PropertyState(**property_manual_details)
        property_manual.save()

        property_missing_details = self.property_state_factory.get_details()
        property_missing_details["organization_id"] = self.org.id
        property_missing_details[
            "geocoding_confidence"] = "Missing address components (N/A)"
        property_missing = PropertyState(**property_missing_details)
        property_missing.save()

        tax_lot_none_details = self.tax_lot_state_factory.get_details()
        tax_lot_none_details["organization_id"] = self.org.id
        tax_lot_none = TaxLotState(**tax_lot_none_details)
        tax_lot_none.save()

        tax_lot_high_details = self.tax_lot_state_factory.get_details()
        tax_lot_high_details["organization_id"] = self.org.id
        tax_lot_high_details["geocoding_confidence"] = "High (P1AAA)"
        tax_lot_high = TaxLotState(**tax_lot_high_details)
        tax_lot_high.save()

        tax_lot_low_details = self.tax_lot_state_factory.get_details()
        tax_lot_low_details["organization_id"] = self.org.id
        tax_lot_low_details["geocoding_confidence"] = "Low (P1CCC)"
        tax_lot_low = TaxLotState(**tax_lot_low_details)
        tax_lot_low.save()

        tax_lot_missing_details = self.tax_lot_state_factory.get_details()
        tax_lot_missing_details["organization_id"] = self.org.id
        tax_lot_missing_details[
            "geocoding_confidence"] = "Missing address components (N/A)"
        tax_lot_missing = TaxLotState(**tax_lot_missing_details)
        tax_lot_missing.save()

        url = reverse('api:v2:geocode-confidence-summary')
        post_params = {
            'organization_id':
            self.org.pk,
            'property_ids': [
                property_none.id, property_high.id, property_low.id,
                property_manual.id, property_missing.id
            ],
            'tax_lot_ids': [
                tax_lot_none.id, tax_lot_high.id, tax_lot_low.id,
                tax_lot_missing.id
            ]
        }

        result = self.client.post(url, post_params)
        result_dict = ast.literal_eval(result.content.decode("utf-8"))

        expectation = {
            "properties": {
                "not_geocoded": 1,
                "high_confidence": 1,
                "low_confidence": 1,
                "manual": 1,
                "missing_address_components": 1
            },
            "tax_lots": {
                "not_geocoded": 1,
                "high_confidence": 1,
                "low_confidence": 1,
                "missing_address_components": 1
            }
        }

        self.assertEqual(result_dict, expectation)

    def test_api_key_endpoint_returns_true_or_false_if_org_has_api_key(self):
        url = reverse('api:v2:geocode-api-key-exists')

        post_params_false = {'organization_id': self.org.pk}
        false_result = self.client.get(url, post_params_false)

        self.assertEqual(b'false', false_result.content)

        org_with_key, _, _ = create_organization(self.user)
        org_with_key.mapquest_api_key = "somekey"
        org_with_key.save()
        post_params_true = {'organization_id': org_with_key.id}
        true_result = self.client.get(url, post_params_true)

        self.assertEqual(b'true', true_result.content)
예제 #8
0
class GeocodeAddresses(TestCase):
    def setUp(self):
        user_details = {
            'username': '******',
            'password': '******',
        }
        self.user = User.objects.create_superuser(
            email='*****@*****.**', **user_details
        )
        self.org, _, _ = create_organization(self.user)
        self.org.mapquest_api_key = test_key
        self.org.save()

        self.property_state_factory = FakePropertyStateFactory(organization=self.org)
        self.tax_lot_state_factory = FakeTaxLotStateFactory(organization=self.org)

    def test_geocode_buildings_successful_when_real_fields_provided(self):
        with base_vcr.use_cassette('seed/tests/data/vcr_cassettes/geocode_base_case.yaml'):
            property_details = self.property_state_factory.get_details()
            property_details['organization_id'] = self.org.id
            property_details['address_line_1'] = "3001 Brighton Blvd"
            property_details['address_line_2'] = "suite 2693"
            property_details['city'] = "Denver"
            property_details['state'] = "Colorado"
            property_details['postal_code'] = "80216"

            property = PropertyState(**property_details)
            property.save()
            properties = PropertyState.objects.filter(pk=property.id)

            tax_lot_details = self.tax_lot_state_factory.get_details()
            tax_lot_details['organization_id'] = self.org.id
            tax_lot_details['address_line_1'] = "2020 Lawrence St"
            tax_lot_details['address_line_2'] = "unit A"
            tax_lot_details['city'] = "Denver"
            tax_lot_details['state'] = "Colorado"
            tax_lot_details['postal_code'] = "80205"

            tax_lot = TaxLotState(**tax_lot_details)
            tax_lot.save()
            tax_lots = TaxLotState.objects.filter(pk=tax_lot.id)

            geocode_buildings(properties)
            geocode_buildings(tax_lots)

            refreshed_property = PropertyState.objects.get(pk=property.id)
            refreshed_tax_lot = TaxLotState.objects.get(pk=tax_lot.id)

            self.assertEqual('POINT (-104.986138 39.765251)', long_lat_wkt(refreshed_property))
            self.assertEqual('High (P1AAA)', refreshed_property.geocoding_confidence)
            self.assertEqual(-104.986138, refreshed_property.longitude)
            self.assertEqual(39.765251, refreshed_property.latitude)

            self.assertEqual('POINT (-104.991046 39.752396)', long_lat_wkt(refreshed_tax_lot))
            self.assertEqual('High (P1AAA)', refreshed_tax_lot.geocoding_confidence)

    def test_geocode_buildings_returns_no_data_when_provided_address_is_ambigious(self):
        with base_vcr.use_cassette('seed/tests/data/vcr_cassettes/geocode_low_geocodequality.yaml'):
            state_zip_only_details = self.property_state_factory.get_details()
            state_zip_only_details['organization_id'] = self.org.id
            state_zip_only_details['address_line_1'] = ""
            state_zip_only_details['address_line_2'] = ""
            state_zip_only_details['city'] = ""
            state_zip_only_details['state'] = "Colorado"
            state_zip_only_details['postal_code'] = "80202"

            state_zip_only_property = PropertyState(**state_zip_only_details)
            state_zip_only_property.save()

            wrong_state_zip_details = self.property_state_factory.get_details()
            wrong_state_zip_details['organization_id'] = self.org.id
            wrong_state_zip_details['address_line_1'] = "3001 Brighton Blvd"
            wrong_state_zip_details['address_line_2'] = "suite 2693"
            wrong_state_zip_details['city'] = "Denver"
            wrong_state_zip_details['state'] = "New Jersey"
            wrong_state_zip_details['postal_code'] = "08081"

            wrong_state_zip_property = PropertyState(**wrong_state_zip_details)
            wrong_state_zip_property.save()

            ids = [state_zip_only_property.id, wrong_state_zip_property.id]

            properties = PropertyState.objects.filter(id__in=ids)

            geocode_buildings(properties)

            state_zip_only_property = PropertyState.objects.get(pk=state_zip_only_property.id)
            wrong_state_zip_property = PropertyState.objects.get(pk=wrong_state_zip_property.id)

            self.assertIsNone(state_zip_only_property.long_lat)
            self.assertIsNone(state_zip_only_property.longitude)
            self.assertIsNone(state_zip_only_property.latitude)
            self.assertEqual("Missing address components (N/A)", state_zip_only_property.geocoding_confidence)

            self.assertIsNone(wrong_state_zip_property.long_lat)
            self.assertIsNone(wrong_state_zip_property.longitude)
            self.assertIsNone(wrong_state_zip_property.latitude)
            self.assertEqual("Low - check address (B3BCA)", wrong_state_zip_property.geocoding_confidence)

    def test_geocode_buildings_is_successful_even_if_two_buildings_have_same_address(self):
        with base_vcr.use_cassette('seed/tests/data/vcr_cassettes/geocode_dup_addresses.yaml'):
            property_details = self.property_state_factory.get_details()
            property_details['organization_id'] = self.org.id
            property_details['address_line_1'] = "3001 Brighton Blvd"
            property_details['address_line_2'] = "suite 2693"
            property_details['city'] = "Denver"
            property_details['state'] = "Colorado"
            property_details['postal_code'] = "80216"

            property_1 = PropertyState(**property_details)
            property_2 = PropertyState(**property_details)
            property_1.save()
            property_2.save()

            ids = [property_1.id, property_2.id]

            properties = PropertyState.objects.filter(id__in=ids)

            geocode_buildings(properties)

            refreshed_properties = PropertyState.objects.filter(id__in=ids)

            for property in refreshed_properties:
                self.assertEqual('POINT (-104.986138 39.765251)', long_lat_wkt(property))
                self.assertEqual('High (P1AAA)', property.geocoding_confidence)
                self.assertEqual(-104.986138, property.longitude)
                self.assertEqual(39.765251, property.latitude)

    def test_geocode_buildings_is_successful_with_over_100_properties(self):
        with batch_vcr.use_cassette('seed/tests/data/vcr_cassettes/geocode_101_unique_addresses.yaml', match_on=['uri_length']):
            property_details = self.property_state_factory.get_details()
            property_details['organization_id'] = self.org.id
            property_details['address_line_2'] = ""
            property_details['city'] = "Denver"
            property_details['state'] = "Colorado"
            property_details['postal_code'] = "80202"

            ids = []
            for n in range(101):
                street_number = n + 1600
                property_details['address_line_1'] = str(street_number) + " Larimer Street"

                property = PropertyState(**property_details)
                property.save()
                ids.append(property.id)

            properties = PropertyState.objects.filter(id__in=ids).order_by('id')

            geocode_buildings(properties)

            refreshed_properties = PropertyState.objects.filter(id__in=ids).order_by('id')

            long_lats = [
                property.long_lat
                for property
                in refreshed_properties
                if property.long_lat is not None
            ]
            geocode_confidence_results = [
                property.geocoding_confidence
                for property
                in refreshed_properties
                if property.geocoding_confidence is not None
            ]

            self.assertTrue(len(long_lats) > 0)
            self.assertTrue(len(geocode_confidence_results) == 101)

    def test_geocode_buildings_is_unsuccessful_when_the_API_key_is_invalid_or_expired(self):
        with base_vcr.use_cassette('seed/tests/data/vcr_cassettes/geocode_invalid_or_expired_key.yaml'):
            self.org_fake_key, _, _ = create_organization(self.user)
            self.org_fake_key.mapquest_api_key = 'fakeapikey'
            self.org_fake_key.save()

            self.property_state_factory_fake_key = FakePropertyStateFactory(organization=self.org_fake_key)

            property_details_fake_key = self.property_state_factory_fake_key.get_details()
            property_details_fake_key['organization_id'] = self.org_fake_key.id
            property_details_fake_key['address_line_1'] = "3001 Brighton Blvd"
            property_details_fake_key['address_line_2'] = "suite 2693"
            property_details_fake_key['city'] = "Denver"
            property_details_fake_key['state'] = "Colorado"
            property_details_fake_key['postal_code'] = "80216"

            property = PropertyState(**property_details_fake_key)
            property.save()

            properties = PropertyState.objects.filter(pk=property.id)

            with self.assertRaises(MapQuestAPIKeyError):
                geocode_buildings(properties)

    def test_geocode_buildings_doesnt_run_an_api_request_when_an_API_key_is_not_provided(self):
        self.org_no_key, _, _ = create_organization(self.user)
        self.property_state_factory_no_key = FakePropertyStateFactory(organization=self.org_no_key)
        property_details_no_key = self.property_state_factory_no_key.get_details()

        property_details_no_key['organization_id'] = self.org_no_key.id
        property_details_no_key['address_line_1'] = "3001 Brighton Blvd"
        property_details_no_key['address_line_2'] = "suite 2693"
        property_details_no_key['city'] = "Denver"
        property_details_no_key['state'] = "Colorado"
        property_details_no_key['postal_code'] = "80216"

        property = PropertyState(**property_details_no_key)
        property.save()

        properties = PropertyState.objects.filter(pk=property.id)

        self.assertIsNone(geocode_buildings(properties))

        refreshed_property = PropertyState.objects.get(pk=property.id)

        self.assertIsNone(refreshed_property.long_lat)
        self.assertIsNone(refreshed_property.geocoding_confidence)

    def test_geocode_address_can_handle_addresses_with_reserved_and_unsafe_characters(self):
        with base_vcr.use_cassette('seed/tests/data/vcr_cassettes/geocode_reserved_and_unsafe_characters.yaml'):
            property_details = self.property_state_factory.get_details()
            property_details['organization_id'] = self.org.id
            property_details['address_line_1'] = r'3001 Brighton Blvd;/?:@=&<>#%{}|"\^~[]`'
            property_details['address_line_2'] = "suite 2693"
            property_details['city'] = "Denver"
            property_details['state'] = "Colorado"
            property_details['postal_code'] = "80216"

            property = PropertyState(**property_details)
            property.save()

            properties = PropertyState.objects.filter(pk=property.id)

            geocode_buildings(properties)

            refreshed_properties = PropertyState.objects.filter(pk=property.id)

            self.assertEqual('POINT (-104.986138 39.765251)', long_lat_wkt(refreshed_properties[0]))

    def test_geocode_address_can_use_prepopulated_lat_and_long_fields(self):
        property_details = self.property_state_factory.get_details()
        property_details['organization_id'] = self.org.id
        property_details['latitude'] = 39.765251
        property_details['longitude'] = -104.986138

        property = PropertyState(**property_details)
        property.save()

        properties = PropertyState.objects.filter(pk=property.id)

        geocode_buildings(properties)

        refreshed_property = PropertyState.objects.get(pk=property.id)

        self.assertEqual('POINT (-104.986138 39.765251)', long_lat_wkt(refreshed_property))
        self.assertEqual("Manually geocoded (N/A)", refreshed_property.geocoding_confidence)

    def test_geocode_address_can_handle_receiving_no_buildings(self):
        self.assertIsNone(geocode_buildings(PropertyState.objects.none()))

    def test_geocoding_an_address_again_after_successful_geocode_executes_successfully(self):
        with base_vcr.use_cassette('seed/tests/data/vcr_cassettes/geocode_same_record_twice.yaml'):
            property_details = self.property_state_factory.get_details()
            property_details['organization_id'] = self.org.id
            property_details['address_line_1'] = "3001 Brighton Blvd"
            property_details['address_line_2'] = "suite 2693"
            property_details['city'] = "Denver"
            property_details['state'] = "Colorado"
            property_details['postal_code'] = "80216"
            property = PropertyState(**property_details)
            property.save()

            properties = PropertyState.objects.filter(pk=property.id)
            geocode_buildings(properties)

            refreshed_property = PropertyState.objects.get(pk=property.id)
            refreshed_property.address_line_1 = "2020 Lawrence St"
            refreshed_property.address_line_2 = "unit A"
            refreshed_property.postal_code = "80205"
            refreshed_property.save()

            refreshed_properties = PropertyState.objects.filter(pk=refreshed_property.id)
            geocode_buildings(refreshed_properties)

            refreshed_updated_property = PropertyState.objects.get(pk=refreshed_property.id)

            self.assertEqual('POINT (-104.991046 39.752396)', long_lat_wkt(refreshed_updated_property))
            self.assertEqual('High (P1AAA)', refreshed_updated_property.geocoding_confidence)
            self.assertEqual(-104.991046, refreshed_updated_property.longitude)
            self.assertEqual(39.752396, refreshed_updated_property.latitude)

    def test_geocoded_fields_are_changed_appropriately_if_a_user_manually_updates_latitude_or_longitude_of_ungeocoded_property(self):
        property_details = self.property_state_factory.get_details()
        property_details['organization_id'] = self.org.id
        property = PropertyState(**property_details)
        property.save()

        refreshed_property = PropertyState.objects.get(pk=property.id)
        self.assertIsNone(long_lat_wkt(refreshed_property))
        self.assertIsNone(refreshed_property.geocoding_confidence)

        refreshed_property.latitude = 39.765251
        refreshed_property.save()

        refreshed_property = PropertyState.objects.get(pk=property.id)
        self.assertEqual(39.765251, refreshed_property.latitude)
        self.assertIsNone(long_lat_wkt(refreshed_property))
        self.assertIsNone(refreshed_property.geocoding_confidence)

        refreshed_property.longitude = -104.986138
        refreshed_property.save()

        refreshed_property = PropertyState.objects.get(pk=property.id)
        self.assertEqual(-104.986138, refreshed_property.longitude)
        self.assertEqual('POINT (-104.986138 39.765251)', long_lat_wkt(refreshed_property))
        self.assertEqual("Manually geocoded (N/A)", refreshed_property.geocoding_confidence)

    def test_geocoded_fields_are_changed_appropriately_if_a_user_manually_updates_latitude_or_longitude_of_geocoded_property(self):
        property_details = self.property_state_factory.get_details()
        property_details['organization_id'] = self.org.id
        property_details['latitude'] = 39.765251
        property_details['longitude'] = -104.986138
        property_details['long_lat'] = 'POINT (-104.986138 39.765251)'
        property_details['geocoding_confidence'] = 'High (P1AAA)'
        property = PropertyState(**property_details)
        property.save()

        # Make sure geocoding_confidence isn't overriden to be Manual given latitude and longitude are updated
        refreshed_property = PropertyState.objects.get(pk=property.id)
        self.assertEqual('High (P1AAA)', refreshed_property.geocoding_confidence)

        # Try updating latitude
        refreshed_property.latitude = 39.81
        refreshed_property.save()

        refreshed_property = PropertyState.objects.get(pk=property.id)
        self.assertEqual(39.81, refreshed_property.latitude)
        self.assertEqual('POINT (-104.986138 39.81)', long_lat_wkt(refreshed_property))
        self.assertEqual("Manually geocoded (N/A)", refreshed_property.geocoding_confidence)

        # If latitude or longitude are not there long_lat and geocoding_confidence should be empty
        refreshed_property.latitude = None
        refreshed_property.save()

        self.assertIsNone(refreshed_property.latitude)
        self.assertIsNone(long_lat_wkt(refreshed_property))
        self.assertIsNone(refreshed_property.geocoding_confidence)
예제 #9
0
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)
예제 #10
0
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'])
예제 #11
0
class GeocodeAddresses(TestCase):
    def setUp(self):
        user_details = {
            'username': '******',
            'password': '******',
        }
        self.user = User.objects.create_superuser(email='*****@*****.**',
                                                  **user_details)
        self.org, _, _ = create_organization(self.user)
        self.org.mapquest_api_key = test_key
        self.org.save()

        self.property_state_factory = FakePropertyStateFactory(
            organization=self.org)
        self.tax_lot_state_factory = FakeTaxLotStateFactory(
            organization=self.org)

    def test_geocode_buildings_successful_when_real_fields_provided(self):
        with base_vcr.use_cassette(
                'seed/tests/data/vcr_cassettes/geocode_base_case.yaml'):
            property_details = self.property_state_factory.get_details()
            property_details['organization_id'] = self.org.id
            property_details['address_line_1'] = "3001 Brighton Blvd"
            property_details['address_line_2'] = "suite 2693"
            property_details['city'] = "Denver"
            property_details['state'] = "Colorado"
            property_details['postal_code'] = "80216"

            property = PropertyState(**property_details)
            property.save()
            properties = PropertyState.objects.filter(pk=property.id)

            tax_lot_details = self.tax_lot_state_factory.get_details()
            tax_lot_details['organization_id'] = self.org.id
            tax_lot_details['address_line_1'] = "2020 Lawrence St"
            tax_lot_details['address_line_2'] = "unit A"
            tax_lot_details['city'] = "Denver"
            tax_lot_details['state'] = "Colorado"
            tax_lot_details['postal_code'] = "80205"

            tax_lot = TaxLotState(**tax_lot_details)
            tax_lot.save()
            tax_lots = TaxLotState.objects.filter(pk=tax_lot.id)

            geocode_buildings(properties)
            geocode_buildings(tax_lots)

            refreshed_property = PropertyState.objects.get(pk=property.id)
            refreshed_tax_lot = TaxLotState.objects.get(pk=tax_lot.id)

            self.assertEqual('POINT (-104.986138 39.765251)',
                             long_lat_wkt(refreshed_property))
            self.assertEqual('High (P1AAA)',
                             refreshed_property.geocoding_confidence)
            self.assertEqual(-104.986138, refreshed_property.longitude)
            self.assertEqual(39.765251, refreshed_property.latitude)

            self.assertEqual('POINT (-104.991046 39.752396)',
                             long_lat_wkt(refreshed_tax_lot))
            self.assertEqual('High (P1AAA)',
                             refreshed_tax_lot.geocoding_confidence)

    def test_geocode_properties_with_custom_fields(self):
        with base_vcr.use_cassette(
                'seed/tests/data/vcr_cassettes/geocode_property_custom_fields.yaml'
        ):
            property_details = self.property_state_factory.get_details()
            property_details['organization_id'] = self.org.id
            property_details['pm_parent_property_id'] = "3001 Brighton Blvd"
            property_details['pm_property_id'] = "suite 2693"
            property_details['property_name'] = None  # can handle empty DB col
            property_details['state'] = "Colorado"
            property_details['extra_data'] = {
                'ed_city': "Denver",
                'ed_zip': 80216,  # can handle numbers
                'ed_empty': None,  # can handle empty extra_data col
            }

            property = PropertyState(**property_details)
            property.save()
            properties = PropertyState.objects.filter(pk=property.id)

            # Activate and order geocoding columns
            self.org.column_set.filter(
                column_name='pm_parent_property_id',
                table_name="PropertyState").update(geocoding_order=1)
            self.org.column_set.filter(
                column_name='pm_property_id',
                table_name="PropertyState").update(geocoding_order=2)
            self.org.column_set.create(column_name='ed_city',
                                       is_extra_data=True,
                                       table_name='PropertyState',
                                       geocoding_order=3)
            self.org.column_set.filter(
                column_name='state',
                table_name="PropertyState").update(geocoding_order=4)
            self.org.column_set.create(column_name='ed_zip',
                                       is_extra_data=True,
                                       table_name='PropertyState',
                                       geocoding_order=5)
            self.org.column_set.create(column_name='ed_empty',
                                       is_extra_data=True,
                                       table_name='PropertyState',
                                       geocoding_order=6)
            self.org.column_set.filter(
                column_name='property_name',
                table_name="PropertyState").update(geocoding_order=7)

            # Deactivate default geocoding columns
            self.org.column_set.filter(
                column_name__in=[
                    'address_line_1', 'address_line_2', 'city', 'postal_code'
                ],
                table_name="PropertyState").update(geocoding_order=0)

            geocode_buildings(properties)

            refreshed_property = PropertyState.objects.get(pk=property.id)

            self.assertEqual('POINT (-104.986138 39.765251)',
                             long_lat_wkt(refreshed_property))
            self.assertEqual('High (P1AAA)',
                             refreshed_property.geocoding_confidence)
            self.assertEqual(-104.986138, refreshed_property.longitude)
            self.assertEqual(39.765251, refreshed_property.latitude)

    def test_geocode_taxlots_with_custom_fields(self):
        with base_vcr.use_cassette(
                'seed/tests/data/vcr_cassettes/geocode_taxlots_custom_fields.yaml'
        ):
            taxlot_details = self.tax_lot_state_factory.get_details()
            taxlot_details['organization_id'] = self.org.id
            taxlot_details['jurisdiction_tax_lot_id'] = "3001 Brighton Blvd"
            taxlot_details['block_number'] = "suite 2693"
            taxlot_details['custom_id_1'] = None  # can handle empty DB col
            taxlot_details['state'] = "Colorado"
            taxlot_details['extra_data'] = {
                'ed_city': "Denver",
                'ed_zip': 80216,  # can handle numbers
                'ed_empty': None,  # can handle empty extra_data col
            }

            taxlot = TaxLotState(**taxlot_details)
            taxlot.save()
            taxlots = TaxLotState.objects.filter(pk=taxlot.id)

            # Activate and order geocoding columns
            self.org.column_set.filter(
                column_name='jurisdiction_tax_lot_id',
                table_name="TaxLotState").update(geocoding_order=1)
            self.org.column_set.filter(
                column_name='block_number',
                table_name="TaxLotState").update(geocoding_order=2)
            self.org.column_set.create(column_name='ed_city',
                                       is_extra_data=True,
                                       table_name='TaxLotState',
                                       geocoding_order=3)
            self.org.column_set.filter(
                column_name='state',
                table_name="TaxLotState").update(geocoding_order=4)
            self.org.column_set.create(column_name='ed_zip',
                                       is_extra_data=True,
                                       table_name='TaxLotState',
                                       geocoding_order=5)
            self.org.column_set.create(column_name='ed_empty',
                                       is_extra_data=True,
                                       table_name='TaxLotState',
                                       geocoding_order=6)
            self.org.column_set.filter(
                column_name='custom_id_1',
                table_name="TaxLotState").update(geocoding_order=7)

            # Deactivate default geocoding columns
            self.org.column_set.filter(
                column_name__in=[
                    'address_line_1', 'address_line_2', 'city', 'postal_code'
                ],
                table_name="TaxLotState").update(geocoding_order=0)

            geocode_buildings(taxlots)

            refreshed_taxlot = TaxLotState.objects.get(pk=taxlot.id)

            self.assertEqual('POINT (-104.986138 39.765251)',
                             long_lat_wkt(refreshed_taxlot))
            self.assertEqual('High (P1AAA)',
                             refreshed_taxlot.geocoding_confidence)
            self.assertEqual(-104.986138, refreshed_taxlot.longitude)
            self.assertEqual(39.765251, refreshed_taxlot.latitude)

    def test_not_enough_geocoding_fields_for_org_leads_to_no_geocoding(self):
        property_details = self.property_state_factory.get_details()
        property_details['organization_id'] = self.org.id
        property_details['address_line_1'] = "3001 Brighton Blvd"
        property_details['address_line_2'] = "suite 2693"
        property_details['city'] = "Denver"
        property_details['state'] = "Colorado"
        property_details['postal_code'] = "80216"

        # Deactivate all PropertyState geocoding columns
        self.org.column_set.filter(table_name="PropertyState").update(
            geocoding_order=0)

        property = PropertyState(**property_details)
        property.save()
        properties = PropertyState.objects.filter(pk=property.id)

        self.assertIsNone(geocode_buildings(properties))

        refreshed_property = PropertyState.objects.get(pk=property.id)

        self.assertIsNone(refreshed_property.long_lat)
        self.assertIsNone(refreshed_property.geocoding_confidence)

    def test_not_enough_geocoding_fields_for_record_leads_to_no_geocoding(
            self):
        property_details = self.property_state_factory.get_details()
        property_details['organization_id'] = self.org.id
        property_details['address_line_1'] = ""
        property_details['address_line_2'] = ""
        property_details['city'] = ""
        property_details['state'] = ""
        property_details['postal_code'] = ""

        property = PropertyState(**property_details)
        property.save()
        properties = PropertyState.objects.filter(pk=property.id)

        self.assertIsNone(geocode_buildings(properties))

        refreshed_property = PropertyState.objects.get(pk=property.id)

        self.assertIsNone(refreshed_property.long_lat)
        self.assertEqual(refreshed_property.geocoding_confidence,
                         "Missing address components (N/A)")

    def test_geocode_buildings_returns_no_data_when_provided_address_is_ambigious(
            self):
        with base_vcr.use_cassette(
                'seed/tests/data/vcr_cassettes/geocode_low_geocodequality.yaml'
        ):
            # 1st Property
            state_zip_only_details = self.property_state_factory.get_details()
            state_zip_only_details['organization_id'] = self.org.id
            state_zip_only_details['address_line_1'] = ""
            state_zip_only_details['address_line_2'] = ""
            state_zip_only_details['city'] = ""
            state_zip_only_details['state'] = "Colorado"
            state_zip_only_details['postal_code'] = "80202"

            state_zip_only_property = PropertyState(**state_zip_only_details)
            state_zip_only_property.save()

            properties = PropertyState.objects.filter(
                id__in=[state_zip_only_property.id])

            geocode_buildings(properties)

            state_zip_only_property = PropertyState.objects.get(
                pk=state_zip_only_property.id)

            self.assertIsNone(state_zip_only_property.long_lat)
            self.assertIsNone(state_zip_only_property.longitude)
            self.assertIsNone(state_zip_only_property.latitude)
            self.assertEqual("Low - check address (Z1XAA)",
                             state_zip_only_property.geocoding_confidence)

            # 2nd Property
            wrong_state_zip_details = self.property_state_factory.get_details()
            wrong_state_zip_details['organization_id'] = self.org.id
            wrong_state_zip_details['address_line_1'] = ""
            wrong_state_zip_details['address_line_2'] = ""
            wrong_state_zip_details['city'] = "Denver"
            wrong_state_zip_details['state'] = "Colorado"
            wrong_state_zip_details['postal_code'] = ""

            wrong_state_zip_property = PropertyState(**wrong_state_zip_details)
            wrong_state_zip_property.save()

            properties = PropertyState.objects.filter(
                id__in=[wrong_state_zip_property.id])

            geocode_buildings(properties)

            wrong_state_zip_property = PropertyState.objects.get(
                pk=wrong_state_zip_property.id)

            self.assertIsNone(wrong_state_zip_property.long_lat)
            self.assertIsNone(wrong_state_zip_property.longitude)
            self.assertIsNone(wrong_state_zip_property.latitude)
            self.assertEqual("Low - check address (A5XAX)",
                             wrong_state_zip_property.geocoding_confidence)

    def test_geocode_buildings_returns_no_data_when_provided_address_returns_multiple_results(
            self):
        with base_vcr.use_cassette(
                'seed/tests/data/vcr_cassettes/geocode_multiple_results.yaml'):
            # 1st Property
            wrong_state_details = self.property_state_factory.get_details()
            wrong_state_details['organization_id'] = self.org.id
            wrong_state_details['address_line_1'] = "101 Market Street"
            wrong_state_details['address_line_2'] = ""
            wrong_state_details['city'] = "Denver"
            wrong_state_details['state'] = "California"
            wrong_state_details['postal_code'] = ""

            wrong_state_property = PropertyState(**wrong_state_details)
            wrong_state_property.save()

            properties = PropertyState.objects.filter(
                id__in=[wrong_state_property.id])

            geocode_buildings(properties)

            wrong_state_property = PropertyState.objects.get(
                pk=wrong_state_property.id)

            self.assertIsNone(wrong_state_property.long_lat)
            self.assertIsNone(wrong_state_property.longitude)
            self.assertIsNone(wrong_state_property.latitude)
            self.assertEqual("Low - check address (Ambiguous)",
                             wrong_state_property.geocoding_confidence)

            # 2nd Property
            not_specific_enough_details = self.property_state_factory.get_details(
            )
            not_specific_enough_details['organization_id'] = self.org.id
            not_specific_enough_details['address_line_1'] = "101 Market Street"
            not_specific_enough_details['address_line_2'] = ""
            not_specific_enough_details['city'] = ""
            not_specific_enough_details['state'] = "California"
            not_specific_enough_details['postal_code'] = ""

            not_specific_enough_property = PropertyState(
                **not_specific_enough_details)
            not_specific_enough_property.save()

            properties = PropertyState.objects.filter(
                id__in=[not_specific_enough_property.id])

            geocode_buildings(properties)

            not_specific_enough_property = PropertyState.objects.get(
                pk=not_specific_enough_property.id)

            self.assertIsNone(not_specific_enough_property.long_lat)
            self.assertIsNone(not_specific_enough_property.longitude)
            self.assertIsNone(not_specific_enough_property.latitude)
            self.assertEqual("Low - check address (Ambiguous)",
                             not_specific_enough_property.geocoding_confidence)

    def test_geocode_buildings_is_successful_even_if_two_buildings_have_same_address(
            self):
        with base_vcr.use_cassette(
                'seed/tests/data/vcr_cassettes/geocode_dup_addresses.yaml'):
            property_details = self.property_state_factory.get_details()
            property_details['organization_id'] = self.org.id
            property_details['address_line_1'] = "3001 Brighton Blvd"
            property_details['address_line_2'] = "suite 2693"
            property_details['city'] = "Denver"
            property_details['state'] = "Colorado"
            property_details['postal_code'] = "80216"

            property_1 = PropertyState(**property_details)
            property_2 = PropertyState(**property_details)
            property_1.save()
            property_2.save()

            ids = [property_1.id, property_2.id]

            properties = PropertyState.objects.filter(id__in=ids)

            geocode_buildings(properties)

            refreshed_properties = PropertyState.objects.filter(id__in=ids)

            for property in refreshed_properties:
                self.assertEqual('POINT (-104.986138 39.765251)',
                                 long_lat_wkt(property))
                self.assertEqual('High (P1AAA)', property.geocoding_confidence)
                self.assertEqual(-104.986138, property.longitude)
                self.assertEqual(39.765251, property.latitude)

    def test_geocode_buildings_is_successful_with_over_100_properties(self):
        with batch_vcr.use_cassette(
                'seed/tests/data/vcr_cassettes/geocode_101_unique_addresses.yaml',
                match_on=['uri_length']):
            property_details = self.property_state_factory.get_details()
            property_details['organization_id'] = self.org.id
            property_details['address_line_2'] = ""
            property_details['city'] = "Denver"
            property_details['state'] = "Colorado"
            property_details['postal_code'] = "80202"

            ids = []
            for n in range(101):
                street_number = n + 1600
                property_details['address_line_1'] = str(
                    street_number) + " Larimer Street"

                property = PropertyState(**property_details)
                property.save()
                ids.append(property.id)

            properties = PropertyState.objects.filter(
                id__in=ids).order_by('id')

            geocode_buildings(properties)

            refreshed_properties = PropertyState.objects.filter(
                id__in=ids).order_by('id')

            long_lats = [
                property.long_lat for property in refreshed_properties
                if property.long_lat is not None
            ]
            geocode_confidence_results = [
                property.geocoding_confidence
                for property in refreshed_properties
                if property.geocoding_confidence is not None
            ]

            self.assertTrue(len(long_lats) > 0)
            self.assertTrue(len(geocode_confidence_results) == 101)

    def test_geocode_buildings_is_unsuccessful_when_the_API_key_is_invalid_or_expired(
            self):
        with base_vcr.use_cassette(
                'seed/tests/data/vcr_cassettes/geocode_invalid_or_expired_key.yaml'
        ):
            self.org_fake_key, _, _ = create_organization(self.user)
            self.org_fake_key.mapquest_api_key = 'fakeapikey'
            self.org_fake_key.save()

            self.property_state_factory_fake_key = FakePropertyStateFactory(
                organization=self.org_fake_key)

            property_details_fake_key = self.property_state_factory_fake_key.get_details(
            )
            property_details_fake_key['organization_id'] = self.org_fake_key.id
            property_details_fake_key['address_line_1'] = "3001 Brighton Blvd"
            property_details_fake_key['address_line_2'] = "suite 2693"
            property_details_fake_key['city'] = "Denver"
            property_details_fake_key['state'] = "Colorado"
            property_details_fake_key['postal_code'] = "80216"

            property = PropertyState(**property_details_fake_key)
            property.save()

            properties = PropertyState.objects.filter(pk=property.id)

            with self.assertRaises(MapQuestAPIKeyError):
                geocode_buildings(properties)

    def test_geocode_buildings_doesnt_run_an_api_request_when_an_API_key_is_not_provided(
            self):
        self.org_no_key, _, _ = create_organization(self.user)
        self.property_state_factory_no_key = FakePropertyStateFactory(
            organization=self.org_no_key)
        property_details_no_key = self.property_state_factory_no_key.get_details(
        )

        property_details_no_key['organization_id'] = self.org_no_key.id
        property_details_no_key['address_line_1'] = "3001 Brighton Blvd"
        property_details_no_key['address_line_2'] = "suite 2693"
        property_details_no_key['city'] = "Denver"
        property_details_no_key['state'] = "Colorado"
        property_details_no_key['postal_code'] = "80216"

        property = PropertyState(**property_details_no_key)
        property.save()

        properties = PropertyState.objects.filter(pk=property.id)

        self.assertIsNone(geocode_buildings(properties))

        refreshed_property = PropertyState.objects.get(pk=property.id)

        self.assertIsNone(refreshed_property.long_lat)
        self.assertIsNone(refreshed_property.geocoding_confidence)

    def test_geocode_address_can_handle_addresses_with_reserved_and_unsafe_characters(
            self):
        with base_vcr.use_cassette(
                'seed/tests/data/vcr_cassettes/geocode_reserved_and_unsafe_characters.yaml'
        ):
            property_details = self.property_state_factory.get_details()
            property_details['organization_id'] = self.org.id
            property_details[
                'address_line_1'] = r'3001 Brighton Blvd;/?:@=&<>#%{}|"\^~[]`'
            property_details['address_line_2'] = "suite 2693"
            property_details['city'] = "Denver"
            property_details['state'] = "Colorado"
            property_details['postal_code'] = "80216"

            property = PropertyState(**property_details)
            property.save()

            properties = PropertyState.objects.filter(pk=property.id)

            geocode_buildings(properties)

            refreshed_properties = PropertyState.objects.filter(pk=property.id)

            self.assertEqual('POINT (-104.986138 39.765251)',
                             long_lat_wkt(refreshed_properties[0]))

    def test_geocode_address_can_use_prepopulated_lat_and_long_fields(self):
        property_details = self.property_state_factory.get_details()
        property_details['organization_id'] = self.org.id
        property_details['latitude'] = 39.765251
        property_details['longitude'] = -104.986138

        property = PropertyState(**property_details)
        property.save()

        properties = PropertyState.objects.filter(pk=property.id)

        geocode_buildings(properties)

        refreshed_property = PropertyState.objects.get(pk=property.id)

        self.assertEqual('POINT (-104.986138 39.765251)',
                         long_lat_wkt(refreshed_property))
        self.assertEqual("Manually geocoded (N/A)",
                         refreshed_property.geocoding_confidence)

    def test_geocode_address_can_handle_receiving_no_buildings(self):
        self.assertIsNone(geocode_buildings(PropertyState.objects.none()))

    def test_geocoding_an_address_again_after_successful_geocode_executes_successfully(
            self):
        with base_vcr.use_cassette(
                'seed/tests/data/vcr_cassettes/geocode_same_record_twice.yaml'
        ):
            property_details = self.property_state_factory.get_details()
            property_details['organization_id'] = self.org.id
            property_details['address_line_1'] = "3001 Brighton Blvd"
            property_details['address_line_2'] = "suite 2693"
            property_details['city'] = "Denver"
            property_details['state'] = "Colorado"
            property_details['postal_code'] = "80216"
            property = PropertyState(**property_details)
            property.save()

            properties = PropertyState.objects.filter(pk=property.id)
            geocode_buildings(properties)

            refreshed_property = PropertyState.objects.get(pk=property.id)
            refreshed_property.address_line_1 = "2020 Lawrence St"
            refreshed_property.address_line_2 = "unit A"
            refreshed_property.postal_code = "80205"
            refreshed_property.save()

            refreshed_properties = PropertyState.objects.filter(
                pk=refreshed_property.id)
            geocode_buildings(refreshed_properties)

            refreshed_updated_property = PropertyState.objects.get(
                pk=refreshed_property.id)

            self.assertEqual('POINT (-104.991046 39.752396)',
                             long_lat_wkt(refreshed_updated_property))
            self.assertEqual('High (P1AAA)',
                             refreshed_updated_property.geocoding_confidence)
            self.assertEqual(-104.991046, refreshed_updated_property.longitude)
            self.assertEqual(39.752396, refreshed_updated_property.latitude)

    def test_geocoded_fields_are_changed_appropriately_if_a_user_manually_updates_latitude_or_longitude_of_ungeocoded_property(
            self):
        property_details = self.property_state_factory.get_details()
        property_details['organization_id'] = self.org.id
        property = PropertyState(**property_details)
        property.save()

        refreshed_property = PropertyState.objects.get(pk=property.id)
        self.assertIsNone(long_lat_wkt(refreshed_property))
        self.assertIsNone(refreshed_property.geocoding_confidence)

        refreshed_property.latitude = 39.765251
        refreshed_property.save()

        refreshed_property = PropertyState.objects.get(pk=property.id)
        self.assertEqual(39.765251, refreshed_property.latitude)
        self.assertIsNone(long_lat_wkt(refreshed_property))
        self.assertIsNone(refreshed_property.geocoding_confidence)

        refreshed_property.longitude = -104.986138
        refreshed_property.save()

        refreshed_property = PropertyState.objects.get(pk=property.id)
        self.assertEqual(-104.986138, refreshed_property.longitude)
        self.assertEqual('POINT (-104.986138 39.765251)',
                         long_lat_wkt(refreshed_property))
        self.assertEqual("Manually geocoded (N/A)",
                         refreshed_property.geocoding_confidence)

    def test_geocoded_fields_are_changed_appropriately_if_a_user_manually_updates_latitude_or_longitude_of_geocoded_property(
            self):
        property_details = self.property_state_factory.get_details()
        property_details['organization_id'] = self.org.id
        property_details['latitude'] = 39.765251
        property_details['longitude'] = -104.986138
        property_details['long_lat'] = 'POINT (-104.986138 39.765251)'
        property_details['geocoding_confidence'] = 'High (P1AAA)'
        property = PropertyState(**property_details)
        property.save()

        # Make sure geocoding_confidence isn't overriden to be Manual given latitude and longitude are updated
        refreshed_property = PropertyState.objects.get(pk=property.id)
        self.assertEqual('High (P1AAA)',
                         refreshed_property.geocoding_confidence)

        # Try updating latitude
        refreshed_property.latitude = 39.81
        refreshed_property.save()

        refreshed_property = PropertyState.objects.get(pk=property.id)
        self.assertEqual(39.81, refreshed_property.latitude)
        self.assertEqual('POINT (-104.986138 39.81)',
                         long_lat_wkt(refreshed_property))
        self.assertEqual("Manually geocoded (N/A)",
                         refreshed_property.geocoding_confidence)

        # If latitude or longitude are not there long_lat and geocoding_confidence should be empty
        refreshed_property.latitude = None
        refreshed_property.save()

        self.assertIsNone(refreshed_property.latitude)
        self.assertIsNone(long_lat_wkt(refreshed_property))
        self.assertIsNone(refreshed_property.geocoding_confidence)
예제 #12
0
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:v3:import_files-pm-meters-preview',
                      kwargs={'pk': self.import_file.id})
        url += f'?organization_id={self.org.pk}'
        result = self.client.get(url)
        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:v3:import_files-pm-meters-preview',
                      kwargs={'pk': import_file_with_invalids.id})
        url += f'?organization_id={self.org.pk}'
        result = self.client.get(url)
        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:v3:import_files-pm-meters-preview',
                      kwargs={'pk': self.import_file.id})
        url += f'?organization_id={self.org.pk}'
        result = self.client.get(url)
        result_dict = ast.literal_eval(result.content.decode("utf-8"))

        expectation = [
            {
                "property_id": self.property_1.id,
                "cycles": self.cycle.name,
                "pm_property_id": "5766973",
                "source_id": "5766973-0",
                "type": 'Electric - Grid',
                "incoming": 2,
            },
            {
                "property_id": self.property_1.id,
                "cycles": self.cycle.name,
                "pm_property_id": "5766973",
                "source_id": "5766973-1",
                "type": 'Natural Gas',
                "incoming": 2,
            },
            {
                "property_id": self.property_2.id,
                "cycles": self.cycle.name,
                "pm_property_id": "5766975",
                "source_id": "5766975-0",
                "type": 'Electric - Grid',
                "incoming": 2,
            },
            {
                "property_id": self.property_2.id,
                "cycles": self.cycle.name,
                "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:v3:import_files-pm-meters-preview',
                      kwargs={'pk': cost_import_file.id})
        url += f'?organization_id={self.org.pk}'
        result = self.client.get(url)
        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 = [
            {
                "property_id": self.property_1.id,
                "cycles": self.cycle.name,
                "pm_property_id": "5766973",
                "source_id": "5766973-0",
                "type": 'Electric - Grid',
                "incoming": 2,
            },
            {
                "property_id": self.property_1.id,
                "cycles": self.cycle.name,
                "pm_property_id": "5766973",
                "source_id": "5766973-1",
                "type": 'Natural Gas',
                "incoming": 2,
            },
            {
                "property_id": self.property_1.id,
                "cycles": self.cycle.name,
                "pm_property_id": "5766973",
                "source_id": "5766973-0",
                "type": 'Cost',
                "incoming": 2,
            },
            {
                "property_id": self.property_1.id,
                "cycles": self.cycle.name,
                "pm_property_id": "5766973",
                "source_id": "5766973-1",
                "type": 'Cost',
                "incoming": 2,
            },
            {
                "property_id": self.property_2.id,
                "cycles": self.cycle.name,
                "pm_property_id": "5766975",
                "source_id": "5766975-0",
                "type": 'Electric - Grid',
                "incoming": 2,
            },
            {
                "property_id": self.property_2.id,
                "cycles": self.cycle.name,
                "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.get(url)
        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:v3:import_files-greenbutton-meters-preview',
                      kwargs={'pk': xml_import_file.id})
        url += f'?organization_id={self.org.pk}&view_id={self.property_view_1.id}'
        result = self.client.get(url)
        result_dict = ast.literal_eval(result.content.decode("utf-8"))

        proposed_imports = [
            {
                "source_id": '409483',
                "property_id": self.property_1.id,
                "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:v3:import_files-pm-meters-preview',
                      kwargs={'pk': self.import_file.id})
        url += f'?organization_id={self.org.pk}'
        result = self.client.get(url)
        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)