File size: 4,564 Bytes
d4f8959
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# backend/entity_resolver.py
import re
from rapidfuzz import fuzz

LEGAL_SUFFIXES = [
    "incorporated", "corporation", "limited", "inc", "ltd", "llc", "corp",
    "co", "plc", "company", "industries", "group", "holdings"
]

KNOWN_ALIASES = {
    "apple": "Apple", "aapl": "Apple", "apple inc": "Apple",
    "microsoft": "Microsoft", "msft": "Microsoft",
    "amazon": "Amazon", "amzn": "Amazon", "amazoncom": "Amazon",
    "google": "Google", "alphabet": "Google", "googl": "Google",
    "tesla": "Tesla", "tsla": "Tesla",
    "3m": "3M",
    "boeing": "Boeing", "the boeing company": "Boeing",
    "amd": "AMD", "advanced micro devices": "AMD",
    "hdfc bank": "HDFC Bank", "hdfc": "HDFC Bank",
    "tcs": "TCS", "tata consultancy services": "TCS",
    "infosys": "Infosys", "infy": "Infosys",
    "reliance": "Reliance Industries", "reliance industries": "Reliance Industries", "ril": "Reliance Industries",
    "icici bank": "ICICI Bank", "icici": "ICICI Bank",
    "wipro": "Wipro",
    "hul": "Hindustan Unilever", "hindustan unilever": "Hindustan Unilever",
    "itc": "ITC",
    "l&t": "Larsen & Toubro", "larsen & toubro": "Larsen & Toubro", "larsen and toubro": "Larsen & Toubro",
    "bajaj finance": "Bajaj Finance",
    "tata motors": "Tata Motors",
    "maruti": "Maruti Suzuki", "maruti suzuki": "Maruti Suzuki",
    "asian paints": "Asian Paints",
    "sun pharma": "Sun Pharma", "sun pharmaceutical": "Sun Pharma",
    "axis bank": "Axis Bank",
    "kotak": "Kotak Mahindra Bank", "kotak mahindra bank": "Kotak Mahindra Bank",
    "titan": "Titan Company",
    "nestle": "Nestle India", "nestle india": "Nestle India",
    "ultratech": "UltraTech Cement", "ultratech cement": "UltraTech Cement",
    "sbi": "State Bank of India", "state bank of india": "State Bank of India",
}


def normalize_org_name(text: str) -> str:
    t = text.lower().strip()
    t = re.sub(r"[^\w\s&]", "", t)
    words = t.split()
    while words and words[-1] in LEGAL_SUFFIXES:
        words.pop()
    return " ".join(words).strip()


def resolve_entity(text: str, label: str, existing_orgs: list = None) -> str:
    """Returns canonical entity ID for graph node deduplication."""
    if label != "ORG":
        clean = re.sub(r"\s+", " ", text.strip())
        return f"{label}_{clean[:50]}"

    normalized = normalize_org_name(text)

    if normalized in KNOWN_ALIASES:
        return f"ORG_{KNOWN_ALIASES[normalized]}"

    if existing_orgs:
        best_match, best_score = None, 0
        for existing in existing_orgs:
            score = fuzz.token_sort_ratio(normalized, existing.lower())
            if score > best_score:
                best_score, best_match = score, existing
        if best_score >= 85:
            return f"ORG_{best_match}"

    return f"ORG_{normalized.title()}"


def format_money(value: float, currency: str = "USD") -> str:
    """Format a raw monetary value for display, currency-aware.

    Takes an explicit `currency` ("INR" or "USD") rather than guessing
    from the company name — guessing is unreliable (the same product
    handles both US and Indian filings, and even Indian companies
    sometimes report figures in USD). Callers should pass the
    `currency` tag that metrics_extractor.py now attaches to each
    extracted metric, e.g.:

        formatted = format_money(raw["value"], raw.get("currency", "USD"))

    INR values are shown in crore (and lakh crore for very large
    figures) since that's the unit Indian filings and their readers
    actually think in — converting to USD billions would require an
    exchange-rate assumption that goes stale and adds a layer of
    unnecessary approximation on top of an already-extracted figure.
    """
    if value is None:
        return "N/A"

    if currency == "INR":
        crore = value / 1_00_00_000  # 1 crore = 10,000,000
        if abs(crore) >= 1_00_000:  # >= 1 lakh crore
            return f"₹{crore / 1_00_000:.2f} Lakh Cr"
        return f"₹{crore:,.1f} Cr"

    # USD (default)
    if abs(value) >= 1_000_000_000:
        return f"${value / 1_000_000_000:.1f}B"
    if abs(value) >= 1_000_000:
        return f"${value / 1_000_000:.1f}M"
    return f"${value:,.2f}"


if __name__ == "__main__":
    existing = ["Apple", "HDFC Bank", "3M"]
    tests = [
        ("Apple Inc.", "ORG"), ("Apple", "ORG"), ("AAPL", "ORG"),
        ("HDFC Bank Limited", "ORG"), ("3M Company", "ORG"),
        ("$394.3 billion", "MONEY"),
    ]
    for text, label in tests:
        print(f"{text!r} ({label}) -> {resolve_entity(text, label, existing)}")