Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions schemas/contact.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ class CreateContact(BaseCreateModel, ValidateContact):
organization: str | None = None
role: Role
contact_type: ContactType = "Primary"
nma_pk_owners: str | None = None
# description: str | None = None
# email: str | None = None
# phone: str | None = None
Expand Down
136 changes: 133 additions & 3 deletions tests/transfers/test_contact_with_multiple_wells.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,18 @@
# limitations under the License.
# ===============================================================================

from db import ThingContactAssociation, Thing, Notes
from types import SimpleNamespace
from uuid import uuid4

from db import ThingContactAssociation, Thing, Notes, Contact
from db.engine import session_ctx
from transfers.contact_transfer import ContactTransfer
from transfers.contact_transfer import ContactTransfer, _add_first_contact
from transfers.well_transfer import WellTransferer


def _run_contact_transfer(pointids: list[str]):
wt = WellTransferer(pointids=pointids)
wt.transfer()
wt.transfer_parallel()

ct = ContactTransfer(pointids=pointids)
ct.transfer()
Expand Down Expand Up @@ -87,4 +90,131 @@ def test_owner_comment_absent_skips_notes():
assert note_count == 0


def test_ownerkey_fallback_name_when_name_and_org_missing(water_well_thing):
with session_ctx() as sess:
thing = sess.get(Thing, water_well_thing.id)
row = SimpleNamespace(
FirstName=None,
LastName=None,
OwnerKey="Fallback OwnerKey Name",
Email=None,
CtctPhone=None,
Phone=None,
CellPhone=None,
StreetAddress=None,
Address2=None,
City=None,
State=None,
Zip=None,
MailingAddress=None,
MailCity=None,
MailState=None,
MailZipCode=None,
PhysicalAddress=None,
PhysicalCity=None,
PhysicalState=None,
PhysicalZipCode=None,
)

# Should not raise "Either name or organization must be provided."
contact = _add_first_contact(
sess, row=row, thing=thing, organization=None, added=[]
)
sess.flush()

assert contact is not None
assert contact.name == "Fallback OwnerKey Name"
assert contact.organization is None


def test_ownerkey_dedupes_when_fallback_name_differs(water_well_thing):
owner_key = f"OwnerKey-{uuid4()}"
with session_ctx() as sess:
first_thing = sess.get(Thing, water_well_thing.id)
second_thing = Thing(
name=f"Second Well {uuid4()}",
thing_type="water well",
release_status="draft",
)
sess.add(second_thing)
sess.flush()

complete_row = SimpleNamespace(
FirstName="Casey",
LastName="Owner",
OwnerKey=owner_key,
Email=None,
CtctPhone=None,
Phone=None,
CellPhone=None,
StreetAddress=None,
Address2=None,
City=None,
State=None,
Zip=None,
MailingAddress=None,
MailCity=None,
MailState=None,
MailZipCode=None,
PhysicalAddress=None,
PhysicalCity=None,
PhysicalState=None,
PhysicalZipCode=None,
)
fallback_row = SimpleNamespace(
FirstName=None,
LastName=None,
OwnerKey=owner_key,
Email=None,
CtctPhone=None,
Phone=None,
CellPhone=None,
StreetAddress=None,
Address2=None,
City=None,
State=None,
Zip=None,
MailingAddress=None,
MailCity=None,
MailState=None,
MailZipCode=None,
PhysicalAddress=None,
PhysicalCity=None,
PhysicalState=None,
PhysicalZipCode=None,
)

added = []
first_contact = _add_first_contact(
sess, row=complete_row, thing=first_thing, organization=None, added=added
)
assert first_contact is not None
assert first_contact.name == "Casey Owner"

second_contact = _add_first_contact(
sess, row=fallback_row, thing=second_thing, organization=None, added=added
)
sess.flush()

# Reused existing contact; no duplicate fallback-name contact created.
assert second_contact is None
contacts = (
sess.query(Contact)
.filter(
Contact.nma_pk_owners == owner_key,
Contact.contact_type == "Primary",
)
.all()
)
assert len(contacts) == 1
assert contacts[0].name == "Casey Owner"

assoc_count = (
sess.query(ThingContactAssociation)
.filter(ThingContactAssociation.contact_id == contacts[0].id)
.count()
)
assert assoc_count == 2


# ============= EOF =============================================
38 changes: 34 additions & 4 deletions transfers/contact_transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ def _add_first_contact(
role = "Owner"
release_status = "private"

name = _make_name(row.FirstName, row.LastName)
name = _safe_make_name(row.FirstName, row.LastName, row.OwnerKey, organization)

contact_data = {
"thing_id": thing.id,
Expand Down Expand Up @@ -326,6 +326,19 @@ def _add_first_contact(
return contact


def _safe_make_name(
first: str | None, last: str | None, ownerkey: str, organization: str | None
) -> str | None:
name = _make_name(first, last)
if name is None and organization is None:
logger.warning(
f"Missing both first and last name and organization for OwnerKey {ownerkey}; "
f"using OwnerKey as fallback name."
)
return ownerkey
return name


def _add_second_contact(
session: Session, row: pd.Series, thing: Thing, organization: str, added: list
) -> None:
Expand Down Expand Up @@ -463,14 +476,31 @@ def _make_contact_and_assoc(
session: Session, data: dict, thing: Thing, added: list
) -> tuple[Contact, bool]:
new_contact = True
if (data["name"], data["organization"]) in added:
contact = None

# Prefer OwnerKey-based dedupe so fallback names don't split the same owner
# into multiple contacts when some rows have real names and others do not.
owner_key = data.get("nma_pk_owners")
contact_type = data.get("contact_type")
if owner_key and contact_type:
contact = (
session.query(Contact)
.filter_by(nma_pk_owners=owner_key, contact_type=contact_type)
.first()
)
if contact is not None:
new_contact = False

if contact is None and (data["name"], data["organization"]) in added:
contact = (
session.query(Contact)
.filter_by(name=data["name"], organization=data["organization"])
.first()
)
new_contact = False
else:
if contact is not None:
new_contact = False

if contact is None:

from schemas.contact import CreateContact

Expand Down