def collect_premises_validation_errors(propertydata: dict | None) → list[dict]:
"""Collect premises rows that repeat the same address for the same subject.
Premises rows are linked to locations by street address, so one address
legitimately carries many rows - one per subject of insurance. A duplicate
is therefore the \*same address with the same subject\*; rows without an
address fall back to the legacy \`\`premises_number\`\` / \`\`building_number\`\`
identity. Every row sharing a duplicated key is reported (including the
first occurrence), so the preview can surface all offending rows.
Rows carrying neither an address nor a full number pair have no identity to
compare and are skipped; \`\`collect_cross_data_premises_validation_errors\`\`
owns the "this row cannot be matched to a location" check.
"""
errors: list[dict] = []
groups: dict[tuple[str, str, str], list[str]] = {}
for key, row in _iter_property_premises_entries(propertydata):
address = normalize_address_text(premises_address_text(row))
premises_number = _premises_number_from_row(row)
building_number = _building_number_from_row(row)
subject = _subject_from_row(row)
if address:
group_key = (address, “”, subject)
elif premises_number and building_number:
group_key = (premises_number, building_number, subject)
else:
# Partial legacy identity - never treated as a duplicate.
continue
groups.setdefault(group_key, []).append(key)
for (first, second, subject), keys in groups.items():
if len(keys) < 2:
continue
identity = (
f"address={first!r}"
if not second
else f"premises_number={first!r} building_number={second!r}"
)
for key in keys:
other_keys = ", ".join(k for k in keys if k != key)
errors.append(
_error_entry(
“DUPLICATE_PREMISES_BUILDING”,
field=f"property.premises.{key}",
message=(
f"Duplicate {identity} subject_of_insurance={subject!r} "
f"(also used at property.premises.{other_keys})."
),
)
)
return errors