/** * Skattebasens beräkningsmodell, version m3.4.0 (rättat omställningsstöd, arbetstidsläge, sex scenarier; föregående större version i audit/m3.2.1). * * Modellen är en förenklad, villkorad analys: "om dessa antaganden gäller, hur ser * då de offentliga finanserna ut jämfört med basåret?". Den är inte en * makroekonomisk prognos och innehåller ingen år-för-år-utveckling. * * Tre tillstånd beräknas alltid: * A. Utgångsläget – basårets verifierade utfall, oförändrat. * B. Utgångsläget + valt AI-scenario, med oförändrade skatte- och utgiftsinställningar. * C. Samma AI-scenario efter användarens skatte- och utgiftsändringar. * Uppdelningen som visas i gränssnittet är (B − A) och (C − B). Varje effekt * förekommer därför exakt en gång och delposterna summerar till C − A. * * Grundprinciper * 1. Varje skattegrupp knyts till ett eller flera skatteunderlag ur * nationalräkenskaperna. Effektiv beskattning kalibreras som intäkt dividerat * med underlag för basåret. Historiska intäktsandelar används aldrig som * skattesatser. * 2. Löner (D11) och ersättning till anställda inklusive arbetsgivaravgifter (D1) * hålls isär. Hushållens inkomstskatt och omställningsstöd räknas på löner, * arbetsgivaravgifter och företagens personalkostnadsbesparing på D1. * 3. Skatt på pension och transfereringar beräknas mot utbetalda sociala förmåner * och faller alltså inte automatiskt när lönerna faller. * 4. Varje produktivitetsvinst fördelas en gång. Andelarna för vinster, priser och * övrig användning normaliseras till 100 procent. * 5. Jobbantagandena gäller privat sektor. Offentlig personalminskning styrs bara * av reglaget för offentlig effektivisering, så samma minskning räknas inte två * gånger. * * Alla samband är deterministiska: samma indata och modellversion ger alltid * samma resultat. */ import { DATA, EXP_GROUPS, TAX_GROUPS, type ExpGroupId, type TaxGroupId, type YearData, } from "../data/dataset"; /** m3.4.0: rättat omställningsstöd (räknas på de förlorade jobbens lön, inte netto * efter nya jobb), nytt arbetstidsläge och produktion kopplad till nya jobb. * Två nya scenarier och en ny variant. */ export const MODEL_VERSION = "m3.4.1"; /** Versioner vars beräkning är identisk med aktuell version. */ /** m3.4.1 ändrar bara förvalen för "AI tar över en stor del av jobben"; beräkningen är densamma som m3.4.0. */ export const SAME_CALCULATION_AS_CURRENT: string[] = ["m3.4.0"]; /** * Växlar som bara används av granskningsskripten för att skilja effekten av * rättade beräkningsfel från effekten av ändrad dataklassificering. I appen är * samtliga alltid på. Alla false återger m3.1.0:s samband för dessa punkter. */ export const MODEL_SWITCHES = { /** Rättat fel: offentlig personalkostnad räknas om från arbetskraftskostnad * (D1 + löneskatt) till D1 innan den dras från D1. */ publicLabourCostToD1: true, /** Ändrad klassificering: skatt på utdelning och realiserade kapitalvinster * bokförs i D51A:s kapitaldel, inte i D51C (avkastningsskatt). */ capitalTaxInD51A: true, /** Ändrat samband: omställningsstöd omfattar även offentligt anställda som * förlorar arbetet genom offentlig effektivisering. */ supportForPublicJobLoss: true, /** Rättad avgränsning (m3.3.0): produktionsunderlaget och lönesumman som * AI-antagandena verkar på avser samma marknadssektorer (S.11, S.12 och * hushållens näringsverksamhet), enligt Eurostats sektorsräkenskaper. Av: hela * ekonomins B2A3G och den redaktionella privata löneandelen 0,72. */ privateSurplusScope: true, /** Omformulerat scenario (m3.3.0): tillväxtscenariot räknas som AI-avvikelse i en * ekonomi av basårets storlek, mot samma referens som övriga scenarier. Av: * skillnaden mellan två framtidsvägar (m3.2). */ relativeGrowth: true, /** Rättat fel (m3.4.0): omställningsstödet i jobbläget räknas på den löneinkomst som * faller bort för dem som förlorar jobbet (brutto). Nya jobbs löner kvittas inte * längre bort, eftersom nya jobb inte automatiskt går till dem som förlorat sina. * Av: m3.3.1:s nettobortfall efter nya jobb, golvat vid noll. */ grossSupportBase: true, }; /** * Redaktionellt valda strukturantaganden (modellantaganden, inte statistik). * Alla värden visas i "Källor och kalkyler". */ export const STRUCTURAL = { /** Andel av hushållens inkomstskatt (D51A) som hänförs till arbetsinkomster. * Bearbetad fördelning, redaktionellt antagande. */ labourShareOfIncomeTax: 0.74, /** Andel av D51A som hänförs till hushållens ränte- och utdelningsinkomster. Den * delen följer företagens överskott, inte lönesumman. Redaktionellt antagande. */ capitalShareOfIncomeTax: 0.04, /** Resten av D51A (1 − de två ovan) hänförs till pension och skattepliktiga * transfereringar och beräknas mot utbetalda sociala förmåner. */ /** Privat sektors andel av lönesumman. Jobbantagandena verkar bara på denna del. */ privateShareOfWages: 0.72, /** Andel av offentlig konsumtion som är personalkostnad. */ publicWageShareOfConsumption: 0.6, /** Andel av D29 (övriga produktionsskatter) som antas följa lönesumman. Resten * följer BNP, eftersom D29 även innehåller produktionsskatter som inte beror på * löner. Samma andel används som uppskattning av arbetsgivarens löneskatter * utöver D1 (se employerTaxRatio). Redaktionellt antagande, inte inläst D29C. */ payrollShareOfProductionTax: 0.9, /** Hur mycket realiserade kapitalvinster hos svenska hushåll följer kvarhållen * vinst efter bolagsskatt. Kapitalvinster följer tillgångspriser och försäljningar, * inte lönesumman, och bara delvis årets vinster. Redaktionellt antagande. * Skatten bokförs i D51A (hushållens inkomstskatt, inkomstslaget kapital), inte i * D51C, som i Sverige är avkastningsskatt på pensions- och försäkringssparande. */ capitalGainsPassthrough: 0.5, /** Lagstadgad skattesats på kapitalinkomst för fysiska personer, 30 procent * (65 kap. 7 § inkomstskattelagen). Lagtext, inte antagande. */ capitalGainsTaxRate: 0.3, /** Lagstadgad skattesats på utdelning, 30 procent (samma lagrum). Lagtext. */ dividendTaxRate: 0.3, /** Andel av utdelningar och kapitalvinster till svenska hushåll som antas * beskattas med den lagstadgade satsen. 1,0 är en uttrycklig förenkling (övre * gräns): investeringssparkonto och kapitalförsäkring (schablonbeskattade), * pensionssparande (avkastningsskatt, D51C) och fåmansföretagsregler modelleras * inte. Det saknas i dataunderlaget en verifierad fördelning av ägandet på * sparformer, så inget annat standardvärde har valts. Redaktionellt antagande. */ directlyTaxedCapitalShare: 1, /** Andel av en förändring i driftsöverskottet som antas slå igenom i * skattemässigt överskott. Räntor, avskrivningar, underskottsavdrag och * internationell vinstförflyttning gör genomslaget mindre än ett. * Redaktionellt antagande. */ profitTaxableShareOfMarginal: 0.85, /** Andel av en prissänkning som når svenska hushåll. Resten går till export, * insatsvaror och offentliga kunder. Redaktionellt antagande. */ priceShareToHouseholds: 0.55, /** Nominellt genomslag när priserna sänks: hushållen får högre real köpkraft, men * de nominella konsumtionsutgifterna – och därmed momsunderlaget – ändras bara * marginellt. 0,9 betyder att 90 procent av prissänkningen används till större * volym och 10 procent kvarstår som lägre nominell utgift. Redaktionellt antagande. */ priceVolumeResponse: 0.9, /** Beteendeeffekt e per skattegrupp. En relativ ändring p (decimal) av effektiv * beskattning ger intäktsfaktorn (1 + p) × (1 − e × p): underlaget krymper med * e × p. Exempel: e = 0,2 och p = 0,10 ger 1,10 × 0,98 = 1,078, alltså knappt 8 * procent högre intäkt. Faktorn når sitt maximum vid p = (1 − e) / (2e); för * e = 0,5 är det vid 50 procents höjning, och vid 100 procent är intäkten tillbaka * på ursprungsnivån. Gäller ett fullt anpassat år, utan övergångsförlopp. */ taxElasticity: { labour_income: 0.2, payroll: 0.2, social_contrib: 0.1, corporate: 0.35, household_capital: 0.5, vat: 0.15, excise: 0.25, property_other: 0.1, } as Record, }; /** * Lagstadgad bolagsskattesats per år. Används bara på förändringen av överskottet, * aldrig på den historiska nivån, som kalibreras mot verifierad intäkt. * Källa: Skatteverket och propositioner om sänkt bolagsskatt. */ export function statutoryCorporateRate(year: number): number { if (year >= 2021) return 0.206; if (year >= 2019) return 0.214; if (year >= 2013) return 0.22; if (year >= 2009) return 0.263; return 0.28; } /** Underlagsnycklar i modellen. */ export type BaseKey = | "wages" // D11, löner före skatt | "compensation" // D1, lön inkl. arbetsgivaravgifter | "surplus" // B2A3G, proxy för företagsvinst | "consumption" // P31_S14 | "gdp" | "benefits"; // D62PAY export const BASE_LABEL: Record = { wages: "Löner före skatt (D11)", compensation: "Lön inkl. arbetsgivaravgifter (D1)", surplus: "Driftsöverskott och blandinkomst (B2A3G)", consumption: "Hushållens konsumtion (P31_S14)", gdp: "BNP (B1GQ)", benefits: "Sociala kontantförmåner (D62PAY)", }; export type Component = { baseKey: BaseKey; eff: number; amount: number }; export type Calibration = { year: number; data: YearData; /** Komponenter per skattegrupp: effektiv beskattning mot ett angivet underlag. */ comp: Record; /** Skatt på pension och transfereringar, bruten ut ur D51A. */ transfer: Component; /** * Löneskatt utöver D1, som andel av D1. Den del av D29 som följer lönesumman * (i Sverige främst allmän löneavgift) är en kostnad för arbetsgivaren men ingår * inte i D1. Arbetsgivarens arbetskraftskostnad är därför D1 × (1 + denna andel), * i linje med SCB:s begrepp arbetskraftskostnad D1 + D29C (NR0103, ENS2010 T23RK). * * Observera: värdet beräknas som 0,9 × D29 / D1 * (STRUCTURAL.payrollShareOfProductionTax), alltså en **bearbetning med * redaktionellt antagande** och inte en inläsning av den verifierade * D29C-serien. Faktorn används på två ställen i simulate(): i * arbetskraftskostnaden och i underlaget för produktionsökningen. */ employerTaxRatio: number; /** Skatt på en förändring av driftsöverskottet: lagstadgad bolagsskattesats * (lagtext) × redaktionellt antaget genomslag i skattemässigt överskott. */ marginalProfitTax: number; /** Andel av D1 som AI-antagandena om jobb och löner verkar på. */ privateWageShare: number; /** Överskott (driftsöverskott och blandinkomst) i samma verksamheter, mdkr. */ privateSurplus: number; }; export function calibrate(year: number): Calibration { const d = DATA[year]!; const labourTax = d.taxes.labour_income * STRUCTURAL.labourShareOfIncomeTax; const capitalIncomeTax = d.taxes.labour_income * STRUCTURAL.capitalShareOfIncomeTax; const transferTax = d.taxes.labour_income - labourTax - capitalIncomeTax; const payrollWage = d.taxes.payroll * STRUCTURAL.payrollShareOfProductionTax; const payrollOther = d.taxes.payroll - payrollWage; const c = (baseKey: BaseKey, amount: number, base: number): Component => ({ baseKey, amount, eff: base > 0 ? amount / base : 0, }); return { year, data: d, comp: { labour_income: [ c("wages", labourTax, d.wages), c("surplus", capitalIncomeTax, d.operatingSurplus), ], payroll: [ c("compensation", payrollWage, d.compensation), c("gdp", payrollOther, d.gdp), ], social_contrib: [c("compensation", d.taxes.social_contrib, d.compensation)], corporate: [c("surplus", d.taxes.corporate, d.operatingSurplus)], // D51C är i Sverige avkastningsskatt på pensions- och försäkringssparande. // Det finns inget sparkapitalunderlag i dataunderlaget; som uttrycklig, // försiktig förenkling följer nivån BNP. Nytillkomna vinster, utdelningar och // realiserade kapitalvinster bokförs aldrig här. household_capital: [c("gdp", d.taxes.household_capital, d.gdp)], vat: [c("consumption", d.taxes.vat, d.householdConsumption)], excise: [c("consumption", d.taxes.excise, d.householdConsumption)], property_other: [c("gdp", d.taxes.property_other, d.gdp)], }, transfer: c("benefits", transferTax, d.socialBenefits), employerTaxRatio: d.compensation > 0 ? payrollWage / d.compensation : 0, marginalProfitTax: statutoryCorporateRate(year) * STRUCTURAL.profitTaxableShareOfMarginal, privateWageShare: MODEL_SWITCHES.privateSurplusScope && d.marketShareOfCompensation !== null ? d.marketShareOfCompensation : STRUCTURAL.privateShareOfWages, privateSurplus: MODEL_SWITCHES.privateSurplusScope && d.marketShareOfSurplus !== null ? d.operatingSurplus * d.marketShareOfSurplus : d.operatingSurplus, }; } export const TRANSFER_TAX_NOTE = "Skatt på pensioner och skattepliktiga transfereringar beräknas separat mot utbetalda sociala kontantförmåner (D62PAY), som ett förenklat underlag, och faller alltså inte automatiskt när lönerna faller."; /** * jobs: jobbantaganden (försvunna, ersatta och kvarvarande jobb). * direct: direkt angiven nettoförändring; en minskning tolkas som att personer * förlorar jobb, och omställningsstödet räknas på den. * counterfactual: uteblivna jobb. Lönesumman är lägre än i ett jämförelsealternativ, * men ingen har sagts upp. Inget omställningsstöd följer automatiskt; eventuella * merkostnader anges uttryckligen som extraSupportCost. */ export type WageInputMode = "jobs" | "direct" | "counterfactual" | "hours"; export type ScenarioParams = { /** Inmatningsläge: jobbantaganden eller direkt angiven löneförändring. */ inputMode: WageInputMode; /** Andel av befintliga jobb i privat sektor som försvinner, procent. */ jobsLostPct: number; /** Andel av de försvunna jobben som ersätts av nya jobb, procent. */ jobsReplacedPct: number; /** Förändrad genomsnittlig löneinkomst per kvarvarande jobb, procent. */ retainedJobWagePct: number; /** Genomsnittlig löneinkomst per nytt jobb i procent av de försvunna jobbens. */ newJobWagePct: number; /** Genomsnittlig löneinkomst i de jobb som försvinner, procent av snittet i privat * sektor. Övriga jobbs snittlön justeras så att den ursprungliga lönesumman bevaras. */ lostJobWagePct: number; /** Produktionsbidrag: ökat förädlingsvärde i privat sektor före de ytterligare * AI-inköp som dras av separat (aiImportCost), procent av produktionsunderlaget. * Hålls åtskilt från kostnadsbesparingar. */ productionGainPct: number; /** Nettoförändring av privata löneinkomster, procent. Används i läget "direkt". */ wageChangePct: number; /** Ytterligare AI-inköp: privata företags köp av utländska AI-tjänster, * miljarder kronor per år. Dras av exakt en gång, från produktionsbidraget. */ aiImportCost: number; /** Andel av kvarvarande besparing som blir svensk beskattningsbar vinst, procent. */ savingToProfitsPct: number; /** Andel av kvarvarande besparing som går till lägre priser, procent. */ savingToPricesPct: number; /** Effektivisering i offentlig sektor, procent av offentlig konsumtion. */ publicEfficiencyPct: number; // Fördjupa /** Införandekostnad i offentlig sektor, procent av bruttobesparingen. */ publicImplementationPct: number; /** Andel av bortfallen löneinkomst (D11) som ersätts med omställningsstöd, procent. */ transferSupportPct: number; /** Andel av förändrad disponibel inkomst som syns i konsumtionen, procent. */ consumptionPassthroughPct: number; /** Andel av ökade svenska vinster som delas ut till svenska hushåll, procent. */ dividendToHouseholdsPct: number; /** * Extra offentligt stöd som inte följer av förlorade jobb, miljarder kronor per * år. Uttryckligt antagande: uteblivna nyanställningar skapar aldrig automatiskt * omställningsstöd. Beskattas som transferering, precis som stödet ovan. */ extraSupportCost: number; // ---- Antaganden som bara används i scenariot "Tillväxt utan motsvarande nya jobb". // Alla värden gäller total förändring till periodens slut, inte per år. /** Tidshorisont i år. Resultaten avser ett års intäkter vid periodens slut. */ growthHorizonYears: number; /** Sysselsättningsökning till periodens slut i jämförelsealternativet, procent. */ growthEmploymentHiringPct: number; /** Sysselsättningsökning till periodens slut i AI-alternativet, procent. */ growthEmploymentAiPct: number; /** Förändrad arbetstid per jobb i jämförelsealternativet, procent. */ growthHoursHiringPct: number; /** Förändrad arbetstid per jobb i AI-alternativet, procent. */ growthHoursAiPct: number; /** Produktion per arbetad timme i jämförelsealternativet, total ökning, procent. */ growthProductivityHiringPct: number; /** Produktion per arbetad timme i AI-alternativet, total ökning, procent. */ growthProductivityAiPct: number; /** Andel av produktivitetsökningen per timme som blir högre timlön, procent. */ growthWagePassHiringPct: number; /** Samma andel i AI-alternativet, procent. */ growthWagePassAiPct: number; /** Kostnad för utländska AI-tjänster i jämförelsealternativet, mdkr per år. */ growthAiImportCostHiring: number; /** Jobbläget (m3.4.0): produktion per nytt jobb i procent av genomsnittlig produktion * per jobb i marknadssektorerna. 0 = nya jobb ger ingen egen produktion (tidigare * samband). Läggs till produktionsbidraget; samma produktion räknas inte två gånger * så länge productionGainPct avser annan produktion än de nya jobbens. */ newJobOutputPct: number; // ---- Arbetstidsläget (inputMode "hours"), m3.4.0. Gäller marknadssektorerna. /** Förändrat antal anställda, procent. */ hoursEmploymentPct: number; /** Förändrad arbetstid per person, procent. */ hoursPerPersonPct: number; /** Förändrad årlig löneinkomst per person, procent. Timlönen härleds. */ hoursAnnualWagePct: number; /** Förändrad produktion per arbetad timme, procent. */ hoursProductivityPct: number; }; /** * Arbetstidsläget håller isär antal anställda, arbetstid per person, timlön, * årslön, produktion per timme och total produktion: * timlön = (1 + årslön) / (1 + arbetstid) − 1 * lönesumma = (1 + anställda) × (1 + årslön) − 1 * produktion = (1 + anställda) × (1 + arbetstid) × (1 + produktion per timme) − 1 * AI-inköpen (aiImportCost) tillkommer separat och dras exakt en gång. Kortare * arbetstid ger aldrig automatiskt färre jobb, lägre årslön eller omställningsstöd. */ export function hoursDerived(s: ScenarioParams) { const e = s.hoursEmploymentPct / 100; const h = s.hoursPerPersonPct / 100; const w = s.hoursAnnualWagePct / 100; const q = s.hoursProductivityPct / 100; return { employmentPct: e * 100, hoursPerPersonPct: h * 100, totalHoursPct: ((1 + e) * (1 + h) - 1) * 100, annualWagePct: w * 100, hourlyWagePct: (1 + h > 0 ? (1 + w) / (1 + h) - 1 : 0) * 100, wageBillPct: ((1 + e) * (1 + w) - 1) * 100, productivityPerHourPct: q * 100, productionPct: ((1 + e) * (1 + h) * (1 + q) - 1) * 100, }; } /** De två utvecklingsvägarna i tillväxtscenariot. */ export type GrowthPath = "hiring" | "ai"; /** * Utvecklingsvägarna bygger på ett uttryckligt samband: * produktion = antal jobb × arbetstid per jobb × produktion per arbetad timme * timlön = 1 + produktivitetsökning per timme × löneandel av ökningen * lönesumma = antal jobb × arbetstid per jobb × timlön * Användaren anger jobb, arbetstid, produktivitet per timme och löneandel. Produktion, * lön per jobb och lönesumma härleds. Därmed kan inget omotiverat produktivitetsfall * uppstå av oberoende förval. Ingen av vägarna bokför uteblivna nyanställningar som * uppsägningar; eventuella stödkostnader kräver ett uttryckligt antagande. */ export function growthPathDerived(s: ScenarioParams, path: GrowthPath) { const h = path === "hiring"; const e = (h ? s.growthEmploymentHiringPct : s.growthEmploymentAiPct) / 100; const hrs = (h ? s.growthHoursHiringPct : s.growthHoursAiPct) / 100; const q = (h ? s.growthProductivityHiringPct : s.growthProductivityAiPct) / 100; const pass = (h ? s.growthWagePassHiringPct : s.growthWagePassAiPct) / 100; const production = (1 + e) * (1 + hrs) * (1 + q) - 1; const wagePerJob = (1 + hrs) * (1 + q * pass) - 1; const wageBill = (1 + e) * (1 + wagePerJob) - 1; return { employmentPct: e * 100, hoursPct: hrs * 100, productivityPerHourPct: q * 100, productivityPerJobPct: ((1 + hrs) * (1 + q) - 1) * 100, wagePerJobPct: wagePerJob * 100, wageBillPct: wageBill * 100, productionPct: production * 100, aiImportCost: h ? s.growthAiImportCostHiring : s.aiImportCost, }; } export function growthPathParams(s: ScenarioParams, path: GrowthPath): ScenarioParams { const g = growthPathDerived(s, path); return { ...s, inputMode: "direct", wageChangePct: g.wageBillPct, productionGainPct: g.productionPct, aiImportCost: g.aiImportCost, }; } /** * Tillväxtscenariot som AI-avvikelse i en ekonomi av basårets storlek (m3.3.0). * Vägarnas gemensamma tillväxt tas bort; kvar blir AI-vägens sammansättning * relativt nyanställningsvägen: * Δjobb = (1 + e_A) / (1 + e_H) − 1 * Δlönesumma = lönesumma_A / lönesumma_H − 1 * Δproduktion = produktion_A / produktion_H − 1 (före AI-inköp) * ΔAI-inköp = (AI_A − AI_H) / produktion_H (basårets skala) * Lönesumman förs in som uteblivna jobb (counterfactual): ingen sägs upp, och * inget omställningsstöd följer automatiskt. */ export function growthRelative(s: ScenarioParams) { const h = growthPathDerived(s, "hiring"); const a = growthPathDerived(s, "ai"); const ph = 1 + h.productionPct / 100; return { employmentPct: ((1 + a.employmentPct / 100) / (1 + h.employmentPct / 100) - 1) * 100, wageBillPct: ((1 + a.wageBillPct / 100) / (1 + h.wageBillPct / 100) - 1) * 100, productionPct: ((1 + a.productionPct / 100) / ph - 1) * 100, aiImportCost: ph > 0 ? (a.aiImportCost - h.aiImportCost) / ph : 0, scale: ph, }; } export function growthRelativeParams(s: ScenarioParams): ScenarioParams { const r = growthRelative(s); return { ...s, inputMode: "counterfactual", wageChangePct: r.wageBillPct, productionGainPct: r.productionPct, aiImportCost: r.aiImportCost, }; } /** De parametrar som tillväxtscenariot faktiskt räknas med i appen. */ export function growthEffectiveParams(s: ScenarioParams): ScenarioParams { return MODEL_SWITCHES.relativeGrowth ? growthRelativeParams(s) : growthPathParams(s, "ai"); } /** Avstämning av en utvecklingsväg: storheter och deras belopp i modellen. */ export function growthReconciliation(cal: Calibration, s: ScenarioParams, path: GrowthPath) { const g = growthPathDerived(s, path); const r = simulate(cal, growthPathParams(s, path), EMPTY_POLICY); const d = cal.data; return { ...g, productionBase: r.detail.productionBase, dLabourCost: r.detail.dLabourCost, productionGain: r.detail.productionGain, netSaving: r.detail.netSaving, toProfits: r.detail.toProfits, toPrices: r.detail.toPrices, labourCostLevel: d.compensation * cal.privateWageShare * (1 + cal.employerTaxRatio) + r.detail.dLabourCost, taxTotal: r.taxTotal, expenditureTotal: r.expenditureTotal, balance: r.balance, }; } export type PolicyParams = { /** Relativ förändring av effektiv beskattning per grupp, procent. */ tax: Partial>; /** Relativ förändring av anslag per utgiftsgrupp, procent. */ spending: Partial>; }; export const EMPTY_POLICY: PolicyParams = { tax: {}, spending: {} }; /** * Sammansättningen av jobben, med bevarad ursprunglig lönesumma. * * De försvunna jobbens relativa snittlön (wLost) får väljas fritt. Övriga jobbs * snittlön (wKept) bestäms då av villkoret * lost · wLost + (1 − lost) · wKept = 1, * det vill säga att lönesumman före förändringen alltid är oförändrad. Utan detta * villkor skulle ett antagande om att låglönejobb försvinner i sig ändra den * historiska lönesumman. */ function jobShares(s: ScenarioParams) { const lost = Math.min(0.999, Math.max(0, s.jobsLostPct / 100)); const wLost = s.lostJobWagePct / 100; const kept = 1 - lost; const wKept = kept > 0 ? (1 - lost * wLost) / kept : 0; return { lost, kept, wLost, wKept, newJobs: lost * (s.jobsReplacedPct / 100), relNewWage: s.newJobWagePct / 100, retainedWage: 1 + s.retainedJobWagePct / 100, }; } /** * Nettoförändring av privata löneinkomster i procent, härledd ur jobbantagandena. * Exempel: av 100 jobb försvinner 10 med snittlön, fem nya tillkommer med samma lön * och de kvarvarande jobben har oförändrad löneinkomst → −5 procent. */ export function wageChangePct(s: ScenarioParams): number { if (s.inputMode === "hours") return hoursDerived(s).wageBillPct; if (s.inputMode !== "jobs") return s.wageChangePct; const j = jobShares(s); const index = j.kept * j.wKept * j.retainedWage + j.newJobs * j.wLost * j.relNewWage; return (index - 1) * 100; } /** * Bortfallen löneinkomst för de personer som förlorar sitt jobb, som andel av * privat sektors lönesumma (m3.4.0: brutto, före nya jobb). * * Nya jobb antas inte automatiskt gå till dem som förlorat sina, och högre löner * för kvarvarande kan inte kvitta bort deras stödbehov. Hur stor del av bortfallet * som ersätts – ersättningsgrad, andel som får stöd och andel av året – samlas i * transferSupportPct. Uteblivna jobb (counterfactual) och kortare arbetstid (hours) * är inga uppsägningar och ger inget stöd. I läget "direkt" finns bara ett netto, * så en nettominskning används som förenklat underlag. */ export function lostWageIncomeShare(s: ScenarioParams): number { if (s.inputMode === "counterfactual" || s.inputMode === "hours") return 0; if (s.inputMode === "direct") return Math.max(0, -s.wageChangePct / 100); const j = jobShares(s); if (MODEL_SWITCHES.grossSupportBase) return j.lost * j.wLost; return Math.max(0, j.lost * j.wLost - j.newJobs * j.wLost * j.relNewWage); } /** Andel nya jobb per ursprungligt privat jobb (jobbläget), annars 0. */ export function newJobShare(s: ScenarioParams): number { return s.inputMode === "jobs" ? jobShares(s).newJobs : 0; } export type Contribution = { id: string; label: string; amount: number; note: string; /** "ai" = orsakas av scenariot, "policy" = orsakas av dina åtgärder. */ kind: "ai" | "policy"; }; export type Result = { taxes: Record; /** skatt på pension och transfereringar, redovisas separat från lönedelen */ transferTax: number; /** Kapitaldelen av D51A: skatt på hushållens ränte-, utdelnings- och * kapitalvinstinkomster. Ingår i taxes.labour_income (samma officiella post). */ capitalIncomeTax: number; /** Samlad skatt på kapitalinkomster och sparande: capitalIncomeTax + D51C. */ capitalTax: number; taxTotal: number; /** * Skatteintäkterna uppdelade efter underlag: "labour" är de komponenter som * beräknas mot löner (D11) eller lön inkl. arbetsgivaravgifter (D1), "other" är * allt annat – vinster, kapitalinkomster, konsumtion, BNP-relaterade skatter och * skatt på pension och transfereringar. Delarna summerar alltid till taxTotal. */ taxByOrigin: { labour: number; other: number }; otherRevenue: number; revenueTotal: number; expenditure: Record; expenditureTotal: number; balance: number; /** Beräknade underlag efter scenariot */ bases: Record; /** Mellanled som visas i beräkningsförklaringen */ detail: { wageChangePct: number; dCompensation: number; /** Förändrad arbetskraftskostnad, D1 + löneskattedelen av D29. */ dLabourCost: number; dWagesGross: number; /** Minskad offentlig D1. Offentlig personalkostnad i konsumtionen räknas om * från arbetskraftskostnad (D1 + löneskatt) till D1 innan den dras från D1. */ publicWageLoss: number; /** Minskad offentlig arbetskraftskostnad (D1 + löneskatt), del av bruttobesparingen. */ publicLabourCost: number; /** Kostnadsminskning (positiv) eller kostnadsökning (negativ) för företagen. */ grossPrivateSaving: number; /** Uttryckligt antagande om ökat förädlingsvärde, skilt från besparingar. * Summan av productionFromAssumption och productionFromNewJobs. */ productionGain: number; /** Del av produktionsbidraget från produktionsantagandet (eller arbetstidsläget). */ productionFromAssumption: number; /** Del av produktionsbidraget från nya jobbs egen produktion (jobbläget). */ productionFromNewJobs: number; /** Produktionsunderlaget: arbetskraftskostnad + överskott i samma verksamheter. */ productionBase: number; aiImportCost: number; netSaving: number; toProfits: number; toPrices: number; /** Den del av prissänkningen som når svenska hushåll. */ pricesToHouseholds: number; /** Bolagsskatt på förändrat överskott, beräknad med marginell skattesats. */ corporateTaxOnChange: number; /** Kvarhållen vinst efter bolagsskatt och utdelning. */ retainedProfit: number; /** Bortfallen löneinkomst (D11) för dem som förlorat jobbet, före stöd. */ lostWageIncome: number; /** Varav offentligt anställda som förlorat jobbet genom effektivisering. */ lostPublicWageIncome: number; support: number; grossPublicSaving: number; implementationCost: number; netPublicSaving: number; dividends: number; dividendTax: number; disposableChange: number; }; }; function baseSet(d: YearData): Record { return { wages: d.wages, compensation: d.compensation, surplus: d.operatingSurplus, consumption: d.householdConsumption, gdp: d.gdp, benefits: d.socialBenefits, }; } /** Intäktsfaktor för en relativ ändring pct (procent) av effektiv beskattning. */ export function behaviourFactor(pct: number, elasticity: number): number { const p = pct / 100; return (1 + p) * (1 - elasticity * p); } /** * Tilläggsbelopp per skattegrupp: skatt på förändringar som beskattas med en * marginell sats i stället för den historiskt kalibrerade nivåsatsen. */ export type TaxExtra = Partial>; function taxesFrom( cal: Calibration, bases: Record, policy: PolicyParams, extra: TaxExtra = {}, capitalExtra = 0, ): { taxes: Record; transferTax: number; capitalIncomeTax: number; byOrigin: { labour: number; other: number }; } { const f = (id: TaxGroupId) => behaviourFactor(policy.tax[id] ?? 0, STRUCTURAL.taxElasticity[id]); const taxes = {} as Record; let labour = 0; let other = 0; let capitalIncomeTax = 0; for (const g of TAX_GROUPS) { const factor = f(g.id); let total = 0; for (const k of cal.comp[g.id]) { // Kapitaldelen av D51A (baseKey "surplus" i gruppen labour_income) styrs av // reglaget för kapitalinkomster och sparande, inte av reglaget för arbete. const isCapitalPart = g.id === "labour_income" && k.baseKey === "surplus"; const kf = isCapitalPart && MODEL_SWITCHES.capitalTaxInD51A ? f("household_capital") : factor; const amount = k.eff * bases[k.baseKey] * kf; total += amount; if (isCapitalPart) capitalIncomeTax += amount; // Bara komponenter som beräknas mot D11 eller D1 är skatt på arbete. if (k.baseKey === "wages" || k.baseKey === "compensation") labour += amount; else other += amount; } const extraAmount = (extra[g.id] ?? 0) * factor; other += extraAmount; taxes[g.id] = total + extraAmount; } // Skatt på utdelning och realiserade kapitalvinster av scenariots förändringar. if (capitalExtra !== 0) { if (MODEL_SWITCHES.capitalTaxInD51A) { const amount = capitalExtra * f("household_capital"); taxes.labour_income += amount; capitalIncomeTax += amount; other += amount; } else { const amount = capitalExtra * f("household_capital"); taxes.household_capital += amount; other += amount; } } // Skatt på pension och transfereringar följer samma reglage som hushållens // inkomstskatt på arbete, eftersom det är samma skatt i lagens mening. const transferTax = cal.transfer.eff * bases.benefits * f("labour_income"); other += transferTax; return { taxes, transferTax, capitalIncomeTax, byOrigin: { labour, other } }; } function assemble( cal: Calibration, bases: Record, policy: PolicyParams, expenditure: Record, detail: Result["detail"], extra: TaxExtra = {}, capitalExtra = 0, ): Result { const d = cal.data; const { taxes, transferTax, capitalIncomeTax, byOrigin } = taxesFrom( cal, bases, policy, extra, capitalExtra, ); const taxTotal = Object.values(taxes).reduce((a, b) => a + b, 0) + transferTax; const expenditureTotal = Object.values(expenditure).reduce((a, b) => a + b, 0); const revenueTotal = taxTotal + d.otherRevenue; return { taxes, transferTax, capitalIncomeTax, capitalTax: capitalIncomeTax + taxes.household_capital, taxTotal, taxByOrigin: byOrigin, otherRevenue: d.otherRevenue, revenueTotal, expenditure, expenditureTotal, balance: revenueTotal - expenditureTotal, bases, detail, }; } const ZERO_DETAIL: Result["detail"] = { wageChangePct: 0, dCompensation: 0, dLabourCost: 0, dWagesGross: 0, publicWageLoss: 0, publicLabourCost: 0, grossPrivateSaving: 0, productionGain: 0, productionFromAssumption: 0, productionFromNewJobs: 0, productionBase: 0, aiImportCost: 0, netSaving: 0, toProfits: 0, toPrices: 0, pricesToHouseholds: 0, corporateTaxOnChange: 0, retainedProfit: 0, lostWageIncome: 0, lostPublicWageIncome: 0, support: 0, grossPublicSaving: 0, implementationCost: 0, netPublicSaving: 0, dividends: 0, dividendTax: 0, disposableChange: 0, }; /** Tillstånd A: basårets verifierade utfall, oförändrat. */ export function reference(cal: Calibration): Result { const d = cal.data; return assemble(cal, baseSet(d), EMPTY_POLICY, { ...d.expenditure }, ZERO_DETAIL); } function normalizeShares(profits: number, prices: number) { const other = Math.max(0, 100 - profits - prices); const sum = profits + prices + other; if (sum <= 0) return { profits: 0, prices: 0, other: 1 }; return { profits: profits / sum, prices: prices / sum, other: other / sum }; } /** * Uttryckligt tillskott av skatteunderlag, miljarder kronor per år. Används av * brytpunktsanalysen för att pröva hur stor ökning som skulle behövas. Tillskottet * går genom modellens vanliga samband, så bolagsskatt, utdelning, kapitalvinst och * konsumtion behandlas på samma sätt som i övrigt och samma krona räknas en gång. */ export type BaseIncrease = { /** Ökat svenskt beskattningsbart överskott (driftsöverskott som proxy). */ surplus: number; /** Ökade beskattningsbara kapitalinkomster hos svenska hushåll. */ capitalIncome: number; /** Ökad nominell hushållskonsumtion. */ consumption: number; }; export const NO_INCREASE: BaseIncrease = { surplus: 0, capitalIncome: 0, consumption: 0 }; /** Tillstånd B eller C beroende på om policy är tom. */ export function simulate( cal: Calibration, s: ScenarioParams, policy: PolicyParams = EMPTY_POLICY, increase: BaseIncrease = NO_INCREASE, ): Result { const d = cal.data; const pct = wageChangePct(s); // 1. Privata löneinkomster. Jobbantagandena gäller privat sektor. const privateCompensation = d.compensation * cal.privateWageShare; const dCompensation = privateCompensation * (pct / 100); // 2. Offentlig effektivisering. Endast här minskar offentlig personalkostnad. const grossPublicSaving = d.publicConsumption * (s.publicEfficiencyPct / 100); const implementationCost = grossPublicSaving * (s.publicImplementationPct / 100); const netPublicSaving = grossPublicSaving - implementationCost; // Personalkostnaden i offentlig konsumtion är arbetskraftskostnad: D1 plus // löneskatt (den löneberoende delen av D29). Den räknas om till D1 innan den // dras från D1; löneskatten faller sedan av sig själv via sitt underlag. const publicLabourCost = grossPublicSaving * STRUCTURAL.publicWageShareOfConsumption; const publicWageLoss = MODEL_SWITCHES.publicLabourCostToD1 ? publicLabourCost / (1 + cal.employerTaxRatio) : publicLabourCost; // 3. Företagens arbetskraftskostnad är D1 plus den löneberoende delen av D29 // (allmän löneavgift m.m.), i linje med SCB:s begrepp arbetskraftskostnad // D1 + D29C. Den löneberoende delen uppskattas som 0,9 × D29 (redaktionellt // antagande), inte som inläst D29C-serie. // Beloppet kan vara både negativt och positivt: stiger lönerna ökar kostnaden. const labourCostFactor = 1 + cal.employerTaxRatio; const dLabourCost = dCompensation * labourCostFactor; const grossPrivateSaving = -dLabourCost; // 3b. Ökad produktion är ett eget, uttryckligt antagande och blandas aldrig // samman med kostnadsbesparingen. // Produktionsunderlaget = arbetskraftskostnad + överskott i samma // verksamheter. productionGainPct avser förädlingsvärde före de ytterligare // AI-inköpen, som dras av exakt en gång här nedan och ingen annanstans. const privateValueAdded = privateCompensation * labourCostFactor + cal.privateSurplus; // I arbetstidsläget härleds produktionen ur anställda × arbetstid × produktion // per timme, och productionGainPct används inte (ingen dubbelräkning). I // jobbläget kan nya jobb ge egen produktion (newJobOutputPct). const productionPct = s.inputMode === "hours" ? hoursDerived(s).productionPct : s.productionGainPct; const productionFromAssumption = privateValueAdded * (productionPct / 100); const productionFromNewJobs = privateValueAdded * newJobShare(s) * ((s.newJobOutputPct ?? 0) / 100); const productionGain = productionFromAssumption + productionFromNewJobs; // Förändrat överskott = produktionsbidrag − Δarbetskraftskostnad − AI-inköp. const netSaving = grossPrivateSaving + productionGain - s.aiImportCost; const shares = normalizeShares(s.savingToProfitsPct, s.savingToPricesPct); // Är nettot negativt bärs hela underskottet av vinsterna; inga prissänkningar sker. const toProfits = netSaving >= 0 ? netSaving * shares.profits : netSaving; const toPrices = netSaving >= 0 ? netSaving * shares.prices : 0; // 4. Hushållens löneinkomst före skatt (D11) förändras i takt med D1. const wageRatio = d.compensation > 0 ? d.wages / d.compensation : 0; const dWagesGross = (dCompensation - publicWageLoss) * wageRatio; // 5. Omställningsstöd beräknas på den löneinkomst före skatt (D11) som faller // bort för de personer som förlorar sitt jobb – inte på nettot i ekonomin och // inte på arbetsgivarens totala personalkostnad. Högre löner för kvarvarande // anställda minskar därför inte stödbehovet. // Offentligt anställda som förlorar arbetet genom effektivisering omfattas av // samma stödantagande. Stödet avser ett årsbelopp i det antagna läget: // transferSupportPct = ersättningsgrad × täckningsgrad × andel av året med stöd. const privateWages = d.wages * cal.privateWageShare; const lostPublicWageIncome = MODEL_SWITCHES.supportForPublicJobLoss ? publicWageLoss * wageRatio : 0; const lostWageIncome = lostWageIncomeShare(s) * privateWages + lostPublicWageIncome; const support = lostWageIncome * (s.transferSupportPct / 100) + s.extraSupportCost; // 6. Vinstkedjan. Nivån på bolagsskatten är kalibrerad mot verifierad intäkt, // men förändringen beskattas med årets lagstadgade sats gånger ett antaget // genomslag i skattemässigt överskott. Ett uttryckligt tillskott av överskott // går in i samma kedja, så det kan inte räknas både som bolagsvinst och som en // oberoende kapitalinkomstökning. const profitChange = toProfits + increase.surplus; const corporateTaxOnChange = profitChange * cal.marginalProfitTax; const profitAfterTax = profitChange - corporateTaxOnChange; // Utdelning tas ur vinsten efter bolagsskatt. Samma krona kan inte både delas // ut och kvarstå i företaget. const dividends = Math.max(0, profitAfterTax) * (s.dividendToHouseholdsPct / 100); const retainedProfit = profitAfterTax - dividends; // Lagstadgad sats 30 procent × antagen andel som beskattas direkt hos hushållen. const directShare = STRUCTURAL.directlyTaxedCapitalShare; const dividendTax = dividends * STRUCTURAL.dividendTaxRate * directShare; const dCapitalGains = retainedProfit * STRUCTURAL.capitalGainsPassthrough; const capitalTaxOnChange = dCapitalGains * STRUCTURAL.capitalGainsTaxRate * directShare + dividendTax + increase.capitalIncome * STRUCTURAL.capitalGainsTaxRate * directShare; // 7. Nya underlag. Överskottet hålls kvar på referensnivå eftersom skatten på // förändringen räknas separat med marginell sats. const compensationAfter = d.compensation + dCompensation - publicWageLoss; const wagesAfter = d.wages + dWagesGross; const benefits = d.socialBenefits + support; const effWageTax = cal.comp.labour_income[0]!.eff; // inkomstskatt på löner före skatt const disposableChange = dWagesGross * (1 - effWageTax) + support * (1 - cal.transfer.eff) + dividends - dividendTax + increase.capitalIncome * (1 - STRUCTURAL.capitalGainsTaxRate * directShare); // Bara den del av prissänkningen som når svenska hushåll påverkar konsumtionen. // Resten gäller export, insatsvaror och offentliga kunder. const pricesToHouseholds = toPrices * STRUCTURAL.priceShareToHouseholds; const consumption = d.householdConsumption + disposableChange * (s.consumptionPassthroughPct / 100) + // Lägre priser ger högre real köpkraft. Den nominella konsumtionen ändras bara // med den del som inte används till större volym. pricesToHouseholds * (STRUCTURAL.priceVolumeResponse - 1) + increase.consumption; const bases: Record = { wages: wagesAfter, compensation: compensationAfter, surplus: d.operatingSurplus, consumption, gdp: d.gdp, benefits, }; // 8. Utgifter: real ambitionsnivå behålls och ändras bara av politik och scenario. const expenditure = {} as Record; for (const g of EXP_GROUPS) { const p = policy.spending[g.id] ?? 0; expenditure[g.id] = d.expenditure[g.id] * (1 + p / 100); } expenditure.social_protection += support; const serviceGroups: ExpGroupId[] = [ "health_care", "education", "defence_justice", "infrastructure", "other", ]; const serviceTotal = serviceGroups.reduce((a, g) => a + expenditure[g], 0); for (const g of serviceGroups) { const share = serviceTotal > 0 ? expenditure[g] / serviceTotal : 0; expenditure[g] += -netPublicSaving * share; } return assemble( cal, bases, policy, expenditure, { wageChangePct: pct, dCompensation, dLabourCost, dWagesGross, publicWageLoss, publicLabourCost, grossPrivateSaving, productionGain, productionFromAssumption, productionFromNewJobs, productionBase: privateValueAdded, aiImportCost: s.aiImportCost, netSaving, toProfits, toPrices, pricesToHouseholds, corporateTaxOnChange, retainedProfit, lostWageIncome, lostPublicWageIncome, support, grossPublicSaving, implementationCost, netPublicSaving, dividends, dividendTax, disposableChange, }, { corporate: corporateTaxOnChange }, capitalTaxOnChange, ); } export type Evaluation = { /** A: historisk referens */ ref: Result; /** B: referens + AI-scenario, oförändrade skatteinställningar */ ai: Result; /** C: samma scenario efter dina åtgärder */ final: Result; /** Förändring av skatteintäkter, B − A. Huvudmåttet. */ taxChangeAi: number; /** Förändring av skatteintäkter, C − A. */ taxChangeTotal: number; /** Saldoförändring B − A */ balanceChangeAi: number; /** Saldoförändring C − B */ balanceChangePolicy: number; /** Summerbar uppdelning: AI-poster (B − A) följt av policyposter (C − B). */ contributions: Contribution[]; }; /** * Beräknar A, B och C samt en uppdelning där varje effekt förekommer exakt en * gång. Summan av contributions är alltid C.balance − A.balance. */ export function evaluate( cal: Calibration, s: ScenarioParams, policy: PolicyParams = EMPTY_POLICY, ): Evaluation { const ref = reference(cal); const ai = simulate(cal, s, EMPTY_POLICY); const final = simulate(cal, s, policy); const c: Contribution[] = []; const push = (id: string, label: string, amount: number, note: string, kind: "ai" | "policy") => c.push({ id, label, amount, note, kind }); // (B − A): AI-scenariots effekt med oförändrade skatte- och utgiftsinställningar. push( "labour", "Skatt på löner och arbetsgivaravgifter", ai.taxes.labour_income - ai.capitalIncomeTax + ai.taxes.payroll + ai.taxes.social_contrib - (ref.taxes.labour_income - ref.capitalIncomeTax + ref.taxes.payroll + ref.taxes.social_contrib), "Inkomstskatt räknas på löner före skatt (D11), arbetsgivaravgifter och sociala avgifter på lön inkl. avgifter (D1). Kapitaldelen av D51A redovisas i posten för kapitalinkomster.", "ai", ); push( "transfer", "Skatt på pension och transfereringar", ai.transferTax - ref.transferTax, "Beräknas mot utbetalda sociala förmåner, separat från löner.", "ai", ); push( "profit", "Skatt på bolagsvinster", ai.taxes.corporate - ref.taxes.corporate, "Bygger på förändrad svensk beskattningsbar vinst: kostnadsförändring plus eventuell produktionsökning, efter avdrag för privata företags AI-inköp.", "ai", ); push( "capital", "Skatt på kapitalinkomster och sparande", ai.capitalTax - ref.capitalTax, "Skatt på utdelningar och realiserade kapitalvinster ingår i hushållens inkomstskatt (D51A). Avkastningsskatten på pensions- och försäkringssparande (D51C) följer inte årets vinster.", "ai", ); push( "consumption", "Moms och övriga konsumtionsskatter", ai.taxes.vat + ai.taxes.excise - (ref.taxes.vat + ref.taxes.excise), "Följer hushållens nominella konsumtion. Företagens AI-inköp ger ingen ny nettomoms eftersom ingående moms är avdragsgill.", "ai", ); push( "other_tax", "Fastighets- och övriga skatter", ai.taxes.property_other - ref.taxes.property_other, "Följer BNP och ändras därför inte av scenariot i den här versionen.", "ai", ); push( "support", "Omställningsstöd (utgift)", -ai.detail.support, "Stödet ökar de offentliga utgifterna. En del kommer tillbaka som skatt när stödet beskattas och används till konsumtion. Återflödet ingår i skatteposterna ovan.", "ai", ); push( "public_eff", "Offentlig effektivisering netto", ai.detail.netPublicSaving, "Bruttobesparing minus införandekostnad. Den minskade offentliga lönesumman sänker samtidigt skatterna på arbete och ingår i posten ovan.", "ai", ); // (C − B): dina åtgärder. Hela skillnaden ligger i exakt två poster. const policyTax = final.taxTotal - ai.taxTotal; const policySpend = -(final.expenditureTotal - ai.expenditureTotal); if (Math.abs(policyTax) > 1e-9) push( "policy_tax", "Dina skatteändringar", policyTax, "Relativ ändring av effektiv beskattning, med enkel beteendeeffekt inräknad. Mäts mot samma AI-scenario utan dina ändringar.", "policy", ); if (Math.abs(policySpend) > 1e-9) push( "policy_spend", "Dina utgiftsändringar", policySpend, "Ändrade anslag i förhållande till basårets nivå, mätt mot samma AI-scenario utan dina ändringar.", "policy", ); return { ref, ai, final, taxChangeAi: ai.taxTotal - ref.taxTotal, taxChangeTotal: final.taxTotal - ref.taxTotal, balanceChangeAi: ai.balance - ref.balance, balanceChangePolicy: final.balance - ai.balance, contributions: c, }; } /** * Tillväxtscenariot jämför två framtida utvecklingsvägar från samma historiska * utgångsläge, över samma tidshorisont och i basårets penningvärde. Egna skatte- * och utgiftsändringar tillämpas bara på AI-alternativet. */ export type GrowthEvaluation = { /** Historiskt utgångsläge (basåret). */ ref: Result; /** Jämförelsepunkt för huvudresultatet: basåret (m3.3.0) eller * nyanställningsvägen (m3.2, bara med relativeGrowth avstängd). */ baseline: Result; /** Bakgrund: nyanställningsvägen vid periodens slut, framtidsekonomins skala. */ hiring: Result; /** Bakgrund: AI-vägen vid periodens slut, framtidsekonomins skala. */ aiFuture: Result; /** Parametrarna som huvudresultatet räknas med. */ params: ScenarioParams; /** AI-avvikelsen utan egna åtgärder. */ aiPath: Result; /** AI-avvikelsen efter egna skatte- och utgiftsändringar. */ final: Result; /** Huvudmåttet: skatteintäkter i final minus baseline. */ taxChange: number; /** Varav AI-avvikelsen. */ taxChangePath: number; /** Varav egna åtgärder. */ taxChangePolicy: number; /** Bakgrund: AI-vägen minus nyanställningsvägen, framtidsekonomins skala (m3.2:s mått). */ twoPathTaxDiff: number; /** Uppdelning post för post, mätt mot basåret. */ evaluation: Evaluation; }; export function evaluateGrowth( cal: Calibration, s: ScenarioParams, policy: PolicyParams = EMPTY_POLICY, ): GrowthEvaluation { const ref = reference(cal); const hiring = simulate(cal, growthPathParams(s, "hiring"), EMPTY_POLICY); const aiFuture = simulate(cal, growthPathParams(s, "ai"), EMPTY_POLICY); const params = growthEffectiveParams(s); const evaluation = evaluate(cal, params, policy); const baseline = MODEL_SWITCHES.relativeGrowth ? ref : hiring; const aiPath = evaluation.ai; const final = evaluation.final; return { ref, baseline, hiring, aiFuture, params, aiPath, final, taxChange: final.taxTotal - baseline.taxTotal, taxChangePath: aiPath.taxTotal - baseline.taxTotal, taxChangePolicy: final.taxTotal - aiPath.taxTotal, twoPathTaxDiff: aiFuture.taxTotal - hiring.taxTotal, evaluation, }; } /** Vad brytpunkten ska återställa. */ export type BreakevenGoal = "taxes" | "finances"; /** Kanaler för större skatteunderlag. */ export type CompensationChannel = "profits" | "capital" | "consumption"; export const CHANNEL_LABEL: Record = { profits: "Bolagsvinster", capital: "Hushållens beskattningsbara kapitalinkomster", consumption: "Konsumtion", }; /** Avståndet till målet: positivt värde betyder att något återstår att kompensera. */ export function goalGap(goal: BreakevenGoal, baseline: Result, result: Result): number { return goal === "taxes" ? baseline.taxTotal - result.taxTotal : baseline.balance - result.balance; } function increaseFor(channels: CompensationChannel[], total: number): BaseIncrease { const share = channels.length > 0 ? total / channels.length : 0; return { surplus: channels.includes("profits") ? share : 0, capitalIncome: channels.includes("capital") ? share : 0, consumption: channels.includes("consumption") ? share : 0, }; } export type BaseBreakeven = { /** Summa ökat underlag, miljarder kronor per år. */ total: number; /** Per kanal: belopp och, där ett verifierat underlag finns, andel av det. */ perChannel: { channel: CompensationChannel; amount: number; pctOfBase: number | null; baseLabel: string | null; }[]; /** Ytterligare skatteintäkter som tillskottet ger, miljarder kronor per år. */ extraTax: number; /** Modellresultatet vid brytpunkten, för kontroll mot målet. */ result: Result; }; /** Övre gräns för sökt underlagsökning, som andel av basårets driftsöverskott. */ export const BASE_BREAKEVEN_MAX_SHARE = 3; /** * Hur mycket större skatteunderlag som krävs för att nå målet. Hela förändringen * beräknas gemensamt genom modellen, med bolagsskatt, utdelning, kapitalvinst och * konsumtion inräknade. Fristående kanalresultat summeras aldrig. * Returnerar null om målet inte nås inom modellens gränser. */ export function baseBreakeven( cal: Calibration, s: ScenarioParams, policy: PolicyParams, baseline: Result, goal: BreakevenGoal, channels: CompensationChannel[], ): BaseBreakeven | null { if (channels.length === 0) return null; const gap = goalGap(goal, baseline, simulate(cal, s, policy)); if (gap <= 0.05) return null; const at = (t: number) => goalGap(goal, baseline, simulate(cal, s, policy, increaseFor(channels, t))); const max = BASE_BREAKEVEN_MAX_SHARE * cal.data.operatingSurplus; // Stegvis sökning från noll följd av bisektion, som i taxRateBreakeven, så att // den minsta ökning som når målet hittas även om sambandet inte vore monotont. const steps = 200; let lo = 0; let hi: number | null = null; for (let i = 1; i <= steps; i++) { const t = (max * i) / steps; if (at(t) <= 0) { hi = t; break; } lo = t; } if (hi === null) return null; let upper: number = hi; for (let i = 0; i < 80; i++) { const mid: number = (lo + upper) / 2; if (at(mid) > 0) lo = mid; else upper = mid; } const total = upper; const increase = increaseFor(channels, total); const solved = simulate(cal, s, policy, increase); const perChannel = channels.map((channel) => { const amount = total / channels.length; if (channel === "profits") return { channel, amount, pctOfBase: (amount / cal.data.operatingSurplus) * 100, baseLabel: "basårets driftsöverskott och blandinkomst", }; if (channel === "consumption") return { channel, amount, pctOfBase: (amount / cal.data.householdConsumption) * 100, baseLabel: "basårets hushållskonsumtion", }; // Hushållens beskattningsbara kapitalinkomster saknar verifierad nivåserie i // dataunderlaget, så någon procentandel redovisas inte. return { channel, amount, pctOfBase: null, baseLabel: null }; }); return { total, perChannel, extraTax: solved.taxTotal - simulate(cal, s, policy).taxTotal, result: solved, }; } /** * Hur stor relativ höjning av effektiv beskattning i valda skattegrupper som * krävs för att nå målet, med modellens beteendeeffekt inräknad. Procenttalet * avser effektiv beskattning av samma underlag, inte procentenheter. */ export const TAX_RATE_BREAKEVEN_MAX = 100; export function taxRateBreakeven( cal: Calibration, s: ScenarioParams, policy: PolicyParams, baseline: Result, goal: BreakevenGoal, groups: TaxGroupId[], maxPct: number = TAX_RATE_BREAKEVEN_MAX, ): { pct: number; extraTax: number; result: Result } | null { if (groups.length === 0) return null; const withPct = (pct: number): PolicyParams => { const tax = { ...policy.tax }; for (const g of groups) tax[g] = (policy.tax[g] ?? 0) + pct; return { ...policy, tax }; }; const base = simulate(cal, s, policy); if (goalGap(goal, baseline, base) <= 0.05) return null; const at = (pct: number) => goalGap(goal, baseline, simulate(cal, s, withPct(pct))); // Beteendeformeln (1 + p)(1 − e·p) har en vändpunkt: intäkten når sitt maximum // vid p = (1 − e) / 2e och sjunker sedan. Varje grupps intäkt är en konkav // andragradsfunktion av den sökta höjningen, så den samlade intäkten är konkav och // avståndet till målet konvext. Sedan m3.2.1 hanteras vändpunkten uttryckligen: // 1. varje grupps egen vändpunkt, förskjuten med redan gjorda ändringar, läggs in // som kandidatpunkt, // 2. den samlade funktionens minimum söks med gyllene snittet, // 3. kandidaterna (rutnät, vändpunkter, minimum, intervallets slut) prövas i // stigande ordning; den första som når målet avgränsas med bisektion. // Eftersom den samlade funktionens minimum alltid finns bland kandidaterna missas // ingen lösning mellan rutnätspunkterna. const candidates = new Set([maxPct]); for (let p = 0.5; p < maxPct; p += 0.5) candidates.add(p); for (const g of groups) { const e = STRUCTURAL.taxElasticity[g]; if (e > 0) { const v = ((1 - e) / (2 * e)) * 100 - (policy.tax[g] ?? 0); if (v > 0 && v < maxPct) candidates.add(v); } } { const phi = (Math.sqrt(5) - 1) / 2; let a = 0; let b = maxPct; let c = b - phi * (b - a); let d = a + phi * (b - a); let fc = at(c); let fd = at(d); for (let i = 0; i < 80; i++) { if (fc <= fd) { b = d; d = c; fd = fc; c = b - phi * (b - a); fc = at(c); } else { a = c; c = d; fc = fd; d = a + phi * (b - a); fd = at(d); } } candidates.add((a + b) / 2); } const sorted = [...candidates].sort((x, y) => x - y); let lo = 0; let hi: number | null = null; for (const p of sorted) { if (at(p) <= 0) { hi = p; break; } lo = p; } if (hi === null) return null; let upper: number = hi; for (let i = 0; i < 80; i++) { const mid: number = (lo + upper) / 2; if (at(mid) > 0) lo = mid; else upper = mid; } const pct = upper; const solved = simulate(cal, s, withPct(pct)); // Kontroll genom den ordinarie motorn: lösningen ska faktiskt nå målet. if (goalGap(goal, baseline, solved) > 1e-6) return null; return { pct, extraTax: solved.taxTotal - base.taxTotal, result: solved }; } /** * Varför en brytpunkt saknas (sedan granskningen 2026-09-30, endast redovisning – * sökningen och resultaten i baseBreakeven/taxRateBreakeven är oförändrade). * - "range": målet nås inte inom det undersökta intervallet; tillskottet ökar * fortfarande vid intervallets slut, så en lösning längre ut är inte utesluten. * - "peaked": den samlade intäkten når sitt maximum inne i intervallet (beteende- * formelns vändpunkt). Målet är större än det högsta tillskott modellen * ger för vald kombination; ytterligare höjning minskar intäkterna. * maxExtra är det högsta prövade tillskottet mot målet (mdkr per år). */ export type BreakevenLimit = { status: "range" | "peaked"; maxExtra: number; limit: number; atMaximum: number }; export function taxRateBreakevenLimit( cal: Calibration, s: ScenarioParams, policy: PolicyParams, baseline: Result, goal: BreakevenGoal, groups: TaxGroupId[], maxPct: number = TAX_RATE_BREAKEVEN_MAX, ): BreakevenLimit { const withPct = (pct: number): PolicyParams => { const tax = { ...policy.tax }; for (const g of groups) tax[g] = (policy.tax[g] ?? 0) + pct; return { ...policy, tax }; }; const at = (pct: number) => goalGap(goal, baseline, simulate(cal, s, withPct(pct))); const phi = (Math.sqrt(5) - 1) / 2; let a = 0; let b = maxPct; for (let i = 0; i < 80; i++) { const c = b - phi * (b - a); const d = a + phi * (b - a); if (at(c) <= at(d)) b = d; else a = c; } const m = (a + b) / 2; const best = at(m) <= at(maxPct) ? m : maxPct; const maxExtra = at(0) - at(best); return { status: best < maxPct - 0.01 ? "peaked" : "range", maxExtra, limit: maxPct, atMaximum: best }; } export function baseBreakevenLimit( cal: Calibration, s: ScenarioParams, policy: PolicyParams, baseline: Result, goal: BreakevenGoal, channels: CompensationChannel[], ): BreakevenLimit { const max = BASE_BREAKEVEN_MAX_SHARE * cal.data.operatingSurplus; const at = (t: number) => goalGap(goal, baseline, simulate(cal, s, policy, increaseFor(channels, t))); const g0 = at(0); let best = 0; let bestGap = g0; for (let i = 1; i <= 200; i++) { const t = (max * i) / 200; const g = at(t); if (g < bestGap) { bestGap = g; best = t; } } return { status: best < max - 1e-9 ? "peaked" : "range", maxExtra: g0 - bestGap, limit: max, atMaximum: best }; } /** * Omställningsstödets nettokostnad. Återflödet definieras som skillnaden i * skatteintäkter mot samma scenario utan stöd (transferSupportPct = 0 och * extraSupportCost = 0), med övriga antaganden och skatteinställningar oförändrade. * Återflödet ingår redan i de redovisade skatteintäkterna och ska inte adderas igen. */ export function withoutSupport(s: ScenarioParams): ScenarioParams { return { ...s, transferSupportPct: 0, extraSupportCost: 0 }; } export function supportBreakdown(withS: Result, withoutS: Result) { const expenditure = withS.expenditureTotal - withoutS.expenditureTotal; const taxReturn = withS.taxTotal - withoutS.taxTotal; return { support: withS.detail.support, expenditure, taxReturn, net: expenditure - taxReturn }; } /** * Brytpunkt för vinster mot den offentliga ekonomin. Sedan m3.2.0 är detta bara en * genväg till baseBreakeven med området "Bolagsvinster", så att alla brytpunkter * använder samma fullständiga modell: bolagsskatt, utdelning, kapitalvinst, * konsumtion, samma jämförelsealternativ och samma skatteinställningar. */ export function profitBreakeven( cal: Calibration, s: ScenarioParams, policy: PolicyParams, baseline: Result, goal: BreakevenGoal = "finances", ): { neededSurplus: number; pctOfSurplus: number } | null { const b = baseBreakeven(cal, s, policy, baseline, goal, ["profits"]); if (!b) return null; return { neededSurplus: b.total, pctOfSurplus: (b.total / cal.data.operatingSurplus) * 100, }; } /** * Normaliserar en historisk profil till en gemensam ekonomisk utgångspunkt: * andelarna av BNP från profilåret appliceras på referensårets BNP och * penningvärde. Skalningen tar bort skillnader i ekonomins storlek och * penningvärde – men inte skillnader i ekonomisk struktur, konjunktur eller * demografi. */ export function normalizedCalibration(year: number, referenceYear: number): Calibration { const cal = calibrate(year); const d = cal.data; const refGdp = DATA[referenceYear]!.gdp; const k = refGdp / d.gdp; const scaled: YearData = { ...d, taxes: Object.fromEntries( Object.entries(d.taxes).map(([key, v]) => [key, v * k]), ) as Record, taxTotal: d.taxTotal * k, otherRevenue: d.otherRevenue * k, revenueTotal: d.revenueTotal * k, expenditure: Object.fromEntries( Object.entries(d.expenditure).map(([key, v]) => [key, v * k]), ) as Record, expenditureTotal: d.expenditureTotal * k, balance: d.balance * k, gdp: d.gdp * k, compensation: d.compensation * k, wages: d.wages * k, operatingSurplus: d.operatingSurplus * k, householdConsumption: d.householdConsumption * k, publicConsumption: d.publicConsumption * k, socialBenefits: d.socialBenefits * k, }; // Effektiv beskattning är oförändrad; bara underlagens nivå skalas. return { ...calibrate(year), data: scaled, comp: cal.comp, transfer: cal.transfer }; }