/** * Skattebasens beräkningsmodell, version m3. * * 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. Referens – basårets verifierade utfall, oförändrat. * B. Referens + 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"; export const MODEL_VERSION = "m3.1.0"; /** * Redaktionellt valda strukturantaganden (modellantaganden, inte statistik). * Alla värden visas i "Källor och så räknar vi". */ 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 underlaget för hushållens kapitalvinstskatt 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. */ capitalGainsPassthrough: 0.5, /** Skatt på tillkommande kapitalvinster hos svenska hushåll. Satsen 30 procent är * lagstadgad (65 kap. inkomstskattelagen), men att tillämpa den fullt ut är ett * antagande om effektiv beskattning: sparformer som ISK och kapitalförsäkring, * utländska ägare och fåmansföretagsregler modelleras inte. */ capitalGainsTaxRate: 0.3, /** Effektiv skatt på utdelning till svenska hushåll. Lagstadgad sats 30 procent * tillämpad på hela utdelningen; redaktionellt antagande om effektiv beskattning * av samma skäl som ovan. */ dividendTaxRate: 0.3, /** 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, /** Halv elasticitet: hur mycket underlaget krymper när effektiv beskattning höjs. * 0,2 innebär att en höjning med 10 procent ger 8 procent högre intäkt. 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 | "capitalBase" // underlag för kapitalvinstskatt | "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)", capitalBase: "Kapitalvinstunderlag (B2A3G med delvis genomslag)", consumption: "Hushållens konsumtion (P31_S14)", gdp: "BNP (B1GQ)", benefits: "Utbetalda sociala fö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; }; 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)], household_capital: [ c("capitalBase", d.taxes.household_capital, d.operatingSurplus), ], 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, }; } export const TRANSFER_TAX_NOTE = "Skatt på pensioner och skattepliktiga transfereringar beräknas separat mot utbetalda sociala förmåner (D62PAY) och faller alltså inte automatiskt när lönerna faller."; export type WageInputMode = "jobs" | "direct"; 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; /** Uttryckligt antagande om ökad produktion (förädlingsvärde) i privat sektor, * procent. Hålls åtskilt från kostnadsbesparingar. */ productionGainPct: number; /** Nettoförändring av privata löneinkomster, procent. Används i läget "direkt". */ wageChangePct: number; /** Inköp av utländska AI-tjänster, miljarder kronor per år. */ 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; /** Löneutveckling per jobb i jämförelsealternativet, procent. */ growthWageHiringPct: number; /** Löneutveckling per jobb i AI-alternativet, procent. */ growthWageAiPct: number; /** Produktionsökning i jämförelsealternativet, procent av förädlingsvärdet. */ growthProductionHiringPct: number; /** Produktionsökning i AI-alternativet, procent av förädlingsvärdet. */ growthProductionAiPct: number; /** Kostnad för utländska AI-tjänster i jämförelsealternativet, mdkr per år. */ growthAiImportCostHiring: number; }; /** De två utvecklingsvägarna i tillväxtscenariot. */ export type GrowthPath = "hiring" | "ai"; /** * Utvecklingsvägarna delar alla gemensamma antaganden. Bara sysselsättning, * löneutveckling, produktionsökning och AI-kostnad skiljer dem åt, och båda * uttrycks som en direkt nettoförändring av privata löneinkomster till periodens * slut. Ingen av vägarna bokför uteblivna nyanställningar som uppsägningar: * inmatningsläget "direct" ger bortfallen löneinkomst noll så länge lönesumman * inte minskar, och omställningsstöd kräver därför ett uttryckligt antagande. */ export function growthPathParams(s: ScenarioParams, path: GrowthPath): ScenarioParams { const employment = path === "hiring" ? s.growthEmploymentHiringPct : s.growthEmploymentAiPct; const wage = path === "hiring" ? s.growthWageHiringPct : s.growthWageAiPct; const production = path === "hiring" ? s.growthProductionHiringPct : s.growthProductionAiPct; const aiImportCost = path === "hiring" ? s.growthAiImportCostHiring : s.aiImportCost; return { ...s, inputMode: "direct", wageChangePct: ((1 + employment / 100) * (1 + wage / 100) - 1) * 100, productionGainPct: production, aiImportCost, }; } 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 === "direct") 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, efter avdrag för de nya jobb som tillkommer. * * Detta mått är avsiktligt skilt från nettoförändringen i hela ekonomin: högre * löner för kvarvarande anställda kan inte kvitta bort stödbehovet hos dem som * blivit utan arbete. */ export function lostWageIncomeShare(s: ScenarioParams): number { if (s.inputMode === "direct") return Math.max(0, -s.wageChangePct / 100); const j = jobShares(s); return Math.max(0, j.lost * j.wLost - j.newJobs * j.wLost * j.relNewWage); } 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; 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; publicWageLoss: 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. */ productionGain: 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 för dem som förlorat jobbet, före stöd. */ lostWageIncome: 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, capitalBase: d.operatingSurplus, consumption: d.householdConsumption, gdp: d.gdp, benefits: d.socialBenefits, }; } /** * 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 = {}, ): { taxes: Record; transferTax: number; byOrigin: { labour: number; other: number }; } { const taxes = {} as Record; let labour = 0; let other = 0; for (const g of TAX_GROUPS) { const pct = policy.tax[g.id] ?? 0; const el = STRUCTURAL.taxElasticity[g.id]; // Relativ ändring av effektiv beskattning, dämpad av en enkel halv elasticitet. const factor = (1 + pct / 100) * (1 - (el * pct) / 100); let level = 0; for (const k of cal.comp[g.id]) { const amount = k.eff * bases[k.baseKey] * factor; level += k.eff * bases[k.baseKey]; // Bara komponenter som beräknas mot D11 eller D1 är skatt på arbete. Den // kapitalinkomstdel av D51A som följer företagens överskott räknas alltså // aldrig som 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] = level * factor + extraAmount; } // Skatt på pension och transfereringar följer samma reglage som hushållens // inkomstskatt, eftersom det är samma skatt i lagens mening. const pct = policy.tax.labour_income ?? 0; const el = STRUCTURAL.taxElasticity.labour_income; const transferTax = cal.transfer.eff * bases.benefits * (1 + pct / 100) * (1 - (el * pct) / 100); other += transferTax; return { taxes, transferTax, byOrigin: { labour, other } }; } function assemble( cal: Calibration, bases: Record, policy: PolicyParams, expenditure: Record, detail: Result["detail"], extra: TaxExtra = {}, ): Result { const d = cal.data; const { taxes, transferTax, byOrigin } = taxesFrom(cal, bases, policy, extra); 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, 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, grossPrivateSaving: 0, productionGain: 0, aiImportCost: 0, netSaving: 0, toProfits: 0, toPrices: 0, pricesToHouseholds: 0, corporateTaxOnChange: 0, retainedProfit: 0, lostWageIncome: 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 * STRUCTURAL.privateShareOfWages; 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; const publicWageLoss = grossPublicSaving * STRUCTURAL.publicWageShareOfConsumption; // 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. const privateValueAdded = privateCompensation * labourCostFactor + d.operatingSurplus; const productionGain = privateValueAdded * (s.productionGainPct / 100); 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. const privateWages = d.wages * STRUCTURAL.privateShareOfWages; const lostWageIncome = lostWageIncomeShare(s) * privateWages; 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; const dividendTax = dividends * STRUCTURAL.dividendTaxRate; const dCapitalBase = retainedProfit * STRUCTURAL.capitalGainsPassthrough; const capitalTaxOnChange = dCapitalBase * STRUCTURAL.capitalGainsTaxRate + dividendTax + increase.capitalIncome * STRUCTURAL.capitalGainsTaxRate; // 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 * (1 - STRUCTURAL.dividendTaxRate) + increase.capitalIncome * (1 - STRUCTURAL.capitalGainsTaxRate); // 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, capitalBase: 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, grossPrivateSaving, productionGain, aiImportCost: s.aiImportCost, netSaving, toProfits, toPrices, pricesToHouseholds, corporateTaxOnChange, retainedProfit, lostWageIncome, support, grossPublicSaving, implementationCost, netPublicSaving, dividends, dividendTax, disposableChange, }, { corporate: corporateTaxOnChange, household_capital: 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.taxes.payroll + ai.taxes.social_contrib - (ref.taxes.labour_income + 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).", "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å den del av personalkostnadsbesparingen som blir svensk beskattningsbar vinst, efter avdrag för AI-kostnader.", "ai", ); push( "capital", "Skatt på hushållens kapitalvinster", ai.taxes.household_capital - ref.taxes.household_capital, "Underlaget följer företagens vinster bara delvis, eftersom kapitalvinster beror på tillgångspriser och försäljningar.", "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, "Offentlig utgift som delvis ersätter bortfallen löneinkomst före skatt.", "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örelsealternativet: tillväxt med fler nyanställningar. */ hiring: Result; /** AI-alternativet utan egna åtgärder. */ aiPath: Result; /** AI-alternativet efter egna skatte- och utgiftsändringar. */ final: Result; /** Skatteintäkter i AI-alternativet minus jämförelsealternativet. Huvudmåttet. */ taxDiffVsHiring: number; /** Skatteintäkter i AI-alternativet minus basåret. */ taxDiffVsRef: number; /** Den del av skillnaden mot jämförelsealternativet som beror på utvecklingsvägen. */ taxDiffPath: number; /** Den del som beror på egna åtgärder. */ taxDiffPolicy: 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 aiParams = growthPathParams(s, "ai"); const aiPath = simulate(cal, aiParams, EMPTY_POLICY); const evaluation = evaluate(cal, aiParams, policy); const final = evaluation.final; return { ref, hiring, aiPath, final, taxDiffVsHiring: final.taxTotal - hiring.taxTotal, taxDiffVsRef: final.taxTotal - ref.taxTotal, taxDiffPath: aiPath.taxTotal - hiring.taxTotal, taxDiffPolicy: final.taxTotal - aiPath.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; }; /** * 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 = 3 * cal.data.operatingSurplus; if (at(max) > 0) return null; let lo = 0; let hi = max; for (let i = 0; i < 80; i++) { const mid = (lo + hi) / 2; if (at(mid) > 0) lo = mid; else hi = mid; } const total = (lo + hi) / 2; 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 function taxRateBreakeven( cal: Calibration, s: ScenarioParams, policy: PolicyParams, baseline: Result, goal: BreakevenGoal, groups: TaxGroupId[], ): { 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))); const max = 100; if (at(max) > 0) return null; let lo = 0; let hi = max; for (let i = 0; i < 60; i++) { const mid = (lo + hi) / 2; if (at(mid) > 0) lo = mid; else hi = mid; } const pct = (lo + hi) / 2; const solved = simulate(cal, s, withPct(pct)); return { pct, extraTax: solved.taxTotal - base.taxTotal, result: solved }; } /** * Brytpunkt: hur mycket måste driftsöverskottet (modellens proxy för svensk * beskattningsbar vinst) öka för att saldot ska bli lika starkt som referensen? * Returnerar null när det inte är möjligt inom modellens antaganden. */ export function profitBreakeven( cal: Calibration, result: Result, ref: Result, ): { neededSurplus: number; pctOfSurplus: number } | null { const gap = ref.balance - result.balance; if (gap <= 0) return null; // Skatt per tillkommande krona överskott: bolagsskatt med marginell sats, plus // kapitalvinstskatt på den kvarhållna delen efter bolagsskatt. const m = cal.marginalProfitTax; const effProfit = m + (1 - m) * STRUCTURAL.capitalGainsPassthrough * STRUCTURAL.capitalGainsTaxRate; if (effProfit <= 0) return null; const needed = gap / effProfit; const pct = (needed / cal.data.operatingSurplus) * 100; if (pct > 200) return null; return { neededSurplus: needed, pctOfSurplus: pct }; } /** * 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 }; }