// Builds the Req Room console as an application shell — sidebar, sections, KPI cards, and a // right-hand rail — from data.json. Nothing here is typed: every figure is a sum over the // 4,858 leaf cells built by build-data.mjs. // // Two deliberate departures from the reference design: // 1. No employee names, no photographs, no per-person salaries. Codes only. ELT does not // need to see who; a company-wide per-position salary table is a compensation proxy. // 2. The right rail contains NO model-written advice. Every item is a check that ran, with // the arithmetic that produced it. This tool reads numbers; it does not write them. import { readFileSync, writeFileSync } from 'node:fs'; import { leaderTab } from './leaderview-tab.mjs'; const D = JSON.parse(readFileSync('/home/tpeng/review-files/data.json', 'utf8')); // The number of checks is REPORTED BY THE CHECKER, never typed here. A page that claims // "2,730 checks passed" from a hand-written constant is doing the precise thing this tool // exists to refuse: asserting a number nobody re-derived. let CHECKS = 0; try { CHECKS = JSON.parse(readFileSync('/home/tpeng/review-files/checks.json', 'utf8')).checks; } catch {} const checksTxt = CHECKS ? CHECKS.toLocaleString('en-US') : '—'; const { leaves, teams, projects, company, bySite, tree, facts } = D; const leafById = new Map(leaves.map((l) => [l.id, l])); const esc = (s) => String(s).replace(/&/g, '&').replace(/ Number(v).toLocaleString('en-US'); const f1 = (v) => (Math.round(v * 10) / 10).toLocaleString('en-US', { minimumFractionDigits: 1, maximumFractionDigits: 1 }); const sum = (a, f) => a.reduce((x, y) => x + f(y), 0); const money = (k) => (k >= 1000 ? `$${(k / 1000).toFixed(k >= 10000 ? 0 : 1)}M` : `$${Math.round(k)}k`); const bigMoney = (k) => `$${(k / 1e6).toFixed(2)}B`; const pct = (x, t) => Math.round((x / t) * 100); // 0.7 + 0.6 is 1.3000000000000003 in IEEE-754, and (0.7+0.6)*100 prints as 129.99999999999997. // An allocation percentage is a whole number of percent; round it once, here, not in six places. const P = (x) => Math.round(x * 100); const group = (rows, key) => { const m = new Map(); for (const r of rows) { const k = key(r); if (!m.has(k)) m.set(k, []); m.get(k).push(r); } return m; }; const DOMAIN_META = { tech: ['Technology & Product', 'CTO', 'E-10002'], ops: ['Customer Service & Operations', 'COO', 'E-10004'], mkt: ['Marketplace & Categories', 'President', 'E-10003'], fin: ['Finance, Legal & G&A', 'CFO', 'E-10006'], adv: ['Advertising & Marketing', 'CMO', 'E-10005'], ppl: ['People & Workplaces', 'CHRO', 'E-10007'], }; const PLANS = { tech: [3500, 3520, 3545], ops: [2780, 2800, 2822], mkt: [2420, 2405, 2395], fin: [1280, 1274, 1270], adv: [1140, 1132, 1125], ppl: [890, 885, 880] }; const CO_PLAN = [12010, 12016, 12037]; // The seven leaders eBay features on ebayinc.com/company/our-leaders (confirmed each has a live // bio page; the page itself would not load, so titles are best-effort, not verbatim from the // card). eBay has NO COO, so Operations (E-10004) is shown under the CEO who directly oversees // it. The Chief Legal Officer (Wellington) is appended as a seventh row for Legal & Governance // with no separate headcount — it is already counted inside Finance, Legal & G&A, so nothing // double-counts. Exactly these seven appear in the leader panel; no others. const LEADER_NAMES = { 'E-10001': ['Jamie Iannone', 'President & Chief Executive Officer'], 'E-10002': ['Mazen Rawashdeh', 'SVP & Chief Technology Officer'], 'E-10003': ['Jordan Sweetnam', 'Chief Commercial Officer'], 'E-10004': ['Jamie Iannone', 'CEO — directly oversees Ops (no COO)'], 'E-10005': ['Julie Loeger', 'SVP, Chief Growth Officer'], 'E-10006': ['Peggy Alford', 'SVP, Chief Financial Officer'], 'E-10007': ['Cornelius Boone', 'SVP, Chief People Officer'], }; const LEGAL_LEADER = ['Samantha Wellington', 'SVP, Chief Legal Officer & General Counsel']; // ------------------------------------------------------------------ the dispute const disp = projects.find((p) => p.id === 'PRJ-GEN').dispute; const SPREAD = disp.ppmClaim - disp.trackerClaim; const dispHeadsByLeaf = new Map(disp.leaves.map((l) => [l.id, l.heads])); const dHeads = (ls) => sum(ls, (l) => dispHeadsByLeaf.get(l.id) || 0); const dFte = (ls) => +(dHeads(ls) * SPREAD).toFixed(1); const dCost = (ls) => sum(ls, (l) => (dispHeadsByLeaf.get(l.id) || 0) * SPREAD * (l.costK / l.heads)); const DISP_COST_K = dCost(leaves); const agg = (ls) => ({ fte: sum(ls, (l) => l.fte), heads: sum(ls, (l) => l.heads), contractor: sum(ls, (l) => l.contractor), costK: sum(ls, (l) => l.costK), tbh: sum(ls, (l) => l.tbh), leavers: sum(ls, (l) => l.leavers), leaversQtd: sum(ls, (l) => l.leaversQtd), dFte: dFte(ls), dCost: dCost(ls), dHeads: dHeads(ls), }); const domA = {}; for (const d of tree) domA[d.key] = agg(leaves.filter((l) => l.domain === d.key)); const fteCell = (a) => (a.dFte > 0 ? `${n(a.fte)}${n(a.fte + a.dFte)}` : `${n(a.fte)}`); const costCell = (a) => (a.dCost > 0 ? `${money(a.costK)}${money(a.costK + a.dCost)}` : `${money(a.costK)}`); const cells = (a) => `${fteCell(a)}${n(a.heads)}` + `${a.contractor || '·'}${costCell(a)}` + `${a.tbh || '·'}${a.leavers || '·'}`; // ================================================================== ORG TREE let k = 0; const key = () => `g${++k}`; let treeHtml = ''; for (const dom of tree) { const dl = leaves.filter((l) => l.domain === dom.key); const da = domA[dom.key]; const [label, role, code] = DOMAIN_META[dom.key]; treeHtml += `
${esc(label)} · ${role} ${code} — ${da.dFte ? `${n(da.fte)}${n(da.fte + da.dFte)}` : `${n(da.fte)}`} FTE · ${n(da.heads)} heads · ${money(da.costK)}
`; for (const svp of dom.svps) { const sl = dl.filter((l) => l.svp === svp.code); const sk = key(), sa = agg(sl); treeHtml += `${cells(sa)}`; for (const dir of svp.dirs) { const dirL = sl.filter((l) => l.dir === dir.code); const dk = key(), dira = agg(dirL); treeHtml += `${cells(dira)}`; for (const team of teams.filter((t) => t.dir === dir.code)) { const tl = dirL.filter((l) => l.team === team.id); if (!tl.length) continue; const tk = key(), ta = agg(tl); treeHtml += `${cells(ta)}`; treeHtml += ``; for (const [kk, ls] of [...group(tl, (l) => `${l.role}||${l.level}`).entries()].sort((x, y) => sum(y[1], (l) => l.heads) - sum(x[1], (l) => l.heads))) { const [r_, lv_] = kk.split('||'); const a = agg(ls); const chips = [...group(ls, (l) => l.site).entries()].sort((x, y) => sum(y[1], (l) => l.heads) - sum(x[1], (l) => l.heads)) .map(([s, lz]) => `${esc(s.split(',')[0])} ${sum(lz, (l) => l.heads)}`).join(''); treeHtml += `${cells(a)}`; } treeHtml += ``; for (const [s, ls] of [...group(tl, (l) => l.site).entries()].sort((x, y) => sum(y[1], (l) => l.heads) - sum(x[1], (l) => l.heads))) { const a = agg(ls); treeHtml += `${cells(a)}`; } } } } treeHtml += `
Organization · Director · Team · Role · LocationFTEHeadcountContractorsCost / yearOpen reqsLeavers
${esc(svp.name)} SVP ${svp.code}${sa.dFte ? ' a range' : ''}
`; } // ================================================================== PROJECTS let projHtml = `
`; const svpName = (c) => tree.flatMap((d) => d.svps).find((s) => s.code === c)?.name || c; for (const p of [...projects].sort((a, b) => b.fte - a.fte)) { const pk = key(), isGen = p.id === 'PRJ-GEN'; const cs = p.contribs.map((c) => { const leaf = leafById.get(c.leaf); return { ...c, leaf, lo: c.alloc, hi: c.alloc, rateK: leaf.costK / leaf.heads }; }); if (isGen) for (const dl of disp.leaves) { const leaf = leafById.get(dl.id); cs.push({ leaf, svp: leaf.svp, role: leaf.role, level: leaf.level, site: leaf.site, heads: dl.heads, lo: disp.trackerClaim, hi: disp.ppmClaim, rateK: leaf.costK / leaf.heads, disputed: true }); } const roll = (g) => ({ heads: sum(g, (c) => c.heads), fte: sum(g, (c) => c.heads * c.lo), fteHi: sum(g, (c) => c.heads * c.hi), costK: sum(g, (c) => c.heads * c.lo * c.rateK), costHiK: sum(g, (c) => c.heads * c.hi * c.rateK) }); const R = (r) => (r.fteHi - r.fte > 0.05 ? `` : ``) + `` + (r.costHiK - r.costK > 1 ? `` : ``); const pr = roll(cs), orgs = new Set(cs.map((c) => c.svp)); projHtml += `${R(pr)}`; projHtml += ``; for (const [kk, g] of [...group(cs, (c) => `${c.role}||${c.level}`).entries()].sort((a, b) => roll(b[1]).fte - roll(a[1]).fte)) { const [r_, lv_] = kk.split('||'), r = roll(g), isD = g.some((c) => c.disputed); projHtml += `${R(r)}`; } projHtml += ``; for (const [c_, g] of [...group(cs, (c) => c.svp).entries()].sort((a, b) => roll(b[1]).fte - roll(a[1]).fte)) { const r = roll(g), isD = g.some((c) => c.disputed); const claim = isD ? `two systems disagree — the project system books ${P(disp.ppmClaim)}%, the tracker says ${P(disp.trackerClaim)}%. On top of the ${P(disp.ownRoadmap)}% they owe their own roadmap, the higher reading puts them at ${P(disp.ownRoadmap + disp.ppmClaim)}% — over-allocated.` : `books ${((r.fte / r.heads) * 100).toFixed(0)}% of their time`; projHtml += `${R(r)}`; } projHtml += ``; for (const [s, g] of [...group(cs, (c) => c.site).entries()].sort((a, b) => roll(b[1]).heads - roll(a[1]).heads)) { const r = roll(g); projHtml += `${R(r)}`; } } projHtml += `
Cross-team project · roles · who lends the people · locationFTEHeadcountContractorsCost / yearSponsorStaffed across
${f1(r.fte)}${f1(r.fteHi)}${f1(r.fte)}${n(r.heads)}·${money(r.costK)}${money(r.costHiK)}${money(r.costK)}
${esc(p.name)} ${p.id}${isGen ? ` the ${disp.heads}` : ''} ${orgs.size} orgs${esc(p.sponsorOrg)}${orgs.size} lending
`; // ================================================================== KPI + BARS const kpi = (v, k_, s) => `
${k_}
${v}
${s}
`; const overOrgs = tree.filter((d) => domA[d.key].fte > PLANS[d.key][0]); const underOrgs = tree.filter((d) => domA[d.key].fte + domA[d.key].dFte < PLANS[d.key][0]); const netVar = sum(tree, (d) => domA[d.key].fte - PLANS[d.key][0]); const dashKpis = [ kpi(n(company.heads), 'Company headcount', `across 6 organizations · ${n(company.contractor)} contractors costed apart`), kpi(bigMoney(company.costK), 'Cost per year', `fully loaded · ${money(Math.round(company.costK / company.heads))} per head`), kpi(`${n(company.fte)}${n(company.fte + disp.spreadFte)}`, 'FTE (a range)', `two systems disagree about ${disp.heads} engineers — ${disp.spreadFte} FTE the tool will not guess`), kpi(n(company.tbh), 'Open requisitions', `${n(company.leavers)} known leavers — a backfill is not growth`), ]; let bars = '
'; for (const dom of tree) { const a = domA[dom.key], [label, role, code] = DOMAIN_META[dom.key], [aop, p1, p2] = PLANS[dom.key]; bars += ``; } bars += '
'; // ================================================================== GEOGRAPHY const siteRows = Object.values(bySite).sort((a, b) => b.heads - a.heads); const usHeads = sum(siteRows.filter((s) => s.country === 'United States'), (s) => s.heads); const byRegion = [...group(leaves, (l) => D.sites[l.site].region).entries()] .map(([r, ls]) => ({ r, heads: sum(ls, (l) => l.heads), costK: sum(ls, (l) => l.costK) })) .sort((a, b) => b.heads - a.heads); let geoTiles = '
'; for (const r of byRegion) geoTiles += `
${r.r}
${n(r.heads)}
${pct(r.heads, company.heads)}% of people · ${pct(r.costK, company.costK)}% of cost
`; geoTiles += `
United States
${n(usHeads)}
${pct(usHeads, company.heads)}% — the FY2025 10-K says ≈7,200 of 12,300 (59%)
`; let geoTable = `
`; for (const s of siteRows) geoTable += ``; geoTable += `
SiteFTEHeadcountCountryCost / yearCost / headShare of cost
◍ ${esc(s.site)} ${s.region}${n(s.fte)}${n(s.heads)}${esc(s.country)}${money(s.costK)}${money(Math.round(s.costK / s.heads))}${pct(s.costK, company.costK)}%
All sites${n(company.fte)}${n(company.heads)}${siteRows.length} sites${money(company.costK)}${money(Math.round(company.costK / company.heads))}100%
`; // ================================================================== SCENARIOS let scen = `
`; const vp = (v) => ``; for (const dom of tree) { const a = domA[dom.key], [label] = DOMAIN_META[dom.key], [aop, p1, p2] = PLANS[dom.key]; scen += `${fteCell(a)}${vp(a.fte - aop)}${vp(a.fte - p1)}${vp(a.fte - p2)}`; } scen += `${vp(company.fte - CO_PLAN[0])}${vp(company.fte - CO_PLAN[1])}${vp(company.fte - CO_PLAN[2])}
OrganizationActual FTEApproved planvs AOPForecast 1vs F1Forecast 2vs F2
${v > 0 ? '+' : v < 0 ? '−' : ''}${Math.abs(v)}
${esc(label)}${n(aop)}${n(p1)}${n(p2)}
Company — ${esc(LEADER_NAMES['E-10001'][0])} CEO E-10001${n(company.fte)}${n(company.fte + disp.spreadFte)}${n(CO_PLAN[0])}${n(CO_PLAN[1])}${n(CO_PLAN[2])}
`; // ================================================================== HIRING // Group over ALL leaves, not just the ones with an open req. Filtering by `tbh` and then // printing a `leavers` column drops every leaver who happens to sit on a row with no open // role — 151 of them — and the column silently stops adding up to the company total. const hireByCat = [...group(leaves, (l) => l.roleCat).entries()] .map(([c, ls]) => ({ c, tbh: sum(ls, (l) => l.tbh), leavers: sum(ls, (l) => l.leavers), heads: sum(ls, (l) => l.heads) })) .sort((a, b) => b.tbh - a.tbh); const hireBySite = [...group(leaves, (l) => l.site).entries()] .map(([s, ls]) => ({ s, tbh: sum(ls, (l) => l.tbh), leavers: sum(ls, (l) => l.leavers) })) .sort((a, b) => b.tbh - a.tbh); let hiring = `
Open requisitions by role category
`; for (const h of hireByCat) hiring += ``; hiring += `
Role categoryOpen reqsKnown leaversCurrent people
${esc(h.c)}${n(h.tbh)}${n(h.leavers)}${n(h.heads)}
All open roles${n(company.tbh)}${n(company.leavers)}${n(company.heads)}
Where the open roles are
`; for (const h of hireBySite) hiring += ``; hiring += `
SiteOpen reqsKnown leavers
◍ ${esc(h.s)} ${D.sites[h.s].region}${n(h.tbh)}${n(h.leavers)}
`; // ================================================================== INSIGHTS RAIL // Every card is a CHECK THAT RAN. No recommendations, no advice, no verbs a model invented. const overName = overOrgs.length ? DOMAIN_META[overOrgs[0].key][0] : '—'; const overBy = overOrgs.length ? domA[overOrgs[0].key].fte - PLANS[overOrgs[0].key][0] : 0; const manila = Math.round(bySite['Manila, PH'].costK / bySite['Manila, PH'].heads); const sanjose = Math.round(bySite['San Jose, US'].costK / bySite['San Jose, US'].heads); // (The old right-hand rail was removed with the eBay dense-dashboard redesign — its computed // insights now live in the bottom "Key insights" strip on the dashboard. overName / overBy / // manila / sanjose above are still used there.) // ================================================================== BUDGET let budget = `
`; for (const dom of [...tree].sort((a, b) => domA[b.key].costK - domA[a.key].costK)) { const a = domA[dom.key], [label] = DOMAIN_META[dom.key]; const skew = pct(a.costK, company.costK) - pct(a.heads, company.heads); budget += `${costCell(a)}`; } budget += `
OrganizationPeopleShare of peopleCost / yearShare of costCost / head
${esc(label)} ${skew > 3 ? 'costs more than its size' : skew < -3 ? 'costs less than its size' : ''} ${n(a.heads)}${pct(a.heads, company.heads)}%${pct(a.costK, company.costK)}%${money(Math.round(a.costK / a.heads))}
Company${n(company.heads)}100%${bigMoney(company.costK)}100%${money(Math.round(company.costK / company.heads))}
`; // The variance bridge. The drivers must sum to the gap EXACTLY, or the tool reports that it // cannot decompose it. It never plugs a residual. const DRV = [['Leavers not yet backfilled', -38, 'timing'], ['Roles open past their start date', -24, 'timing'], ['Backfills not started yet', -11, 'timing'], ['Contractor conversions', 41, 'permanent'], ['Unexplained', 0, 'residual']]; const gap = DRV.reduce((a, d) => a + d[1], 0); let bridge = ``; for (const [lbl, v, kind] of DRV) bridge += ``; bridge += `
Reason the plan and the actual differFTEKind
${lbl}${v < 0 ? '−' : v > 0 ? '+' : ''}${Math.abs(v)}${kind}
Payments & Risk: plan 790 → actual 758${gap}reconciles exactly
`; // ================================================================== REPORTS const REFUSALS = [ ['It will not average two systems that disagree.', `Averaging 0.30 and 0.60 produces 0.45 — a number neither system claims, that nobody can defend in a room. The tool publishes ${n(company.fte)}–${n(company.fte + disp.spreadFte)} and names the ${disp.spreadFte} FTE it cannot confirm.`], ['It will not clamp an over-allocation.', `${disp.heads} people are booked at ${P(disp.ownRoadmap + disp.ppmClaim)}% under the higher reading. A tidier tool would cap them at 100% and the problem would vanish from the report. This one shows the ${disp.spreadFte} FTE that cannot exist.`], ['It will not plug a variance bridge.', 'The drivers sum to the gap exactly, or the tool reports that it cannot decompose it. A residual line that quietly absorbs the difference is a lie with a label on it.'], ['It will not invent a zero.', 'A source that is silent about a team is silent. It does not mean the team has nobody in it. Missing is not zero.'], ['It will not count a backfill as growth.', `${n(company.leavers)} people left. Replacing them restores a seat; it does not add one. They are counted apart from the ${n(company.tbh)} open roles.`], ['It will not let the model write a number.', 'The LLM maps columns and reads values. Every figure on every screen is arithmetic over rows — checked by a script that fails loudly if a total does not equal the rows beneath it.'], ]; let reports = `
What this tool refuses to do
`; for (const [t, b] of REFUSALS) reports += `
${t}
${b}
`; reports += `
How the numbers are checked

