/** * Skattebasens beräkningsmodell, version m3.2.1 (m3.2.0 ± brytpunktssökning; föregående större version i audit/m3.1.0). * * 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.2.1"; /** * 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, }; /** * 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 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; }; 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, }; } export const TRANSFER_TAX_NOTE = "Skatt på pensioner och skattepliktiga transfereringar beräknas separat mot utbetalda sociala kontantfö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; /** 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; }; /** 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, }; } /** 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, dLabourCost: r.detail.dLabourCost, productionGain: r.detail.productionGain, netSaving: r.detail.netSaving, toProfits: r.detail.toProfits, toPrices: r.detail.toPrices, labourCostLevel: d.compensation * STRUCTURAL.privateShareOfWages * (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 === "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; /** 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. */ 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 (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, 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 * 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; // 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. 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. // Offentligt anställda som förlorar arbetet genom effektivisering omfattas av // samma stödantagande. Stödet avser ett helt år i periodens sluttillstånd: // transferSupportPct = ersättningsgrad × täckningsgrad × andel av året med stöd. const privateWages = d.wages * STRUCTURAL.privateShareOfWages; 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, 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, "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; }; /** Ö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 }; } /** * 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 }; }