def main(args):
    skip_count = 0
    new_or_updated_count = 0
    matched_count = 0
    with open(args.file, 'r') as csv_file:
        sites_reader = csv.DictReader(csv_file)
        hpo_dao = HPODao()
        site_dao = SiteDao()
        existing_site_map = {
            site.googleGroup: site
            for site in site_dao.get_all()
        }

        with site_dao.session() as session:
            for row in sites_reader:
                site = _site_from_row(row, hpo_dao)
                if site is None:
                    skip_count += 1
                    continue
                changed = _upsert_site(site,
                                       existing_site_map.get(site.googleGroup),
                                       site_dao, session, args.dry_run)
                if changed:
                    new_or_updated_count += 1
                else:
                    matched_count += 1

    logging.info(
        'Done%s. %d skipped, %d sites new/updated, %d sites not changed.',
        ' (dry run)' if args.dry_run else '', skip_count, new_or_updated_count,
        matched_count)
class SiteImporter(CsvImporter):
    def __init__(self):
        args = parser.parse_args()
        self.organization_dao = OrganizationDao()
        self.stub_geocoding = args.stub_geocoding
        self.ACTIVE = SiteStatus.ACTIVE
        self.status_exception_list = ['hpo-site-walgreensphoenix']
        self.instance = args.instance
        self.creds_file = args.creds_file
        self.new_sites_list = []
        self.project = None
        if args.project:
            self.project = args.project

        if self.project in ENV_LIST:
            self.environment = ' ' + self.project.split('-')[-1].upper()
        else:
            self.environment = ' ' + ENV_TEST.split('-')[-1].upper()

        super(SiteImporter,
              self).__init__('site', SiteDao(), 'siteId', 'googleGroup', [
                  SITE_ORGANIZATION_ID_COLUMN, SITE_SITE_ID_COLUMN,
                  SITE_SITE_COLUMN, SITE_STATUS_COLUMN + self.environment,
                  ENROLLING_STATUS_COLUMN + self.environment,
                  DIGITAL_SCHEDULING_STATUS_COLUMN + self.environment
              ])

    def run(self, filename, dry_run):
        super(SiteImporter, self).run(filename, dry_run)
        insert_participants = False
        if not dry_run:
            if self.environment:
                current_env = ENV_STABLE
                if self.environment.strip() == 'STABLE' and len(
                        self.new_sites_list) > 0:
                    from googleapiclient.discovery import build
                    logging.info(
                        'Starting reboot of app instances to insert new test participants'
                    )
                    service = build('appengine', 'v1', cache_discovery=False)
                    request = service.apps().services().versions().list(
                        appsId=current_env, servicesId='default')
                    versions = request.execute()

                    for version in versions['versions']:
                        if version['servingStatus'] == 'SERVING':
                            _id = version['id']
                            request = service.apps().services().versions(
                            ).instances().list(servicesId='default',
                                               versionsId=_id,
                                               appsId=current_env)
                            instances = request.execute()

                            try:
                                for instance in instances['instances']:
                                    sha = instance['name'].split('/')[-1]
                                    delete_instance = service.apps().services(
                                    ).versions().instances().delete(
                                        appsId=current_env,
                                        servicesId='default',
                                        versionsId=_id,
                                        instancesId=sha)

                                    response = delete_instance.execute()
                                    if response['done']:
                                        insert_participants = True
                                        logging.info(
                                            'Reboot of instance: %s in stable complete.',
                                            instance['name'])
                                    else:
                                        logging.warn(
                                            'Not able to reboot instance on server, Error: %s',
                                            response)

                            except KeyError:
                                logging.warn('No running instance for %s',
                                             version['name'])

                    if insert_participants:
                        logging.info('Starting import of test participants.')
                        self._insert_new_participants(self.new_sites_list)

    def delete_sql_statement(self, session, str_list):
        sql = """
          DELETE FROM site
          WHERE site_id IN ({str_list})
          AND NOT EXISTS(
          SELECT * FROM participant WHERE site_id = site.site_id)
          AND NOT EXISTS(
          SELECT * FROM participant_summary WHERE site_id = site.site_id
          OR physical_measurements_finalized_site_id = site.site_id
          OR physical_measurements_created_site_id = site.site_id
          OR biospecimen_source_site_id = site.site_id
          OR biospecimen_collected_site_id = site.site_id
          OR biospecimen_processed_site_id = site.site_id
          OR biospecimen_finalized_site_id = site.site_id
          )
          AND NOT EXISTS(
          SELECT * FROM participant_history WHERE site_id = site.site_id)
          AND NOT EXISTS(
          SELECT * FROM physical_measurements WHERE created_site_id = site.site_id
          OR finalized_site_id = site.site_id)
          AND NOT EXISTS(
          SELECT * FROM biobank_order WHERE finalized_site_id = site.site_id
          OR source_site_id = site.site_id
          OR collected_site_id = site.site_id
          OR processed_site_id = site.site_id
          )
          """.format(str_list=str_list)

        session.execute(sql)

    def _cleanup_old_entities(self, session, row_list, dry_run):
        log_prefix = '(dry run) ' if dry_run else ''
        self.site_dao = SiteDao()
        existing_sites = set(site.googleGroup
                             for site in self.site_dao.get_all())
        site_group_list_from_sheet = [
            str(row[SITE_SITE_ID_COLUMN].lower()) for row in row_list
        ]

        sites_to_remove = existing_sites - set(site_group_list_from_sheet)
        if sites_to_remove:
            site_id_list = []
            for site in sites_to_remove:
                logging.info(
                    log_prefix +
                    'Deleting old Site no longer in Google sheet: %s', site)
                old_site = self.site_dao.get_by_google_group(site)
            if old_site and old_site.isObsolete != ObsoleteStatus.OBSOLETE:
                site_id_list.append(old_site.siteId)
                self.deletion_count += 1
            elif old_site and old_site.isObsolete == ObsoleteStatus.OBSOLETE:
                logging.info(
                    'Not attempting to delete site [%s] with existing obsolete status',
                    old_site.googleGroup)

            if site_id_list and not dry_run:
                str_list = ','.join([str(i) for i in site_id_list])
                logging.info(log_prefix + 'Marking old site as obsolete : %s',
                             old_site)
                sql = """ UPDATE site
            SET is_obsolete = 1
            WHERE site_id in ({site_id_list})""".format(site_id_list=str_list)

                session.execute(sql)

                self.site_dao._invalidate_cache()
                # Try to delete old sites.
                self.delete_sql_statement(session, str_list)

    def _insert_new_participants(self, entity):
        num_participants = 0
        participants = {
            'zip_code': '20001',
            'date_of_birth': '1933-3-3',
            'gender_identity': 'GenderIdentity_Woman',
            'withdrawalStatus': 'NOT_WITHDRAWN',
            'suspensionStatus': 'NOT_SUSPENDED'
        }

        client = Client('rdr/v1', False, self.creds_file, self.instance)
        client_log.setLevel(logging.WARN)
        questionnaire_to_questions, consent_questionnaire_id_and_version = _setup_questionnaires(
            client)
        consent_questions = questionnaire_to_questions[
            consent_questionnaire_id_and_version]
        for site in entity:
            for participant, v in enumerate(range(1, 21), 1):
                num_participants += 1
                participant = participants
                participant.update(
                    {'last_name': site.googleGroup.split('-')[-1]})
                participant.update({'first_name': 'Participant {}'.format(v)})
                participant.update({'site': site.googleGroup})

                import_participant(participant, client,
                                   consent_questionnaire_id_and_version,
                                   questionnaire_to_questions,
                                   consent_questions, num_participants)

        logging.info('%d participants imported.' % num_participants)

    def _entity_from_row(self, row):
        google_group = row[SITE_SITE_ID_COLUMN].lower()
        organization = self.organization_dao.get_by_external_id(
            row[SITE_ORGANIZATION_ID_COLUMN].upper())
        if organization is None:
            logging.warn('Invalid organization ID %s importing site %s',
                         row[SITE_ORGANIZATION_ID_COLUMN].upper(),
                         google_group)
            self.errors.append(
                'Invalid organization ID {} importing site {}'.format(
                    row[SITE_ORGANIZATION_ID_COLUMN].upper(), google_group))
            return None

        launch_date = None
        launch_date_str = row.get(SITE_LAUNCH_DATE_COLUMN)
        if launch_date_str:
            try:
                launch_date = parse(launch_date_str).date()
            except ValueError:
                logging.warn('Invalid launch date %s for site %s',
                             launch_date_str, google_group)
                self.errors.append('Invalid launch date {} for site {}'.format(
                    launch_date_str, google_group))
                return None
        name = row[SITE_SITE_COLUMN]
        mayolink_client_number = None
        mayolink_client_number_str = row.get(
            SITE_MAYOLINK_CLIENT_NUMBER_COLUMN)
        if mayolink_client_number_str:
            try:
                mayolink_client_number = int(mayolink_client_number_str)
            except ValueError:
                logging.warn('Invalid Mayolink Client # %s for site %s',
                             mayolink_client_number_str, google_group)
                self.errors.append(
                    'Invalid Mayolink Client # {} for site {}'.format(
                        mayolink_client_number_str, google_group))
                return None
        notes = row.get(SITE_NOTES_COLUMN)
        notes_es = row.get(SITE_NOTES_COLUMN_ES)
        try:
            site_status = SiteStatus(row[SITE_STATUS_COLUMN +
                                         self.environment].upper())
        except TypeError:
            logging.warn('Invalid site status %s for site %s',
                         row[SITE_STATUS_COLUMN + self.environment],
                         google_group)
            self.errors.append('Invalid site status {} for site {}'.format(
                row[SITE_STATUS_COLUMN + self.environment], google_group))
            return None
        try:
            enrolling_status = EnrollingStatus(row[ENROLLING_STATUS_COLUMN +
                                                   self.environment].upper())
        except TypeError:
            logging.warn('Invalid enrollment site status %s for site %s',
                         row[ENROLLING_STATUS_COLUMN + self.environment],
                         google_group)
            self.errors.append(
                'Invalid enrollment site status {} for site {}'.format(
                    row[ENROLLING_STATUS_COLUMN + self.environment],
                    google_group))

        directions = row.get(SITE_DIRECTIONS_COLUMN)
        physical_location_name = row.get(SITE_PHYSICAL_LOCATION_NAME_COLUMN)
        address_1 = row.get(SITE_ADDRESS_1_COLUMN)
        address_2 = row.get(SITE_ADDRESS_2_COLUMN)
        city = row.get(SITE_CITY_COLUMN)
        state = row.get(SITE_STATE_COLUMN)
        zip_code = row.get(SITE_ZIP_COLUMN)
        phone = row.get(SITE_PHONE_COLUMN)
        admin_email_addresses = row.get(SITE_ADMIN_EMAIL_ADDRESSES_COLUMN)
        link = row.get(SITE_LINK_COLUMN)
        digital_scheduling_status = DigitalSchedulingStatus(
            row[DIGITAL_SCHEDULING_STATUS_COLUMN + self.environment].upper())
        schedule_instructions = row.get(SCHEDULING_INSTRUCTIONS)
        schedule_instructions_es = row.get(SCHEDULING_INSTRUCTIONS_ES)
        return Site(siteName=name,
                    googleGroup=google_group,
                    mayolinkClientNumber=mayolink_client_number,
                    organizationId=organization.organizationId,
                    hpoId=organization.hpoId,
                    siteStatus=site_status,
                    enrollingStatus=enrolling_status,
                    digitalSchedulingStatus=digital_scheduling_status,
                    scheduleInstructions=schedule_instructions,
                    scheduleInstructions_ES=schedule_instructions_es,
                    launchDate=launch_date,
                    notes=notes,
                    notes_ES=notes_es,
                    directions=directions,
                    physicalLocationName=physical_location_name,
                    address1=address_1,
                    address2=address_2,
                    city=city,
                    state=state,
                    zipCode=zip_code,
                    phoneNumber=phone,
                    adminEmails=admin_email_addresses,
                    link=link)

    def _update_entity(self, entity, existing_entity, session, dry_run):
        self._populate_lat_lng_and_time_zone(entity, existing_entity)
        if entity.siteStatus == self.ACTIVE and (entity.latitude == None
                                                 or entity.longitude == None):
            self.errors.append(
                'Skipped active site without geocoding: {}'.format(
                    entity.googleGroup))
            return None, True
        return super(SiteImporter,
                     self)._update_entity(entity, existing_entity, session,
                                          dry_run)

    def _insert_entity(self, entity, existing_map, session, dry_run):
        self._populate_lat_lng_and_time_zone(entity, None)
        if entity.siteStatus == self.ACTIVE and (entity.latitude == None
                                                 or entity.longitude == None):
            self.errors.append(
                'Skipped active site without geocoding: {}'.format(
                    entity.googleGroup))
            return False
        self.new_sites_list.append(entity)
        super(SiteImporter, self)._insert_entity(entity, existing_map, session,
                                                 dry_run)

    def _populate_lat_lng_and_time_zone(self, site, existing_site):
        if site.address1 and site.city and site.state:
            if existing_site:
                if (existing_site.address1 == site.address1
                        and existing_site.city == site.city
                        and existing_site.state == site.state
                        and existing_site.latitude is not None
                        and existing_site.longitude is not None
                        and existing_site.timeZoneId is not None):
                    # Address didn't change, use the existing lat/lng and time zone.
                    site.latitude = existing_site.latitude
                    site.longitude = existing_site.longitude
                    site.timeZoneId = existing_site.timeZoneId
                    return
            if self.stub_geocoding:
                # Set dummy latitude and longitude when importing sites locally / on a CircleCI box.
                site.latitude = 32.176
                site.longitude = -110.93
                site.timeZoneId = 'America/Phoenix'
            else:
                latitude, longitude = self._get_lat_long_for_site(
                    site.address1, site.city, site.state)
                site.latitude = latitude
                site.longitude = longitude
                if latitude and longitude:
                    site.timeZoneId = self._get_time_zone(latitude, longitude)
        else:
            if site.googleGroup not in self.status_exception_list:
                if site.siteStatus == self.ACTIVE:
                    self.errors.append(
                        'Active site must have valid address. Site: {}, Group: {}'
                        .format(site.siteName, site.googleGroup))

    def _get_lat_long_for_site(self, address_1, city, state):
        self.full_address = address_1 + ' ' + city + ' ' + state
        try:
            self.api_key = os.environ.get('API_KEY')
            self.gmaps = googlemaps.Client(key=self.api_key)
            try:
                geocode_result = self.gmaps.geocode(address_1 + '' + city +
                                                    ' ' + state)[0]
            except IndexError:
                self.errors.append(
                    'Bad address for {}, could not geocode.'.format(
                        self.full_address))
                return None, None
            if geocode_result:
                geometry = geocode_result.get('geometry')
                if geometry:
                    location = geometry.get('location')
                if location:
                    latitude = location.get('lat')
                    longitude = location.get('lng')
                    return latitude, longitude
                else:
                    logging.warn('Can not find lat/long for %s',
                                 self.full_address)
                    self.errors.append('Can not find lat/long for {}'.format(
                        self.full_address))
                    return None, None
            else:
                logging.warn('Geocode results failed for %s.',
                             self.full_address)
                self.errors.append('Geocode results failed for {}'.format(
                    self.full_address))
                return None, None
        except ValueError as e:
            logging.exception('Invalid geocode key: %s. ERROR: %s',
                              self.api_key, e)
            self.errors.append('Invalid geocode key: {}. ERROR: {}'.format(
                self.api_key, e))
            return None, None
        except IndexError as e:
            logging.exception(
                'Geocoding failure Check that address is correct. ERROR: %s',
                e)
            self.errors.append(
                'Geocoding failured Check that address is correct. ERROR: {}'.
                format(self.api_key, e))
            return None, None

    def _get_time_zone(self, latitude, longitude):
        time_zone = self.gmaps.timezone(location=(latitude, longitude))
        if time_zone['status'] == 'OK':
            time_zone_id = time_zone['timeZoneId']
            return time_zone_id
        else:
            logging.info('can not retrieve time zone from %s',
                         self.full_address)
            self.errors.append('Can not retrieve time zone from {}'.format(
                self.full_address))
            return None