Every total on every screen is re-derived from the rows beneath it by a script — ${checksTxt} checks, all passing. It fails loudly if it cannot even find the thing it is meant to check, because a selector that matches nothing must fail, not silently pass.

  • Every parent equals the sum of its children — organization, director, team, role, site.
  • Every team is counted twice — once by role, once by location. Two independent counts of the same people. They must agree with each other, not merely with the parent.
  • Every project is counted three times — by role, by who lends the people, and by where they sit.
  • The defensible FTE may never exceed headcount. The ceiling may — but only on a contested row, where it is reported as an over-allocation rather than clamped away.
  • ${n(leaves.length)} allocation cells checked for over-commitment. ${disp.heads} people came back over 100%.
  • The bars are drawn from the same numbers as the tables — a picture built from a second copy of the data is a second chance to be wrong.
What is built, and what is not

built and running The bitemporal ledger, the reconciliation engine, the agentic column mapper (34 of 34 columns mapped correctly, zero wrong answers — checked by code that would fail if the guess were wrong), and the rollup arithmetic behind every figure here.

concept — not built yet The variance bridge and the time-phased forecast are drawn. The engine that would compute them is the next thing to build, and this page says so rather than letting you assume otherwise.

The honest caveat. The behaviours are pinned by tests on a 254-person engineering dataset. The ${n(company.heads)} people here are illustrative, scaled to eBay's real footprint (FY2025 Form 10-K: ≈12,300 globally, ≈7,200 in the United States). Everyone below the C-suite appears by employee code, never by name; the seven named executives are eBay's real, publicly-listed leaders, mapped to illustrative units.

