#!/usr/bin/env python3 """Generate config/sources.d/30-edgar-filings.yaml — SEC EDGAR submissions sensors (`edgar` connector) for a list of tickers. For each ticker the script fetches the submissions JSON once (name, website, SIC, exchanges), then either extends an existing source (matched by website apex domain, then by normalized name) or creates a new organization entry with the company's own website as domain. Usage: python3 scripts/gen-edgar.py fetch TICKER… # → /tmp/edgar_meta.json (cached, polite: ≤ 6 req/s) python3 scripts/gen-edgar.py emit > config/sources.d/30-edgar-filings.yaml Requires: `pip install pyyaml requests` (system python has both here). """ import glob import json import re import sys import time from urllib.parse import urlparse import requests import yaml UA = "WebSensor registry generator (contact@websensor.io)" META = "/tmp/edgar_meta.json" TICKERS = "/tmp/tickers.json" # https://www.sec.gov/files/company_tickers.json MULTI = {"co.uk", "com.au", "co.jp", "com.br", "co.il", "com.mx", "co.in", "com.tw", "co.kr"} STOP = {"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", "&"} def apex(host: str) -> str: parts = host.lower().strip(".").split(".") if len(parts) >= 3 and ".".join(parts[-2:]) in MULTI: return ".".join(parts[-3:]) return ".".join(parts[-2:]) def norm(name: str) -> str: words = re.sub(r"[^a-z0-9 ]+", " ", name.lower().replace("'", "")).split() return " ".join(w for w in words if w not in STOP) def slug(name: str) -> str: s = re.sub(r"[^a-z0-9]+", "-", norm(name)).strip("-") return s or re.sub(r"[^a-z0-9]+", "-", name.lower()).strip("-") def fetch(tickers: list[str]) -> None: try: meta = json.load(open(META)) except FileNotFoundError: meta = {} tk = {v["ticker"]: v for v in json.load(open(TICKERS)).values()} for t in tickers: if t in meta or t not in tk: continue cik = f"{int(tk[t]['cik_str']):010d}" r = requests.get(f"https://data.sec.gov/submissions/CIK{cik}.json", headers={"User-Agent": UA, "Accept": "application/json"}, timeout=30) if r.status_code != 200: print(f"{t}: HTTP {r.status_code}", file=sys.stderr) continue j = r.json() 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", []))} json.dump(meta, open(META, "w"), indent=1) time.sleep(0.2) print(f"{len(meta)} companies in {META}", file=sys.stderr) def load_ids() -> tuple[dict[str, str], dict[str, str]]: """id → domain, normalized name → id (founding file + fragments < 30).""" by_domain: dict[str, str] = {} by_name: dict[str, str] = {} files = ["config/sources.yaml"] + sorted(f for f in glob.glob("config/sources.d/*.yaml") if int(f.split("/")[-1][:2]) < 30) for f in files: d = yaml.safe_load(open(f)) or {} for s in d.get("sources", []): if s.get("extend"): continue by_domain.setdefault(apex(s["domain"]), s["id"]) by_name.setdefault(norm(s["name"]), s["id"]) return by_domain, by_name # Ticker → website domain when EDGAR has none (submissions JSON rarely carries `website`). DOMAINS = {"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"} # Ticker → existing id when domain/name matching is not enough. MANUAL = {"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"} def emit() -> None: meta = json.load(open(META)) by_domain, by_name = load_ids() all_ids = set(by_domain.values()) | set(by_name.values()) print("# WebSensor — SEC EDGAR filings sensor class (generated 2026-09-08 by scripts/gen-edgar.py from") 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…") print("# (insider forms 3/4/5 and 144 excluded). Existing organizations are extended; the others are created with") print("# their own website as domain. Tier B for mega-caps, C otherwise.") print("sources:") used: set[str] = set() n_ext = n_new = 0 for t, m in sorted(meta.items(), key=lambda kv: kv[1]["name"]): host = urlparse(m["website"] if m["website"].startswith("http") else "https://" + m["website"]).hostname if m["website"] else None ap = apex(host) if host else None target = MANUAL.get(t) if MANUAL.get(t) in all_ids else None if not target and ap and ap in by_domain: target = by_domain[ap] if not target and norm(m["name"]) in by_name: target = by_name[norm(m["name"])] if not target: # prefix match on normalized names ("costco wholesale" ↔ "costco"), ≥ 5 chars, unambiguous nm = norm(m["name"]) cands = [sid for n, sid in by_name.items() if len(n) >= 5 and (nm.startswith(n + " ") or nm == n or n.startswith(nm + " "))] if len(set(cands)) == 1: target = cands[0] if not host and t in DOMAINS: host = DOMAINS[t] ap = apex(host) if not target and ap in by_domain: target = by_domain[ap] 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"} }}' if target: if target in used: continue used.add(target) print(f" - id: {target}") print(" extend: true") print(" categories: [filings]") print(f" aliases: [{t.lower()}]") print(" sensors:") print(sensor) n_ext += 1 continue if not ap: print(f"# {t} ({m['name']}): no website in EDGAR and no DOMAINS entry — skipped", file=sys.stderr) continue sid = slug(m["name"]) if sid in all_ids or sid in used: sid = f"{sid}-{t.lower()}" used.add(sid) name = pretty_name(t, m["name"]) print(f" - id: {sid}") print(f" name: {json.dumps(name, ensure_ascii=False)}") print(f" domain: {ap}") print(f" homepage: https://{'www.' + host if host.count('.') == 1 else host}") print(f" categories: [filings, {sector(m.get('sic') or '')}]") print(f" tier: {'B' if t in MEGA else 'C'}") print(f" aliases: [{t.lower()}]") print(f" notes: \"SIC: {m.get('sic') or 'n/a'} · exchanges: {', '.join(m.get('exchanges') or [])} · CIK {int(m['cik'])}\"") print(" discover: { rss: true }") print(" sensors:") print(sensor) n_new += 1 print(f"{n_ext} extended, {n_new} new", file=sys.stderr) MEGA = 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()) NAMES = {"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"} def pretty_name(ticker: str, raw: str) -> str: if ticker in NAMES: return NAMES[ticker] n = re.sub(r"\s*/\s*[A-Z]{2}\s*/?\s*$", "", raw) # trailing state fragments "/DE/", "/MA/" n = re.sub(r"\s*/\s*(NEW|OLD)\s*/?\s*$", "", n, flags=re.I) words = [] for w in n.split(): lw = w.lower().strip(",.") if lw in ("inc", "corp", "co", "ltd", "plc", "nv", "n.v", "llc", "lp", "sa", "se", "ag"): 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]) elif w.isupper() and len(w) > 3: words.append(w.title()) else: words.append(w) return re.sub(r"\s+", " ", " ".join(words)).replace(" ,", ",").strip(" ,") def sector(sic: str) -> str: s = sic.lower() if any(k in s for k in ("pharma", "biolog", "medic", "surgical", "health", "hospital")): return "health" if any(k in s for k in ("bank", "finance", "insurance", "security", "invest", "loan", "credit")): return "finance" if any(k in s for k in ("computer", "software", "semiconductor", "electronic", "telephone", "communication", "data processing")): return "technology" if any(k in s for k in ("petroleum", "oil", "gas", "electric", "energy", "mining", "coal")): return "energy" if any(k in s for k in ("retail", "store", "restaurant", "apparel", "grocer", "eating")): return "retail" if any(k in s for k in ("motor", "aircraft", "air transport", "railroad", "truck", "ship")): return "transport" if any(k in s for k in ("real estate", "reit")): return "real-estate" if any(k in s for k in ("food", "beverage", "agricultur", "tobacco")): return "food" return "enterprise" if __name__ == "__main__": if sys.argv[1:2] == ["fetch"]: fetch(sys.argv[2:]) else: emit()