/** * Skattebasens beräkningsmodell, version m2. * * 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 = "m2.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 löner. Resten hänförs * till pension och skattepliktiga transfereringar. Bearbetad fördelning, grovt * kalibrerad mot att pensioner och skattepliktiga transfereringar utgör knappt en * fjärdedel av hushållens skattepliktiga inkomster. Redaktionellt antagande. */ labourShareOfIncomeTax: 0.78, /** 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. Redaktionellt antagande. */ payrollShareOfProductionTax: 0.9, /** Hur mycket underlaget för hushållens kapitalvinstskatt följer förändrade * företagsvinster. Kapitalvinster följer tillgångspriser och försäljningar, inte * lönesumman, och bara delvis årets vinster. Redaktionellt antagande. */ capitalGainsPassthrough: 0.5, /** Effektiv skatt på utdelning till svenska hushåll. Redaktionellt antagande. */ dividendTaxRate: 0.3, /** 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. */ 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, }; /** 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; }; export function calibrate(year: number): Calibration { const d = DATA[year]!; const labourTax = d.taxes.labour_income * STRUCTURAL.labourShareOfIncomeTax; const transferTax = d.taxes.labour_income - labourTax; 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)], 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), }; } 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; /** 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; }; 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: {} }; /** * Nettoförändring av privata löneinkomster i procent, härledd ur jobbantagandena. * Exempel: av 100 jobb försvinner 10, 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 lost = s.jobsLostPct / 100; const replaced = s.jobsReplacedPct / 100; const retainedWage = 1 + s.retainedJobWagePct / 100; const relWage = s.newJobWagePct / 100; const retainedJobs = 1 - lost; const newJobs = lost * replaced; return (retainedJobs * retainedWage + newJobs * relWage - 1) * 100; } 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; 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; dWagesGross: number; publicWageLoss: number; grossPrivateSaving: number; aiImportCost: number; netSaving: number; toProfits: number; toPrices: number; support: number; grossPublicSaving: number; implementationCost: number; netPublicSaving: number; dividends: 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, }; } function taxesFrom( cal: Calibration, bases: Record, policy: PolicyParams, ): { taxes: Record; transferTax: number } { const taxes = {} as Record; 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); taxes[g.id] = cal.comp[g.id].reduce((a, k) => a + k.eff * bases[k.baseKey] * factor, 0); } // 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); return { taxes, transferTax }; } function assemble( cal: Calibration, bases: Record, policy: PolicyParams, expenditure: Record, detail: Result["detail"], ): Result { const d = cal.data; const { taxes, transferTax } = taxesFrom(cal, bases, policy); 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, otherRevenue: d.otherRevenue, revenueTotal, expenditure, expenditureTotal, balance: revenueTotal - expenditureTotal, bases, detail, }; } const ZERO_DETAIL: Result["detail"] = { wageChangePct: 0, dCompensation: 0, dWagesGross: 0, publicWageLoss: 0, grossPrivateSaving: 0, aiImportCost: 0, netSaving: 0, toProfits: 0, toPrices: 0, support: 0, grossPublicSaving: 0, implementationCost: 0, netPublicSaving: 0, dividends: 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 }; } /** Tillstånd B eller C beroende på om policy är tom. */ export function simulate( cal: Calibration, s: ScenarioParams, policy: PolicyParams = EMPTY_POLICY, ): 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 personalkostnadsbesparing räknas på D1 (inkl. arbetsgivaravgifter). const grossPrivateSaving = Math.max(0, -dCompensation); const netSaving = grossPrivateSaving - s.aiImportCost; const shares = normalizeShares(s.savingToProfitsPct, s.savingToPricesPct); // Om AI-kostnaden överstiger besparingen faller vinsterna med hela underskottet. 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å bortfallen löneinkomst före skatt (D11), // alltså den inkomst stödet faktiskt ska ersätta – inte arbetsgivarens // totala personalkostnad. const support = Math.max(0, -dCompensation * wageRatio) * (s.transferSupportPct / 100); // 6. Nya underlag. const compensationAfter = d.compensation + dCompensation - publicWageLoss; const wagesAfter = d.wages + dWagesGross; const surplus = d.operatingSurplus + toProfits; const capitalBase = d.operatingSurplus + toProfits * STRUCTURAL.capitalGainsPassthrough; const benefits = d.socialBenefits + support; const dividends = Math.max(0, toProfits) * (s.dividendToHouseholdsPct / 100); 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); 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. toPrices * (STRUCTURAL.priceVolumeResponse - 1); const bases: Record = { wages: wagesAfter, compensation: compensationAfter, surplus, capitalBase, consumption, gdp: d.gdp, benefits, }; // 7. 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, dWagesGross, publicWageLoss, grossPrivateSaving, aiImportCost: s.aiImportCost, netSaving, toProfits, toPrices, support, grossPublicSaving, implementationCost, netPublicSaving, dividends, disposableChange, }); } 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, }; } /** * 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; const effProfit = (cal.comp.corporate[0]?.eff ?? 0) + (cal.comp.household_capital[0]?.eff ?? 0) * STRUCTURAL.capitalGainsPassthrough; 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 }; }