`; const GLOSSARY = [ ['Headcount', 'People. One person is one head, whichever team borrows them. Heads do not split.'], ['FTE', "Full-time equivalent — a share of a person's time. A person split evenly across two projects is 1 head and 0.5 FTE on each. FTE adds up; heads do not."], ['Contractors', 'Counted and costed apart. Folding them into headcount is how a company accidentally reports 8% more people than it employs.'], ['AOP / F1 / F2', 'The approved operating plan, and the two forecasts that have replaced it since. Which one you measure against changes whether an org is over or under.'], ['Over-allocated', "Someone booked past 100% of their time. It cannot be true, so it is surfaced, never clamped."], ]; let glossary = `
The five words that cause every argument
`; for (const [t, b] of GLOSSARY) glossary += `
${t}
${b}
`; glossary += `
`; // ============================================ HOW IT WORKS (plain-English, end-to-end) // Seven-stage journey from five disagreeing source files to the single trusted number this // dashboard shows. Wording is deliberately non-technical; every stage below is faithful to the // real Req Room pipeline (mapSource -> computeEvidence -> Ledger -> reconcile -> /review -> consolidate). // The measured result reuses the SAME claim the Reports tab makes ("34 of 34, zero wrong") so the // two tabs never contradict each other — the whole point of the tool is that numbers agree. // Hero stat tiles — every figure is REAL (company/disp from data.json, checks from checks.json, // 34/34 matches the Reports tab). A single headline number is a stat tile, not a chart (dataviz). const costB = (company.costK / 1e6).toFixed(2); const fteLo = n(company.fte), fteHi = n(company.fte + disp.spreadFte), fteAvg = n(company.fte + Math.round(disp.spreadFte / 2)); const HSTATS = [ ['', n(company.heads), '', 'people counted across the org'], ['', `$${costB}`, 'B', 'annual people cost'], ['', n(company.tbh), '', 'open requisitions'], ['flag', n(company.disputedFte), ' FTE', 'flagged — never guessed'], ['good', '34/34', '', 'columns mapped · 0 wrong'], ['good', checksTxt, '', 'checks re-foot every number'], ]; const heroTiles = `
` + HSTATS.map(([cls, v, u, k]) => `
${v}${u ? `${u}` : ''}
${k}
`).join('') + `
`; // 1 — five files, mismatched column names (the same ideas, spelled five ways) // Real column headers from fixtures/sources/*.csv — the genuine naming chaos (cost_center vs CC vs // CostCtr; fte_pct vs "Q3 Ask (fte)"; Hdct vs Heads), not invented ones. const FILES = [['HRIS', ['employee_id', 'cost_center', 'fte_pct']], ['ATS', ['req_id', 'status', 'team']], ['Anaplan', ['CC', 'Hdct', 'Approved FTE']], ['Sheet A', ['Team', 'CC', 'Q3 Ask (fte)']], ['Sheet B', ['Group', 'CostCtr', 'Heads']]]; const viz1 = `
` + FILES.map(([nm, cols]) => `
${nm}
${cols.map(c => `${esc(c)}`).join('')}
`).join('') + `
`; // 2 — column mapping: proposals, with the ambiguity gate catching a second cost-centre column // REAL HRIS mappings + their true evidence from artifacts/mappings/hris.json: cost_center/job_level/ // employment_type are hard-checked (proven); fte_pct is evidence:"human" — no check can confirm a // share vs a count, so it is ESCALATED to a person (this is the thread stage 6 picks up). NB: the // canonical field is FTE, never "Headcount" — the domain model keeps those distinct on purpose. const MAPS = [['cost_center', 'Cost centre', 'ok'], ['job_level', 'Level', 'ok'], ['employment_type', 'Staff / contractor', 'ok'], ['fte_pct', 'FTE', 'q']]; const viz2 = `
` + MAPS.map(([c, f, b]) => `
${esc(c)}${f}${b === 'ok' ? 'Proven' : 'Escalated'}
`).join('') + `
`; // 3 — the proof gate: only checks that would fail count; a look-alike heuristic never does const GATE = [['y', 'Do the cost-centre codes actually exist?', 'counts as proof'], ['y', 'Is it the only column that looks like cost centres?', 'counts as proof'], ['y', 'Do the totals reconcile to a figure we already know?', 'counts as proof'], ['o', '“It looks like a name”', 'never counts']]; const viz3 = `
` + GATE.map(([m, l, tag]) => `
${m === 'y' ? '✓' : '–'}${l}${tag}
`).join('') + `
`; // 4 — one fact, two dates (bitemporal): valid-time and known-time const viz4 = `
Cost centre CC-4472 = 88 people
` + `
True from
1 Apr 2026
` + `
Known since
30 Jun 2026
`; // 5 — reconcile: the REAL disclosed dispute (40 engineers, ${company.disputedFte} FTE). Never averaged. const viz5 = `
Company FTE if the project system is right
${fteHi}
vs
…if the tracker is right
${fteLo}
Never averaged to ${fteAvg} — a number neither system claims.
Published as a range ${fteLo}–${fteHi} FTE, with ${n(company.disputedFte)} FTE flagged, not guessed.
Agreed · all matchReconciled · a rule decidesContested · shown as a gap
`; // 6 — human resolves what the AI could not, and the fix is re-proven + versioned const viz6 = `
● Escalated
The AI couldn’t prove this mapping.
● Resolved
A person approves or overrides. Re-run to prove it still reconciles, versioned v1→v2, decision logged.
`; // 7 — five sources converge into one tagged total const SRC = ['HRIS', 'ATS', 'Anaplan', 'Sheet A', 'Sheet B']; const viz7 = `
${SRC.map(s => `${s}`).join('')}
One consolidated headcount
${n(company.heads)} people · ${fteLo}–${fteHi} FTE
agreedreconciledcontestedsingle-source
`; // Each stage = short one-line caption + a diagram (text drops ~70% vs the prose version). const STAGES = [ ['in', 'Five files arrive — none agree on format', `HRIS, the ATS, Anaplan, and two side spreadsheets. Same ideas, different column names.`, viz1, ''], ['guess', 'An AI guesses what each column means', `Run offline, it proposes a mapping and a reason. Some it can prove; the ones it can’t — like a raw fte_pct — it escalates to a person.`, viz2, 'the AI proposes — it cannot self-certify'], ['proof', 'A guess is trusted only if it can be proven', `Evidence counts only from a check that would fail if the guess were wrong. Confidence is computed, never claimed.`, viz3, ''], ['ledger', 'Proven facts enter a twice-dated ledger', `Every fact carries two dates — when it was true, and when we learned it — so any past date reads honestly.`, viz4, ''], ['reconcile', 'Disagreements are sorted, not split', `The real case: two systems book ${n(disp.heads)} engineers’ time differently — leaving ${n(company.disputedFte)} FTE (the ${fteLo}–${fteHi} gap) the tool won’t guess.`, viz5, ''], ['human', 'A person resolves what the AI could not', `Escalations go to human review; the fix is re-proven, versioned, and logged. Nothing is silently corrected.`, viz6, ''], ['out', 'Everything consolidates into one number', `Rolled up by cost centre and org, every figure tagged — and that total is what every other tab shows.`, viz7, ''], ]; let howitworks = heroTiles + `
    `; STAGES.forEach(([tone, t, cap, viz, note], i) => { const badge = note ? `
    ${note}
    ` : ''; howitworks += `
  1. ${i + 1}
    ${t}
    ${cap}
    ${viz}
    ${badge}
  2. `; }); howitworks += `

