TypeScript 55.4%
Python 43.2%
SQL 1.2%
1#!/usr/bin/env python32"""Generate config/sources.d/30-edgar-filings.yaml — SEC EDGAR submissions sensors (`edgar` connector) for a3list of tickers. For each ticker the script fetches the submissions JSON once (name, website, SIC, exchanges),4then either extends an existing source (matched by website apex domain, then by normalized name) or creates a5new organization entry with the company's own website as domain.67Usage:8 python3 scripts/gen-edgar.py fetch TICKER… # → /tmp/edgar_meta.json (cached, polite: ≤ 6 req/s)9 python3 scripts/gen-edgar.py emit > config/sources.d/30-edgar-filings.yaml10Requires: `pip install pyyaml requests` (system python has both here).11"""12import glob13import json14import re15import sys16import time17from urllib.parse import urlparse1819import requests20import yaml2122UA = "WebSensor registry generator (contact@websensor.io)"23META = "/tmp/edgar_meta.json"24TICKERS = "/tmp/tickers.json" # https://www.sec.gov/files/company_tickers.json25MULTI = {"co.uk", "com.au", "co.jp", "com.br", "co.il", "com.mx", "co.in", "com.tw", "co.kr"}26STOP = {"inc", "corp", "corporation", "co", "company", "ltd", "limited", "plc", "holdings", "holding", "group", "the", "nv", "sa", "se", "ag", "llc", "lp", "de", "class", "a", "b", "com", "trust", "n", "v", "&"}272829def apex(host: str) -> str:30 parts = host.lower().strip(".").split(".")31 if len(parts) >= 3 and ".".join(parts[-2:]) in MULTI:32 return ".".join(parts[-3:])33 return ".".join(parts[-2:])343536def norm(name: str) -> str:37 words = re.sub(r"[^a-z0-9 ]+", " ", name.lower().replace("'", "")).split()38 return " ".join(w for w in words if w not in STOP)394041def slug(name: str) -> str:42 s = re.sub(r"[^a-z0-9]+", "-", norm(name)).strip("-")43 return s or re.sub(r"[^a-z0-9]+", "-", name.lower()).strip("-")444546def fetch(tickers: list[str]) -> None:47 try:48 meta = json.load(open(META))49 except FileNotFoundError:50 meta = {}51 tk = {v["ticker"]: v for v in json.load(open(TICKERS)).values()}52 for t in tickers:53 if t in meta or t not in tk:54 continue55 cik = f"{int(tk[t]['cik_str']):010d}"56 r = requests.get(f"https://data.sec.gov/submissions/CIK{cik}.json", headers={"User-Agent": UA, "Accept": "application/json"}, timeout=30)57 if r.status_code != 200:58 print(f"{t}: HTTP {r.status_code}", file=sys.stderr)59 continue60 j = r.json()61 meta[t] = {"cik": cik, "name": j.get("name"), "website": j.get("website") or j.get("investorWebsite") or "", "sic": j.get("sicDescription"), "exchanges": j.get("exchanges"), "tickers": j.get("tickers"), "state": j.get("stateOfIncorporation"), "recent": len(((j.get("filings") or {}).get("recent") or {}).get("form", []))}62 json.dump(meta, open(META, "w"), indent=1)63 time.sleep(0.2)64 print(f"{len(meta)} companies in {META}", file=sys.stderr)656667def load_ids() -> tuple[dict[str, str], dict[str, str]]:68 """id → domain, normalized name → id (founding file + fragments < 30)."""69 by_domain: dict[str, str] = {}70 by_name: dict[str, str] = {}71 files = ["config/sources.yaml"] + sorted(f for f in glob.glob("config/sources.d/*.yaml") if int(f.split("/")[-1][:2]) < 30)72 for f in files:73 d = yaml.safe_load(open(f)) or {}74 for s in d.get("sources", []):75 if s.get("extend"):76 continue77 by_domain.setdefault(apex(s["domain"]), s["id"])78 by_name.setdefault(norm(s["name"]), s["id"])79 return by_domain, by_name808182# Ticker → website domain when EDGAR has none (submissions JSON rarely carries `website`).83DOMAINS = {"MMM": "3m.com", "ANF": "abercrombie.com", "AAP": "advanceautoparts.com", "AEO": "ae.com", "AXP": "americanexpress.com", "AMT": "americantower.com", "AMAT": "appliedmaterials.com", "ABG": "asburyauto.com", "ASML": "asml.com", "AN": "autonation.com", "AZO": "autozone.com", "ACN": "accenture.com", "APD": "airproducts.com", "ABNB": "airbnb.com", "ACI": "albertsonscompanies.com", "AA": "alcoa.com", "ADSK": "autodesk.com", "BALL": "ball.com", "BKR": "bakerhughes.com", "BBWI": "bbwinc.com", "BKNG": "bookingholdings.com", "BURL": "burlington.com", "KMX": "carmax.com", "CVNA": "carvana.com", "CAT": "caterpillar.com", "CBRE": "cbre.com", "CHTR": "charter.com", "CHD": "churchdwight.com", "CLF": "clevelandcliffs.com", "CLX": "thecloroxcompany.com", "CNH": "cnh.com", "KO": "coca-colacompany.com", "CL": "colgatepalmolive.com", "COP": "conocophillips.com", "ED": "conedison.com", "COST": "costco.com", "CCI": "crowncastle.com", "CCK": "crowncork.com", "CPRI": "capriholdings.com", "CARG": "cargurus.com", "LNG": "cheniere.com", "DE": "deere.com", "DG": "dollargeneral.com", "DLTR": "dollartree.com", "DOV": "dovercorporation.com", "DOW": "dow.com", "DASH": "doordash.com", "DD": "dupont.com", "ECL": "ecolab.com", "EMR": "emerson.com", "EOG": "eogresources.com", "EL": "elcompanies.com", "ETN": "eaton.com", "ET": "energytransfer.com", "EXPE": "expediagroup.com", "FIVE": "fivebelow.com", "FCX": "fcx.com", "GE": "geaerospace.com", "GIS": "generalmills.com", "GPC": "genpt.com", "GPI": "group1auto.com", "GOLD": "gold.com", "GRAB": "grab.com", "HAL": "halliburton.com", "HSY": "thehersheycompany.com", "HON": "honeywell.com", "HII": "hii.com", "HLT": "hilton.com", "HWM": "howmet.com", "ITW": "itw.com", "IP": "internationalpaper.com", "SJM": "jmsmucker.com", "KMB": "kimberly-clark.com", "KLAC": "kla.com", "KHC": "kraftheinzcompany.com", "LRCX": "lamresearch.com", "LIN": "linde.com", "LAD": "lithiainvestors.com", "LYFT": "lyft.com", "LYB": "lyondellbasell.com", "MAR": "marriott.com", "MCD": "mcdonalds.com", "MPC": "marathonpetroleum.com", "MDLZ": "mondelezinternational.com", "NEM": "newmont.com", "NKE": "nike.com", "NUE": "nucor.com", "ORLY": "oreillyauto.com", "OXY": "oxy.com", "OLLI": "ollies.us", "PKG": "packagingcorp.com", "PAG": "penskeautomotive.com", "PEP": "pepsico.com", "PPG": "ppg.com", "PG": "pg.com", "PLTR": "palantir.com", "PH": "parker.com", "PSX": "phillips66.com", "PSA": "publicstorage.com", "RL": "ralphlauren.com", "O": "realtyincome.com", "REGN": "regeneron.com", "RH": "rh.com", "ROK": "rockwellautomation.com", "ROST": "rossstores.com", "RTX": "rtx.com", "SRE": "sempra.com", "SHW": "sherwin-williams.com", "SPG": "simon.com", "SLB": "slb.com", "SAH": "sonicautomotive.com", "SBUX": "starbucks.com", "STLD": "steeldynamics.com", "TPR": "tapestry.com", "TXN": "ti.com", "TJX": "tjx.com", "TSCO": "tractorsupply.com", "TDG": "transdigm.com", "URBN": "urbn.com", "UBER": "uber.com", "ULTA": "ulta.com", "VLO": "valero.com", "VRTX": "vrtx.com", "VICI": "viciproperties.com", "WEC": "wecenergygroup.com", "WELL": "welltower.com", "WSM": "williams-sonomainc.com", "XEL": "xcelenergy.com", "UNH": "unitedhealthgroup.com", "XOM": "exxonmobil.com", "JNJ": "jnj.com", "HD": "homedepot.com", "WMT": "walmart.com", "CVX": "chevron.com", "LOW": "lowes.com", "TGT": "target.com", "KR": "thekrogerco.com", "BBY": "bestbuy.com", "EBAY": "ebayinc.com", "ETSY": "etsy.com", "W": "wayfair.com", "CHWY": "chewy.com", "DAL": "delta.com", "UAL": "united.com", "AAL": "aa.com", "LUV": "southwest.com", "JBLU": "jetblue.com", "ALK": "alaskaair.com", "LMT": "lockheedmartin.com", "NOC": "northropgrumman.com", "GD": "gd.com", "LHX": "l3harris.com", "BA": "boeing.com", "UNP": "up.com", "DUK": "duke-energy.com", "NEE": "nexteraenergy.com", "SO": "southerncompany.com", "D": "dominionenergy.com", "EXC": "exeloncorp.com", "AEP": "aep.com", "KMI": "kindermorgan.com", "WMB": "williams.com", "T": "att.com", "VZ": "verizon.com", "TMUS": "t-mobile.com", "CMCSA": "corporate.comcast.com", "DIS": "thewaltdisneycompany.com", "AVGO": "broadcom.com", "MDT": "medtronic.com", "SYK": "stryker.com", "BSX": "bostonscientific.com", "BDX": "bd.com", "EW": "edwards.com", "DXCM": "dexcom.com", "ISRG": "intuitive.com", "ZBH": "zimmerbiomet.com", "PLD": "prologis.com", "EQIX": "equinix.com", "DLR": "digitalrealty.com", "ABT": "abbott.com", "ABBV": "abbvie.com", "AMGN": "amgen.com", "BMY": "bms.com", "GILD": "gilead.com", "BNTX": "biontech.com", "CVS": "cvshealth.com", "CI": "thecignagroup.com", "HUM": "humana.com", "ELV": "elevancehealth.com", "TMO": "thermofisher.com", "LULU": "lululemon.com", "SNOW": "snowflake.com", "DDOG": "datadoghq.com", "TEAM": "atlassian.com", "WDAY": "workday.com", "INTU": "intuit.com", "ADBE": "adobe.com", "NET": "cloudflare.com", "MDB": "mongodb.com", "ANET": "arista.com", "PANW": "paloaltonetworks.com", "CRWD": "crowdstrike.com", "FTNT": "fortinet.com", "ZS": "zscaler.com", "SPGI": "spglobal.com", "GS": "goldmansachs.com", "MS": "morganstanley.com", "BLK": "blackrock.com", "JPM": "jpmorganchase.com", "BAC": "bankofamerica.com", "WFC": "wellsfargo.com", "C": "citigroup.com", "HOOD": "robinhood.com", "SOFI": "sofi.com", "AFRM": "affirm.com", "CTVA": "corteva.com", "CPB": "thecampbellscompany.com", "CAG": "conagrabrands.com", "MRNA": "modernatx.com", "PFE": "pfizer.com", "MRK": "merck.com", "LLY": "lilly.com", "AGCO": "agcocorp.com", "TSM": "tsmc.com", "ARM": "arm.com", "ORCL": "oracle.com", "IBM": "ibm.com", "NFLX": "netflix.com", "TSLA": "tesla.com", "F": "ford.com", "GM": "gm.com", "RIVN": "rivian.com", "LCID": "lucidmotors.com", "TM": "toyota.com", "HMC": "honda.com", "STLA": "stellantis.com", "V": "visa.com", "MA": "mastercard.com", "PYPL": "paypal.com", "COIN": "coinbase.com", "SHOP": "shopify.com", "CRM": "salesforce.com", "NOW": "servicenow.com", "AMD": "amd.com", "INTC": "intel.com", "QCOM": "qualcomm.com", "MU": "micron.com", "CSCO": "cisco.com", "META": "meta.com", "GOOGL": "abc.xyz", "AMZN": "amazon.com", "AAPL": "apple.com", "MSFT": "microsoft.com", "NVDA": "nvidia.com", "BRK-B": "berkshirehathaway.com", "ASML": "asml.com", "AXP": "americanexpress.com"}8485# Ticker → existing id when domain/name matching is not enough.86MANUAL = {"GOOGL": "google", "META": "meta-ai", "BRK-B": "berkshire-hathaway", "AMZN": "amazon", "TSM": "tsmc", "SQ": "block", "COIN": "coinbase", "SHOP": "shopify", "ARM": "arm", "CRM": "salesforce", "NOW": "servicenow", "MSFT": "microsoft", "AAPL": "apple", "NVDA": "nvidia", "AMD": "amd", "INTC": "intel", "QCOM": "qualcomm", "MU": "micron", "ORCL": "oracle-cloud", "IBM": "ibm-cloud", "NFLX": "netflix", "TSLA": "tesla", "F": "ford", "GM": "gm", "RIVN": "rivian", "LCID": "lucid", "TM": "toyota", "HMC": "honda", "STLA": "stellantis", "PFE": "pfizer", "MRNA": "moderna", "MRK": "merck", "LLY": "eli-lilly", "AZN": "astrazeneca", "NVS": "novartis", "GSK": "gsk", "SNY": "sanofi", "V": "visa", "MA": "mastercard", "PYPL": "paypal", "UBER": "uber", "ANET": "arista", "PANW": "palo-alto-networks", "CRWD": "crowdstrike", "FTNT": "fortinet", "NET": "cloudflare", "MDB": "mongodb", "TEAM": "atlassian", "DELL": "dell", "HPQ": "hp", "WDC": "western-digital", "STX": "seagate", "HOOD": "robinhood", "SNOW": "snowflake", "DDOG": "datadog", "ZS": "zscaler", "WDAY": "workday", "ADSK": "autodesk", "ADBE": "adobe", "INTU": "intuit", "CSCO": "cisco-security", "DIS": "disney", "CMCSA": "comcast", "T": "att", "VZ": "verizon", "TMUS": "t-mobile", "SPGI": "sp-global", "BLK": "blackrock", "GS": "goldman-sachs", "MS": "morgan-stanley", "JPM": "jpmorgan", "BAC": "bank-of-america", "WFC": "wells-fargo", "C": "citi"}878889def emit() -> None:90 meta = json.load(open(META))91 by_domain, by_name = load_ids()92 all_ids = set(by_domain.values()) | set(by_name.values())93 print("# WebSensor — SEC EDGAR filings sensor class (generated 2026-09-08 by scripts/gen-edgar.py from")94 print("# data.sec.gov submissions; validated live). One `edgar` sensor per issuer: 8-K/10-K/10-Q/S-1/6-K/13D/DEF 14A…")95 print("# (insider forms 3/4/5 and 144 excluded). Existing organizations are extended; the others are created with")96 print("# their own website as domain. Tier B for mega-caps, C otherwise.")97 print("sources:")98 used: set[str] = set()99 n_ext = n_new = 0100 for t, m in sorted(meta.items(), key=lambda kv: kv[1]["name"]):101 host = urlparse(m["website"] if m["website"].startswith("http") else "https://" + m["website"]).hostname if m["website"] else None102 ap = apex(host) if host else None103 target = MANUAL.get(t) if MANUAL.get(t) in all_ids else None104 if not target and ap and ap in by_domain:105 target = by_domain[ap]106 if not target and norm(m["name"]) in by_name:107 target = by_name[norm(m["name"])]108 if not target:109 # prefix match on normalized names ("costco wholesale" ↔ "costco"), ≥ 5 chars, unambiguous110 nm = norm(m["name"])111 cands = [sid for n, sid in by_name.items() if len(n) >= 5 and (nm.startswith(n + " ") or nm == n or n.startswith(nm + " "))]112 if len(set(cands)) == 1:113 target = cands[0]114 if not host and t in DOMAINS:115 host = DOMAINS[t]116 ap = apex(host)117 if not target and ap in by_domain:118 target = by_domain[ap]119 sensor = f' - {{ name: edgar filings, url: "https://data.sec.gov/submissions/CIK{m["cik"]}.json", type: REST_API, connector: edgar, tier: {"B" if t in MEGA else "C"} }}'120 if target:121 if target in used:122 continue123 used.add(target)124 print(f" - id: {target}")125 print(" extend: true")126 print(" categories: [filings]")127 print(f" aliases: [{t.lower()}]")128 print(" sensors:")129 print(sensor)130 n_ext += 1131 continue132 if not ap:133 print(f"# {t} ({m['name']}): no website in EDGAR and no DOMAINS entry — skipped", file=sys.stderr)134 continue135 sid = slug(m["name"])136 if sid in all_ids or sid in used:137 sid = f"{sid}-{t.lower()}"138 used.add(sid)139 name = pretty_name(t, m["name"])140 print(f" - id: {sid}")141 print(f" name: {json.dumps(name, ensure_ascii=False)}")142 print(f" domain: {ap}")143 print(f" homepage: https://{'www.' + host if host.count('.') == 1 else host}")144 print(f" categories: [filings, {sector(m.get('sic') or '')}]")145 print(f" tier: {'B' if t in MEGA else 'C'}")146 print(f" aliases: [{t.lower()}]")147 print(f" notes: \"SIC: {m.get('sic') or 'n/a'} · exchanges: {', '.join(m.get('exchanges') or [])} · CIK {int(m['cik'])}\"")148 print(" discover: { rss: true }")149 print(" sensors:")150 print(sensor)151 n_new += 1152 print(f"{n_ext} extended, {n_new} new", file=sys.stderr)153154155MEGA = set("AAPL MSFT GOOGL AMZN NVDA META TSLA BRK-B JPM V MA UNH XOM JNJ PG HD COST ABBV MRK CVX KO PEP WMT BAC CRM ORCL AMD INTC NFLX ADBE CSCO AVGO LLY TSM ASML".split())156157158NAMES = {"MMM": "3M", "ANF": "Abercrombie & Fitch", "AAP": "Advance Auto Parts", "AEO": "American Eagle Outfitters", "AXP": "American Express", "AMT": "American Tower", "AMAT": "Applied Materials", "ABG": "Asbury Automotive Group", "ASML": "ASML", "AN": "AutoNation", "AZO": "AutoZone", "ACN": "Accenture", "APD": "Air Products", "ACI": "Albertsons Companies", "ADSK": "Autodesk", "BALL": "Ball Corporation", "BKR": "Baker Hughes", "BBWI": "Bath & Body Works", "BKNG": "Booking Holdings", "BURL": "Burlington Stores", "KMX": "CarMax", "CVNA": "Carvana", "CAT": "Caterpillar", "CBRE": "CBRE Group", "CHTR": "Charter Communications", "CHD": "Church & Dwight", "CLF": "Cleveland-Cliffs", "CLX": "Clorox", "CNH": "CNH Industrial", "KO": "The Coca-Cola Company", "CL": "Colgate-Palmolive", "COP": "ConocoPhillips", "ED": "Consolidated Edison", "COST": "Costco Wholesale", "CCI": "Crown Castle", "CCK": "Crown Holdings", "CPRI": "Capri Holdings", "CARG": "CarGurus", "LNG": "Cheniere Energy", "DE": "Deere & Company", "DG": "Dollar General", "DLTR": "Dollar Tree", "DOV": "Dover Corporation", "DOW": "Dow", "DASH": "DoorDash", "DD": "DuPont", "ECL": "Ecolab", "EMR": "Emerson Electric", "EOG": "EOG Resources", "EL": "The Estée Lauder Companies", "ETN": "Eaton", "ET": "Energy Transfer", "EXPE": "Expedia Group", "FIVE": "Five Below", "FCX": "Freeport-McMoRan", "GE": "GE Aerospace", "GIS": "General Mills", "GPC": "Genuine Parts Company", "GPI": "Group 1 Automotive", "GOLD": "Gold.com", "GRAB": "Grab Holdings", "HAL": "Halliburton", "HSY": "The Hershey Company", "HON": "Honeywell", "HII": "Huntington Ingalls Industries", "HLT": "Hilton Worldwide", "HWM": "Howmet Aerospace", "ITW": "Illinois Tool Works", "IP": "International Paper", "SJM": "The J.M. Smucker Company", "KMB": "Kimberly-Clark", "KLAC": "KLA Corporation", "KHC": "Kraft Heinz", "LRCX": "Lam Research", "LIN": "Linde", "LAD": "Lithia Motors", "LYFT": "Lyft", "LYB": "LyondellBasell", "MAR": "Marriott International", "MCD": "McDonald's", "MPC": "Marathon Petroleum", "MDLZ": "Mondelez International", "NEM": "Newmont", "NKE": "Nike", "NUE": "Nucor", "ORLY": "O'Reilly Automotive", "OXY": "Occidental Petroleum", "OLLI": "Ollie's Bargain Outlet", "PKG": "Packaging Corporation of America", "PAG": "Penske Automotive Group", "PEP": "PepsiCo", "PPG": "PPG Industries", "PG": "Procter & Gamble", "PLTR": "Palantir Technologies", "PH": "Parker-Hannifin", "PSX": "Phillips 66", "PSA": "Public Storage", "RL": "Ralph Lauren", "O": "Realty Income", "REGN": "Regeneron Pharmaceuticals", "RH": "RH", "ROK": "Rockwell Automation", "ROST": "Ross Stores", "RTX": "RTX", "SRE": "Sempra", "SHW": "Sherwin-Williams", "SPG": "Simon Property Group", "SLB": "SLB", "SAH": "Sonic Automotive", "SBUX": "Starbucks", "STLD": "Steel Dynamics", "TPR": "Tapestry", "TXN": "Texas Instruments", "TJX": "TJX Companies", "TSCO": "Tractor Supply", "TDG": "TransDigm Group", "URBN": "Urban Outfitters", "UBER": "Uber", "ULTA": "Ulta Beauty", "VLO": "Valero Energy", "VRTX": "Vertex Pharmaceuticals", "VICI": "VICI Properties", "WEC": "WEC Energy Group", "WELL": "Welltower", "WSM": "Williams-Sonoma", "XEL": "Xcel Energy", "UNH": "UnitedHealth Group", "XOM": "ExxonMobil", "JNJ": "Johnson & Johnson", "HD": "The Home Depot", "WMT": "Walmart", "CVX": "Chevron", "LOW": "Lowe's", "TGT": "Target", "KR": "Kroger", "BBY": "Best Buy", "EBAY": "eBay", "W": "Wayfair", "CHWY": "Chewy", "DAL": "Delta Air Lines", "UAL": "United Airlines", "AAL": "American Airlines", "LUV": "Southwest Airlines", "JBLU": "JetBlue", "ALK": "Alaska Air Group", "LMT": "Lockheed Martin", "NOC": "Northrop Grumman", "GD": "General Dynamics", "LHX": "L3Harris", "BA": "Boeing", "UNP": "Union Pacific", "DUK": "Duke Energy", "NEE": "NextEra Energy", "SO": "Southern Company", "D": "Dominion Energy", "EXC": "Exelon", "AEP": "American Electric Power", "KMI": "Kinder Morgan", "WMB": "Williams Companies", "T": "AT&T", "VZ": "Verizon", "TMUS": "T-Mobile US", "CMCSA": "Comcast", "DIS": "The Walt Disney Company", "AVGO": "Broadcom", "MDT": "Medtronic", "SYK": "Stryker", "BSX": "Boston Scientific", "BDX": "BD", "EW": "Edwards Lifesciences", "DXCM": "Dexcom", "ISRG": "Intuitive Surgical", "ZBH": "Zimmer Biomet", "PLD": "Prologis", "EQIX": "Equinix", "DLR": "Digital Realty", "ABT": "Abbott", "ABBV": "AbbVie", "AMGN": "Amgen", "BMY": "Bristol Myers Squibb", "GILD": "Gilead Sciences", "BNTX": "BioNTech", "CVS": "CVS Health", "CI": "The Cigna Group", "HUM": "Humana", "ELV": "Elevance Health", "TMO": "Thermo Fisher Scientific", "LULU": "Lululemon", "AGCO": "AGCO Corporation", "BRK-B": "Berkshire Hathaway", "AA": "Alcoa", "ABNB": "Airbnb"}159160161def pretty_name(ticker: str, raw: str) -> str:162 if ticker in NAMES:163 return NAMES[ticker]164 n = re.sub(r"\s*/\s*[A-Z]{2}\s*/?\s*$", "", raw) # trailing state fragments "/DE/", "/MA/"165 n = re.sub(r"\s*/\s*(NEW|OLD)\s*/?\s*$", "", n, flags=re.I)166 words = []167 for w in n.split():168 lw = w.lower().strip(",.")169 if lw in ("inc", "corp", "co", "ltd", "plc", "nv", "n.v", "llc", "lp", "sa", "se", "ag"):170 words.append({"inc": "Inc.", "corp": "Corp.", "co": "Co.", "ltd": "Ltd.", "plc": "plc", "nv": "N.V.", "n.v": "N.V.", "llc": "LLC", "lp": "LP", "sa": "SA", "se": "SE", "ag": "AG"}[lw])171 elif w.isupper() and len(w) > 3:172 words.append(w.title())173 else:174 words.append(w)175 return re.sub(r"\s+", " ", " ".join(words)).replace(" ,", ",").strip(" ,")176177178def sector(sic: str) -> str:179 s = sic.lower()180 if any(k in s for k in ("pharma", "biolog", "medic", "surgical", "health", "hospital")):181 return "health"182 if any(k in s for k in ("bank", "finance", "insurance", "security", "invest", "loan", "credit")):183 return "finance"184 if any(k in s for k in ("computer", "software", "semiconductor", "electronic", "telephone", "communication", "data processing")):185 return "technology"186 if any(k in s for k in ("petroleum", "oil", "gas", "electric", "energy", "mining", "coal")):187 return "energy"188 if any(k in s for k in ("retail", "store", "restaurant", "apparel", "grocer", "eating")):189 return "retail"190 if any(k in s for k in ("motor", "aircraft", "air transport", "railroad", "truck", "ship")):191 return "transport"192 if any(k in s for k in ("real estate", "reit")):193 return "real-estate"194 if any(k in s for k in ("food", "beverage", "agricultur", "tobacco")):195 return "food"196 return "enterprise"197198199if __name__ == "__main__":200 if sys.argv[1:2] == ["fetch"]:201 fetch(sys.argv[2:])202 else:203 emit()204