main
py 56 lines 2.21 KB
Raw
1 from __future__ import annotations
2
3 import re
4 from urllib.parse import urlparse
5
6 from app.models import CompanyResearchProfile, EntityResolution
7
8
9 class EntityResolver:
10 def __init__(self, profiles: list[CompanyResearchProfile]):
11 self.profiles = profiles
12
13 def resolve(self, title: str | None, text: str, url: str) -> EntityResolution:
14 haystack = f"{title or ''} {text}".lower()
15 host = (urlparse(url).hostname or "").lower()
16 best = EntityResolution(instrument_id=None, company_id=None, confidence=0.0, matched_on=[])
17 for profile in self.profiles:
18 score = 0.0
19 matched: list[str] = []
20 if profile.isin and profile.isin.lower() in haystack:
21 score += 0.45
22 matched.append("isin")
23 if _contains_identity(haystack, profile.company_name):
24 score += 0.30
25 matched.append("company_name")
26 for alias in profile.aliases:
27 if _contains_identity(haystack, alias):
28 score += 0.30 if len(alias.strip()) >= 4 else 0.18
29 matched.append("alias")
30 break
31 if _contains_identity(haystack, profile.ticker) and _contains_identity(haystack, profile.exchange):
32 score += 0.18
33 matched.append("ticker_exchange")
34 if _contains_identity(haystack, profile.country):
35 score += 0.04
36 matched.append("country")
37 if any(host.endswith(domain.lower()) for domain in profile.known_domains):
38 score += 0.35
39 matched.append("known_domain")
40 score = min(score, 1.0)
41 if score > best.confidence:
42 best = EntityResolution(
43 instrument_id=profile.instrument_id,
44 company_id=profile.company_id,
45 confidence=score,
46 matched_on=matched,
47 )
48 return best
49
50
51 def _contains_identity(haystack: str, value: str) -> bool:
52 identity = value.strip().lower()
53 if not identity:
54 return False
55 pattern = r"(?<![a-z0-9])" + re.escape(identity) + r"(?![a-z0-9])"
56 return re.search(pattern, haystack) is not None