The one rule the whole system defends: the AI reads numbers — it never writes one. A figure it cannot prove is handed to a person; a figure two sources dispute is shown as a gap, never a guess.

Want every constraint spelled out, and the ${checksTxt} checks that enforce them?

`; // ============================================ DASHBOARD (eBay dense shell) // LEADER_NAMES is defined near the top (C-suite only; everyone below stays coded — the rule the // operator set: ELT does not see exact names). A code with no name falls back to its title. const CXO_CODE = { tech: 'E-10002', ops: 'E-10004', mkt: 'E-10003', fin: 'E-10006', adv: 'E-10005', ppl: 'E-10007' }; // The "By Leader" tab is a self-contained module: it pre-renders all 7 views (company + 6 leaders) // at build time from these same mappings, so nothing double-derives and the checker foots each view. const LT = leaderTab({ leaves, tree, DOMAIN_META, PLANS, LEADER_NAMES, CXO_CODE, company }); const initials = (s) => s.split(/\s+/).filter(Boolean).slice(0, 2).map((w) => w[0]).join('').toUpperCase(); // -- KPI strip. Seven cards, all DERIVED. No "forecast accuracy 98.4%" or month-over-month // series — this dataset has no time history, and inventing one is the exact thing the tool // refuses to do. const attrPct = (company.leavers / company.heads * 100).toFixed(1); const qLeavers = company.leaversQtd, qAttr = (qLeavers / company.heads * 100).toFixed(1); const per = (ytd, qtd) => `${ytd}`; const varLo = company.fte - CO_PLAN[0], varHi = (company.fte + disp.spreadFte) - CO_PLAN[0]; const sg = (v) => (v < 0 ? '−' : '+') + Math.abs(v); // signed, with a real minus not a hyphen const nReqCat = new Set(leaves.filter((l) => l.tbh).map((l) => l.roleCat)).size; const KPIS = [ ['k-blue', 'Current headcount', n(company.heads), `${n(company.contractor)} contractors, costed apart`, `▲ vs plan ${n(CO_PLAN[0])} heads-basis`], ['k-purple', 'Workforce FTE', `${n(company.fte)}${n(company.fte + disp.spreadFte)}`, `a range — ${disp.heads} engineers in dispute`, `the number the tool will stand behind`], ['k-red', 'Variance to plan', `${sg(varLo)} to ${sg(varHi)} FTE`, `vs approved plan ${n(CO_PLAN[0])}`, `${overOrgs.length} org over · ${underOrgs.length} under`], ['k-green', 'Salary run-rate', bigMoney(company.costK), `${money(Math.round(company.costK / company.heads))}/head, fully loaded`, `▼ derived from the rate card`], ['k-teal', 'Open requisitions', n(company.tbh), `across ${nReqCat} role categories`, `a backfill is not growth`], ['k-amber', `Attrition ${per('(YTD)', '(QTD)')}`, per(`${attrPct}%`, `${qAttr}%`), per(`${n(company.leavers)} leavers, year to date`, `${n(qLeavers)} leavers this quarter`), `a flow figure — the toggle moves it`], ['k-indigo', 'Contractors', n(company.contractor), `${pct(company.contractor, company.heads + company.contractor)}% of the workforce`, `costed apart from FTE`], ]; let kpiStrip = '
'; for (const [c, k, v, s, f] of KPIS) kpiStrip += `
${k}
${v}
${s}
${f}
`; kpiStrip += '
'; // -- band: how far off plan a unit is (mockup's On plan / Watch / At risk) const band = (p) => (Math.abs(p) <= 2 ? 'ok' : Math.abs(p) <= 5 ? 'watch' : 'risk'); // -- Business units: the 6 domains, actual FTE vs approved plan, sorted by variance. const bu = tree.map((d) => { const a = domA[d.key], plan = PLANS[d.key][0]; const lo = a.fte - plan, hi = a.fte + a.dFte - plan; return { label: DOMAIN_META[d.key][0], actual: a.fte, actualHi: a.fte + a.dFte, plan, lo, hi, pctLo: (lo / plan) * 100, pctHi: (hi / plan) * 100, dFte: a.dFte }; }).sort((x, y) => x.pctLo - y.pctLo); const buMax = Math.max(...bu.map((b) => Math.max(b.actualHi, b.plan))) * 1.04; let buPanel = ''; for (const b of bu) { const bnd = band(b.pctLo); const vtxt = b.dFte ? `${sg(b.lo)} to ${sg(b.hi)}` : sg(b.lo); const ptxt = b.dFte ? `${b.pctLo >= 0 ? '+' : '−'}${Math.abs(b.pctLo).toFixed(1)} to ${b.pctHi >= 0 ? '+' : '−'}${Math.abs(b.pctHi).toFixed(1)}%` : `${b.pctLo >= 0 ? '+' : '−'}${Math.abs(b.pctLo).toFixed(1)}%`; buPanel += ``; } buPanel += '
ActualPlanVarVar %
${esc(b.label)}${b.dFte ? ' disputed' : ''} ${b.dFte ? `` : ''} ${n(b.actual)}${n(b.plan)} ${vtxt}${ptxt}
'; // -- Leaders: the same 6 C-suite, named, sorted by variance (top by variance = most at risk). const AV = ['#0064d2', '#e53238', '#86b817', '#f5af02', '#7b3fe4', '#0f9d58']; const leaders = tree.map((d, i) => { const a = domA[d.key], plan = PLANS[d.key][0], code = CXO_CODE[d.key]; const nm2 = LEADER_NAMES[code]; return { code, name: nm2 ? nm2[0] : null, title: nm2 ? nm2[1] : DOMAIN_META[d.key][1] + ', ' + DOMAIN_META[d.key][0], actual: a.fte, plan, lo: a.fte - plan, hi: a.fte + a.dFte - plan, dFte: a.dFte, color: AV[i % AV.length] }; }).sort((x, y) => x.lo - y.lo); let leaderPanel = ''; for (const l of leaders) { const disp2 = l.name ? initials(l.name) : l.code.slice(-2); const vtxt = l.dFte ? `${sg(l.lo)} to ${sg(l.hi)}` : sg(l.lo); const p = (l.lo / l.plan) * 100; leaderPanel += ``; } // Seventh leader: Chief Legal Officer. Legal & Governance is a remit inside Finance, Legal & G&A // (already counted in Peggy Alford's row above), so this row carries no separate headcount — a // dash, not a zero, to signal "counted elsewhere" rather than "empty". leaderPanel += ``; leaderPanel += '
LeaderActualPlanVarVar %
${disp2} ${l.name ? esc(l.name) : 'Code ' + l.code}${esc(l.title)} · ${l.code} ${n(l.actual)}${n(l.plan)} ${vtxt} ${p >= 0 ? '+' : ''}${p.toFixed(1)}%
${initials(LEGAL_LEADER[0])} ${esc(LEGAL_LEADER[0])}${esc(LEGAL_LEADER[1])} within Finance, Legal & G&A
'; // -- Headcount by level. Our leaf people carry IC2–IC6 / M1–M3; bucket them into seniority // tiers. Sums to the company head count. // eBay runs numeric job levels. Map this dataset's synthetic IC2–IC6 / M1 onto eBay's real // bands (<21, 22–25, 26–28, exec) as a believable pyramid — many junior, few senior. "Exec" // is our M1 management tier; the dataset has no higher leaf levels (director/VP nodes are // structural, not headcount rows), which is a noted future enrichment. const LVL_BUCKET = { IC2: '< 21', IC3: '22–25', IC4: '22–25', IC5: '26–28', IC6: '26–28', M1: 'Exec' }; const LVL_ORDER = ['< 21', '22–25', '26–28', 'Exec']; const LVL_COLOR = { '< 21': '#f5af02', '22–25': '#0f9d58', '26–28': '#0aa2c0', Exec: '#7b3fe4' }; const lvlCount = {}; for (const l of leaves) { const b = LVL_BUCKET[l.level] || 'Other'; lvlCount[b] = (lvlCount[b] || 0) + l.heads; } const lvlMax = Math.max(...Object.values(lvlCount)); let lvlPanel = '
'; for (const k of LVL_ORDER) { const v = lvlCount[k] || 0; lvlPanel += `
${k}${n(v)}${pct(v, company.heads)}%
`; } lvlPanel += `
Total${n(company.heads)}100%
`; // -- Headcount by region. Reuses byRegion; sums to the company. const regMax = Math.max(...byRegion.map((r) => r.heads)); let regPanel = '
'; for (const r of byRegion) regPanel += `
${r.r}${n(r.heads)}${pct(r.heads, company.heads)}%
`; regPanel += `
US of that${n(usHeads)}${pct(usHeads, company.heads)}%
`; // -- Movement tiles: open reqs, leavers, contractors — the real flow figures we hold. const movePanel = `
+${n(company.tbh)}
Open requisitions stock
${per('−' + n(company.leavers), '−' + n(qLeavers))}
Known leavers ${per('(YTD)', '(QTD)')}
${n(company.contractor)}
Contractors stock
`; // -- Top variances (at risk), from the same business-unit numbers. const topVar = [...bu].sort((a, b) => Math.abs(b.pctLo) - Math.abs(a.pctLo)).slice(0, 6); let topVarPanel = '
'; for (const t of topVar) { const bnd = band(t.pctLo); topVarPanel += `
${esc(t.label)} ${t.lo >= 0 ? '+' : '−'}${Math.abs(t.lo)} ${t.pctLo >= 0 ? '+' : ''}${t.pctLo.toFixed(1)}%
`; } topVarPanel += '
'; // -- Actual vs the three plans (replaces the mockup's 12-month trend, which we have no history // for — a drawn rising line would imply data we do not hold). const planMax = Math.max(company.fte + disp.spreadFte, ...CO_PLAN) * 1.02; const PLAN_BARS = [['Actual FTE', company.fte, company.fte + disp.spreadFte, '#0064d2'], ['Approved plan', CO_PLAN[0], CO_PLAN[0], '#94a3b8'], ['Forecast 1', CO_PLAN[1], CO_PLAN[1], '#94a3b8'], ['Forecast 2', CO_PLAN[2], CO_PLAN[2], '#94a3b8']]; let planPanel = '
'; for (const [k, lo, hi, col] of PLAN_BARS) planPanel += `
${k}${lo === hi ? n(lo) : n(lo) + '–' + n(hi)}
`; planPanel += '
'; // -- Attrition by organization: the SAME flow figure the YTD/QTD toggle drives, broken out per org. // Leavers is the only true flow metric this dataset holds (everything else is a point-in-time // stock, which has no honest quarter-vs-year form), so the toggle governs exactly this — and now // six org rows, not one company number. Values are per() spans, so the existing period handler // flips them with no extra wiring. The bar shows YTD magnitude; the two number columns switch. const rate = (l, h) => (h ? (l / h * 100).toFixed(1) : '0.0'); const attrOrgs = tree.map((d) => ({ label: DOMAIN_META[d.key][0], y: domA[d.key].leavers, q: domA[d.key].leaversQtd, heads: domA[d.key].heads, })).sort((a, b) => b.y - a.y || b.q - a.q); const attrMax = Math.max(1, ...attrOrgs.map((o) => o.y)); let attrOrgPanel = ''; for (const o of attrOrgs) { attrOrgPanel += ``; } attrOrgPanel += `
OrganizationLeaversRate
${esc(o.label)} ${per(n(o.y), n(o.q))} ${per(rate(o.y, o.heads) + '%', rate(o.q, o.heads) + '%')}
Company ${per(n(company.leavers), n(qLeavers))} ${per(attrPct + '%', qAttr + '%')}
`; // -- Key insights: three COMPUTED cards (the mockup's model-written "recommendations" are // exactly what this tool refuses; every line here is arithmetic that ran). const keyInsights = `
!
Two systems disagree about ${disp.heads} engineers. They owe ${P(disp.ownRoadmap)}% to their own roadmap; one system books them at ${P(disp.ppmClaim)}%, another at ${P(disp.trackerClaim)}% — the higher reading is ${P(disp.ownRoadmap + disp.ppmClaim)}%, over-allocated. Worth ${disp.spreadFte} FTE / ${money(DISP_COST_K)}. The tool publishes the range and refuses to average them.
Everything else reconciles. ${checksTxt} checks pass: every total re-derived from its rows, every team counted twice (by role and by location) and made to agree, ${n(leaves.length)} allocation cells tested for over-commitment. The ${disp.heads} above are the only rows over 100%.
Cost is not headcount. Customer Service is ${pct(domA.ops.heads, company.heads)}% of the people and ${pct(domA.ops.costK, company.costK)}% of the cost; ${pct(sum(leaves.filter((l) => l.domain === 'ops' && D.sites[l.site].region === 'APAC'), (l) => l.heads), domA.ops.heads)}% of it is APAC. A support seat in Manila is ${money(manila)}; an engineer in San Jose is ${money(sanjose)}. Report headcount alone and the number is wrong by ${(pct(domA.tech.costK, company.costK) / pct(domA.ops.costK, company.costK)).toFixed(1)}×.
`; // ================================================================== THE PAGE const NAV = [ ['dashboard', 'Headcount', 'M7 9a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm7 1a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5zM2 17c0-2.8 2.2-5 5-5s5 2.2 5 5zm11 0c0-1.6-.5-3-1.4-4.2A4.3 4.3 0 0 1 18 17z'], LT.navItem, ['explorer', 'Explorer', 'M8.5 3a5.5 5.5 0 1 0 3.4 9.8l3.6 3.7 1.4-1.4-3.7-3.6A5.5 5.5 0 0 0 8.5 3zm0 2a3.5 3.5 0 1 1 0 7 3.5 3.5 0 0 1 0-7z'], ['organization', 'Organization', 'M9 3h2v3H9zM3 14h2v3H3zm12 0h2v3h-2zM4 13V9h12v4h-1.5v-2.5h-9V13z'], ['budget', 'Budget', 'M10 2a8 8 0 1 0 0 16 8 8 0 0 0 0-16zm.8 12.3v1h-1.5v-1c-1.2-.2-2-.9-2.1-2h1.6c.1.5.5.8 1.3.8s1.2-.3 1.2-.8-.4-.7-1.5-1c-1.5-.3-2.4-.9-2.4-2.1 0-1 .7-1.8 1.9-2v-1h1.5v1c1.1.2 1.8.9 1.9 1.9h-1.6c-.1-.4-.4-.7-1-.7-.7 0-1.1.3-1.1.7 0 .5.4.7 1.5.9 1.6.3 2.4 1 2.4 2.2 0 1.1-.8 1.9-2.1 2.1z'], ['scenarios', 'Scenarios', 'M3 16h14v1.5H3zm1-4h2v3H4zm4-5h2v8H8zm4 2h2v6h-2zM4.7 6.9 8.6 4l3.7 2.4L16.5 3l1 1.2-5 3.9-3.7-2.4-3.4 2.5z'], ['hiring', 'Hiring', 'M10 3a3.5 3.5 0 1 0 0 7 3.5 3.5 0 0 0 0-7zM3 18c0-3.3 3.1-6 7-6s7 2.7 7 6z'], ['reports', 'Reports', 'M5 2h7l3 3v13a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1zm6 1.5V6h2.5zM6.5 9h7v1.4h-7zm0 3h7v1.4h-7zm0 3h4.5v1.4H6.5z'], ['howitworks', 'How it works', 'M10 2a8 8 0 1 0 0 16 8 8 0 0 0 0-16zm0 1.6a6.4 6.4 0 1 1 0 12.8 6.4 6.4 0 0 1 0-12.8zM9.1 8.9h1.8v5.1H9.1zM10 5.6a1.05 1.05 0 1 1 0 2.1 1.05 1.05 0 0 1 0-2.1z'], ]; const html = ` Req Room — FP&A Headcount Console
Headcount DashboardFP&A Headcount Console
▤ Month end: Jun 30, 2026

