Runner-up number fills the contact's secondary phone number 🟩

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Daniel Roth 2026-07-06 13:29:38 +00:00
parent 10636c1752
commit 52f2286ee9
2 changed files with 45 additions and 7 deletions

View file

@ -1,4 +1,5 @@
import xml.etree.ElementTree as ET
from dataclasses import dataclass
from datetime import date
from typing import List, Optional, Tuple, Union
@ -38,10 +39,45 @@ def _phone_priority(entry: ET.Element) -> Tuple[int, int]:
return (1, 0)
def _select_phone_number(entries: List[ET.Element]) -> Optional[str]:
usable = [entry for entry in entries if (entry.text or "").strip()]
ranked = sorted(usable, key=_phone_priority) # stable: ties keep document order
return (ranked[0].text or "").strip() if ranked else None
def _best_phone_number(entries: List[ET.Element]) -> Optional[ET.Element]:
ranked = sorted(entries, key=_phone_priority) # stable: ties keep document order
return ranked[0] if ranked else None
@dataclass(frozen=True)
class _SelectedPhoneNumbers:
mobile: Optional[str]
telephone: Optional[str]
secondary: Optional[str]
def _select_phone_numbers(tenant_element: ET.Element) -> _SelectedPhoneNumbers:
"""Best mobile, best landline, then the best of the leftovers of both kinds.
All three picks use the same rule: lowest numeric priority wins, entries
without a usable priority sort last, ties break by document order, blank
numbers are ignored.
"""
usable = [
entry
for entry in tenant_element
if entry.tag in ("Mobile", "Telephone") and (entry.text or "").strip()
]
best_mobile = _best_phone_number([e for e in usable if e.tag == "Mobile"])
best_telephone = _best_phone_number([e for e in usable if e.tag == "Telephone"])
runner_up = _best_phone_number(
[e for e in usable if e is not best_mobile and e is not best_telephone]
)
def _text(entry: Optional[ET.Element]) -> Optional[str]:
return (entry.text or "").strip() if entry is not None else None
return _SelectedPhoneNumbers(
mobile=_text(best_mobile),
telephone=_text(best_telephone),
secondary=_text(runner_up),
)
class AbriClient:
@ -211,12 +247,13 @@ class AbriClient:
def _parse_tenant(element: ET.Element) -> Tenant:
# Data minimisation: person_title, main-contact and the per-person
# reference (the element text) are deliberately not read.
numbers = _select_phone_numbers(element)
return Tenant(
forenames=(element.get("forenames") or "").strip(),
surname=(element.get("surname") or "").strip(),
mobile=_select_phone_number(element.findall("Mobile")),
telephone=_select_phone_number(element.findall("Telephone")),
secondary_number=None,
mobile=numbers.mobile,
telephone=numbers.telephone,
secondary_number=numbers.secondary,
vulnerabilities=(),
)

View file

@ -12,6 +12,7 @@ def _contact_properties(tenant: Tenant) -> Dict[str, str]:
"lastname": tenant.surname,
"mobilephone": tenant.mobile,
"phone": tenant.telephone,
"secondary_phone_number": tenant.secondary_number,
}
properties = {name: value for name, value in candidates.items() if value}
# Always written, so "not vulnerable" is distinguishable from "not synced".