who lends the people— the org that owns them, and the share of their time this project books.
`;
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 += `
${esc(svpName(c_))}${c_} ${claim}
${R(r)}
·
·
`;
}
projHtml += `
where they sit— the project's real geography, which is not the sponsor's.
`;
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 += `
`;
// ================================================================== 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
Role category
Open reqs
Known leavers
Current people
`;
for (const h of hireByCat) hiring += `
${esc(h.c)}
${n(h.tbh)}
${n(h.leavers)}
${n(h.heads)}
`;
hiring += `
All open roles
${n(company.tbh)}
${n(company.leavers)}
${n(company.heads)}
Where the open roles are
Site
Open reqs
Known leavers
`;
for (const h of hireBySite) hiring += `
◍ ${esc(h.s)}${D.sites[h.s].region}
${n(h.tbh)}
${n(h.leavers)}
`;
hiring += `
`;
// ================================================================== 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 = `
Organization
People
Share of people
Cost / year
Share of cost
Cost / head
`;
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 += `
${esc(label)} ${skew > 3 ? 'costs more than its size' : skew < -3 ? 'costs less than its size' : ''}
`;
// 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 = `
Reason the plan and the actual differ
FTE
Kind
`;
for (const [lbl, v, kind] of DRV) bridge += `
${lbl}
${v < 0 ? '−' : v > 0 ? '+' : ''}${Math.abs(v)}
${kind}
`;
bridge += `
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.
const PIPELINE = [
['in', 'Five files arrive — and none of them agree on format',
`Headcount data shows up from five places at once: HRIS (who is actually on payroll), the ATS (open requisitions), Anaplan (the approved budget), and two side spreadsheets — sheet A and sheet B — that individual orgs keep on their own. Every file names its columns differently. None is wrong; none matches the others.`, ''],
['guess', 'An AI reads each file and guesses what every column means',
`Run offline — never live inside this dashboard — the model looks at each column and proposes a mapping: this column is the cost centre, that one is the headcount. It hands back only a guess and a reason. It is not allowed to declare its own guess correct.`, 'the AI proposes — it cannot self-certify'],
['proof', 'The system trusts a guess only if it can prove it',
`A guess counts as evidence only if it survives a check that would have failed had the guess been wrong: do the cost-centre codes actually exist? Is this the only column whose values look like cost centres? Do the totals reconcile to a figure we already know? “It looks like a name” proves nothing and is never counted. Confidence is computed here, never claimed by the AI — and anything it cannot prove is set aside for a person, not waved through.`, ''],
['ledger', 'Proven facts go into a permanent, twice-dated ledger',
`Each proven fact is written once and never overwritten, stamped with two dates: when it became true, and when we learned it. So you can always ask “what did the numbers look like on 30 June, as far as we knew at the time” — and get an honest answer, not today’s numbers wearing yesterday’s date.`, ''],
['reconcile', 'When two sources disagree, the number is sorted — not split',
`The engine compares every claim about the same figure and files it as one of three things. A contested number is left blank on purpose: a disputed figure is more honest as a visible gap than as a guess that quietly enters the company total.`, ''],
['human', 'A person resolves whatever the AI could not',
`The escalations and contested figures go to a human review screen, where a person can approve, override, or hold. The system then re-runs the mapping to prove it still reconciles, versions the record so every change is auditable, and logs who decided what. Nothing is silently corrected.`, ''],
['out', 'Everything consolidates into the number you see here',
`Finally the proven facts are rolled up by cost centre and by org, every figure tagged agreed, reconciled, contested, or single-source. That consolidated total — and only that — is what every other tab in this dashboard shows you.`, ''],
];
let howitworks = ``;
PIPELINE.forEach(([tone, t, b, note], i) => {
const states = tone === 'reconcile'
? `
Agreed — all sources matchReconciled — a written rule decides (e.g. Anaplan wins for budget)Contested — no rule, no majority
`
: '';
const badge = note ? `
${note}
` : '';
howitworks += `
${i + 1}
${t}
${b}
${states}${badge}
`;
});
howitworks += `
Does the guessing actually work?
On the mapping test the agent got 34 of 34 columns right — zero wrong answers, each one confirmed by code that would fail if the guess were mistaken, not by the model’s say-so. And every total this dashboard renders is itself re-derived from the rows beneath it ${checksTxt} times on every build. The dashboard does not ask you to trust it — it shows its work.
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. That single rule is why a leader can act on what is here.
Want the constraints spelled out, and the 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}${qtd}`;
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 = '
`;
}
// 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 += `
`;
// -- 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 += `
';
// -- 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 += '
';
// -- 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)}×.
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.
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
Group
People
FTE
Cost / year
Share 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}
${treeHtml}
Cross-team projects — all ${projects.length}, and exactly who staffs each one
${projHtml}
${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(/