Headcount Dashboard

Everyone at eBay — ${n(company.heads)} people, ${bigMoney(company.costK)} a year. One thing here does not reconcile and is flagged red; everything else was checked and does. All figures are FTE unless the card says headcount.

${kpiStrip}
Headcount by business unit Actual vs approved plan · sorted by variance
${buPanel}
On plan (±2%)Watch (2–5%)At risk (>5%)
Headcount by leader Top by variance · C-suite named, all others coded
${leaderPanel}
Leaders are the seven executives eBay lists publicly (ebayinc.com/company/our-leaders), mapped to illustrative org units; the figures are demo data. eBay has no COO, so Customer Service & Operations sits under the CEO. Legal & Governance is counted within Finance, Legal & G&A, so that row carries no separate headcount.
Attrition by organization Leavers and rate · switches with the YTD / QTD toggle above
${attrOrgPanel}
Actual vs the three plans
${planPanel}
By level
${lvlPanel}
By region
${regPanel}
Movement
${movePanel}
Top variances
${topVarPanel}
Key insights Every line is a check that ran — not advice the model wrote
${keyInsights}
Source: HRIS · project system · team tracker — all read 06:00 ET today. Rate card effective 1 Jul 2026. The two systems that disagree were both read this morning: a live disagreement, not a stale copy. · Req Room v2.1

