// Builds the packages table, CPI table and saved-analysis rows for // dataset:wa-tax-package-sizes. Run: node build.js const fs = require('fs'); const crypto = require('crypto'); const cpi = {}; for (const line of fs.readFileSync('cpi_u_annual.csv', 'utf8').trim().split('\n').slice(1)) { const [y, v, m] = line.split(','); cpi[+y] = { v: +v, m: +m }; } const CPI_2025 = cpi[2025].v; // One row per legislative session's tax package. newRevenueMillions = state // general-fund revenue from the session's tax legislation in the biennium it // was enacted, as estimated at enactment. revenueBaseMillions = general-fund // revenue collected (or latest forecast) for that same biennium. const packages = [ { id: '1981-nov', label: '1981 (Nov.)', session: '1981 2nd special session', year: 1981, biennium: '1981-83', bills: 'Sales tax 4.5% to 5.5% and other 1981 2nd ex. session revenue acts', fund: 'GF-S (old definition)', newRevenueMillions: 563.0, revenueBaseMillions: 6892.9, partOfBienniumOnly: true, sourceIds: 'leg-1981-2nd;lbn-1989', note: 'Enacted mid-biennium; about 19 months of collections. One-cent sales tax alone $527.4M.' }, { id: '1982-spring', label: '1982 (spring)', session: '1982 1st special session', year: 1982, biennium: '1981-83', bills: 'SB 4250: sales tax on food for 14 months and 4% surtax on most other taxes', fund: 'GF-S (old definition)', newRevenueMillions: 300.0, revenueBaseMillions: 6892.9, partOfBienniumOnly: true, sourceIds: 'leg-1982-2nd;flr-1982;lbn-1989', note: 'A second legislative report gives $326M; $300M used. Temporary to June 30, 1983.' }, { id: '1982-june', label: '1982 (June)', session: '1982 2nd special session', year: 1982, biennium: '1981-83', bills: 'Surtax increase, inventory credit deferral, lottery and other revenue acts', fund: 'GF-S (old definition)', newRevenueMillions: 144.3, revenueBaseMillions: 6892.9, partOfBienniumOnly: true, sourceIds: 'leg-1982-2nd;lbn-1989', note: 'Includes some non-rate items (a credit deferral, lottery revenue).' }, { id: '1993', label: '1993', session: '1993 regular and 1st special sessions', year: 1993, biennium: '1993-95', bills: '2ESSB 5967 (omnibus: services sales tax, B&O rates, 6.5% B&O surtax) + E2SSB 5304 (tobacco, alcohol, health taxes)', fund: 'GF-S + Health Services Account', newRevenueMillions: 900.1, revenueBaseMillions: 16259.6, partOfBienniumOnly: false, sourceIds: 'lbn-1993;fbr-5304;lbn-1994', note: '$648.7M GF-S from 5967 plus $251.4M to the Health Services Account from 5304. The base excludes Health Services Account revenue, so the share is slightly high.' }, { id: '2010', label: '2010', session: '2010 regular and 1st special sessions', year: 2010, biennium: '2009-11', bills: '2ESSB 6143 (B&O, sales, beer, pop) + ESHB 2493 (tobacco) and others', fund: 'GF-S (current definition)', newRevenueMillions: 761.0, revenueBaseMillions: 28218, partOfBienniumOnly: true, sourceIds: 'erfc-jun-2010;erfc-sep-2026', note: 'Enacted mid-biennium (about 15 months). $1,658.7M in 2011-13. I-1107 repealed $63M (2009-11) and $218M (2011-13).' }, { id: '2017', label: '2017', session: '2017 sessions', year: 2017, biennium: '2017-19', bills: 'EHB 2242 (state school property tax) + EHB 2163 (remote sellers) and others', fund: 'NGF-O', newRevenueMillions: 2046.5, revenueBaseMillions: 46081, partOfBienniumOnly: false, sourceIds: 'lbn-2017;facthub-gf-growth', note: 'The state property tax increase was paired with limits on local school levies, which this row does not net out.' }, { id: '2019', label: '2019', session: '2019 session', year: 2019, biennium: '2019-21', bills: 'ESSB 5998 (graduated real estate excise tax), SHB 2167 (bank surcharge) and others', fund: 'NGF-O (as defined in 2019)', newRevenueMillions: 349.6, revenueBaseMillions: 53132, partOfBienniumOnly: false, sourceIds: 'lbn-2019;erfc-sep-2026', note: 'Excludes the higher-education B&O surcharge ($380M in 2019-21 per the same notes), then deposited outside NGF-O.' }, { id: '2021', label: '2021', session: '2021 session', year: 2021, biennium: '2021-23', bills: 'ESSB 5096 (7% capital gains tax) and others', fund: 'NGF-O', newRevenueMillions: 414.0, revenueBaseMillions: 64734, partOfBienniumOnly: false, sourceIds: 'lbn-2021;erfc-sep-2026', note: 'Capital gains alone $415M; other 2021 bills net about zero. Climate Commitment Act revenue is outside NGF-O.' }, { id: '2025', label: '2025', session: '2025 session', year: 2025, biennium: '2025-27', bills: 'ESHB 2081 (B&O), ESSB 5814 (sales tax on services), ESSB 5813 (capital gains, estate), ESSB 5794, SHB 2077', fund: 'NGF-O', newRevenueMillions: 3603.9, revenueBaseMillions: 76258, partOfBienniumOnly: false, sourceIds: 'lbn-2025;wrc-2025;erfc-sep-2026', note: 'Tax bills only; excludes $609M of 2SSB 5802 transfers from transportation accounts and the ESSB 5801 transportation taxes ($746.2M in 2025-27). Base is the June 2026 forecast for 2025-27.' }, ]; const r1 = x => Math.round(x * 10) / 10; const r2 = x => Math.round(x * 100) / 100; for (const p of packages) { p.cpiYear = p.year; p.cpiU = cpi[p.year].v; p.real2025Millions = Math.round(p.newRevenueMillions * CPI_2025 / p.cpiU); p.sharePct = r2(100 * p.newRevenueMillions / p.revenueBaseMillions); } const cols = ['id', 'label', 'session', 'year', 'biennium', 'bills', 'fund', 'newRevenueMillions', 'revenueBaseMillions', 'sharePct', 'cpiYear', 'cpiU', 'real2025Millions', 'partOfBienniumOnly', 'sourceIds', 'note']; const q = v => (typeof v === 'string' && /[",;\n]/.test(v)) ? '"' + v.replace(/"/g, '""') + '"' : String(v); const csv = cols.join(',') + '\n' + packages.map(p => cols.map(c => q(p[c])).join(',')).join('\n') + '\n'; fs.writeFileSync('packages.csv', csv); // Combined 1981-83 (three sessions in one biennium) const s81 = packages.filter(p => p.biennium === '1981-83'); const comb = { newRevenueMillions: r1(s81.reduce((a, p) => a + p.newRevenueMillions, 0)), real2025Millions: s81.reduce((a, p) => a + p.real2025Millions, 0), }; comb.sharePct = r2(100 * comb.newRevenueMillions / 6892.9); const out = { CPI_2025, real: packages.map(p => ({ session: p.label, real2025Billions: r2(p.real2025Millions / 1000) })), nominal: packages.map(p => ({ session: p.label, nominalBillions: r2(p.newRevenueMillions / 1000) })), share: packages.map(p => ({ session: p.label, sharePct: r1(p.sharePct) })), comb, // Sensitivities y1993GfsOnlySharePct: r2(100 * 648.7 / 16259.6), y1993GfsOnlyReal: Math.round(648.7 * CPI_2025 / cpi[1993].v), y1982alt326Real: Math.round((563 * CPI_2025 / cpi[1981].v) + (326 + 144.3) * CPI_2025 / cpi[1982].v), y1983hb1075Real: Math.round(63.5 * CPI_2025 / cpi[1983].v), y2025vsPrePackagePct: r2(100 * 3603.9 / 70951.6), y2025fourYearPct: r2(100 * 9047 / (76258 + 82594)), y2025firstFullPct: r2(100 * 5444 / 82594), y2010twoBienniaPct: r2(100 * (761.0 + 1658.7) / (28218 + 30657)), y2025withTransportMillions: r1(3603.9 + 746.2), }; fs.writeFileSync('results.json', JSON.stringify(out, null, 1)); for (const f of ['packages.csv', 'cpi_u_annual.csv']) { const b = fs.readFileSync(f); console.log(f, b.length, 'sha256:' + crypto.createHash('sha256').update(b).digest('hex')); } console.log(JSON.stringify(out));