Spaces:
Sleeping
Sleeping
Download pipeline/resolver.py from Shivam4096/Financial2API: direct link, hf CLI and curl.
- Browser
- Download file 14.9 kB
-
https://huggingface.co/spaces/Shivam4096/Financial2API/resolve/main/pipeline/resolver.py
- Command line
-
hf download hf://spaces/Shivam4096/Financial2API/pipeline/resolver.py
-
curl -L -o resolver.py https://huggingface.co/spaces/Shivam4096/Financial2API/resolve/main/pipeline/resolver.py
14.9 kB
| """ | |
| pipeline/resolver.py -- Resolve a company name or ticker to structured metadata. | |
| Source priority: | |
| 1. yahooquery (free, no key, excellent Indian stock support) | |
| 2. FMP API (if key available) | |
| 3. Mark as unlisted with clear message | |
| """ | |
| from __future__ import annotations | |
| import sys | |
| from pathlib import Path | |
| from typing import Optional | |
| sys.path.insert(0, str(Path(__file__).parent.parent)) | |
| from config import FMP_API_KEY, FMP_BASE, REQUEST_TIMEOUT | |
| import requests | |
| # -- CompanyMeta --------------------------------------------------------------- | |
| class CompanyMeta: | |
| def __init__(self, name, ticker, exchange, currency, sector, industry, | |
| country, description, website, mkt_cap, share_price, is_listed, | |
| source_note="", fiscal_year_end="March"): | |
| self.name = name | |
| self.ticker = ticker | |
| self.exchange = exchange | |
| self.currency = currency | |
| self.sector = sector | |
| self.industry = industry | |
| self.country = country | |
| self.description = description | |
| self.website = website | |
| self.mkt_cap = mkt_cap | |
| self.share_price = share_price | |
| self.is_listed = is_listed | |
| self.source_note = source_note | |
| self.fiscal_year_end = fiscal_year_end | |
| def to_dict(self): | |
| return self.__dict__.copy() | |
| # -- Indian company name -> NSE ticker map ------------------------------------- | |
| INDIAN_TICKERS = { | |
| # IT | |
| "tcs": "TCS.NS", "tata consultancy": "TCS.NS", "tata consultancy services": "TCS.NS", | |
| "infosys": "INFY.NS", "infy": "INFY.NS", | |
| "wipro": "WIPRO.NS", | |
| "hcl": "HCLTECH.NS", "hcl tech": "HCLTECH.NS", "hcl technologies": "HCLTECH.NS", | |
| "tech mahindra": "TECHM.NS", "techm": "TECHM.NS", | |
| "ltimindtree": "LTIM.NS", "lti mindtree": "LTIM.NS", | |
| "mphasis": "MPHASIS.NS", | |
| "persistent": "PERSISTENT.NS", | |
| "coforge": "COFORGE.NS", | |
| # Banking | |
| "hdfc bank": "HDFCBANK.NS", "hdfcbank": "HDFCBANK.NS", | |
| "icici bank": "ICICIBANK.NS", "icicibank": "ICICIBANK.NS", | |
| "axis bank": "AXISBANK.NS", "axisbank": "AXISBANK.NS", | |
| "sbi": "SBIN.NS", "state bank": "SBIN.NS", "state bank of india": "SBIN.NS", | |
| "kotak": "KOTAKBANK.NS", "kotak bank": "KOTAKBANK.NS", "kotak mahindra bank": "KOTAKBANK.NS", | |
| "yes bank": "YESBANK.NS", | |
| "indusind bank": "INDUSINDBK.NS", | |
| "bank of baroda": "BANKBARODA.NS", | |
| "pnb": "PNB.NS", "punjab national bank": "PNB.NS", | |
| "canara bank": "CANARABANK.NS", | |
| # Conglomerates / Energy | |
| "reliance": "RELIANCE.NS", "reliance industries": "RELIANCE.NS", "ril": "RELIANCE.NS", | |
| "tata": "TATAMOTORS.NS", | |
| "adani enterprises": "ADANIENT.NS", "adani ports": "ADANIPORTS.NS", | |
| "adani green": "ADANIGREEN.NS", "adani total gas": "ATGL.NS", | |
| "ongc": "ONGC.NS", | |
| "oil and natural gas": "ONGC.NS", | |
| "ioc": "IOC.NS", "indian oil": "IOC.NS", | |
| "bpcl": "BPCL.NS", "bharat petroleum": "BPCL.NS", | |
| "coal india": "COALINDIA.NS", | |
| # FMCG | |
| "hindustan unilever": "HINDUNILVR.NS", "hul": "HINDUNILVR.NS", | |
| "itc": "ITC.NS", | |
| "nestle india": "NESTLEIND.NS", "nestle": "NESTLEIND.NS", | |
| "britannia": "BRITANNIA.NS", | |
| "dabur": "DABUR.NS", | |
| "marico": "MARICO.NS", | |
| "godrej consumer": "GODREJCP.NS", "godrej": "GODREJCP.NS", | |
| "emami": "EMAMILTD.NS", | |
| # Pharma | |
| "sun pharma": "SUNPHARMA.NS", "sun pharmaceutical": "SUNPHARMA.NS", | |
| "cipla": "CIPLA.NS", | |
| "dr reddy": "DRREDDY.NS", "dr reddys": "DRREDDY.NS", | |
| "divi's labs": "DIVISLAB.NS", "divis": "DIVISLAB.NS", | |
| "aurobindo": "AUROPHARMA.NS", | |
| "lupin": "LUPIN.NS", | |
| "torrent pharma": "TORNTPHARM.NS", | |
| # Auto | |
| "maruti": "MARUTI.NS", "maruti suzuki": "MARUTI.NS", | |
| "tata motors": "TATAMOTORS.NS", | |
| "m&m": "M&M.NS", "mahindra": "M&M.NS", "mahindra and mahindra": "M&M.NS", | |
| "hero motocorp": "HEROMOTOCO.NS", "hero": "HEROMOTOCO.NS", | |
| "bajaj auto": "BAJAJ-AUTO.NS", | |
| "eicher": "EICHERMOT.NS", "royal enfield": "EICHERMOT.NS", | |
| "tvs motor": "TVSMOTOR.NS", | |
| # Finance (non-bank) | |
| "bajaj finance": "BAJFINANCE.NS", | |
| "bajaj finserv": "BAJAJFINSV.NS", | |
| "hdfc life": "HDFCLIFE.NS", | |
| "sbi life": "SBILIFE.NS", | |
| "lic": "LICI.NS", | |
| "icici prudential": "ICICIPRULI.NS", | |
| "muthoot": "MUTHOOTFIN.NS", | |
| # Infra / Utilities | |
| "larsen": "LT.NS", "l&t": "LT.NS", "larsen and toubro": "LT.NS", | |
| "ntpc": "NTPC.NS", | |
| "power grid": "POWERGRID.NS", | |
| "tata power": "TATAPOWER.NS", | |
| "adani power": "ADANIPOWER.NS", | |
| # Cement / Materials | |
| "ultratech": "ULTRACEMCO.NS", "ultratech cement": "ULTRACEMCO.NS", | |
| "shree cement": "SHREECEM.NS", | |
| "ambuja": "AMBUJACEM.NS", | |
| "acc": "ACC.NS", | |
| "grasim": "GRASIM.NS", | |
| "tata steel": "TATASTEEL.NS", | |
| "jsw steel": "JSWSTEEL.NS", | |
| "hindalco": "HINDALCO.NS", | |
| "vedanta": "VEDL.NS", | |
| "jspl": "JINDALSTEL.NS", | |
| # Paints / Consumer | |
| "asian paints": "ASIANPAINT.NS", | |
| "berger paints": "BERGEPAINT.NS", | |
| "pidilite": "PIDILITIND.NS", | |
| "havells": "HAVELLS.NS", | |
| "voltas": "VOLTAS.NS", | |
| "titan": "TITAN.NS", | |
| "tanishq": "TITAN.NS", | |
| # Retail / Consumer | |
| "dmart": "DMART.NS", "avenue supermarts": "DMART.NS", | |
| "trent": "TRENT.NS", | |
| "zomato": "ZOMATO.NS", | |
| "nykaa": "NYKAA.NS", | |
| "policybazaar": "POLICYBZR.NS", | |
| # Telecom | |
| "airtel": "BHARTIARTL.NS", "bharti airtel": "BHARTIARTL.NS", | |
| "jio": "RELIANCE.NS", # Jio is part of Reliance | |
| # IT Infrastructure | |
| "hcl infosystems": "HCLISEC.NS", | |
| "dixon": "DIXON.NS", | |
| } | |
| def _guess_ticker(query: str) -> Optional[str]: | |
| """Try to find an Indian NSE ticker for a given name/query.""" | |
| q = query.lower().strip() | |
| # Clean up standard suffixes for safer matching | |
| clean_q = q.replace(".ns", "").replace(".bo", "").replace(" ltd", "").replace(" limited", "").strip() | |
| # Exact match | |
| if clean_q in INDIAN_TICKERS: | |
| return INDIAN_TICKERS[clean_q] | |
| if q in INDIAN_TICKERS: | |
| return INDIAN_TICKERS[q] | |
| return None | |
| # -- yahooquery resolution ------------------------------------------------------- | |
| def _yq_resolve(ticker: str, log_fn=None) -> Optional[CompanyMeta]: | |
| """Try to resolve a ticker via yahooquery. Returns CompanyMeta or None.""" | |
| def log(msg): | |
| if log_fn: log_fn(msg) | |
| try: | |
| import yahooquery as yq | |
| t = yq.Ticker(ticker) | |
| # Yahooquery returns dicts with ticker as the key | |
| price_data = t.price.get(ticker, {}) | |
| profile_data = t.summary_profile.get(ticker, {}) | |
| if isinstance(price_data, str) or isinstance(profile_data, str): | |
| # Yahooquery sometimes returns strings like "No fundamentals data found" | |
| raise ValueError(f"yahooquery returned string instead of dict: {price_data}") | |
| name = price_data.get("longName") or price_data.get("shortName", "") | |
| if not name: | |
| raise ValueError(f"yahooquery returned empty name for {ticker}") | |
| exchange_code = price_data.get("exchange", "") | |
| currency = price_data.get("currency", "INR") | |
| exchange_map = { | |
| "NSI": "NSE (India)", "BSE": "BSE (India)", | |
| "NMS": "NASDAQ (USA)", "NYQ": "NYSE (USA)", | |
| "LSE": "LSE (UK)", | |
| } | |
| exchange_display = exchange_map.get(exchange_code, exchange_code or "Unknown") | |
| country = profile_data.get("country", "India") | |
| # Fiscal year end | |
| fy_end = "March" if currency == "INR" else "December" | |
| log(f" [OK] yahooquery: {name} | {ticker} | {exchange_display}") | |
| return CompanyMeta( | |
| name=name, | |
| ticker=ticker, | |
| exchange=exchange_display, | |
| currency=currency, | |
| sector=profile_data.get("sector", ""), | |
| industry=profile_data.get("industry", ""), | |
| country=country, | |
| description=profile_data.get("longBusinessSummary", ""), | |
| website=profile_data.get("website", ""), | |
| mkt_cap=price_data.get("marketCap"), | |
| share_price=price_data.get("regularMarketPrice"), | |
| is_listed=True, | |
| source_note="Data sourced from Yahoo Finance (yahooquery).", | |
| fiscal_year_end=fy_end, | |
| ) | |
| except Exception as e: | |
| log(f" [ERR] yahooquery failed for {ticker}: {e}") | |
| # FALLBACK: If yahooquery fails, try screener.in for Indian tickers | |
| if ticker.endswith(".NS") or ticker.endswith(".BO"): | |
| log(f" -> Trying screener.in fallback for {ticker}...") | |
| try: | |
| base = ticker.split(".")[0].upper() | |
| import requests | |
| from bs4 import BeautifulSoup | |
| r = requests.get(f"https://www.screener.in/company/{base}/", headers={"User-Agent": "Mozilla/5.0"}, timeout=10) | |
| if r.ok: | |
| soup = BeautifulSoup(r.text, "lxml") | |
| h1 = soup.find("h1") | |
| if h1: | |
| name = h1.get_text(strip=True) | |
| desc_div = soup.find("div", class_="company-profile") | |
| desc = desc_div.get_text(strip=True) if desc_div else "" | |
| log(f" [OK] screener.in resolved: {name}") | |
| return CompanyMeta( | |
| name=name, ticker=ticker, exchange="NSE/BSE (India)", | |
| currency="INR", sector="Unknown", industry="Unknown", | |
| country="India", description=desc[:500], website="", | |
| mkt_cap=None, share_price=None, is_listed=True, | |
| source_note="Data sourced from screener.in (yahooquery fallback).", | |
| fiscal_year_end="March" | |
| ) | |
| except Exception as e2: | |
| log(f" [ERR] screener.in fallback failed: {e2}") | |
| return None | |
| # -- FMP resolution ------------------------------------------------------------ | |
| def _fmp_get(path: str, params: dict | None = None): | |
| if not FMP_API_KEY: | |
| return None | |
| url = f"{FMP_BASE}{path}" | |
| p = {"apikey": FMP_API_KEY} | |
| if params: p.update(params) | |
| try: | |
| r = requests.get(url, params=p, timeout=REQUEST_TIMEOUT) | |
| r.raise_for_status() | |
| return r.json() | |
| except Exception: | |
| return None | |
| def _fmp_resolve(query: str, log_fn=None) -> Optional[CompanyMeta]: | |
| """Try FMP search for global companies.""" | |
| def log(msg): | |
| if log_fn: log_fn(msg) | |
| if not FMP_API_KEY: | |
| return None | |
| log(" -> Searching FMP ...") | |
| # Direct profile | |
| profile = _fmp_get(f"/v3/profile/{query.upper()}") | |
| if not (profile and isinstance(profile, list) and profile[0].get("symbol")): | |
| # Search | |
| results = _fmp_get("/v3/search", {"query": query, "limit": 5}) | |
| if results: | |
| best = results[0] | |
| profile = _fmp_get(f"/v3/profile/{best['symbol']}") | |
| if not (profile and isinstance(profile, list)): | |
| return None | |
| p = profile[0] | |
| if not p.get("companyName"): | |
| return None | |
| exchange = p.get("exchange", "") or p.get("exchangeShortName", "") | |
| exchange_map = {"NSE":"NSE (India)","BSE":"BSE (India)","NASDAQ":"NASDAQ (USA)","NYSE":"NYSE (USA)"} | |
| log(f" [OK] FMP: {p.get('companyName')} ({p.get('symbol')})") | |
| return CompanyMeta( | |
| name=p.get("companyName",""), | |
| ticker=p.get("symbol",""), | |
| exchange=exchange_map.get(exchange, exchange), | |
| currency=p.get("currency","USD"), | |
| sector=p.get("sector",""), | |
| industry=p.get("industry",""), | |
| country=p.get("country",""), | |
| description=p.get("description",""), | |
| website=p.get("website",""), | |
| mkt_cap=p.get("mktCap"), | |
| share_price=p.get("price"), | |
| is_listed=True, | |
| source_note="Data sourced from FMP.", | |
| fiscal_year_end="December", | |
| ) | |
| # -- yahooquery search by company name ------------------------------------------ | |
| def _yq_search(query: str, log_fn=None) -> Optional[CompanyMeta]: | |
| """Use yahooquery's search to find a ticker from a company name.""" | |
| def log(msg): | |
| if log_fn: log_fn(msg) | |
| try: | |
| import yahooquery as yq | |
| log(f" -> yahooquery search for '{query}' ...") | |
| results = yq.search(query) | |
| quotes = results.get("quotes", []) | |
| if not quotes: | |
| return None | |
| # Take the first relevant result | |
| for q in quotes: | |
| symbol = q.get("symbol", "") | |
| qtype = q.get("quoteType", "") | |
| if qtype in ("EQUITY", "MUTUALFUND") and symbol: | |
| log(f" -> Found: {q.get('longname', symbol)} ({symbol})") | |
| return _yq_resolve(symbol, log_fn) | |
| return None | |
| except Exception as e: | |
| log(f" [ERR] yahooquery search failed: {e}") | |
| return None | |
| # -- Public resolve() ---------------------------------------------------------- | |
| def resolve(query: str, log_fn=None) -> CompanyMeta: | |
| """ | |
| Resolve a company name or ticker to CompanyMeta. | |
| Source priority: Indian map -> yahooquery direct -> yahooquery search -> FMP -> unlisted | |
| """ | |
| def log(msg): | |
| if log_fn: log_fn(msg) | |
| query = query.strip() | |
| log(f"Resolving: '{query}' ...") | |
| # 1. Check Indian name map -> resolve via yahooquery | |
| indian_ticker = _guess_ticker(query) | |
| if indian_ticker: | |
| log(f" [OK] Matches known Indian map -> {indian_ticker}") | |
| meta = _yq_resolve(indian_ticker, log_fn) | |
| if meta: return meta | |
| # 2. Try direct yahooquery (query as-is, and with .NS suffix) | |
| for candidate in [query.upper(), f"{query.upper()}.NS", f"{query.upper()}.BO"]: | |
| log(f" -> Trying direct ticker: {candidate}") | |
| meta = _yq_resolve(candidate, log_fn) | |
| if meta: return meta | |
| # 3. yahooquery search | |
| meta = _yq_search(query, log_fn) | |
| if meta: return meta | |
| # 4. FMP Search (for global companies) | |
| meta = _fmp_resolve(query, log_fn) | |
| if meta: | |
| return meta | |
| # 5. Unlisted fallback | |
| log(f" ⚠ '{query}' not found in any public market database.") | |
| log(" -> Treating as unlisted / private company.") | |
| return CompanyMeta( | |
| name=query, ticker="UNLISTED", exchange="Unlisted / Private", | |
| currency="INR", sector="Unknown", industry="Unknown", | |
| country="India", description="", website="", | |
| mkt_cap=None, share_price=None, is_listed=False, | |
| source_note=( | |
| f"'{query}' could not be found. It may be unlisted, private, or use a different name. " | |
| "Try using the exact BSE/NSE ticker (e.g. RELIANCE, TCS, HDFCBANK)." | |
| ), | |
| fiscal_year_end="March", | |
| ) | |