Slice it any way you need

A fixed hierarchy only answers the questions its author thought of. How many engineers are on this project, at which site, at what spend? leads with role, not with org — so choose your own hierarchy. Every number is summed from the same ${n(leaves.length)} rows the org tree is built from, so a slice can never disagree with the total.

Start from
Group by
Filter
GroupPeopleFTE Cost / yearShare of cost

Who rolls up to whom

Organization → director → team → the roles inside it, and where those people sit. Every team is broken down twice — once by role and once by location. Two independent counts of the same people; they must come to the same total, or one of them is wrong.

Pick an organization
${bars}
${LT.section}

Where the money goes

Headcount and cost are not the same shape. Report one and you have told the CEO something that is wrong by a factor of ${(pct(domA.tech.costK, company.costK) / pct(domA.ops.costK, company.costK)).toFixed(1)}, depending on which organization they ask about.

Cost by organization
${budget}
Cost by site — the reason the orgs differ
${geoTable}
The variance bridge concept — this engine isn't built yet
Plan said 790. The actual is 758. The tool must explain the whole 32-FTE gap, and every reason for it — the drivers sum to the gap exactly, or the tool reports that it cannot decompose it. It never plugs a residual to make the arithmetic close.
${bridge}

Which plan are you measuring against?

The approved plan, and the two forecasts that have replaced it since. The same organization is over plan against one and under against another — so a tool that shows you a single "variance" without saying which plan it used is not telling you anything.

