SPB Git forge
38commits 1branches 0releases
338.7 MBsize
maindefault branch
6 h agolast push
HTML 53.9% TypeScript 44.5% JavaScript 0.6% SQL 0.5%
21.5 KB · 248 lines typescript
Raw Blame History
1import type { OperatorKind } from "@dci/core";23/**4 * Curated operator lexicon used by the news / planning parsers to recognise who is building, buying or5 * operating a data center. Canonical `name` is what we emit; `aliases` are matched with word boundaries6 * (case-sensitive for proper nouns and acronyms so that "Switch" or "Meta" cannot match ordinary words).7 * An alias may be a RegExp when a plain word is too ambiguous. Order = priority when several match at the8 * same position (hyperscalers first).9 */10export interface OperatorEntry {11  name: string;12  aliases: Array<string | RegExp>;13  kind: OperatorKind;14  hq?: string; // ISO-2 of headquarters, informative only15}1617/**18 * Words that may follow an ambiguous company name in a headline or sentence ("Meta scraps…", "Aligned delays…",19 * "Switch to build…"). Used as a lookahead so plain English words are never mistaken for operators.20 */21const VERBS = "is|has|had|will|would|could|may|might|plans?|planned|planning|announced?|announces|announcing|to|data|campus|said|says|and|the|for|in|–|-|—|AI|Platforms|scraps|scrapped|cancels|cancelled|canceled|pulls|drops|dropped|buys|bought|acquires|acquired|breaks|broke|opens|opened|unveils|unveiled|confirms|confirmed|signs|signed|secures|secured|wins|won|expands|expanded|invests|invested|files|filed|seeks|sought|eyes|eyed|eyeing|mulls|weighs|reportedly|also|now|just|wants|looks|set|slated|expected|delays|delayed|postpones|pauses|paused|completes|completed|begins|began|starts|started|launches|launched|picks|picked|selects|selected|commits|committed|pledges|pledged|reveals|revealed|considers|considering|targets|targeting|inks|inked|lands|landed|leases|leased|proposes|proposed|submits|submitted|receives|received|gets|got|halts|halted|abandons|abandoned|revives|revived|doubles|adds|added|brings|brought|powers|powered|taps|tapped|partners|partnered|teams|teamed|moves|moved|readies|prepares|preparing|weighing|exploring|explores|explored|CEO|CFO|CTO|president|spokesperson|executive|shares|stock";22const after = (name: string) => new RegExp(`${name}(?:'s)?(?= (?:${VERBS})\\b)`);2324export const OPERATORS: OperatorEntry[] = [25  // ─── Hyperscalers / cloud ───────────────────────────────────────────────────────────────────────26  { name: "Amazon Web Services", aliases: ["Amazon Web Services", "AWS", after("Amazon(?:\\.com)?"), "Amazon Web Services'", "Amazon Data Services"], kind: "hyperscaler", hq: "US" },27  { name: "Microsoft", aliases: ["Microsoft", "Microsoft Azure", "Azure"], kind: "hyperscaler", hq: "US" },28  { name: "Google", aliases: ["Google", "Google Cloud", "Alphabet Inc", "Alphabet's"], kind: "hyperscaler", hq: "US" },29  { name: "Meta", aliases: ["Meta Platforms", "Facebook", after("Meta")], kind: "hyperscaler", hq: "US" },30  { name: "Apple", aliases: ["Apple Inc", after("Apple")], kind: "hyperscaler", hq: "US" },31  { name: "Oracle", aliases: ["Oracle", "Oracle Cloud", "Oracle Cloud Infrastructure", "OCI"], kind: "hyperscaler", hq: "US" },32  { name: "IBM", aliases: ["IBM", "IBM Cloud"], kind: "cloud", hq: "US" },33  { name: "Alibaba Cloud", aliases: ["Alibaba Cloud", "Alibaba", "Aliyun"], kind: "hyperscaler", hq: "CN" },34  { name: "Tencent Cloud", aliases: ["Tencent Cloud", "Tencent"], kind: "hyperscaler", hq: "CN" },35  { name: "Huawei Cloud", aliases: ["Huawei Cloud", "Huawei"], kind: "cloud", hq: "CN" },36  { name: "Baidu", aliases: ["Baidu", "Baidu AI Cloud"], kind: "cloud", hq: "CN" },37  { name: "ByteDance", aliases: ["ByteDance", "TikTok"], kind: "enterprise", hq: "CN" },38  { name: "OpenAI", aliases: ["OpenAI"], kind: "enterprise", hq: "US" },39  { name: "xAI", aliases: ["xAI", "X.AI"], kind: "enterprise", hq: "US" },40  { name: "Anthropic", aliases: ["Anthropic"], kind: "enterprise", hq: "US" },41  { name: "CoreWeave", aliases: ["CoreWeave"], kind: "cloud", hq: "US" },42  { name: "Lambda", aliases: ["Lambda Labs", after("Lambda")], kind: "cloud", hq: "US" },43  { name: "Crusoe", aliases: ["Crusoe", "Crusoe Energy"], kind: "developer", hq: "US" },44  { name: "Nebius", aliases: ["Nebius"], kind: "cloud", hq: "NL" },45  { name: "Nscale", aliases: ["Nscale"], kind: "cloud", hq: "GB" },46  { name: "Fluidstack", aliases: ["Fluidstack"], kind: "cloud", hq: "GB" },47  { name: "Voltage Park", aliases: ["Voltage Park"], kind: "cloud", hq: "US" },48  { name: "Vultr", aliases: ["Vultr"], kind: "cloud", hq: "US" },49  { name: "DigitalOcean", aliases: ["DigitalOcean"], kind: "cloud", hq: "US" },50  { name: "OVHcloud", aliases: ["OVHcloud", "OVH"], kind: "cloud", hq: "FR" },51  { name: "Scaleway", aliases: ["Scaleway"], kind: "cloud", hq: "FR" },52  { name: "Hetzner", aliases: ["Hetzner"], kind: "cloud", hq: "DE" },53  { name: "IONOS", aliases: ["IONOS"], kind: "cloud", hq: "DE" },54  { name: "Akamai", aliases: ["Akamai", "Linode"], kind: "cloud", hq: "US" },55  { name: "Cloudflare", aliases: ["Cloudflare"], kind: "cloud", hq: "US" },56  { name: "Salesforce", aliases: ["Salesforce"], kind: "enterprise", hq: "US" },57  { name: "SAP", aliases: ["SAP SE", after("SAP")], kind: "enterprise", hq: "DE" },58  { name: "Nvidia", aliases: ["Nvidia", "NVIDIA"], kind: "enterprise", hq: "US" },59  { name: "Deutsche Telekom", aliases: ["Deutsche Telekom", "T-Systems"], kind: "carrier", hq: "DE" },60  { name: "Tata Communications", aliases: ["Tata Communications"], kind: "carrier", hq: "IN" },61  { name: "Singtel", aliases: ["Singtel", "Nxera"], kind: "carrier", hq: "SG" },62  { name: "KDDI", aliases: ["KDDI", "Telehouse"], kind: "carrier", hq: "JP" },63  { name: "NTT", aliases: ["NTT Global Data Centers", "NTT Data", "NTT DATA", "NTT Ltd", "NTT Communications", "NTT"], kind: "colocation", hq: "JP" },6465  // ─── Colocation / wholesale — Americas ──────────────────────────────────────────────────────────66  { name: "Equinix", aliases: ["Equinix"], kind: "colocation", hq: "US" },67  { name: "Digital Realty", aliases: ["Digital Realty", "Digital Realty Trust", "Interxion", "Teraco", "Ascenty"], kind: "colocation", hq: "US" },68  { name: "CyrusOne", aliases: ["CyrusOne"], kind: "colocation", hq: "US" },69  { name: "QTS", aliases: ["QTS Data Centers", "QTS Realty", "QTS"], kind: "colocation", hq: "US" },70  { name: "Vantage Data Centers", aliases: ["Vantage Data Centers", "Vantage Data Centres", "Vantage's", after("Vantage")], kind: "colocation", hq: "US" },71  { name: "Aligned Data Centers", aliases: ["Aligned Data Centers", "Aligned's", after("Aligned")], kind: "colocation", hq: "US" },72  { name: "STACK Infrastructure", aliases: ["STACK Infrastructure", "Stack Infrastructure", "STACK's", after("STACK")], kind: "colocation", hq: "US" },73  { name: "Compass Datacenters", aliases: ["Compass Datacenters", "Compass Data Centers", "Compass's"], kind: "developer", hq: "US" },74  { name: "EdgeCore", aliases: ["EdgeCore", "EdgeCore Digital Infrastructure"], kind: "developer", hq: "US" },75  { name: "EdgeConneX", aliases: ["EdgeConneX"], kind: "colocation", hq: "US" },76  { name: "Prime Data Centers", aliases: ["Prime Data Centers"], kind: "developer", hq: "US" },77  { name: "Sabey Data Centers", aliases: ["Sabey Data Centers", "Sabey"], kind: "colocation", hq: "US" },78  { name: "Iron Mountain", aliases: ["Iron Mountain", "Iron Mountain Data Centers"], kind: "colocation", hq: "US" },79  { name: "Flexential", aliases: ["Flexential"], kind: "colocation", hq: "US" },80  { name: "DataBank", aliases: ["DataBank"], kind: "colocation", hq: "US" },81  { name: "Cologix", aliases: ["Cologix"], kind: "colocation", hq: "US" },82  { name: "Evoque", aliases: ["Evoque", "Cyxtera"], kind: "colocation", hq: "US" },83  { name: "TierPoint", aliases: ["TierPoint"], kind: "colocation", hq: "US" },84  { name: "Switch", aliases: ["Switch Inc", "Switch, Inc", "Switch's", after("Switch")], kind: "colocation", hq: "US" },85  { name: "Novva Data Centers", aliases: ["Novva Data Centers", "Novva"], kind: "colocation", hq: "US" },86  { name: "Element Critical", aliases: ["Element Critical"], kind: "colocation", hq: "US" },87  { name: "365 Data Centers", aliases: ["365 Data Centers"], kind: "colocation", hq: "US" },88  { name: "H5 Data Centers", aliases: ["H5 Data Centers"], kind: "colocation", hq: "US" },89  { name: "Stream Data Centers", aliases: ["Stream Data Centers"], kind: "developer", hq: "US" },90  { name: "Skybox Datacenters", aliases: ["Skybox Datacenters", "Skybox Data Centers"], kind: "developer", hq: "US" },91  { name: "T5 Data Centers", aliases: ["T5 Data Centers"], kind: "developer", hq: "US" },92  { name: "Corscale", aliases: ["Corscale", "Corscale Data Centers"], kind: "developer", hq: "US" },93  { name: "CloudHQ", aliases: ["CloudHQ", "Cloud HQ"], kind: "developer", hq: "US" },94  { name: "Yondr", aliases: ["Yondr", "Yondr Group"], kind: "developer", hq: "GB" },95  { name: "Colovore", aliases: ["Colovore"], kind: "colocation", hq: "US" },96  { name: "Ada Infrastructure", aliases: ["Ada Infrastructure"], kind: "developer", hq: "GB" },97  { name: "PowerHouse Data Centers", aliases: ["PowerHouse Data Centers", "PowerHouse"], kind: "developer", hq: "US" },98  { name: "Tract", aliases: ["Tract Capital", after("Tract")], kind: "developer", hq: "US" },99  { name: "Rowan Digital Infrastructure", aliases: ["Rowan Digital Infrastructure", "Rowan Digital"], kind: "developer", hq: "US" },100  { name: "Lancium", aliases: ["Lancium"], kind: "developer", hq: "US" },101  { name: "Soluna", aliases: ["Soluna", "Soluna Holdings"], kind: "developer", hq: "US" },102  { name: "Chirisa", aliases: ["Chirisa", "Chirisa Technology Parks"], kind: "developer", hq: "US" },103  { name: "Applied Digital", aliases: ["Applied Digital"], kind: "developer", hq: "US" },104  { name: "Core Scientific", aliases: ["Core Scientific"], kind: "developer", hq: "US" },105  { name: "IREN", aliases: ["IREN", "Iris Energy"], kind: "developer", hq: "AU" },106  { name: "TeraWulf", aliases: ["TeraWulf"], kind: "developer", hq: "US" },107  { name: "Cipher Mining", aliases: ["Cipher Mining"], kind: "developer", hq: "US" },108  { name: "Hut 8", aliases: ["Hut 8"], kind: "developer", hq: "US" },109  { name: "Riot Platforms", aliases: ["Riot Platforms", "Riot Blockchain"], kind: "developer", hq: "US" },110  { name: "MARA", aliases: ["MARA Holdings", "Marathon Digital", "MARA"], kind: "developer", hq: "US" },111  { name: "Bitdeer", aliases: ["Bitdeer"], kind: "developer", hq: "SG" },112  { name: "Bitfarms", aliases: ["Bitfarms"], kind: "developer", hq: "CA" },113  { name: "Galaxy Digital", aliases: ["Galaxy Digital"], kind: "developer", hq: "US" },114  { name: "Northern Data", aliases: ["Northern Data"], kind: "developer", hq: "DE" },115  { name: "Cielo Digital Infrastructure", aliases: ["Cielo Digital Infrastructure"], kind: "developer", hq: "US" },116  { name: "Quantum Loophole", aliases: ["Quantum Loophole"], kind: "developer", hq: "US" },117  { name: "Scala Data Centers", aliases: ["Scala Data Centers", "Scala"], kind: "colocation", hq: "BR" },118  { name: "ODATA", aliases: ["ODATA", "Odata"], kind: "colocation", hq: "BR" },119  { name: "Elea Data Centers", aliases: ["Elea Data Centers", "Elea Digital"], kind: "colocation", hq: "BR" },120  { name: "Cirion", aliases: ["Cirion", "Cirion Technologies"], kind: "colocation", hq: "US" },121  { name: "KIO Networks", aliases: ["KIO Networks", "KIO"], kind: "colocation", hq: "MX" },122  { name: "Urbacon", aliases: ["Urbacon Data Centre Solutions", "Urbacon"], kind: "developer", hq: "CA" },123  { name: "eStruxture", aliases: ["eStruxture"], kind: "colocation", hq: "CA" },124125  // ─── Europe ─────────────────────────────────────────────────────────────────────────────────────126  { name: "Global Switch", aliases: ["Global Switch"], kind: "colocation", hq: "GB" },127  { name: "Colt DCS", aliases: ["Colt DCS", "Colt Data Centre Services", "Colt Technology Services"], kind: "colocation", hq: "GB" },128  { name: "VIRTUS Data Centres", aliases: ["VIRTUS Data Centres", "Virtus Data Centres", "VIRTUS"], kind: "colocation", hq: "GB" },129  { name: "Ark Data Centres", aliases: ["Ark Data Centres"], kind: "colocation", hq: "GB" },130  { name: "Kao Data", aliases: ["Kao Data"], kind: "colocation", hq: "GB" },131  { name: "Pure DC", aliases: ["Pure DC", "Pure Data Centres"], kind: "developer", hq: "GB" },132  { name: "Echelon Data Centres", aliases: ["Echelon Data Centres", "Echelon DC"], kind: "developer", hq: "IE" },133  { name: "Data4", aliases: ["Data4", "DATA4"], kind: "colocation", hq: "FR" },134  { name: "atNorth", aliases: ["atNorth"], kind: "colocation", hq: "IS" },135  { name: "Green Mountain", aliases: ["Green Mountain"], kind: "colocation", hq: "NO" },136  { name: "Bulk Infrastructure", aliases: ["Bulk Infrastructure", "Bulk Data Centers"], kind: "colocation", hq: "NO" },137  { name: "Verne", aliases: ["Verne Global", "Verne"], kind: "colocation", hq: "GB" },138  { name: "EcoDataCenter", aliases: ["EcoDataCenter"], kind: "colocation", hq: "SE" },139  { name: "Penta Infra", aliases: ["Penta Infra"], kind: "colocation", hq: "NL" },140  { name: "maincubes", aliases: ["maincubes", "Maincubes"], kind: "colocation", hq: "DE" },141  { name: "NorthC", aliases: ["NorthC"], kind: "colocation", hq: "NL" },142  { name: "Serverfarm", aliases: ["Serverfarm"], kind: "colocation", hq: "US" },143  { name: "Portus Data Centers", aliases: ["Portus Data Centers", "Portus"], kind: "colocation", hq: "DE" },144  { name: "AQ Compute", aliases: ["AQ Compute"], kind: "developer", hq: "NO" },145  { name: "Ardent Data Centers", aliases: ["Ardent Data Centers"], kind: "developer", hq: "GB" },146  { name: "LCL Data Centers", aliases: ["LCL Data Centers", "LCL"], kind: "colocation", hq: "BE" },147  { name: "Kevlinx", aliases: ["Kevlinx"], kind: "developer", hq: "BE" },148  { name: "Etix Everywhere", aliases: ["Etix Everywhere"], kind: "colocation", hq: "LU" },149  { name: "Green Datacenter", aliases: ["Green Datacenter", "Green.ch"], kind: "colocation", hq: "CH" },150  { name: "Nabiax", aliases: ["Nabiax"], kind: "colocation", hq: "ES" },151  { name: "Start Campus", aliases: ["Start Campus"], kind: "developer", hq: "PT" },152  { name: "Merlin Properties", aliases: ["Merlin Properties", "MERLIN Properties"], kind: "developer", hq: "ES" },153  { name: "Telefónica Tech", aliases: ["Telefónica Tech", "Telefonica Tech"], kind: "carrier", hq: "ES" },154  { name: "Aruba", aliases: ["Aruba S.p.A", "Aruba Enterprise", after("Aruba")], kind: "colocation", hq: "IT" },155  { name: "Stack EMEA", aliases: ["Digiplex", "DigiPlex"], kind: "colocation", hq: "NO" },156  { name: "Ficolo", aliases: ["Ficolo"], kind: "colocation", hq: "FI" },157  { name: "Telia", aliases: ["Telia", "Telia Helsinki Data Center"], kind: "carrier", hq: "SE" },158  { name: "Elisa", aliases: ["Elisa"], kind: "carrier", hq: "FI" },159160  // ─── Asia-Pacific ───────────────────────────────────────────────────────────────────────────────161  { name: "AirTrunk", aliases: ["AirTrunk"], kind: "colocation", hq: "AU" },162  { name: "NEXTDC", aliases: ["NEXTDC", "NextDC"], kind: "colocation", hq: "AU" },163  { name: "CDC Data Centres", aliases: ["CDC Data Centres", "Canberra Data Centres"], kind: "colocation", hq: "AU" },164  { name: "Macquarie Data Centres", aliases: ["Macquarie Data Centres", "Macquarie Technology Group"], kind: "colocation", hq: "AU" },165  { name: "Firmus", aliases: ["Firmus", "Firmus Technologies"], kind: "developer", hq: "AU" },166  { name: "STT GDC", aliases: ["ST Telemedia Global Data Centres", "STT GDC"], kind: "colocation", hq: "SG" },167  { name: "Keppel", aliases: ["Keppel Data Centres", "Keppel DC REIT", "Keppel"], kind: "colocation", hq: "SG" },168  { name: "Princeton Digital Group", aliases: ["Princeton Digital Group", "PDG"], kind: "colocation", hq: "SG" },169  { name: "Bridge Data Centres", aliases: ["Bridge Data Centres", "Bridge DC"], kind: "colocation", hq: "SG" },170  { name: "DayOne", aliases: ["DayOne Data Centers", "DayOne"], kind: "colocation", hq: "SG" },171  { name: "GDS", aliases: ["GDS Holdings", "GDS"], kind: "colocation", hq: "CN" },172  { name: "Chindata", aliases: ["Chindata", "Bain-backed Chindata"], kind: "colocation", hq: "CN" },173  { name: "VNET", aliases: ["VNET", "21Vianet"], kind: "colocation", hq: "CN" },174  { name: "SpaceDC", aliases: ["SpaceDC"], kind: "colocation", hq: "SG" },175  { name: "Empyrion Digital", aliases: ["Empyrion Digital", "Empyrion DC"], kind: "colocation", hq: "SG" },176  { name: "BDx", aliases: ["BDx", "Big Data Exchange"], kind: "colocation", hq: "SG" },177  { name: "Evolution Data Centres", aliases: ["Evolution Data Centres"], kind: "developer", hq: "SG" },178  { name: "YTL Data Center", aliases: ["YTL Data Center", "YTL Power"], kind: "developer", hq: "MY" },179  { name: "Yotta", aliases: ["Yotta Data Services", "Yotta Infrastructure", "Yotta"], kind: "colocation", hq: "IN" },180  { name: "AdaniConneX", aliases: ["AdaniConneX", "Adani Enterprises", "Adani Group"], kind: "developer", hq: "IN" },181  { name: "Sify", aliases: ["Sify Technologies", "Sify Infinit Spaces", "Sify"], kind: "colocation", hq: "IN" },182  { name: "Nxtra", aliases: ["Nxtra", "Nxtra by Airtel", "Airtel Nxtra"], kind: "colocation", hq: "IN" },183  { name: "CtrlS", aliases: ["CtrlS", "CtrlS Datacenters"], kind: "colocation", hq: "IN" },184  { name: "Reliance Jio", aliases: ["Reliance Jio", "Reliance Industries", "Jio Platforms"], kind: "enterprise", hq: "IN" },185  { name: "SoftBank", aliases: ["SoftBank", "SB Energy"], kind: "investor", hq: "JP" },186  { name: "SK Group", aliases: ["SK Group", "SK Telecom", "SK Broadband"], kind: "carrier", hq: "KR" },187  { name: "Naver", aliases: ["Naver", "NAVER Cloud"], kind: "cloud", hq: "KR" },188  { name: "Kakao", aliases: ["Kakao", "Kakao Enterprise"], kind: "cloud", hq: "KR" },189190  // ─── Middle East & Africa ───────────────────────────────────────────────────────────────────────191  { name: "Khazna Data Centers", aliases: ["Khazna Data Centers", "Khazna"], kind: "colocation", hq: "AE" },192  { name: "Gulf Data Hub", aliases: ["Gulf Data Hub"], kind: "colocation", hq: "AE" },193  { name: "center3", aliases: ["center3", "Center3"], kind: "colocation", hq: "SA" },194  { name: "DataVolt", aliases: ["DataVolt"], kind: "developer", hq: "SA" },195  { name: "Africa Data Centres", aliases: ["Africa Data Centres", "Cassava Technologies"], kind: "colocation", hq: "ZA" },196  { name: "Rack Centre", aliases: ["Rack Centre"], kind: "colocation", hq: "NG" },197  { name: "MainOne", aliases: ["MainOne", "MDXi"], kind: "colocation", hq: "NG" },198  { name: "Open Access Data Centres", aliases: ["Open Access Data Centres", "OADC"], kind: "colocation", hq: "ZA" },199  { name: "Raxio", aliases: ["Raxio", "Raxio Group"], kind: "colocation", hq: "UG" },200  { name: "G42", aliases: ["G42", "Core42"], kind: "enterprise", hq: "AE" },201202  // ─── Investors / owners frequently named as the developer ──────────────────────────────────────203  { name: "Blackstone", aliases: ["Blackstone"], kind: "investor", hq: "US" },204  { name: "Brookfield", aliases: ["Brookfield", "Brookfield Infrastructure", "Brookfield Asset Management"], kind: "investor", hq: "CA" },205  { name: "KKR", aliases: ["KKR"], kind: "investor", hq: "US" },206  { name: "Blue Owl", aliases: ["Blue Owl", "Blue Owl Capital"], kind: "investor", hq: "US" },207  { name: "DigitalBridge", aliases: ["DigitalBridge"], kind: "investor", hq: "US" },208  { name: "Stonepeak", aliases: ["Stonepeak"], kind: "investor", hq: "US" },209  { name: "GIC", aliases: ["GIC"], kind: "investor", hq: "SG" },210  { name: "Macquarie Asset Management", aliases: ["Macquarie Asset Management"], kind: "investor", hq: "AU" },211  { name: "Related Digital", aliases: ["Related Digital", "Related Companies"], kind: "developer", hq: "US" },212  { name: "Hines", aliases: [after("Hines")], kind: "developer", hq: "US" },213];214215function escapeRe(s: string): string { return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); }216217const compiled: Array<{ entry: OperatorEntry; re: RegExp }> = OPERATORS.map((entry) => {218  const parts = entry.aliases.map((a) => (a instanceof RegExp ? `(?:${a.source})` : escapeRe(a)));219  // word boundaries that also work for names starting/ending with digits or symbols220  return { entry, re: new RegExp(`(?<![A-Za-z0-9])(?:${parts.join("|")})(?![A-Za-z0-9])`, "g") };221});222223export interface OperatorHit { name: string; kind: OperatorKind; index: number; matched: string }224225/** All operators mentioned in `text`, ordered by first appearance (then lexicon priority). */226export function detectOperators(text: string): OperatorHit[] {227  if (!text) return [];228  const hits = new Map<string, OperatorHit>();229  for (const { entry, re } of compiled) {230    re.lastIndex = 0;231    const m = re.exec(text);232    if (!m) continue;233    const prev = hits.get(entry.name);234    if (!prev || m.index < prev.index) hits.set(entry.name, { name: entry.name, kind: entry.kind, index: m.index, matched: m[0] });235  }236  return [...hits.values()].sort((a, b) => a.index - b.index);237}238239/** Primary operator: first one named in the title, else first one in the body. */240export function primaryOperator(title: string | null, text: string): OperatorHit | null {241  const inTitle = detectOperators(title ?? "");242  if (inTitle.length) return inTitle[0]!;243  const inText = detectOperators(text.slice(0, 3000));244  return inText[0] ?? null;245}246247export function operatorNames(): string[] { return OPERATORS.map((o) => o.name); }248