// Writes the two tables added in dataset:wa-general-fund-growth v3 from spending_vs_need.json. // Run after spending_vs_need.js: node millionaires-tax/data/build_v3_tables.js const fs = require("fs"), path = require("path"); const r = JSON.parse(fs.readFileSync(path.join(__dirname, "spending_vs_need.json"), "utf8")); const p1315 = r.paths.find(p => p.base === "2013-15").paths; const R = r.revenue, g = r.outlookGap; const need = [ ["Spending above population x US CPI path", "2023-25 spending above 2013-15 path", "2023-25", p1315["pop x US CPI"].aboveB], ["Spending above population x state-local government price path", "2023-25 spending above 2013-15 path", "2023-25", p1315["pop x S&L govt price index"].aboveB], ["Spending above population x Seattle CPI path", "2023-25 spending above 2013-15 path", "2023-25", p1315["pop x Seattle CPI"].aboveB], ["Spending above 2013-15 share of personal income", "2023-25 spending above 2013-15 path", "2023-25", p1315["personal income (constant share)"].aboveB], ["Spending above 2005-07 share of personal income", "2023-25 spending above 2005-07 share", "2023-25", r.at2005share.aboveB], ["Millionaires tax, net in budget outlook", "Millionaires tax revenue", "2027-29", Math.round(R.outlookNet2729 * 100) / 100], ["Millionaires tax, gross (fiscal note)", "Millionaires tax revenue", "2027-29", R.fiscalNoteGross2729], ["Millionaires tax, gross (Sept 2026 forecast)", "Millionaires tax revenue", "2027-29", R.erfcSepGross["2027-29"]], ["Millionaires tax, gross (Sept 2026 forecast, approx.)", "Millionaires tax revenue", "2029-31", R.erfcSepGross["2029-31"]], ].map(([item, group, period, billions]) => ({ item, group, period, billions })); const gap = [ ["Adopted outlook, with the tax", g.endingBalanceWithTax, null, null], ["Adopted outlook minus net tax revenue", g.gapNet, g.growthToClose_netPct, g.cutShareOf2729_netPct], ["Adopted outlook minus gross tax revenue (relief kept)", g.gapGrossApril, g.growthToClose_grossAprilPct, g.cutShareOf2729_grossAprilPct], ["Adopted outlook minus Sept 2026 gross tax revenue", g.gapGrossSep, g.growthToClose_grossSepPct, g.cutShareOf2729_grossSepPct], ["WRC-adjusted balance minus Sept 2026 gross tax revenue", g.gapWrcAdjustedSep, g.growthToClose_wrcAdjustedSepPct, g.cutShareOf2729_wrcAdjustedSepPct], ].map(([scenario, balance2729Billions, spendingGrowthToClosePct, cutShareOfSpendingPct]) => ({ scenario, balance2729Billions, spendingGrowthToClosePct, cutShareOfSpendingPct })); const toCsv = rows => { const k = Object.keys(rows[0]); return [k.join(","), ...rows.map(o => k.map(x => o[x] === null ? "" : (typeof o[x] === "string" ? `"${o[x]}"` : o[x])).join(","))].join("\n") + "\n"; }; fs.writeFileSync(path.join(__dirname, "wa-need-vs-growth.csv"), toCsv(need)); fs.writeFileSync(path.join(__dirname, "wa-outlook-gap-2027-29.csv"), toCsv(gap)); fs.writeFileSync(path.join(__dirname, "v3-rows.json"), JSON.stringify({ need, gap, outlookGrowth2729Pct: g.outlookGrowth2729Pct, path2729: g.path2729_popCpi_from1315, outlook2729AbovePath: g.outlook2729AbovePath, noTaxAbovePath: [g.noTaxWrcAdjusted2729AbovePath, g.noTaxGrossApril2729AbovePath] }, null, 1)); console.log(toCsv(need)); console.log(toCsv(gap));