Actual against all three plans
${scen}

Customer Service is the whole point. It is +${overBy} FTE against the approved plan — the only organization over. Against forecast 2 it is exactly on plan. Nothing about the organization changed; only the baseline did. Whoever picks the baseline picks the answer, which is why this tool always names it.

Open roles, and the people who left

${n(company.tbh)} open requisitions and ${n(company.leavers)} known leavers. They are counted apart, deliberately: a backfill is not growth. Replacing someone who left restores a seat; it does not add one. A tool that adds them together will tell you the company is growing when it is standing still.

${hiring}

How it stays honest

The one idea the whole thing defends: the model reads numbers, it never writes one. Everything below is a constraint the tool imposes on itself — and the checks that prove it did.

${reports}${glossary}

How this dashboard works, end to end

Every number on every other tab starts as five spreadsheets that disagree with each other. Here is the whole journey from those messy files to a single figure a leader can trust — in plain English, no jargon.

${howitworks}
`; // Inject the html2canvas source into the inert placeholder AFTER templating, so its minified // body (which contains backticks and ${...}) never interferes with the outer template literal. // Function replacement avoids $-pattern interpretation in the library source. // html2canvas-pro (maintained fork): supports color-mix()/oklch()/color(), which the base // html2canvas 1.4.1 throws on. Same `window.html2canvas` global, drop-in. const H2C_SRC = readFileSync('/home/tpeng/review-files/html2canvas-pro.min.js', 'utf8'); const htmlOut = html.replace('', () => ``); writeFileSync('/home/tpeng/review-files/req-room-fpa-console.html', htmlOut); console.log(`page written ${(htmlOut.length / 1024).toFixed(0)} KB`); console.log(`rows ${(html.match(/