Skip to content

Commit 727bea1

Browse files
committed
fix: enhance contact name generation logic to use OwnerKey as fallback and add deduplication handling
1 parent b5f84ad commit 727bea1

3 files changed

Lines changed: 154 additions & 6 deletions

File tree

schemas/contact.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ class CreateContact(BaseCreateModel, ValidateContact):
150150
organization: str | None = None
151151
role: Role
152152
contact_type: ContactType = "Primary"
153+
nma_pk_owners: str | None = None
153154
# description: str | None = None
154155
# email: str | None = None
155156
# phone: str | None = None

tests/transfers/test_contact_with_multiple_wells.py

Lines changed: 132 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@
1414
# limitations under the License.
1515
# ===============================================================================
1616

17-
from db import ThingContactAssociation, Thing, Notes
17+
from types import SimpleNamespace
18+
from uuid import uuid4
19+
20+
from db import ThingContactAssociation, Thing, Notes, Contact
1821
from db.engine import session_ctx
19-
from transfers.contact_transfer import ContactTransfer
22+
from transfers.contact_transfer import ContactTransfer, _add_first_contact
2023
from transfers.well_transfer import WellTransferer
2124

2225

@@ -87,4 +90,131 @@ def test_owner_comment_absent_skips_notes():
8790
assert note_count == 0
8891

8992

93+
def test_ownerkey_fallback_name_when_name_and_org_missing(water_well_thing):
94+
with session_ctx() as sess:
95+
thing = sess.get(Thing, water_well_thing.id)
96+
row = SimpleNamespace(
97+
FirstName=None,
98+
LastName=None,
99+
OwnerKey="Fallback OwnerKey Name",
100+
Email=None,
101+
CtctPhone=None,
102+
Phone=None,
103+
CellPhone=None,
104+
StreetAddress=None,
105+
Address2=None,
106+
City=None,
107+
State=None,
108+
Zip=None,
109+
MailingAddress=None,
110+
MailCity=None,
111+
MailState=None,
112+
MailZipCode=None,
113+
PhysicalAddress=None,
114+
PhysicalCity=None,
115+
PhysicalState=None,
116+
PhysicalZipCode=None,
117+
)
118+
119+
# Should not raise "Either name or organization must be provided."
120+
contact = _add_first_contact(
121+
sess, row=row, thing=thing, organization=None, added=[]
122+
)
123+
sess.flush()
124+
125+
assert contact is not None
126+
assert contact.name == "Fallback OwnerKey Name"
127+
assert contact.organization is None
128+
129+
130+
def test_ownerkey_dedupes_when_fallback_name_differs(water_well_thing):
131+
owner_key = f"OwnerKey-{uuid4()}"
132+
with session_ctx() as sess:
133+
first_thing = sess.get(Thing, water_well_thing.id)
134+
second_thing = Thing(
135+
name=f"Second Well {uuid4()}",
136+
thing_type="water well",
137+
release_status="draft",
138+
)
139+
sess.add(second_thing)
140+
sess.flush()
141+
142+
complete_row = SimpleNamespace(
143+
FirstName="Casey",
144+
LastName="Owner",
145+
OwnerKey=owner_key,
146+
Email=None,
147+
CtctPhone=None,
148+
Phone=None,
149+
CellPhone=None,
150+
StreetAddress=None,
151+
Address2=None,
152+
City=None,
153+
State=None,
154+
Zip=None,
155+
MailingAddress=None,
156+
MailCity=None,
157+
MailState=None,
158+
MailZipCode=None,
159+
PhysicalAddress=None,
160+
PhysicalCity=None,
161+
PhysicalState=None,
162+
PhysicalZipCode=None,
163+
)
164+
fallback_row = SimpleNamespace(
165+
FirstName=None,
166+
LastName=None,
167+
OwnerKey=owner_key,
168+
Email=None,
169+
CtctPhone=None,
170+
Phone=None,
171+
CellPhone=None,
172+
StreetAddress=None,
173+
Address2=None,
174+
City=None,
175+
State=None,
176+
Zip=None,
177+
MailingAddress=None,
178+
MailCity=None,
179+
MailState=None,
180+
MailZipCode=None,
181+
PhysicalAddress=None,
182+
PhysicalCity=None,
183+
PhysicalState=None,
184+
PhysicalZipCode=None,
185+
)
186+
187+
added = []
188+
first_contact = _add_first_contact(
189+
sess, row=complete_row, thing=first_thing, organization=None, added=added
190+
)
191+
assert first_contact is not None
192+
assert first_contact.name == "Casey Owner"
193+
194+
second_contact = _add_first_contact(
195+
sess, row=fallback_row, thing=second_thing, organization=None, added=added
196+
)
197+
sess.flush()
198+
199+
# Reused existing contact; no duplicate fallback-name contact created.
200+
assert second_contact is None
201+
contacts = (
202+
sess.query(Contact)
203+
.filter(
204+
Contact.nma_pk_owners == owner_key,
205+
Contact.contact_type == "Primary",
206+
)
207+
.all()
208+
)
209+
assert len(contacts) == 1
210+
assert contacts[0].name == "Casey Owner"
211+
212+
assoc_count = (
213+
sess.query(ThingContactAssociation)
214+
.filter(ThingContactAssociation.contact_id == contacts[0].id)
215+
.count()
216+
)
217+
assert assoc_count == 2
218+
219+
90220
# ============= EOF =============================================

transfers/contact_transfer.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ def _add_first_contact(
328328

329329
def _safe_make_name(
330330
first: str | None, last: str | None, ownerkey: str, organization: str | None
331-
) -> str:
331+
) -> str | None:
332332
name = _make_name(first, last)
333333
if name is None and organization is None:
334334
logger.warning(
@@ -476,14 +476,31 @@ def _make_contact_and_assoc(
476476
session: Session, data: dict, thing: Thing, added: list
477477
) -> tuple[Contact, bool]:
478478
new_contact = True
479-
if (data["name"], data["organization"]) in added:
479+
contact = None
480+
481+
# Prefer OwnerKey-based dedupe so fallback names don't split the same owner
482+
# into multiple contacts when some rows have real names and others do not.
483+
owner_key = data.get("nma_pk_owners")
484+
contact_type = data.get("contact_type")
485+
if owner_key and contact_type:
486+
contact = (
487+
session.query(Contact)
488+
.filter_by(nma_pk_owners=owner_key, contact_type=contact_type)
489+
.first()
490+
)
491+
if contact is not None:
492+
new_contact = False
493+
494+
if contact is None and (data["name"], data["organization"]) in added:
480495
contact = (
481496
session.query(Contact)
482497
.filter_by(name=data["name"], organization=data["organization"])
483498
.first()
484499
)
485-
new_contact = False
486-
else:
500+
if contact is not None:
501+
new_contact = False
502+
503+
if contact is None:
487504

488505
from schemas.contact import CreateContact
489506

0 commit comments

Comments
 (0)