Example #3
0
class HierarchyContentApiTest(FlaskTestBase):
    def setUp(self):
        super(HierarchyContentApiTest, self).setUp(with_data=False)

        hpo_dao = HPODao()
        hpo_dao.insert(
            HPO(hpoId=UNSET_HPO_ID,
                name='UNSET',
                displayName='Unset',
                organizationType=OrganizationType.UNSET,
                resourceId='h123456'))
        hpo_dao.insert(
            HPO(hpoId=PITT_HPO_ID,
                name='PITT',
                displayName='Pittsburgh',
                organizationType=OrganizationType.HPO,
                resourceId='h123457'))
        hpo_dao.insert(
            HPO(hpoId=AZ_HPO_ID,
                name='AZ_TUCSON',
                displayName='Arizona',
                organizationType=OrganizationType.HPO,
                resourceId='h123458'))
        self.site_dao = SiteDao()
        self.org_dao = OrganizationDao()

    def test_create_new_hpo(self):
        self._setup_data()
        request_json = {
            "resourceType":
            "Organization",
            "id":
            "a893282c-2717-4a20-b276-d5c9c2c0e51f",
            'meta': {
                'versionId': '1'
            },
            "extension": [{
                "url": "http://all-of-us.org/fhir/sites/awardee-type",
                "valueString": "DV"
            }],
            "identifier": [{
                "system": "http://all-of-us.org/fhir/sites/awardee-id",
                "value": "TEST_HPO_NAME"
            }],
            "active":
            True,
            "type": [{
                "coding": [{
                    "code": "AWARDEE",
                    "system": "http://all-of-us.org/fhir/sites/type"
                }]
            }],
            "name":
            "Test new HPO display name"
        }
        self.send_put('organization/hierarchy', request_data=request_json)
        result = self.send_get('Awardee/TEST_HPO_NAME')
        self.assertEquals(
            _make_awardee_resource('TEST_HPO_NAME',
                                   'Test new HPO display name', 'DV'), result)

    def test_update_existing_hpo(self):
        self._setup_data()
        request_json = {
            "resourceType":
            "Organization",
            "id":
            "a893282c-2717-4a20-b276-d5c9c2c0e51f",
            'meta': {
                'versionId': '2'
            },
            "extension": [{
                "url": "http://all-of-us.org/fhir/sites/awardee-type",
                "valueString": "DV"
            }],
            "identifier": [{
                "system": "http://all-of-us.org/fhir/sites/awardee-id",
                "value": "PITT"
            }],
            "active":
            True,
            "type": [{
                "coding": [{
                    "code": "AWARDEE",
                    "system": "http://all-of-us.org/fhir/sites/type"
                }]
            }],
            "name":
            "Test update HPO display name"
        }
        self.send_put('organization/hierarchy', request_data=request_json)
        result = self.send_get('Awardee/PITT')
        self.assertEqual(result['displayName'], 'Test update HPO display name')
        self.assertEqual(result['type'], 'DV')

    def test_create_new_organization(self):
        self._setup_data()
        request_json = {
            "resourceType":
            "Organization",
            "id":
            "a893282c-2717-4a20-b276-d5c9c2c0e51f",
            'meta': {
                'versionId': '1'
            },
            "extension": [],
            "identifier": [{
                "system": "http://all-of-us.org/fhir/sites/organization-id",
                "value": "TEST_NEW_ORG"
            }],
            "active":
            True,
            "type": [{
                "coding": [{
                    "code": "ORGANIZATION",
                    "system": "http://all-of-us.org/fhir/sites/type"
                }]
            }],
            "name":
            "Test create organization display name",
            "partOf": {
                "reference": "Organization/h123457"
            }
        }

        result_before = self.send_get('Awardee/PITT')
        self.assertEqual(2, len(result_before['organizations']))

        self.send_put('organization/hierarchy', request_data=request_json)

        result_after = self.send_get('Awardee/PITT')
        self.assertEqual(3, len(result_after['organizations']))
        self.assertIn(
            {
                'displayName': 'Test create organization display name',
                'id': 'TEST_NEW_ORG'
            }, result_after['organizations'])

    def test_update_existing_organization(self):
        self._setup_data()
        request_json = {
            "resourceType":
            "Organization",
            "id":
            "a893282c-2717-4a20-b276-d5c9c2c0e51f",
            'meta': {
                'versionId': '2'
            },
            "extension": [],
            "identifier": [{
                "system": "http://all-of-us.org/fhir/sites/organization-id",
                "value": "AARDVARK_ORG"
            }],
            "active":
            True,
            "type": [{
                "coding": [{
                    "code": "ORGANIZATION",
                    "system": "http://all-of-us.org/fhir/sites/type"
                }]
            }],
            "name":
            "Test update organization display name",
            "partOf": {
                "reference": "Organization/h123457"
            }
        }

        result_before = self.send_get('Awardee/PITT')
        self.assertEqual(2, len(result_before['organizations']))

        self.send_put('organization/hierarchy', request_data=request_json)

        result_after = self.send_get('Awardee/PITT')
        self.assertEqual(2, len(result_after['organizations']))
        self.assertIn(
            {
                'displayName': 'Test update organization display name',
                'id': 'AARDVARK_ORG'
            }, result_after['organizations'])

    @mock.patch(
        'dao.organization_hierarchy_sync_dao.OrganizationHierarchySyncDao.'
        '_get_lat_long_for_site')
    @mock.patch(
        'dao.organization_hierarchy_sync_dao.OrganizationHierarchySyncDao.'
        '_get_time_zone')
    def test_create_new_site(self, time_zone, lat_long):
        self._setup_data()
        lat_long.return_value = 100, 110
        time_zone.return_value = 'America/Los_Angeles'
        request_json = {
            "resourceType":
            "Organization",
            "id":
            "a893282c-2717-4a20-b276-d5c9c2c0e51f",
            'meta': {
                'versionId': '1'
            },
            "extension": [{
                "url":
                "http://all-of-us.org/fhir/sites/enrollmentStatusActive",
                "valueBoolean": True
            }, {
                "url":
                "http://all-of-us.org/fhir/sites/digitalSchedulingStatusActive",
                "valueBoolean": True
            }, {
                "url":
                "http://all-of-us.org/fhir/sites/schedulingStatusActive",
                "valueBoolean": True
            }, {
                "url": "http://all-of-us.org/fhir/sites/notes",
                "valueString": "This is a note about an organization"
            }, {
                "url":
                "http://all-of-us.org/fhir/sites/schedulingInstructions",
                "valueString":
                "Please schedule appointments up to a week before intended date."
            }, {
                "url": "http://all-of-us.org/fhir/sites/anticipatedLaunchDate",
                "valueDate": "07-02-2010"
            }, {
                "url": "http://all-of-us.org/fhir/sites/locationName",
                "valueString": "Thompson Building"
            }, {
                "url":
                "http://all-of-us.org/fhir/sites/directions",
                "valueString":
                "Exit 95 N and make a left onto Fake Street"
            }],
            "identifier": [{
                "system": "http://all-of-us.org/fhir/sites/site-id",
                "value": "hpo-site-awesome-testing"
            }, {
                "system":
                "http://all-of-us.org/fhir/sites/mayo-link-identifier",
                "value": "123456"
            }, {
                "system":
                "http://all-of-us.org/fhir/sites/google-group-identifier",
                "value": "Awesome Genomics Testing"
            }],
            "active":
            True,
            "type": [{
                "coding": [{
                    "code": "SITE",
                    "system": "http://all-of-us.org/fhir/sites/type"
                }]
            }],
            "name":
            "Awesome Genomics Testing",
            "partOf": {
                "reference": "Organization/o123457"
            },
            "address": [{
                "line": ["1855 4th Street", "AAC5/6"],
                "city": "San Francisco",
                "state": "CA",
                "postalCode": "94158"
            }],
            "contact": [{
                "telecom": [{
                    "system": "phone",
                    "value": "7031234567"
                }]
            }, {
                "telecom": [{
                    "system": "email",
                    "value": "*****@*****.**"
                }]
            }, {
                "telecom": [{
                    "system": "url",
                    "value": "http://awesome-genomic-testing.com"
                }]
            }]
        }

        self.send_put('organization/hierarchy', request_data=request_json)

        self.send_get('Awardee/PITT')

        existing_map = {
            entity.googleGroup: entity
            for entity in self.site_dao.get_all()
        }
        existing_entity = existing_map.get('hpo-site-awesome-testing')

        self.assertEqual(existing_entity.adminEmails,
                         '*****@*****.**')
        self.assertEqual(existing_entity.siteStatus, SiteStatus('ACTIVE'))
        self.assertEqual(existing_entity.isObsolete, None)
        self.assertEqual(existing_entity.city, 'San Francisco')
        self.assertEqual(existing_entity.googleGroup,
                         'hpo-site-awesome-testing')
        self.assertEqual(existing_entity.state, 'CA')
        self.assertEqual(existing_entity.digitalSchedulingStatus,
                         DigitalSchedulingStatus('ACTIVE'))
        self.assertEqual(existing_entity.mayolinkClientNumber, 123456)
        self.assertEqual(existing_entity.address1, '1855 4th Street')
        self.assertEqual(existing_entity.address2, 'AAC5/6')
        self.assertEqual(existing_entity.zipCode, '94158')
        self.assertEqual(existing_entity.directions,
                         'Exit 95 N and make a left onto Fake Street')
        self.assertEqual(existing_entity.notes,
                         'This is a note about an organization')
        self.assertEqual(existing_entity.enrollingStatus,
                         EnrollingStatus('ACTIVE'))
        self.assertEqual(
            existing_entity.scheduleInstructions,
            'Please schedule appointments up to a week before intended date.')
        self.assertEqual(existing_entity.physicalLocationName,
                         'Thompson Building')
        self.assertEqual(existing_entity.link,
                         'http://awesome-genomic-testing.com')
        self.assertEqual(existing_entity.launchDate, datetime.date(2010, 7, 2))
        self.assertEqual(existing_entity.phoneNumber, '7031234567')

    @mock.patch(
        'dao.organization_hierarchy_sync_dao.OrganizationHierarchySyncDao.'
        '_get_lat_long_for_site')
    @mock.patch(
        'dao.organization_hierarchy_sync_dao.OrganizationHierarchySyncDao.'
        '_get_time_zone')
    def test_update_existing_site(self, time_zone, lat_long):
        self._setup_data()
        lat_long.return_value = 100, 110
        time_zone.return_value = 'America/Los_Angeles'
        request_json = {
            "resourceType":
            "Organization",
            "id":
            "a893282c-2717-4a20-b276-d5c9c2c0e51f",
            'meta': {
                'versionId': '2'
            },
            "extension": [{
                "url":
                "http://all-of-us.org/fhir/sites/enrollmentStatusActive",
                "valueBoolean": True
            }, {
                "url":
                "http://all-of-us.org/fhir/sites/digitalSchedulingStatusActive",
                "valueBoolean": False
            }, {
                "url":
                "http://all-of-us.org/fhir/sites/schedulingStatusActive",
                "valueBoolean": True
            }, {
                "url": "http://all-of-us.org/fhir/sites/notes",
                "valueString": "This is a note about an organization"
            }, {
                "url":
                "http://all-of-us.org/fhir/sites/schedulingInstructions",
                "valueString":
                "Please schedule appointments up to a week before intended date."
            }, {
                "url": "http://all-of-us.org/fhir/sites/anticipatedLaunchDate",
                "valueDate": "07-02-2010"
            }, {
                "url": "http://all-of-us.org/fhir/sites/locationName",
                "valueString": "Thompson Building"
            }, {
                "url":
                "http://all-of-us.org/fhir/sites/directions",
                "valueString":
                "Exit 95 N and make a left onto Fake Street"
            }],
            "identifier": [{
                "system": "http://all-of-us.org/fhir/sites/site-id",
                "value": "hpo-site-1"
            }, {
                "system":
                "http://all-of-us.org/fhir/sites/mayo-link-identifier",
                "value": "123456"
            }, {
                "system":
                "http://all-of-us.org/fhir/sites/google-group-identifier",
                "value": "Awesome Genomics Testing"
            }],
            "active":
            True,
            "type": [{
                "coding": [{
                    "code": "SITE",
                    "system": "http://all-of-us.org/fhir/sites/type"
                }]
            }],
            "name":
            "Awesome Genomics Testing",
            "partOf": {
                "reference": "Organization/o123456"
            },
            "address": [{
                "line": ["1855 4th Street", "AAC5/6"],
                "city": "San Francisco",
                "state": "CA",
                "postalCode": "94158"
            }],
            "contact": [{
                "telecom": [{
                    "system": "phone",
                    "value": "7031234567"
                }]
            }, {
                "telecom": [{
                    "system": "email",
                    "value": "*****@*****.**"
                }]
            }, {
                "telecom": [{
                    "system": "url",
                    "value": "http://awesome-genomic-testing.com"
                }]
            }]
        }

        self.send_put('organization/hierarchy', request_data=request_json)

        existing_map = {
            entity.googleGroup: entity
            for entity in self.site_dao.get_all()
        }
        existing_entity = existing_map.get('hpo-site-1')

        self.assertEqual(existing_entity.adminEmails,
                         '*****@*****.**')
        self.assertEqual(existing_entity.siteStatus, SiteStatus('ACTIVE'))
        self.assertEqual(existing_entity.isObsolete, None)
        self.assertEqual(existing_entity.city, 'San Francisco')
        self.assertEqual(existing_entity.googleGroup, 'hpo-site-1')
        self.assertEqual(existing_entity.state, 'CA')
        self.assertEqual(existing_entity.digitalSchedulingStatus,
                         DigitalSchedulingStatus('INACTIVE'))
        self.assertEqual(existing_entity.mayolinkClientNumber, 123456)
        self.assertEqual(existing_entity.address1, '1855 4th Street')
        self.assertEqual(existing_entity.address2, 'AAC5/6')
        self.assertEqual(existing_entity.zipCode, '94158')
        self.assertEqual(existing_entity.directions,
                         'Exit 95 N and make a left onto Fake Street')
        self.assertEqual(existing_entity.notes,
                         'This is a note about an organization')
        self.assertEqual(existing_entity.enrollingStatus,
                         EnrollingStatus('ACTIVE'))
        self.assertEqual(
            existing_entity.scheduleInstructions,
            'Please schedule appointments up to a week before intended date.')
        self.assertEqual(existing_entity.physicalLocationName,
                         'Thompson Building')
        self.assertEqual(existing_entity.link,
                         'http://awesome-genomic-testing.com')
        self.assertEqual(existing_entity.launchDate, datetime.date(2010, 7, 2))
        self.assertEqual(existing_entity.phoneNumber, '7031234567')

    @mock.patch(
        'dao.organization_hierarchy_sync_dao.OrganizationHierarchySyncDao.'
        '_get_lat_long_for_site')
    @mock.patch(
        'dao.organization_hierarchy_sync_dao.OrganizationHierarchySyncDao.'
        '_get_time_zone')
    def test_create_hpo_org_site(self, time_zone, lat_long):
        hpo_json = {
            "resourceType":
            "Organization",
            "id":
            "a893282c-2717-4a20-b276-d5c9c2c0e51f",
            'meta': {
                'versionId': '1'
            },
            "extension": [{
                "url": "http://all-of-us.org/fhir/sites/awardee-type",
                "valueString": "DV"
            }],
            "identifier": [{
                "system": "http://all-of-us.org/fhir/sites/awardee-id",
                "value": "TEST_HPO_NAME"
            }],
            "active":
            True,
            "type": [{
                "coding": [{
                    "code": "AWARDEE",
                    "system": "http://all-of-us.org/fhir/sites/type"
                }]
            }],
            "name":
            "Test new HPO display name"
        }
        self.send_put('organization/hierarchy', request_data=hpo_json)

        org_json = {
            "resourceType":
            "Organization",
            "id":
            "a893282c-2717-4a20-b276-d5c9c2c0e123",
            'meta': {
                'versionId': '1'
            },
            "extension": [],
            "identifier": [{
                "system": "http://all-of-us.org/fhir/sites/organization-id",
                "value": "TEST_NEW_ORG"
            }],
            "active":
            True,
            "type": [{
                "coding": [{
                    "code": "ORGANIZATION",
                    "system": "http://all-of-us.org/fhir/sites/type"
                }]
            }],
            "name":
            "Test create organization display name",
            "partOf": {
                "reference":
                "Organization/a893282c-2717-4a20-b276-d5c9c2c0e51f"
            }
        }

        self.send_put('organization/hierarchy', request_data=org_json)

        lat_long.return_value = 100, 110
        time_zone.return_value = 'America/Los_Angeles'
        site_json = {
            "resourceType":
            "Organization",
            "id":
            "a893282c-2717-4a20-b276-d5c9c2c0e234",
            'meta': {
                'versionId': '1'
            },
            "extension": [{
                "url":
                "http://all-of-us.org/fhir/sites/enrollmentStatusActive",
                "valueBoolean": True
            }, {
                "url":
                "http://all-of-us.org/fhir/sites/digitalSchedulingStatusActive",
                "valueBoolean": True
            }, {
                "url":
                "http://all-of-us.org/fhir/sites/schedulingStatusActive",
                "valueBoolean": True
            }, {
                "url": "http://all-of-us.org/fhir/sites/notes",
                "valueString": "This is a note about an organization"
            }, {
                "url":
                "http://all-of-us.org/fhir/sites/schedulingInstructions",
                "valueString":
                "Please schedule appointments up to a week before intended date."
            }, {
                "url": "http://all-of-us.org/fhir/sites/anticipatedLaunchDate",
                "valueDate": "07-02-2010"
            }, {
                "url": "http://all-of-us.org/fhir/sites/locationName",
                "valueString": "Thompson Building"
            }, {
                "url":
                "http://all-of-us.org/fhir/sites/directions",
                "valueString":
                "Exit 95 N and make a left onto Fake Street"
            }],
            "identifier": [{
                "system": "http://all-of-us.org/fhir/sites/site-id",
                "value": "hpo-site-awesome-testing"
            }, {
                "system":
                "http://all-of-us.org/fhir/sites/mayo-link-identifier",
                "value": "123456"
            }, {
                "system":
                "http://all-of-us.org/fhir/sites/google-group-identifier",
                "value": "Awesome Genomics Testing"
            }],
            "active":
            True,
            "type": [{
                "coding": [{
                    "code": "SITE",
                    "system": "http://all-of-us.org/fhir/sites/type"
                }]
            }],
            "name":
            "Awesome Genomics Testing",
            "partOf": {
                "reference":
                "Organization/a893282c-2717-4a20-b276-d5c9c2c0e123"
            },
            "address": [{
                "line": ["1855 4th Street", "AAC5/6"],
                "city": "San Francisco",
                "state": "CA",
                "postalCode": "94158"
            }],
            "contact": [{
                "telecom": [{
                    "system": "phone",
                    "value": "7031234567"
                }]
            }, {
                "telecom": [{
                    "system": "email",
                    "value": "*****@*****.**"
                }]
            }, {
                "telecom": [{
                    "system": "url",
                    "value": "http://awesome-genomic-testing.com"
                }]
            }]
        }

        self.send_put('organization/hierarchy', request_data=site_json)

        result = self.send_get('Awardee/TEST_HPO_NAME')
        self.assertEqual(
            {
                u'displayName':
                u'Test new HPO display name',
                u'type':
                u'DV',
                u'id':
                u'TEST_HPO_NAME',
                u'organizations': [{
                    u'displayName':
                    u'Test create organization display name',
                    u'id':
                    u'TEST_NEW_ORG',
                    u'sites': [{
                        u'mayolinkClientNumber':
                        123456,
                        u'timeZoneId':
                        u'America/Los_Angeles',
                        u'displayName':
                        u'Awesome Genomics Testing',
                        u'notes':
                        u'This is a note about an organization',
                        u'launchDate':
                        u'2010-07-02',
                        u'notesEs':
                        u'',
                        u'enrollingStatus':
                        u'ACTIVE',
                        u'longitude':
                        110.0,
                        u'schedulingInstructions':
                        u'Please schedule appointments up '
                        u'to a week before intended date.',
                        u'latitude':
                        100.0,
                        u'physicalLocationName':
                        u'Thompson Building',
                        u'phoneNumber':
                        u'7031234567',
                        u'siteStatus':
                        u'ACTIVE',
                        u'address': {
                            u'postalCode': u'94158',
                            u'city': u'San Francisco',
                            u'line': [u'1855 4th Street', u'AAC5/6'],
                            u'state': u'CA'
                        },
                        u'directions':
                        u'Exit 95 N and make a left onto Fake Street',
                        u'link':
                        u'http://awesome-genomic-testing.com',
                        u'id':
                        u'hpo-site-awesome-testing',
                        u'adminEmails': [u'*****@*****.**'],
                        u'digitalSchedulingStatus':
                        u'ACTIVE'
                    }]
                }]
            }, result)

    def _setup_data(self):
        organization_dao = OrganizationDao()
        site_dao = SiteDao()
        org_1 = organization_dao.insert(
            Organization(externalId='ORG_1',
                         displayName='Organization 1',
                         hpoId=PITT_HPO_ID,
                         resourceId='o123456'))
        organization_dao.insert(
            Organization(externalId='AARDVARK_ORG',
                         displayName='Aardvarks Rock',
                         hpoId=PITT_HPO_ID,
                         resourceId='o123457'))

        site_dao.insert(
            Site(siteName='Site 1',
                 googleGroup='hpo-site-1',
                 mayolinkClientNumber=123456,
                 organizationId=org_1.organizationId,
                 siteStatus=SiteStatus.ACTIVE,
                 enrollingStatus=EnrollingStatus.ACTIVE,
                 launchDate=datetime.datetime(2016, 1, 1),
                 notes='notes',
                 latitude=12.1,
                 longitude=13.1,
                 directions='directions',
                 physicalLocationName='locationName',
                 address1='address1',
                 address2='address2',
                 city='Austin',
                 state='TX',
                 zipCode='78751',
                 phoneNumber='555-555-5555',
                 adminEmails='[email protected], [email protected]',
                 link='http://www.example.com'))
        site_dao.insert(
            Site(siteName='Zebras Rock',
                 googleGroup='aaaaaaa',
                 organizationId=org_1.organizationId,
                 enrollingStatus=EnrollingStatus.INACTIVE,
                 siteStatus=SiteStatus.INACTIVE))
