import fs from 'fs' const all = JSON.parse(fs.readFileSync(process.argv[2], 'utf8')) const norm = s => s.toLowerCase().replace(/[^a-z0-9 ]/g, ' ').replace(/\s+/g, ' ').trim() // Closing roles only. `enterprise sales` was deliberately left out of INCLUDE: // it added 46 postings, all of them sales engineers, managers, directors and // one recruiter, while "Enterprise Account Executive" is already matched. // EXCLUDE then removes leadership and pre-sales titles so the sample stays // individual-contributor closers — a sales manager's posting asks for different // things and would blur the finding. const INCLUDE = /\b(account executive|sales representative)\b/i const EXCLUDE = /\b(engineer|architect|developer|recruiter|manager|director|vice president|vp|head of|intern|enablement|operations)\b/i const seen = new Set(), jobs = [] for (const j of all.filter(x => INCLUDE.test(x.title) && !EXCLUDE.test(x.title))) { const k = j.co + '|' + norm(j.title) + '|' + j.content.length if (!seen.has(k)) { seen.add(k); jobs.push(j) } } const unesc = s => s.replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&') .replace(/"/g,'"').replace(/'/g,"'").replace(/ /g,' ') const texts = jobs.map(j => unesc(j.content).replace(/<[^>]+>/g,' ').replace(/\s+/g,' ')) // Deliberately absent, and said so on the page: // Outreach — 13.6% loose against 6.0% case-sensitive. The gap is generic // "customer outreach" / "outreach efforts", and capitalised // sentence-initial use makes case no help. Unfixable by regex. // Apollo — 0.5%, and Apollo GraphQL is in this corpus. Both rare and dirty. // Chorus — 0.4%. Real, too rare to report. // Three terms are matched narrowly because the bare word was materially dirty: // closing 57.7% bare against 23.0% with a deal object ("closing date", // "closing the loop"). Narrow form used. // new logos 30.0% bare against 17.6% for "net new logos". Narrow form used. // discovery 23.0% bare against 2.1% for "discovery call/meeting/process". // Bare "discovery" in a posting is usually not the sales stage. const T = [ ['Methodology','MEDDIC / MEDDPICC',/\bMEDDP?I?CC?\b|\bMEDDIC\b/i], ['Methodology','Challenger',/\bChallenger\b/], ['Methodology','SPIN',/\bSPIN\b/], ['Methodology','Sandler',/\bSandler\b/i], ['Methodology','Force Management / Command of the Message',/\bForce Management\b|\bCommand of the Message\b/i], ['Methodology','BANT',/\bBANT\b/i], ['Methodology','value selling',/\bvalue[- ]based selling\b|\bvalue selling\b/i], ['Methodology','solution selling',/\bsolution selling\b/i], ['Tools & platforms','Salesforce',/\bSalesforce\b/i], ['Tools & platforms','LinkedIn Sales Navigator',/\bSales Navigator\b/i], ['Tools & platforms','Gong',/\bGong\b/], ['Tools & platforms','ZoomInfo',/\bZoomInfo\b/i], ['Tools & platforms','Salesloft',/\bSales ?loft\b/i], ['Tools & platforms','Clari',/\bClari\b/], ['Tools & platforms','HubSpot',/\bHubSpot\b/i], ['Tools & platforms','CRM (generic)',/\bCRM\b/i], ['Metrics & targets','quota',/\bquota\b/i], ['Metrics & targets','pipeline',/\bpipeline\b/i], ['Metrics & targets','ARR',/\bARR\b/], ['Metrics & targets','forecasting',/\bforecast/i], ['Metrics & targets','bookings',/\bbookings\b/i], ['Metrics & targets','ACV',/\bACV\b/], ['Metrics & targets','win rate',/\bwin rate\b/i], ['Metrics & targets','quota attainment',/\bquota attainment\b|\battainment\b/i], ['Metrics & targets',"President's Club",/\bPresident'?s Club\b/i], ['Metrics & targets','net new logos',/\bnet[- ]new logos?\b/i], ['Sales motion','prospecting',/\bprospect(ing|ed)?\b/i], ['Sales motion','discovery calls',/\bdiscovery (call|meeting|process|session)/i], ['Sales motion','negotiation',/\bnegotiat/i], ['Sales motion','closing deals',/\bclosing (deals|business|new|accounts|opportunities)\b|\bclose deals\b/i], ['Sales motion','cold calling / outbound',/\bcold[- ]call/i], ['Sales motion','outbound',/\boutbound\b/i], ['Sales motion','land and expand',/\bland[- ]and[- ]expand\b/i], ['Sales motion','upsell / cross-sell',/\bup[- ]?sell|\bcross[- ]?sell/i], ['Sales motion','territory planning',/\bterritory plan|\bterritory management\b/i], ['Sales motion','multithreading',/\bmulti[- ]?thread/i], ['Sales motion','C-level / executive engagement',/\bC[- ]level\b|\bC[- ]suite\b|\bexecutive stakeholders?\b/i], ['Sales motion','RFP',/\bRFP\b|\bRFI\b/i], ['Sales motion','channel / partner',/\bchannel partner|\bpartner ecosystem\b|\bresellers?\b/i], ['Segment & context','enterprise',/\benterprise\b/i], ['Segment & context','mid-market',/\bmid[- ]market\b/i], ['Segment & context','SMB',/\bSMB\b|\bsmall and medium\b/i], ['Segment & context','SaaS',/\bSaaS\b/i], // A bare \bfederal\b matched "protected by federal, state or local laws" in the EEO // boilerplate: on the 2026-09-25 corpus 179 of 261 matches named no public-sector // customer, and the published row overstated the segment about threefold. Federal // counts only when it qualifies a buyer (agencies, civilian, accounts, government). ['Segment & context','public sector / SLED',/\bSLED\b|\bpublic sector\b|\bstate and local government\b|\bfederal (?:government|agenc(?:y|ies)|sector|civilian|accounts?|customers?|market|space|business|sales|team|territor(?:y|ies)|systems? integrators?)\b/i], ] const n = texts.length const moe = p => +(1.96 * Math.sqrt((p/100)*(1-p/100)/n) * 100).toFixed(1) const rows = T.map(([cat, term, re]) => { const c = texts.filter(t => re.test(t)).length const pct = +(100*c/n).toFixed(1) return { cat, term, n: c, pct, moe: moe(pct) } }).sort((a,b) => b.n - a.n) const out = { role: 'Sales / Account Executive', n, companies: [...new Set(jobs.map(j=>j.co))].sort(), collected: new Date(Date.now() - new Date().getTimezoneOffset() * 60000).toISOString().slice(0,10), medianWords: texts.map(t=>t.split(' ').length).sort((a,b)=>a-b)[Math.floor(n/2)], rows } fs.writeFileSync(process.argv[3], JSON.stringify(out, null, 1)) console.log(`n=${n} from ${out.companies.length} companies, median ${out.medianWords} words`) rows.forEach((r,i)=>console.log(` ${String(i+1).padStart(2)}. ${r.term.padEnd(42)} ${String(r.pct).padStart(5)}% ±${r.moe} (${r.n})`))