From 35a756d185bd0aa8d60cb9fbc99935179d83898f Mon Sep 17 00:00:00 2001 From: Alexander-M-Waldman Date: Mon, 9 Mar 2026 18:59:20 +0000 Subject: [PATCH 1/7] Add model for OpaAccountNumChanges --- ais/models.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ais/models.py b/ais/models.py index 324e075c..cd5f95ff 100644 --- a/ais/models.py +++ b/ais/models.py @@ -203,6 +203,11 @@ class OpaProperty(db.Model): tencode = db.Column(db.Text) owners = db.Column(db.Text) +class OpaAccountNumChanges(db.Model): + id = db.Column(db.Integer, primary_key=True) + pin = db.Column(db.Text, index=True) + old_opa_account_num = db.Column(db.Text) + new_opa_account_num = db.Column(db.Text) ################## # ADDRESS POINTS # From d0c7ac9c32993fb5da07364e066470eb492a3514 Mon Sep 17 00:00:00 2001 From: mbjackson-capp Date: Thu, 28 Aug 2025 16:39:28 +0000 Subject: [PATCH 2/7] add base address to AddressJsonSerializer output take numero uno --- ais/api/serializers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ais/api/serializers.py b/ais/api/serializers.py index e337fcc6..a156b81e 100644 --- a/ais/api/serializers.py +++ b/ais/api/serializers.py @@ -385,6 +385,7 @@ def model_to_data(self, address): ('match_type', match_type if match_type else None), ('properties', OrderedDict([ ('street_address', address.street_address), + ('base_address', address.base_address if address.base_address else self.base_address), ('address_low', address.address_low), ('address_low_suffix', address.address_low_suffix), ('address_low_frac', address.address_low_frac), From d575ff21912ecf32800664e4a4712844c91ccaa0 Mon Sep 17 00:00:00 2001 From: mbjackson-capp Date: Thu, 28 Aug 2025 19:52:03 +0000 Subject: [PATCH 3/7] fully add a base_address class attribute to Address and AddressSummary, to be carried into serializer --- ais/api/serializers.py | 2 +- ais/models.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ais/api/serializers.py b/ais/api/serializers.py index a156b81e..bd4dbe5f 100644 --- a/ais/api/serializers.py +++ b/ais/api/serializers.py @@ -385,7 +385,7 @@ def model_to_data(self, address): ('match_type', match_type if match_type else None), ('properties', OrderedDict([ ('street_address', address.street_address), - ('base_address', address.base_address if address.base_address else self.base_address), + ('base_address', address.base_address), ('address_low', address.address_low), ('address_low_suffix', address.address_low_suffix), ('address_low_frac', address.address_low_frac), diff --git a/ais/models.py b/ais/models.py index cd5f95ff..1b5e7fba 100644 --- a/ais/models.py +++ b/ais/models.py @@ -238,6 +238,7 @@ class NG911AddressPoint(db.Model): ############# ADDRESS_FIELDS = [ + 'base_address', 'address_low', 'address_low_suffix', 'address_low_frac', @@ -283,6 +284,7 @@ class Address(db.Model): id = db.Column(db.Integer, primary_key=True) street_address = db.Column(db.Text) + base_address = db.Column(db.Text) address_low = db.Column(db.Integer) address_low_suffix = db.Column(db.Text) address_low_frac = db.Column(db.Text) @@ -359,6 +361,7 @@ def __init__(self, *args, **kwargs): raise ValueError('Invalid range address') kwargs = { + 'base_address': c['base_address'], 'address_low': c['address']['low_num'], 'address_low_suffix': c['address']['addr_suffix'], # passyunk change 'address_low_frac': c['address']['fractional'], # passyunk change @@ -465,10 +468,6 @@ def address_full_num(self): num += '-' + str(address_high)[-2:] return num - @property - def base_address(self): - return ' '.join([self.address_full, self.street_full]) - @property def base_address_no_suffix(self): return '{} {}'.format(self.address_full_num, self.street_full) @@ -1201,6 +1200,7 @@ class AddressSummary(db.Model): id = db.Column(db.Integer, primary_key=True) street_address = db.Column(db.Text, unique=True) + base_address = db.Column(db.Text) address_low = db.Column(db.Integer) address_low_suffix = db.Column(db.Text) address_low_frac = db.Column(db.Text) From cbd8a15bc943b744fdfbe6fc6c2ca3d484b5fa25 Mon Sep 17 00:00:00 2001 From: mbjackson-capp Date: Fri, 29 Aug 2025 16:43:03 +0000 Subject: [PATCH 4/7] make sure base_address is carried into precursor tables --- ais/engine/scripts/load_dor_condos.py | 4 +++- ais/engine/scripts/load_ng911_address_points.py | 1 + ais/engine/scripts/load_opa_properties.py | 1 + ais/models.py | 5 +++++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/ais/engine/scripts/load_dor_condos.py b/ais/engine/scripts/load_dor_condos.py index 7ee1736a..cb27f5e2 100644 --- a/ais/engine/scripts/load_dor_condos.py +++ b/ais/engine/scripts/load_dor_condos.py @@ -42,7 +42,9 @@ def main(): # Read DOR Parcel rows from engine db print("Reading parcels...") dor_parcel_read_stmt = ''' - select parcel_id, street_address, address_low, address_low_suffix, address_low_frac, address_high, street_predir, + select parcel_id, street_address, + base_address, + address_low, address_low_suffix, address_low_frac, address_high, street_predir, street_name, street_suffix, street_postdir, street_full from {dor_parcel_table} '''.format(dor_parcel_table='dor_parcel') engine_dor_parcel_rows = etl.fromdb(pg_db, dor_parcel_read_stmt) diff --git a/ais/engine/scripts/load_ng911_address_points.py b/ais/engine/scripts/load_ng911_address_points.py index c9a33b99..a77d3f86 100644 --- a/ais/engine/scripts/load_ng911_address_points.py +++ b/ais/engine/scripts/load_ng911_address_points.py @@ -89,6 +89,7 @@ def main(): 'unit_num': comps['address_unit']['unit_num'] or '', 'unit_type': comps['address_unit']['unit_type'] or '', 'street_address': street_address, + 'base_address': comps['base_address'], } address_points.append(address_point) diff --git a/ais/engine/scripts/load_opa_properties.py b/ais/engine/scripts/load_opa_properties.py index 54b54d03..aa64c885 100644 --- a/ais/engine/scripts/load_opa_properties.py +++ b/ais/engine/scripts/load_opa_properties.py @@ -99,6 +99,7 @@ def main(): 'unit_num': comps['address_unit']['unit_num'] or '', 'unit_type': comps['address_unit']['unit_type'] or '', 'street_address': street_address, + 'base_address': comps['base_address'] } props.append(prop) diff --git a/ais/models.py b/ais/models.py index 1b5e7fba..f1c1b5b1 100644 --- a/ais/models.py +++ b/ais/models.py @@ -126,6 +126,7 @@ class PwdParcel(db.Model): id = db.Column(db.Integer, primary_key=True) parcel_id = db.Column(db.Integer, index=True) street_address = db.Column(db.Text) + base_address = db.Column(db.Text) address_low = db.Column(db.Integer) address_low_suffix = db.Column(db.Text) address_low_frac = db.Column(db.Text) @@ -143,6 +144,7 @@ class DorParcel(db.Model): id = db.Column(db.Integer, primary_key=True) parcel_id = db.Column(db.Text, index=True) street_address = db.Column(db.Text) + base_address = db.Column(db.Text) address_low = db.Column(db.Integer) address_low_suffix = db.Column(db.Text) address_low_frac = db.Column(db.Text) @@ -167,6 +169,7 @@ class DorCondominium(db.Model): id = db.Column(db.Integer, primary_key=True) parcel_id = db.Column(db.Text, index=True) street_address = db.Column(db.Text) + base_address = db.Column(db.Text) address_low = db.Column(db.Integer) address_low_suffix = db.Column(db.Text) address_low_frac = db.Column(db.Text) @@ -188,6 +191,7 @@ class OpaProperty(db.Model): id = db.Column(db.Integer, primary_key=True) account_num = db.Column(db.Text, index=True) street_address = db.Column(db.Text) + base_address = db.Column(db.Text) address_low = db.Column(db.Integer) address_low_suffix = db.Column(db.Text) address_low_frac = db.Column(db.Text) @@ -216,6 +220,7 @@ class OpaAccountNumChanges(db.Model): class NG911AddressPoint(db.Model): id = db.Column(db.Integer, primary_key=True) street_address = db.Column(db.Text) + base_address = db.Column(db.Text) address_low = db.Column(db.Integer) address_low_suffix = db.Column(db.Text) address_low_frac = db.Column(db.Text) From 59a47eef28651436a1ad1a20767c09d984a44606 Mon Sep 17 00:00:00 2001 From: Alexander-M-Waldman Date: Mon, 9 Mar 2026 21:08:18 +0000 Subject: [PATCH 5/7] Add config for opa account num changes table --- config.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/config.py b/config.py index a2d47bb3..bcf44913 100644 --- a/config.py +++ b/config.py @@ -123,6 +123,15 @@ 'geom': 'shape', }, }, + 'opa_account_num_changes': { + 'db': 'citygeo', + 'table': 'viewer_opa.pin_account_number_change_map', + 'field_map': { + 'pin': 'pin', + 'old_opa_account_num': 'old_opa_account_num', + 'new_opa_account_num': 'new_opa_account_num', + }, + }, 'ng911_address_points': { 'db': 'citygeo', 'table': 'viewer_ng911.ng911_siteaddresses', From 7205c38d844b8ed291dad5201befecf70898d5f9 Mon Sep 17 00:00:00 2001 From: Alexander-M-Waldman Date: Tue, 10 Mar 2026 22:20:01 +0000 Subject: [PATCH 6/7] Add view logic to handle old opa account num query against change table and return address response of related active (new) opa account num, with match_type = changed_key --- ais/api/serializers.py | 5 ++++- ais/api/views.py | 13 ++++++++++++- ais/engine/bin/build_engine.sh | 1 + ais/models.py | 2 +- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/ais/api/serializers.py b/ais/api/serializers.py index bd4dbe5f..bb41cf9b 100644 --- a/ais/api/serializers.py +++ b/ais/api/serializers.py @@ -371,7 +371,10 @@ def model_to_data(self, address): 'pwd_parcel_id': 'exact_key', 'opa_account': 'exact_key' } - match_type = match_type_key[self.metadata['search_type']] + if self.metadata['search_type'] == 'opa_account' and self.metadata['normalized'] != address.opa_account_num: + match_type = 'changed_key' + else: + match_type = match_type_key[self.metadata['search_type']] else: match_type = self.estimated diff --git a/ais/api/views.py b/ais/api/views.py index 9fcea363..375b915a 100644 --- a/ais/api/views.py +++ b/ais/api/views.py @@ -14,7 +14,7 @@ from sqlalchemy import func, desc from passyunk.parser import PassyunkParser from ais import app, util, app_db as db -from ais.models import Address, AddressSummary, StreetIntersection, StreetSegment, Geocode, AddressTag, DorParcel, PwdParcel, OpaProperty, ENGINE_SRID +from ais.models import Address, AddressSummary, StreetIntersection, StreetSegment, Geocode, AddressTag, DorParcel, PwdParcel, OpaProperty, OpaAccountNumChanges, ENGINE_SRID from ..util import NotNoneDict from .errors import json_error from .paginator import QueryPaginator, Paginator @@ -696,6 +696,17 @@ def account(query): tagged_opa_account_nums = OpaProperty.query \ .filter(OpaProperty.account_num == normalized).all() + + # If account number isn't found, look for any addresses that were tagged with that account number in the past. + if not tagged_opa_account_nums: + changed_opa_account_nums = OpaAccountNumChanges.query \ + .filter(OpaAccountNumChanges.old_opa_account_num == normalized).all() + + if changed_opa_account_nums: + new_opa_account_nums = tuple(set([x.new_opa_account_num for x in changed_opa_account_nums])) + tagged_opa_account_nums = OpaProperty.query \ + .filter(OpaProperty.account_num.in_(new_opa_account_nums)).all() + street_addresses = tuple(set([x.street_address for x in tagged_opa_account_nums])) addresses = AddressSummary.query \ diff --git a/ais/engine/bin/build_engine.sh b/ais/engine/bin/build_engine.sh index 21620c46..5d3ba0dc 100755 --- a/ais/engine/bin/build_engine.sh +++ b/ais/engine/bin/build_engine.sh @@ -26,6 +26,7 @@ SCRIPTS=( "load_street_aliases" "make_street_intersections" "load_opa_properties" + "load_opa_account_changes" "load_ng911_address_points" "load_dor_parcels" "load_dor_condos" diff --git a/ais/models.py b/ais/models.py index f1c1b5b1..3a29dedc 100644 --- a/ais/models.py +++ b/ais/models.py @@ -210,7 +210,7 @@ class OpaProperty(db.Model): class OpaAccountNumChanges(db.Model): id = db.Column(db.Integer, primary_key=True) pin = db.Column(db.Text, index=True) - old_opa_account_num = db.Column(db.Text) + old_opa_account_num = db.Column(db.Text, index=True) new_opa_account_num = db.Column(db.Text) ################## From 16da26e6d6e686022f88ecddff4f320f256e6764 Mon Sep 17 00:00:00 2001 From: Alexander-M-Waldman Date: Tue, 24 Mar 2026 22:01:02 +0000 Subject: [PATCH 7/7] Fix handling of zips for unmatched addresses - prioritize passyunk over service area --- ais/api/serializers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ais/api/serializers.py b/ais/api/serializers.py index bb41cf9b..e562223f 100644 --- a/ais/api/serializers.py +++ b/ais/api/serializers.py @@ -359,6 +359,8 @@ def model_to_data(self, address): sa_data[col.name] = getattr(address.service_areas, col.name) else: sa_data = self.sa_data + sa_data['zip_code'] = address.zip_code if address.zip_code else sa_data['zip_code'] + sa_data['zip_4'] = address.zip_4 if address.zip_4 else sa_data['zip_4'] if self.metadata['search_type'] in ('address', 'street'): match_type = self.get_address_response_relationships(address=address, ref_addr=self.ref_addr) if not self.estimated else 'unmatched' else: