DEV Community

Cover image for Your entity resolution pipeline works on database records. Then you point it at news text.
Tae Kim
Tae Kim

Posted on Originally published at hannune.ai

Your entity resolution pipeline works on database records. Then you point it at news text.

I built the entity resolution pipeline at 2asy.ai against structured registry data. I benchmarked it against structured registry data. The match scores looked good. I moved on.

About a month later I started ingesting news articles. The first batch turned into two weeks of cleanup.

What I hadn't tested was what happens when the source text doesn't use formal company names. News doesn't. A Korean financial article calls "삼성전자 주식회사" just "삼성." A piece in English calls it "Samsung" without specifying which Samsung entity, because to a human reading with context, it's obvious which one. The pipeline had no reading context. It had a blocking key built on the first four characters of a normalized name.

"삼성" hits the same blocking bucket as Samsung Electronics, Samsung SDI, Samsung C&T, and a dozen other entities under the group umbrella. The EM scoring then gets candidate pairs with name similarity 1.0 across all of them and nothing else to work with.

ON LEFT(l.name_normalized, 4) = LEFT(r.name_normalized, 4)
Enter fullscreen mode Exit fullscreen mode

This was the blocking rule. It was designed for formal names where "Samsung Electronics" and "삼성전자" both normalize to something starting with "sams." It's also what causes a short news mention to block with every Samsung subsidiary in the registry.

What I added was a post-scoring filter using signals from the article itself. The article a mention comes from has context: what other entities appear nearby, what sector terms show up in the paragraph. When the top match by name similarity has no overlap with those signals, I look at the second-best match.

def extract_context_signals(article: dict, mention_span: tuple) -> dict:
    window = article["text"][max(0, mention_span[0]-200):mention_span[1]+200]
    return {
        "mentioned_with": extract_entity_mentions(window),
        "sector_keywords": extract_sector_terms(window),
        "article_category": article.get("category", "unknown"),
    }
Enter fullscreen mode Exit fullscreen mode

This helped with the Samsung group disambiguation specifically. It doesn't do much for companies that don't have siblings with similar names. And when the article context is thin — short breaking news items especially — the signals aren't reliable enough to use.

I also raised the confidence floor for what I'm calling informal mentions: short Korean-character forms, English names that don't include the entity's legal suffix. The threshold for a two-character Korean name merging to a registry entry is higher than for a full formal name match.

if mention_is_short_informal(mention_text, source_language):
    threshold = max(threshold, 0.90)
Enter fullscreen mode Exit fullscreen mode

The 0.90 came from watching the false-merge rate and adjusting until it dropped to something workable. It's not principled.

The benchmark I ran when building this is still the benchmark I'm running. It's all registry-vs-registry pairs. I don't have a labeled test set for news mentions, so I don't actually know what the precision-recall curve looks like for the domain I'm now running against daily. That's the part I'm not comfortable with.


I build er-api, a multilingual entity resolution service for Korean, Japanese, Chinese, and English corporate data, used in 2asy.ai for news-to-knowledge-graph ingestion. More at hannune.ai.

Top comments (0)