Bug Description
With merge_entities=True and default settings, GraphBuilder merges two entities that share
no identifier, no type and no name. A Person named "Alice" and an Organization
named "Acme" are collapsed into a single entity, and the Organization is lost.
merge_entities=True is the value used in the Neo4j example in docs/quickstart.md, so this is
reachable by following the documentation.
This is distinct from #1085 / #1110 / #1111, which concern preserving entity_id aliases and
remapping endpoints when merging entities that genuinely are duplicates. Here the problem is
upstream of that: two entities that are not duplicates by any field are grouped as duplicates.
Steps to Reproduce
No graph store required — reproduces purely in memory:
from semantica.kg import GraphBuilder
entities = [
{"id": "e1", "type": "Person", "text": "Alice", "name": "Alice"},
{"id": "e2", "type": "Organization", "text": "Acme", "name": "Acme"},
]
relationships = [{"source_id": "e1", "target_id": "e2", "type": "knows"}]
for merge in (False, True):
g = GraphBuilder(merge_entities=merge).build(
{"entities": entities, "relationships": relationships}
)
ents = g.get("entities") or g.get("nodes") or []
print(merge, len(ents), [e.get("name") or e.get("text") for e in ents])
Expected Behavior
False 2 ['Alice', 'Acme']
True 2 ['Alice', 'Acme']
Two entities differing in id, type and name are not duplicates under any reasonable
similarity threshold.
Actual Behavior
False 2 ['Alice', 'Acme']
True 1 ['Alice']
The Organization entity is silently dropped. Progress output shows
DuplicateDetector creating one candidate pair and MergeStrategyManager merging via
keep_most_complete, so the pair is being scored as a duplicate rather than discarded by a
threshold.
Persisting the same input to Neo4j writes 1 node instead of 2, which is how this was first noticed.
Environment
- OS: macOS (Darwin 25.6.0, arm64)
- Python Version: 3.12.14
- Semantica Version: 0.6.5 (
origin/main @ 2a303cf)
- Installation Method: pipx (
pipx install "semantica[graph-neo4j]")
Notes / possible directions
- A type guard would catch this specific case cheaply: entities with different
type values are
rarely duplicates, and refusing to merge across types would be a conservative default.
- Worth checking what similarity the default
DuplicateDetector actually assigns to this pair —
if it is above threshold, the scoring may be dominated by a field that is empty in both
(both entities here have no properties), which would make short/sparse entities merge readily.
- Silently dropping an entity is a strong default. Even with merging enabled, reporting what was
merged into what (or requiring an explicit strategy) would make the behaviour auditable.
Happy to test a patch against this reproduction.
Bug Description
With
merge_entities=Trueand default settings,GraphBuildermerges two entities that shareno identifier, no type and no name. A
Personnamed "Alice" and anOrganizationnamed "Acme" are collapsed into a single entity, and the
Organizationis lost.merge_entities=Trueis the value used in the Neo4j example indocs/quickstart.md, so this isreachable by following the documentation.
This is distinct from #1085 / #1110 / #1111, which concern preserving
entity_idaliases andremapping endpoints when merging entities that genuinely are duplicates. Here the problem is
upstream of that: two entities that are not duplicates by any field are grouped as duplicates.
Steps to Reproduce
No graph store required — reproduces purely in memory:
Expected Behavior
Two entities differing in
id,typeandnameare not duplicates under any reasonablesimilarity threshold.
Actual Behavior
The
Organizationentity is silently dropped. Progress output showsDuplicateDetectorcreating one candidate pair andMergeStrategyManagermerging viakeep_most_complete, so the pair is being scored as a duplicate rather than discarded by athreshold.
Persisting the same input to Neo4j writes 1 node instead of 2, which is how this was first noticed.
Environment
origin/main@2a303cf)pipx install "semantica[graph-neo4j]")Notes / possible directions
typevalues arerarely duplicates, and refusing to merge across types would be a conservative default.
DuplicateDetectoractually assigns to this pair —if it is above threshold, the scoring may be dominated by a field that is empty in both
(both entities here have no
properties), which would make short/sparse entities merge readily.merged into what (or requiring an explicit strategy) would make the behaviour auditable.
Happy to test a patch against this reproduction.