Implement address quality and reversible contact merges
This commit is contained in:
@@ -43,6 +43,10 @@ from govoplan_addresses.backend.provider_state import (
|
||||
|
||||
|
||||
_addresses_table_retirement_provider = drop_table_retirement_provider(
|
||||
addresses_models.ContactFieldProvenance,
|
||||
addresses_models.ContactRedirect,
|
||||
addresses_models.ContactMergeRecord,
|
||||
addresses_models.ContactPointQualityDecision,
|
||||
addresses_models.ContactPointSnapshot,
|
||||
addresses_models.AddressSyncDiagnostic,
|
||||
addresses_models.AddressSyncConflict,
|
||||
@@ -154,6 +158,8 @@ def _tenant_summary(session, tenant_id: str) -> dict[str, int]:
|
||||
AddressList,
|
||||
AddressSyncSource,
|
||||
Contact,
|
||||
ContactMergeRecord,
|
||||
ContactPointQualityDecision,
|
||||
ContactPointSnapshot,
|
||||
)
|
||||
|
||||
@@ -161,6 +167,8 @@ def _tenant_summary(session, tenant_id: str) -> dict[str, int]:
|
||||
"address_books": session.query(AddressBook).filter(AddressBook.tenant_id == tenant_id, AddressBook.deleted_at.is_(None)).count(),
|
||||
"address_lists": session.query(AddressList).filter(AddressList.tenant_id == tenant_id, AddressList.deleted_at.is_(None)).count(),
|
||||
"contacts": session.query(Contact).filter(Contact.tenant_id == tenant_id, Contact.deleted_at.is_(None)).count(),
|
||||
"active_contact_merges": session.query(ContactMergeRecord).filter(ContactMergeRecord.tenant_id == tenant_id, ContactMergeRecord.status == "active").count(),
|
||||
"contact_quality_decisions": session.query(ContactPointQualityDecision).filter(ContactPointQualityDecision.tenant_id == tenant_id).count(),
|
||||
"contact_point_snapshots": session.query(ContactPointSnapshot).filter(ContactPointSnapshot.tenant_id == tenant_id).count(),
|
||||
"sync_sources": session.query(AddressSyncSource).filter(AddressSyncSource.tenant_id == tenant_id, AddressSyncSource.enabled.is_(True)).count(),
|
||||
}
|
||||
@@ -286,6 +294,10 @@ manifest = ModuleManifest(
|
||||
addresses_models.AddressSyncSource,
|
||||
addresses_models.AddressListEntry,
|
||||
addresses_models.AddressList,
|
||||
addresses_models.ContactFieldProvenance,
|
||||
addresses_models.ContactRedirect,
|
||||
addresses_models.ContactMergeRecord,
|
||||
addresses_models.ContactPointQualityDecision,
|
||||
addresses_models.ContactPointSnapshot,
|
||||
addresses_models.AddressBook,
|
||||
addresses_models.Contact,
|
||||
@@ -328,6 +340,25 @@ manifest = ModuleManifest(
|
||||
related_modules=("dist_lists", "campaigns", "policy", "templates"),
|
||||
order=31,
|
||||
),
|
||||
DocumentationTopic(
|
||||
id="addresses.quality-and-merge",
|
||||
title="Contact quality, duplicates, and reversible merges",
|
||||
summary="Review address quality and duplicate suggestions without losing source evidence.",
|
||||
body=(
|
||||
"Addresses preserves original and normalized contact-point values, records field-level provenance, "
|
||||
"and projects invalid, returned, stale, or undeliverable states into recipient resolution with stable "
|
||||
"reason codes. Duplicate suggestions are bounded and explain their matching features. An operator can "
|
||||
"choose the surviving values, merge contact points, and later undo or split the merge while the recorded "
|
||||
"post-merge evidence still matches. Contact redirects keep stored references resolvable, and address-list "
|
||||
"memberships are repaired transactionally. Audit remains an optional integration; the Addresses change "
|
||||
"sequence and merge evidence are always retained."
|
||||
),
|
||||
layer="configured",
|
||||
documentation_types=("admin", "user"),
|
||||
audience=("tenant_admin", "operator", "module_admin"),
|
||||
related_modules=("campaigns", "dist_lists", "policy", "audit"),
|
||||
order=32,
|
||||
),
|
||||
),
|
||||
external_providers=(CARDDAV_PROVIDER,),
|
||||
external_provider_state_providers=(
|
||||
|
||||
Reference in New Issue
Block a user