class OrganizationHierarchySyncDao(BaseDao):
    def __init__(self):
        super(OrganizationHierarchySyncDao, self).__init__(HPO)
        self.hpo_dao = HPODao()
        self.organization_dao = OrganizationDao()
        self.site_dao = SiteDao()

    def from_client_json(self,
                         resource_json,
                         id_=None,
                         expected_version=None,
                         client_id=None):  # pylint: disable=unused-argument
        try:
            fhir_org = lib_fhir.fhirclient_3_0_0.models.organization.Organization(
                resource_json)
        except FHIRValidationError:
            raise BadRequest('Invalid FHIR format in payload data.')

        if not fhir_org.meta or not fhir_org.meta.versionId:
            raise BadRequest('No versionId info found in payload data.')
        try:
            fhir_org.version = int(fhir_org.meta.versionId)
        except ValueError:
            raise BadRequest('Invalid versionId in payload data.')

        return fhir_org

    def to_client_json(self, hierarchy_org_obj):
        return hierarchy_org_obj.as_json()

    def get_etag(self, id_, pid):  # pylint: disable=unused-argument
        return None

    def update(self, hierarchy_org_obj):
        obj_type = self._get_type(hierarchy_org_obj)

        operation_funcs = {
            'AWARDEE': self._update_awardee,
            'ORGANIZATION': self._update_organization,
            'SITE': self._update_site
        }

        if obj_type not in operation_funcs:
            raise BadRequest('No awardee-type info found in payload data.')

        operation_funcs[obj_type](hierarchy_org_obj)

    def _update_awardee(self, hierarchy_org_obj):
        if hierarchy_org_obj.id is None:
            raise BadRequest('No id found in payload data.')
        awardee_id = self._get_value_from_identifier(
            hierarchy_org_obj, HIERARCHY_CONTENT_SYSTEM_PREFIX + 'awardee-id')
        if awardee_id is None:
            raise BadRequest(
                'No organization-identifier info found in payload data.')
        is_obsolete = ObsoleteStatus(
            'OBSOLETE') if not hierarchy_org_obj.active else None
        awardee_type = self._get_value_from_extention(
            hierarchy_org_obj,
            HIERARCHY_CONTENT_SYSTEM_PREFIX + 'awardee-type')

        try:
            organization_type = OrganizationType(awardee_type)
            if organization_type == OrganizationType.UNSET:
                organization_type = None
        except TypeError:
            raise BadRequest(
                'Invalid organization type {} for awardee {}'.format(
                    awardee_type, awardee_id))

        entity = HPO(name=awardee_id.upper(),
                     displayName=hierarchy_org_obj.name,
                     organizationType=organization_type,
                     isObsolete=is_obsolete,
                     resourceId=hierarchy_org_obj.id)

        existing_map = {
            entity.name: entity
            for entity in self.hpo_dao.get_all()
        }
        existing_entity = existing_map.get(entity.name)

        with self.hpo_dao.session() as session:
            if existing_entity:
                hpo_id = existing_entity.hpoId
                new_dict = entity.asdict()
                new_dict['hpoId'] = None
                existing_dict = existing_entity.asdict()
                existing_dict['hpoId'] = None
                if existing_dict == new_dict:
                    logging.info('Not updating {}.'.format(new_dict['name']))
                else:
                    existing_entity.displayName = entity.displayName
                    existing_entity.organizationType = entity.organizationType
                    existing_entity.isObsolete = entity.isObsolete
                    existing_entity.resourceId = entity.resourceId
                    self.hpo_dao.update_with_session(session, existing_entity)
            else:
                entity.hpoId = len(existing_map)
                hpo_id = entity.hpoId
                self.hpo_dao.insert_with_session(session, entity)
        bq_hpo_update_by_id(hpo_id)

    def _update_organization(self, hierarchy_org_obj):
        if hierarchy_org_obj.id is None:
            raise BadRequest('No id found in payload data.')
        organization_id = self._get_value_from_identifier(
            hierarchy_org_obj,
            HIERARCHY_CONTENT_SYSTEM_PREFIX + 'organization-id')
        if organization_id is None:
            raise BadRequest(
                'No organization-identifier info found in payload data.')
        is_obsolete = ObsoleteStatus(
            'OBSOLETE') if not hierarchy_org_obj.active else None
        resource_id = self._get_reference(hierarchy_org_obj)

        hpo = self.hpo_dao.get_by_resource_id(resource_id)
        if hpo is None:
            raise BadRequest(
                'Invalid partOf reference {} importing organization {}'.format(
                    resource_id, organization_id))

        entity = Organization(externalId=organization_id.upper(),
                              displayName=hierarchy_org_obj.name,
                              hpoId=hpo.hpoId,
                              isObsolete=is_obsolete,
                              resourceId=hierarchy_org_obj.id)
        existing_map = {
            entity.externalId: entity
            for entity in self.organization_dao.get_all()
        }
        existing_entity = existing_map.get(entity.externalId)
        with self.organization_dao.session() as session:
            if existing_entity:
                new_dict = entity.asdict()
                new_dict['organizationId'] = None
                existing_dict = existing_entity.asdict()
                existing_dict['organizationId'] = None
                if existing_dict == new_dict:
                    logging.info('Not updating {}.'.format(
                        new_dict['externalId']))
                else:
                    existing_entity.displayName = entity.displayName
                    existing_entity.hpoId = entity.hpoId
                    existing_entity.isObsolete = entity.isObsolete
                    existing_entity.resourceId = entity.resourceId
                    self.organization_dao.update_with_session(
                        session, existing_entity)
            else:
                self.organization_dao.insert_with_session(session, entity)
        org_id = self.organization_dao.get_by_external_id(
            organization_id.upper()).organizationId
        bq_organization_update_by_id(org_id)

    def _update_site(self, hierarchy_org_obj):
        if hierarchy_org_obj.id is None:
            raise BadRequest('No id found in payload data.')
        google_group = self._get_value_from_identifier(
            hierarchy_org_obj, HIERARCHY_CONTENT_SYSTEM_PREFIX + 'site-id')
        if google_group is None:
            raise BadRequest(
                'No organization-identifier info found in payload data.')
        google_group = google_group.lower()
        is_obsolete = ObsoleteStatus(
            'OBSOLETE') if not hierarchy_org_obj.active else None
        resource_id = self._get_reference(hierarchy_org_obj)

        organization = self.organization_dao.get_by_resource_id(resource_id)
        if organization is None:
            raise BadRequest(
                'Invalid partOf reference {} importing site {}'.format(
                    resource_id, google_group))

        launch_date = None
        launch_date_str = self._get_value_from_extention(
            hierarchy_org_obj,
            HIERARCHY_CONTENT_SYSTEM_PREFIX + 'anticipatedLaunchDate',
            'valueDate')
        if launch_date_str:
            try:
                launch_date = parse(launch_date_str).date()
            except ValueError:
                raise BadRequest('Invalid launch date {} for site {}'.format(
                    launch_date_str, google_group))

        name = hierarchy_org_obj.name
        mayolink_client_number = None
        mayolink_client_number_str = self._get_value_from_identifier(
            hierarchy_org_obj,
            HIERARCHY_CONTENT_SYSTEM_PREFIX + 'mayo-link-identifier')
        if mayolink_client_number_str:
            try:
                mayolink_client_number = int(mayolink_client_number_str)
            except ValueError:
                raise BadRequest(
                    'Invalid Mayolink Client # {} for site {}'.format(
                        mayolink_client_number_str, google_group))

        notes = self._get_value_from_extention(
            hierarchy_org_obj, HIERARCHY_CONTENT_SYSTEM_PREFIX + 'notes')

        site_status_bool = self._get_value_from_extention(
            hierarchy_org_obj,
            HIERARCHY_CONTENT_SYSTEM_PREFIX + 'schedulingStatusActive',
            'valueBoolean')
        try:
            site_status = SiteStatus(
                'ACTIVE' if site_status_bool else 'INACTIVE')
        except TypeError:
            raise BadRequest('Invalid site status {} for site {}'.format(
                site_status, google_group))

        enrolling_status_bool = self._get_value_from_extention(
            hierarchy_org_obj,
            HIERARCHY_CONTENT_SYSTEM_PREFIX + 'enrollmentStatusActive',
            'valueBoolean')
        try:
            enrolling_status = EnrollingStatus(
                'ACTIVE' if enrolling_status_bool else 'INACTIVE')
        except TypeError:
            raise BadRequest(
                'Invalid enrollment site status {} for site {}'.format(
                    enrolling_status_bool, google_group))

        digital_scheduling_bool = self._get_value_from_extention(
            hierarchy_org_obj,
            HIERARCHY_CONTENT_SYSTEM_PREFIX + 'digitalSchedulingStatusActive',
            'valueBoolean')
        try:
            digital_scheduling_status = DigitalSchedulingStatus(
                'ACTIVE' if digital_scheduling_bool else 'INACTIVE')
        except TypeError:
            raise BadRequest(
                'Invalid digital scheduling status {} for site {}'.format(
                    digital_scheduling_bool, google_group))

        directions = self._get_value_from_extention(
            hierarchy_org_obj, HIERARCHY_CONTENT_SYSTEM_PREFIX + 'directions')
        physical_location_name = self._get_value_from_extention(
            hierarchy_org_obj,
            HIERARCHY_CONTENT_SYSTEM_PREFIX + 'locationName')
        address_1, address_2, city, state, zip_code = self._get_address(
            hierarchy_org_obj)

        phone = self._get_contact_point(hierarchy_org_obj, 'phone')
        admin_email_addresses = self._get_contact_point(
            hierarchy_org_obj, 'email')
        link = self._get_contact_point(hierarchy_org_obj, 'url')

        schedule_instructions = self._get_value_from_extention(
            hierarchy_org_obj,
            HIERARCHY_CONTENT_SYSTEM_PREFIX + 'schedulingInstructions')

        entity = Site(siteName=name,
                      googleGroup=google_group,
                      mayolinkClientNumber=mayolink_client_number,
                      organizationId=organization.organizationId,
                      hpoId=organization.hpoId,
                      siteStatus=site_status,
                      enrollingStatus=enrolling_status,
                      digitalSchedulingStatus=digital_scheduling_status,
                      scheduleInstructions=schedule_instructions,
                      scheduleInstructions_ES='',
                      launchDate=launch_date,
                      notes=notes,
                      notes_ES='',
                      directions=directions,
                      physicalLocationName=physical_location_name,
                      address1=address_1,
                      address2=address_2,
                      city=city,
                      state=state,
                      zipCode=zip_code,
                      phoneNumber=phone,
                      adminEmails=admin_email_addresses,
                      link=link,
                      isObsolete=is_obsolete,
                      resourceId=hierarchy_org_obj.id)

        existing_map = {
            entity.googleGroup: entity
            for entity in self.site_dao.get_all()
        }

        existing_entity = existing_map.get(entity.googleGroup)
        with self.site_dao.session() as session:
            if existing_entity:
                self._populate_lat_lng_and_time_zone(entity, existing_entity)
                if entity.siteStatus == SiteStatus.ACTIVE and \
                  (entity.latitude is None or entity.longitude is None):
                    raise BadRequest(
                        'Active site without geocoding: {}'.format(
                            entity.googleGroup))

                new_dict = entity.asdict()
                new_dict['siteId'] = None
                existing_dict = existing_entity.asdict()
                existing_dict['siteId'] = None
                if existing_dict == new_dict:
                    logging.info('Not updating {}.'.format(
                        new_dict['googleGroup']))
                else:
                    for k, v in entity.asdict().iteritems():
                        if k != 'siteId' and k != 'googleGroup':
                            setattr(existing_entity, k, v)
                    self.site_dao.update_with_session(session, existing_entity)
            else:
                self._populate_lat_lng_and_time_zone(entity, None)
                if entity.siteStatus == SiteStatus.ACTIVE and \
                  (entity.latitude is None or entity.longitude is None):
                    raise BadRequest(
                        'Active site without geocoding: {}'.format(
                            entity.googleGroup))
                self.site_dao.insert_with_session(session, entity)
        site_id = self.site_dao.get_by_google_group(google_group).siteId
        bq_site_update_by_id(site_id)

    def _get_type(self, hierarchy_org_obj):
        obj_type = None
        type_arr = hierarchy_org_obj.type
        for type_item in type_arr:
            code_arr = type_item.coding
            for code_item in code_arr:
                if code_item.system == HIERARCHY_CONTENT_SYSTEM_PREFIX + 'type':
                    obj_type = code_item.code
                    break

        return obj_type

    def _get_value_from_identifier(self, hierarchy_org_obj, system):
        identifier_arr = hierarchy_org_obj.identifier
        for identifier in identifier_arr:
            if identifier.system == system:
                return identifier.value
        else:
            return None

    def _get_value_from_extention(self,
                                  hierarchy_org_obj,
                                  url,
                                  value_key='valueString'):
        extension_arr = hierarchy_org_obj.extension
        for extension in extension_arr:
            if extension.url == url:
                ext_json = extension.as_json()
                return ext_json[value_key]
        else:
            return None

    def _get_contact_point(self, hierarchy_org_obj, code):
        contact_arr = hierarchy_org_obj.contact
        for contact in contact_arr:
            telecom_arr = contact.telecom
            for telecom in telecom_arr:
                if telecom.system == code:
                    return telecom.value
        else:
            return None

    def _get_address(self, hierarchy_org_obj):
        address = hierarchy_org_obj.address[0]
        address_1 = address.line[0] if len(address.line) > 0 else ''
        address_2 = address.line[1] if len(address.line) > 1 else ''
        city = address.city
        state = address.state
        postal_code = address.postalCode

        return address_1, address_2, city, state, postal_code

    def _get_reference(self, hierarchy_org_obj):
        try:
            return hierarchy_org_obj.partOf.reference.split('/')[1]
        except IndexError:
            return None

    def _populate_lat_lng_and_time_zone(self, site, existing_site):
        if site.address1 and site.city and site.state:
            if existing_site:
                if (existing_site.address1 == site.address1
                        and existing_site.city == site.city
                        and existing_site.state == site.state
                        and existing_site.latitude is not None
                        and existing_site.longitude is not None
                        and existing_site.timeZoneId is not None):
                    # Address didn't change, use the existing lat/lng and time zone.
                    site.latitude = existing_site.latitude
                    site.longitude = existing_site.longitude
                    site.timeZoneId = existing_site.timeZoneId
                    return
            latitude, longitude = self._get_lat_long_for_site(
                site.address1, site.city, site.state)
            site.latitude = latitude
            site.longitude = longitude
            if latitude and longitude:
                site.timeZoneId = self._get_time_zone(latitude, longitude)
        else:
            if site.googleGroup not in self.status_exception_list:
                if site.siteStatus == self.ACTIVE:
                    logging.warn(
                        'Active site must have valid address. Site: {}, Group: {}'
                        .format(site.siteName, site.googleGroup))

    def _get_lat_long_for_site(self, address_1, city, state):
        self.full_address = address_1 + ' ' + city + ' ' + state
        try:
            self.api_key = os.environ.get('API_KEY')
            self.gmaps = googlemaps.Client(key=self.api_key)
            try:
                geocode_result = self.gmaps.geocode(address_1 + '' + city +
                                                    ' ' + state)[0]
            except IndexError:
                logging.warn('Bad address for {}, could not geocode.'.format(
                    self.full_address))
                return None, None
            if geocode_result:
                geometry = geocode_result.get('geometry')
                if geometry:
                    location = geometry.get('location')
                if location:
                    latitude = location.get('lat')
                    longitude = location.get('lng')
                    return latitude, longitude
                else:
                    logging.warn('Can not find lat/long for %s',
                                 self.full_address)
                    return None, None
            else:
                logging.warn('Geocode results failed for %s.',
                             self.full_address)
                return None, None
        except ValueError as e:
            logging.exception('Invalid geocode key: %s. ERROR: %s',
                              self.api_key, e)
            return None, None
        except IndexError as e:
            logging.exception(
                'Geocoding failure Check that address is correct. ERROR: %s',
                e)
            return None, None

    def _get_time_zone(self, latitude, longitude):
        time_zone = self.gmaps.timezone(location=(latitude, longitude))
        if time_zone['status'] == 'OK':
            time_zone_id = time_zone['timeZoneId']
            return time_zone_id
        else:
            logging.info('can not retrieve time zone from %s',
                         self.full_address)
            return None