| |
| 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 |
| if abs(crore) >= 1_00_000: |
| return f"₹{crore / 1_00_000:.2f} Lakh Cr" |
| return f"₹{crore:,.1f} Cr" |
|
|
| |
| 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)}") |
|
|