import { EMPTY_POLICY, STRUCTURAL, growthPathDerived, growthRelative, hoursDerived, simulate, wageChangePct, type Calibration, type ScenarioParams, } from "./engine"; /** * Fyra AI-scenarier. De är villkorade antaganden, inte prognoser och inte * sannolikhetsbedömningar. Alla förvalda värden är redaktionellt valda * exempelvärden som är synliga och ändringsbara. Både negativa och positiva * utfall kan undersökas. * * Jobbantagandena gäller privat sektor. Offentlig personalminskning styrs bara * av reglaget för offentlig effektivisering. */ export type ScenarioId = | "productivity" | "transition" | "capital" | "growth" | "morejobs" | "fourday"; /** * Gemensamma förval. Tillväxtantagandena används bara av scenariot "Tillväxt utan * motsvarande nya jobb", men finns i alla scenarier så att en delad länk alltid * kan återskapa exakt samma uppsättning antaganden. */ /** * Förval för tillväxtscenariot (m3.2.0), alla redaktionella framtidsantaganden: * * Sysselsättning, jämförelsealternativet +3 procent på fem år (cirka 0,6 procent * per år). Motivering: SCB:s befolkningsframskrivning visar att befolkningen i * arbetsför ålder (20–64 år) växer långsamt, i storleksordningen några tiondels * procent per år. Därtill antas en måttligt stigande sysselsättningsgrad. Fler * nyanställningar än så kräver en arbetskraft som inte finns. * Sysselsättning, AI-alternativet +1 procent: färre nyanställningar än * arbetskraftens tillväxt medger. * Arbetstid per jobb: oförändrad i båda alternativen (hålls konstant). * Produktion per arbetad timme: +7,5 procent på fem år i jämförelsealternativet * (cirka 1,5 procent per år, i nivå med svensk arbetsproduktivitet i näringslivet * över längre perioder), +13 procent i AI-alternativet (cirka 1 procentenhet * snabbare per år). * Löneandel av produktivitetsökningen: 100 procent i jämförelsealternativet, det * vill säga oförändrad löneandel. 50 procent i AI-alternativet, eftersom en del av * ökningen antas bero på köpta AI-tjänster och tillfalla kapitalet. */ const COMMON = { extraSupportCost: 0, growthHorizonYears: 5, growthEmploymentHiringPct: 3, growthEmploymentAiPct: 1, growthHoursHiringPct: 0, growthHoursAiPct: 0, growthProductivityHiringPct: 7.5, growthProductivityAiPct: 13, growthWagePassHiringPct: 100, growthWagePassAiPct: 50, growthAiImportCostHiring: 0, newJobOutputPct: 0, hoursEmploymentPct: 0, hoursPerPersonPct: 0, hoursAnnualWagePct: 0, hoursProductivityPct: 0, }; export const SCENARIOS: { id: ScenarioId; label: string; summary: string; why: string; params: ScenarioParams; }[] = [ { id: "productivity", label: "AI hjälper oss att producera mer", summary: "AI gör arbetet effektivare. Färre jobb försvinner än i de andra omställningsscenarierna, och lönerna i kvarvarande jobb ökar.", why: "Exempelvärdena speglar en utveckling där få jobb försvinner och lönerna i de kvarvarande jobben stiger. Om kostnadsökningen blir större än produktionsökningen minskar vinsterna, även när löner och produktivitet ökar. Utfallet för vinster och priser redovisas ovan.", params: { ...COMMON, inputMode: "jobs", jobsLostPct: 6, jobsReplacedPct: 90, retainedJobWagePct: 3, newJobWagePct: 105, lostJobWagePct: 100, productionGainPct: 1.5, wageChangePct: 3, aiImportCost: 40, savingToProfitsPct: 30, savingToPricesPct: 50, publicEfficiencyPct: 1.5, publicImplementationPct: 30, transferSupportPct: 0, consumptionPassthroughPct: 90, dividendToHouseholdsPct: 40, }, }, { id: "transition", label: "Jobb försvinner – nya betalar mindre", summary: "Nya jobb tillkommer, men de ger lägre löneinkomster än de jobb som försvinner.", why: "Exempelvärdena visar ett läge där bortfall och nya inkomster delvis tar ut varandra, men där offentliga stöd behövs under omställningen.", params: { ...COMMON, inputMode: "jobs", jobsLostPct: 12, jobsReplacedPct: 60, retainedJobWagePct: 0, newJobWagePct: 90, lostJobWagePct: 100, productionGainPct: 1, wageChangePct: -5.5, aiImportCost: 90, savingToProfitsPct: 40, savingToPricesPct: 35, publicEfficiencyPct: 3, publicImplementationPct: 35, transferSupportPct: 35, consumptionPassthroughPct: 90, dividendToHouseholdsPct: 35, }, }, { id: "growth", label: "Företagen växer – jobben hänger inte med", summary: "Företagen producerar mer med hjälp av AI, medan antalet jobb är oförändrat eller ökar svagt. En mindre del av produktivitetsökningen går till löner.", why: "Två antagna vägar beskriver skillnaden: fler nyanställningar eller tillväxt med AI. Beräkningen lägger AI-vägens avvikelse på basårets ekonomi, så resultatet mäts mot samma referens som övriga scenarier.", params: { ...COMMON, growthEmploymentAiPct: 0, growthWagePassAiPct: 35, // Används inte direkt: tillväxtscenariot räknas alltid via growthRelativeParams. inputMode: "counterfactual", jobsLostPct: 0, jobsReplacedPct: 0, retainedJobWagePct: 0, newJobWagePct: 100, lostJobWagePct: 100, productionGainPct: 0, wageChangePct: 0, aiImportCost: 120, savingToProfitsPct: 45, savingToPricesPct: 25, publicEfficiencyPct: 0, publicImplementationPct: 30, transferSupportPct: 0, consumptionPassthroughPct: 90, dividendToHouseholdsPct: 35, }, }, { id: "capital", label: "AI tar över en stor del av jobben", summary: "Ett kraftigt stresstest där många jobb försvinner och bara en del ersätts av nya.", why: "Exempelvärdena speglar att vinsten av automatiseringen i hög grad tillfaller ägare, och att en del av den inte beskattas i Sverige.", params: { ...COMMON, inputMode: "jobs", // m3.4.1: fall C ur avstämningens avsnitt 10 (tidigare förval A: 20 % och 160 mdkr). jobsLostPct: 30, jobsReplacedPct: 35, retainedJobWagePct: 0, newJobWagePct: 85, lostJobWagePct: 100, productionGainPct: 0.5, wageChangePct: -14, aiImportCost: 240, savingToProfitsPct: 45, savingToPricesPct: 15, publicEfficiencyPct: 4, publicImplementationPct: 40, transferSupportPct: 45, consumptionPassthroughPct: 90, dividendToHouseholdsPct: 25, }, }, /** * m3.4.0, redaktionellt exempel, inte prognos: 6 jobb försvinner och 7,2 nya * tillkommer per 100 ursprungliga privata jobb (ersättningsandel 120 procent). * Nya jobb har samma lön och samma produktion som ett genomsnittligt jobb * (newJobOutputPct 100): de finansierar sin egen lönekostnad genom sin produktion. * De försvunna jobbens uppgifter antas utföras med AI, vars kostnad är AI-inköpen. * Inget fristående produktionsbidrag (productionGainPct 0), så de nya jobbens * produktion räknas exakt en gång. Kvarvarande jobbs löner oförändrade. * AI-inköp, fördelning och offentlig effektivisering som i "AI hjälper oss att * producera mer". Omställningsstöd 35 procent som i "Jobb försvinner". */ { id: "morejobs", label: "AI skapar fler jobb än den tar", summary: "Några jobb försvinner, men fler nya tillkommer. Det förutsätter att fler personer kan och vill arbeta.", why: "Exempelvärdena är ett redaktionellt antagande, inte en prognos. Scenarioantagande: det som de försvunna jobben producerade görs i stället med AI, så den produktionen finns kvar. De nya jobben antas ha genomsnittlig lön och produktion, och de som förlorar jobbet antas inte automatiskt få de nya.", params: { ...COMMON, inputMode: "jobs", jobsLostPct: 6, jobsReplacedPct: 120, retainedJobWagePct: 0, newJobWagePct: 100, lostJobWagePct: 100, productionGainPct: 0, newJobOutputPct: 100, wageChangePct: 1.2, aiImportCost: 40, savingToProfitsPct: 30, savingToPricesPct: 50, publicEfficiencyPct: 1.5, publicImplementationPct: 30, transferSupportPct: 35, consumptionPassthroughPct: 90, dividendToHouseholdsPct: 40, }, }, /** * m3.4.0, redaktionellt exempel: 20 procent kortare arbetstid per person med * bibehållen årslön och oförändrat antal anställda i marknadssektorerna. 25 * procent högre produktion per timme ger 0,8 × 1,25 = 1: oförändrad produktion. * Timlönen blir 25 procent högre. AI-inköpen (40 mdkr, som i "AI hjälper oss * att producera mer") tillkommer och finansieras inte av produktivitetsökningen. * Offentlig sektor omfattas inte (effektivisering 0). */ { id: "fourday", label: "AI ger oss en fyradagarsvecka", summary: "AI gör att samma produktion klaras på kortare tid. Arbetstiden blir kortare medan årslönen och antalet anställda är oförändrade.", why: "Exemplet avser privat sektor (marknadssektorerna). Offentlig sektor ingår inte. Produktivitetsökningen täcker den kortare arbetstiden, men inte kostnaden för AI-tjänsterna.", params: { ...COMMON, inputMode: "hours", hoursEmploymentPct: 0, hoursPerPersonPct: -20, hoursAnnualWagePct: 0, hoursProductivityPct: 25, jobsLostPct: 0, jobsReplacedPct: 0, retainedJobWagePct: 0, newJobWagePct: 100, lostJobWagePct: 100, productionGainPct: 0, wageChangePct: 0, aiImportCost: 40, savingToProfitsPct: 30, savingToPricesPct: 50, publicEfficiencyPct: 0, publicImplementationPct: 30, transferSupportPct: 0, consumptionPassthroughPct: 90, dividendToHouseholdsPct: 40, }, }, ]; export const GROWTH_SCENARIO_ID: ScenarioId = "growth"; export function isGrowthScenario(id: string): boolean { return id === GROWTH_SCENARIO_ID; } export function scenarioById(id: string) { return SCENARIOS.find((s) => s.id === id) ?? SCENARIOS[0]!; } const n = (v: number, d = 0) => v.toLocaleString("sv-SE", { minimumFractionDigits: d, maximumFractionDigits: d }); /** Sysselsättningsindex i utgångsläget. Jobben mäts som index, inte som personer. */ export const EMPLOYMENT_INDEX_BASE = 100; const pct1 = (v: number) => `${v >= 0 ? "+" : "−"}${n(Math.abs(v), 1)} procent`; /** * Beskrivning av de två utvecklingsvägarna i tillväxtscenariot, genererad ur * exakt samma parametrar och härledda storheter som beräkningen använder. */ export function describeGrowth(p: ScenarioParams): string[] { const h = growthPathDerived(p, "hiring"); const a = growthPathDerived(p, "ai"); const r = growthRelative(p); return [ `Tidshorisont ${n(p.growthHorizonYears)} år. Jobben ökar med ${pct1(h.employmentPct)} i jämförelsealternativet och ${pct1(a.employmentPct)} i AI-alternativet (sysselsättningsindex ${EMPLOYMENT_INDEX_BASE} → ${n(EMPLOYMENT_INDEX_BASE * (1 + h.employmentPct / 100), 0)} respektive ${n(EMPLOYMENT_INDEX_BASE * (1 + a.employmentPct / 100), 0)}).`, `Produktion = jobb × arbetstid per jobb × produktion per timme. Arbetstid per jobb: ${pct1(h.hoursPct)} respektive ${pct1(a.hoursPct)}${h.hoursPct === 0 && a.hoursPct === 0 ? " (hålls konstant)" : ""}. Produktion per timme: ${pct1(h.productivityPerHourPct)} respektive ${pct1(a.productivityPerHourPct)}.`, `Härlett: produktionen ökar ${pct1(h.productionPct)} i jämförelsealternativet och ${pct1(a.productionPct)} i AI-alternativet. Lön per jobb ${pct1(h.wagePerJobPct)} respektive ${pct1(a.wagePerJobPct)}, lönesumman ${pct1(h.wageBillPct)} respektive ${pct1(a.wageBillPct)}.`, `Av produktivitetsökningen per timme antas ${n(p.growthWagePassHiringPct)} procent bli högre timlön i jämförelsealternativet och ${n(p.growthWagePassAiPct)} procent i AI-alternativet.`, `Privata företags köp av utländska AI-tjänster: ${n(h.aiImportCost)} miljarder kronor per år i jämförelsealternativet och ${n(a.aiImportCost)} miljarder i AI-alternativet. Kostnaden minskar privata företags vinster.`, `Beräkningen använder skillnaden mellan vägarna i basårets ekonomi: ${pct1(r.employmentPct)} jobb, ${pct1(r.wageBillPct)} lönesumma, ${pct1(r.productionPct)} produktionsbidrag före AI-inköp och ${n(r.aiImportCost, 1)} miljarder kronor i ytterligare AI-inköp per år. Den gemensamma tillväxten är borträknad; det är ett stresstest, inte en prognos för ett visst år.`, `Färre nyanställningar är uteblivna jobb, inte uppsägningar, så modellen lägger inte in något omställningsstöd automatiskt. Uteblivna jobb kan ändå påverka offentliga stöd; merkostnader för stöd är ett uttryckligt antagande, nu ${n(p.extraSupportCost)} miljarder kronor per år${p.extraSupportCost === 0 ? " (inga sådana merkostnader inlagda)" : ""}.`, ]; } /** * Genererar scenariobeskrivningen ur exakt samma parametrar som beräkningen * använder, så att text och resultat aldrig kan säga olika saker. */ export function describeScenario( p: ScenarioParams, id?: string, cal?: Calibration, ): string[] { if (id && isGrowthScenario(id)) return describeGrowth(p); const out: string[] = []; const pct = wageChangePct(p); if (p.inputMode === "jobs") { const lost = p.jobsLostPct; const added = (lost * p.jobsReplacedPct) / 100; const retained = 100 - lost; out.push( `Av 100 jobb i privat sektor försvinner ${n(lost, 1)}, ${n(retained, 1)} finns kvar och ${n(added, 1)} nya tillkommer.`, ); out.push( p.retainedJobWagePct === 0 ? "De kvarvarande jobben har oförändrad genomsnittlig löneinkomst." : `De kvarvarande jobben får ${n(Math.abs(p.retainedJobWagePct), 1)} procent ${p.retainedJobWagePct > 0 ? "högre" : "lägre"} genomsnittlig löneinkomst.`, ); out.push( p.lostJobWagePct === 100 ? "De jobb som försvinner hade genomsnittlig löneinkomst." : `De jobb som försvinner hade ${n(p.lostJobWagePct)} procent av genomsnittlig löneinkomst. Övriga jobbs snittlön justeras så att den ursprungliga lönesumman bevaras.`, ); out.push( p.newJobWagePct === 100 ? "De nya jobben har samma genomsnittliga löneinkomst som de jobb som försvann." : `De nya jobben har ${n(p.newJobWagePct)} procent av de försvunna jobbens genomsnittliga löneinkomst.`, ); out.push( `Samlade löneinkomster i privat sektor förändras därmed med ${n(pct, 1)} procent. Privat sektor (marknadssektorerna enligt sektorsräkenskaperna) svarar för ${n((cal?.privateWageShare ?? STRUCTURAL.privateShareOfWages) * 100, 1)} procent av lönesumman${cal ? ` år ${cal.year}` : ""}.`, ); if (p.newJobOutputPct > 0) out.push( `Varje nytt jobb antas producera ${n(p.newJobOutputPct)} procent av ett genomsnittligt jobbs produktion i marknadssektorerna. Den produktionen räknas en gång, som del av produktionsbidraget.`, ); const net = added - lost; if (net > 0.05) out.push( `Nettoökningen på ${n(net, 1)} jobb per 100 förutsätter att fler personer kan och vill arbeta. Modellen har inget tak för arbetskraften; den visar nettobehovet av fler sysselsatta.`, ); } else if (p.inputMode === "hours") { const h = hoursDerived(p); out.push( `Omfattning: privat sektor (marknadssektorerna). Antal anställda ${pct1(h.employmentPct)}, arbetstid per person ${pct1(h.hoursPerPersonPct)}, årslön per person ${pct1(h.annualWagePct)}. Timlönen blir därmed ${pct1(h.hourlyWagePct)} och lönesumman ${pct1(h.wageBillPct)}.`, ); out.push( `Produktion per arbetad timme ${pct1(h.productivityPerHourPct)}. Total produktion = anställda × arbetstid × produktion per timme = ${pct1(h.productionPct)}.`, ); out.push( "Kortare arbetstid är inga uppsägningar: ingen förlorar jobbet och inget omställningsstöd läggs in.", ); } else if (p.inputMode === "counterfactual") { out.push( `Lönesumman i privat sektor är ${n(Math.abs(pct), 1)} procent ${pct < 0 ? "lägre" : "högre"} genom uteblivna eller tillkommande jobb. Ingen sägs upp, så inget omställningsstöd följer automatiskt.`, ); } else { out.push( `Löneinkomsterna i privat sektor förändras netto med ${n(pct, 1)} procent. Det är en direkt angiven nettoförändring efter förlorade jobb, nya jobb och löneförändringar.`, ); } if (p.inputMode !== "hours") out.push( p.productionGainPct === 0 ? p.inputMode === "jobs" && p.newJobOutputPct > 0 ? "Inget ytterligare produktionsbidrag utöver de nya jobbens produktion." : "Ingen ökad produktion antas. Modellen räknar då bara med förändrade kostnader." : `Produktionsbidraget före AI-inköp antas vara ${n(p.productionGainPct, 1)} procent av produktionsunderlaget (arbetskraftskostnad plus överskott i marknadssektorerna). Det är ett eget antagande, skilt från kostnadsbesparingen, och fördelas med samma andelar som besparingen.`, ); out.push( `Ytterligare AI-inköp: privata företag köper utländska AI-tjänster för ${n(p.aiImportCost)} miljarder kronor per år. Kostnaden dras en gång från produktionsbidraget och minskar deras svenska vinster. Offentlig sektors AI-kostnader ingår i stället i den offentliga införandekostnaden.`, ); const other = Math.max(0, 100 - p.savingToProfitsPct - p.savingToPricesPct); out.push( `Av det som återstår efter AI-kostnaden – kostnadsbesparing plus eventuell produktionsökning – blir ${n(p.savingToProfitsPct)} procent svensk beskattningsbar vinst, ${n(p.savingToPricesPct)} procent lägre priser och ${n(other)} procent annan användning som inte beskattas i Sverige.`, ); out.push( `Offentlig sektor effektiviserar ${n(p.publicEfficiencyPct, 1)} procent av sin konsumtion, med ${n(p.publicImplementationPct)} procent av bruttobesparingen i införandekostnad.`, ); out.push( `Omställningsstöd motsvarande ${n(p.transferSupportPct)} procent av den löneinkomst före skatt som faller bort för dem som förlorar jobbet, i privat eller offentlig sektor. Talet är ett sammansatt antagande: ersättningsgrad × andel av de berörda som får stöd × andel av året stödet betalas. Nya jobb minskar inte stödet, eftersom de inte antas gå till dem som förlorat sina. Stödet beskattas som transferering. ${n(p.consumptionPassthroughPct)} procent av förändrad disponibel inkomst syns i konsumtionen och ${n(p.dividendToHouseholdsPct)} procent av ökade vinster delas ut till svenska hushåll.`, ); if (cal) { const r = simulate(cal, p, EMPTY_POLICY).detail; const profitText = r.toProfits >= 0 ? `ökar med ${n(r.toProfits, 1)} miljarder kronor` : `minskar med ${n(-r.toProfits, 1)} miljarder kronor`; out.push( `Utfall i modellen: företagens arbetskraftskostnad ${r.dLabourCost >= 0 ? "ökar" : "minskar"} med ${n(Math.abs(r.dLabourCost), 1)} miljarder och produktionsbidraget före AI-inköp är ${n(r.productionGain, 1)} miljarder. Svenska beskattningsbara vinster ${profitText}${r.toPrices > 0 ? ` och priserna sänks med ${n(r.toPrices, 1)} miljarder` : ", och ingen prissänkning uppstår"}.`, ); } if (p.extraSupportCost > 0) out.push( `Utöver det antas extra offentligt stöd på ${n(p.extraSupportCost)} miljarder kronor per år, som ett uttryckligt eget antagande.`, ); return out; } export const SCENARIO_CAVEAT = "Automatiserade arbetsuppgifter, förlorade jobb och förändrade löneinkomster är tre olika saker. Att en uppgift kan automatiseras betyder inte att jobbet försvinner, och ett borttaget jobb betyder inte att hela löneinkomsten försvinner ur ekonomin."; /** * Varianter inom tillväxtscenariot (m3.3.0). Båda är redaktionella stresstester, * inte prognoser. Varianten "Företagen växer utan fler jobb" ändrar bara AI- * alternativets sysselsättning (1 → 0 procent) och lönegenomslag (50 → 35 procent). * Referensalternativet, produktivitet per timme (+13 procent), arbetstid och övriga * antaganden är oförändrade. Varianten räknas med samma AI-avvikelse mot basårets * ekonomi som övriga scenarier. */ export type GrowthVariantId = "fewer" | "nojobs"; export const GROWTH_VARIANTS: { id: GrowthVariantId; label: string; overrides: Partial; }[] = [ { id: "nojobs", label: "Företagen växer utan fler jobb", overrides: { growthEmploymentAiPct: 0, growthWagePassAiPct: 35 }, }, { id: "fewer", label: "Företagen växer med få nya jobb", overrides: { growthEmploymentAiPct: 1, growthWagePassAiPct: 50 }, }, ]; export function growthVariantParams(p: ScenarioParams, id: GrowthVariantId): ScenarioParams { const variant = GROWTH_VARIANTS.find((v) => v.id === id); return variant ? { ...p, ...variant.overrides } : p; } const GROWTH_DESCRIPTION_KEYS: (keyof ScenarioParams)[] = [ "growthEmploymentHiringPct", "growthEmploymentAiPct", "growthHoursHiringPct", "growthHoursAiPct", "growthProductivityHiringPct", "growthProductivityAiPct", "growthWagePassHiringPct", "growthWagePassAiPct", ]; /** Vilken variants förval de relevanta tillväxtantagandena motsvarar, eller null vid egna värden. */ export function activeGrowthVariant(p: ScenarioParams): GrowthVariantId | null { const base = scenarioById(GROWTH_SCENARIO_ID).params; const v = GROWTH_VARIANTS.find((variant) => { const defaults = growthVariantParams(base, variant.id); return GROWTH_DESCRIPTION_KEYS.every((key) => p[key] === defaults[key]); }); return v?.id ?? null; } export function growthVariantLabel(p: ScenarioParams): string { const id = activeGrowthVariant(p); return GROWTH_VARIANTS.find((v) => v.id === id)?.label ?? "Egna antaganden"; } /** * Förklaring av jobbavvikelsen, genererad ur aktuella antaganden. Avvikelsen är * skillnaden mot jämförelsealternativet, inte en minskning mot basåret. */ export function explainGrowthJobs(p: ScenarioParams): string { const r = growthRelative(p); const h = 100 * (1 + p.growthEmploymentHiringPct / 100); const a = 100 * (1 + p.growthEmploymentAiPct / 100); const dir = r.employmentPct < 0 ? "färre" : "fler"; return `${pct1s(r.employmentPct)} jobb avser skillnaden mot jämförelsealternativet: där blir jobben ${n(p.growthEmploymentHiringPct, 1)} procent fler (index 100 → ${n(h, 0)}), i AI-alternativet ${p.growthEmploymentAiPct === 0 ? "lika många som i basåret" : `${n(Math.abs(p.growthEmploymentAiPct), 1)} procent ${p.growthEmploymentAiPct > 0 ? "fler" : "färre"}`} (index 100 → ${n(a, 0)}). ${n(a, 0)}/${n(h, 0)} ger ${n(Math.abs(r.employmentPct), 2)} procent ${dir} jobb. Det är ${r.employmentPct < 0 ? "uteblivna jobb som aldrig tillkommer" : "jobb som tillkommer utöver jämförelsealternativet"}, inte uppsägningar${p.growthEmploymentAiPct >= 0 ? ", och antalet jobb minskar inte jämfört med basåret" : ""}.`; } const pct1s = (v: number) => `${v >= 0 ? "+" : "−"}${n(Math.abs(v), 2)} procent`; /** * Varianter per scenario (m3.4.0). Tillväxtscenariots varianter finns ovan; här * samlas alla, så att förval, delningslänkar och återställning fungerar likadant. * "Lika många jobb – lägre löner" ändrar bara ersättningsandelen (60 → 100 procent); * de nya jobbens lön (90 procent av de försvunnas) och alla övriga antaganden är * desamma som i scenariots förval, så skillnaden kan tillskrivas antalet jobb. */ export type VariantId = string; export type ScenarioVariant = { id: VariantId; label: string; help?: string; overrides: Partial; }; export const SCENARIO_VARIANTS: Partial> = { growth: GROWTH_VARIANTS, transition: [ { id: "partial", label: "Färre nya jobb än de som försvinner", help: "Sex av tio försvunna jobb ersätts av nya jobb med lägre lön.", overrides: { jobsReplacedPct: 60, newJobWagePct: 90 }, }, { id: "equal", label: "Lika många jobb – lägre löner", help: "Lika många nya jobb som försvinner, men de nya har lägre lön.", overrides: { jobsReplacedPct: 100, newJobWagePct: 90 }, }, ], }; const VARIANT_KEYS: Partial> = { growth: GROWTH_DESCRIPTION_KEYS, transition: ["jobsReplacedPct", "newJobWagePct"], }; export function scenarioVariants(id: ScenarioId): ScenarioVariant[] { return SCENARIO_VARIANTS[id] ?? []; } export function variantParams(id: ScenarioId, p: ScenarioParams, variant: VariantId): ScenarioParams { const v = scenarioVariants(id).find((x) => x.id === variant); return v ? { ...p, ...v.overrides } : p; } /** Aktiv variant för scenariot, eller null vid egna värden eller om varianter saknas. */ export function activeVariant(id: ScenarioId, p: ScenarioParams): VariantId | null { if (id === GROWTH_SCENARIO_ID) return activeGrowthVariant(p); const keys = VARIANT_KEYS[id]; if (!keys) return null; const base = scenarioById(id).params; const v = scenarioVariants(id).find((variant) => { const d = variantParams(id, base, variant.id); return keys.every((k) => p[k] === d[k]); }); return v?.id ?? null; } export function variantLabel(id: ScenarioId, p: ScenarioParams): string | null { if (scenarioVariants(id).length === 0) return null; const a = activeVariant(id, p); return scenarioVariants(id).find((v) => v.id === a)?.label ?? "Egna antaganden"; } /** Arbetstidslägets mått för presentation. */ export { hoursDerived }; /** * Visningsordning för de sex huvudscenarierna: berättande, från möjligheter till * större påfrestningar. Ingen rangordning efter skatteutfall och ingen automatisk * omsortering. SCENARIOS behåller sin ordning, eftersom SCENARIOS[0] är förvalet. */ export const SCENARIO_DISPLAY_ORDER: ScenarioId[] = [ "morejobs", "productivity", "fourday", "growth", "transition", "capital", ]; export const SCENARIOS_IN_DISPLAY_ORDER = SCENARIO_DISPLAY_ORDER.map((id) => scenarioById(id));