CI (SIAX Cloud) / security (push) Successful in 16s
Security Scan (SIAX Cloud) / security (pull_request) Successful in 27s
CI (SIAX Cloud) / integration (push) Successful in 31s
CI (SIAX Cloud) / integration (pull_request) Successful in 32s
Security Scan (SIAX Cloud) / security (push) Successful in 43s
CI (SIAX Cloud) / security (pull_request) Successful in 43s
CI (SIAX Cloud) / quality (pull_request) Successful in 53s
CI (SIAX Cloud) / quality (push) Successful in 55s
94 lines
3.2 KiB
Python
94 lines
3.2 KiB
Python
from workers.entity_resolution import (
|
|
EntityCandidate,
|
|
ResolutionDecision,
|
|
rank_resolution_candidates,
|
|
resolve_entity_pair,
|
|
)
|
|
|
|
|
|
def organization(entity_id: str, **overrides):
|
|
values = {
|
|
"entity_id": entity_id,
|
|
"entity_type": "organization",
|
|
"name": "Example Technology AB",
|
|
"source_ref": f"source:{entity_id}",
|
|
"registration_country": "SE",
|
|
"registration_number": None,
|
|
"website": None,
|
|
"city": "Stockholm",
|
|
}
|
|
values.update(overrides)
|
|
return EntityCandidate(**values)
|
|
|
|
|
|
def person(entity_id: str, **overrides):
|
|
values = {
|
|
"entity_id": entity_id,
|
|
"entity_type": "person",
|
|
"name": "Anna Andersson",
|
|
"source_ref": f"source:{entity_id}",
|
|
"email": None,
|
|
"organization_id": None,
|
|
}
|
|
values.update(overrides)
|
|
return EntityCandidate(**values)
|
|
|
|
|
|
def test_registration_number_is_a_strong_organization_identifier():
|
|
result = resolve_entity_pair(
|
|
organization("left", registration_number="556123-4567"),
|
|
organization("right", registration_number="5561234567"),
|
|
)
|
|
assert result.decision is ResolutionDecision.EXACT_LINK
|
|
assert result.score == 1.0
|
|
assert result.requires_human_review is False
|
|
|
|
|
|
def test_conflicting_registration_numbers_fail_distinct_even_when_names_match():
|
|
result = resolve_entity_pair(
|
|
organization("left", registration_number="5561234567"),
|
|
organization("right", registration_number="5569999999"),
|
|
)
|
|
assert result.decision is ResolutionDecision.DISTINCT
|
|
assert result.score == 0.0
|
|
|
|
|
|
def test_same_person_email_is_exact_link():
|
|
result = resolve_entity_pair(
|
|
person("left", email="Anna@example.com"),
|
|
person("right", email=" anna@EXAMPLE.com "),
|
|
)
|
|
assert result.decision is ResolutionDecision.EXACT_LINK
|
|
assert result.requires_human_review is False
|
|
|
|
|
|
def test_fuzzy_organization_similarity_never_auto_merges():
|
|
result = resolve_entity_pair(
|
|
organization("left", name="Example Technology AB", website="example.se"),
|
|
organization("right", name="Example Technologies AB", website="https://www.example.se"),
|
|
)
|
|
assert result.score > 0.72
|
|
assert result.decision is ResolutionDecision.REVIEW
|
|
assert result.requires_human_review is True
|
|
|
|
|
|
def test_different_entity_types_are_distinct():
|
|
result = resolve_entity_pair(
|
|
organization("org"),
|
|
person("person"),
|
|
)
|
|
assert result.decision is ResolutionDecision.DISTINCT
|
|
|
|
|
|
def test_candidate_ranking_prefers_exact_then_review_and_is_stable():
|
|
query = organization("query", registration_number="5561234567", website="example.se")
|
|
candidates = [
|
|
organization("weak", name="Completely Different Company", registration_country="NO", city="Oslo"),
|
|
organization("review", name="Example Technologies AB", website="example.se"),
|
|
organization("exact", registration_number="556123-4567"),
|
|
]
|
|
ranked = rank_resolution_candidates(query, candidates)
|
|
assert [result.right_id for result in ranked] == ["exact", "review", "weak"]
|
|
assert ranked[0].decision is ResolutionDecision.EXACT_LINK
|
|
assert ranked[1].decision is ResolutionDecision.REVIEW
|