Files
geograsim/App/src/sims/sim-09-energiemix/logic.ts
T
Adminator f22c5ebbfe Stand 2026-04-13: PHP/MySQL Infrastruktur, Flussmanagement, Stadt-Prototyp
- PHP/MySQL Backend (XAMPP + Produktionsserver)
- Front-Controller, API-Endpunkte, Session-Management
- Flussmanagement-Simulation (Echtzeit, Punkt-basierter Fluss)
- Stadt & Raumplanung (Prototyp, Top-Down Kachelsystem)
- Klimawaechter 3D: Deiche kleiner, Baeume kippen, Budget angepasst
- persistence.ts: Dualer Speicher (localStorage + Server-API)
- 6 Unit-Test-Dateien fuer bestehende Simulationen

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 16:43:42 +02:00

189 lines
6.2 KiB
TypeScript

/**
* SIM-09: Energiemix-Simulator — LOGIK
*
* Modell:
* - Schüler*innen stellen einen Energiemix zusammen
* - Drei Zielgrößen: CO₂-Ausstoß, Kosten, Versorgungssicherheit
* - Jede Energiequelle hat spezifische Werte für alle drei Dimensionen
* - Zielkonflikt sichtbar machen: billiger = dreckiger, sauber = teurer/unsicherer
*
* Didaktik:
* - Keine "richtige" Antwort — es geht um das Abwägen
* - Urteilskompetenz: verschiedene Perspektiven einnehmen
* - Gamification: Highscore für besten Kompromiss
*/
import { Simulation, SimulationMeta } from '@core/simulation'
const META: SimulationMeta = {
id: 'sim-09',
name: 'Energiemix-Simulator',
educationLevels: [5, 6, 7, 8, 9, 10],
primaryLevel: 6,
kompetenzbereich: 'Nachhaltiger Umgang mit Energie und Ressourcen',
lernziele: [
'Erneuerbare und nicht erneuerbare Energieträger vergleichen',
'Zielkonflikte zwischen Kosten, Umwelt und Versorgungssicherheit erkennen',
'Eigene Position zu Energiepolitik bilden und begründen',
],
basiskonzepte: ['Leistungserstellung und Nachhaltigkeit', 'Ökonomische Prinzipien und Entscheidungsfindung'],
dpiMinuten: 25,
typ: 'sachsimulation',
tier: 1,
requiresReading: true,
}
export interface EnergySource {
id: string
name: string
emoji: string
color: string
co2PerGWh: number // Tonnen CO₂ pro GWh (Lifecycle)
costPerMWh: number // EUR pro MWh
reliability: number // 0-1 (1 = immer verfügbar)
maxShare: number // maximaler realistischer Anteil (0-1)
renewable: boolean
description: string
}
export const ENERGY_SOURCES: EnergySource[] = [
{
id: 'coal', name: 'Kohle', emoji: '🪨', color: '#4a4a4a',
co2PerGWh: 820, costPerMWh: 65, reliability: 0.85, maxShare: 1,
renewable: false, description: 'Billig aber sehr CO₂-intensiv'
},
{
id: 'gas', name: 'Erdgas', emoji: '🔥', color: '#c4a35a',
co2PerGWh: 490, costPerMWh: 55, reliability: 0.87, maxShare: 0.8,
renewable: false, description: 'Hälfte des CO₂ von Kohle, flexibel'
},
{
id: 'nuclear', name: 'Atomkraft', emoji: '⚛️', color: '#8a5caa',
co2PerGWh: 12, costPerMWh: 90, reliability: 0.92, maxShare: 0.6,
renewable: false, description: 'Kaum CO₂, aber teuer und Atommüll'
},
{
id: 'wind', name: 'Windkraft', emoji: '🌬️', color: '#5a9aaa',
co2PerGWh: 11, costPerMWh: 45, reliability: 0.35, maxShare: 0.5,
renewable: true, description: 'Günstig und sauber, aber wetterabhängig'
},
{
id: 'solar', name: 'Solarenergie', emoji: '☀️', color: '#e8c84a',
co2PerGWh: 45, costPerMWh: 40, reliability: 0.25, maxShare: 0.4,
renewable: true, description: 'Billigste Quelle, aber nur tagsüber'
},
{
id: 'hydro', name: 'Wasserkraft', emoji: '💧', color: '#4a7c8a',
co2PerGWh: 24, costPerMWh: 50, reliability: 0.55, maxShare: 0.3,
renewable: true, description: 'Zuverlässig, aber Standort-begrenzt'
},
]
export interface MixResult {
totalCO2: number // Tonnen CO₂ pro GWh (gewichteter Durchschnitt)
totalCost: number // EUR pro MWh
totalReliability: number // 0-1
renewableShare: number // 0-100%
score: number // Gesamtbewertung 0-100
rating: string // z.B. "Gut balanciert"
}
/**
* Berechnet die Ergebnisse eines Energiemixes
* @param mix Map von SourceID → Anteil (0-100, Summe sollte 100 sein)
*/
export function computeMixResult(mix: Record<string, number>): MixResult {
let totalCO2 = 0
let totalCost = 0
let totalReliability = 0
let renewableShare = 0
let totalShare = 0
for (const source of ENERGY_SOURCES) {
const share = (mix[source.id] || 0) / 100
totalShare += share
totalCO2 += source.co2PerGWh * share
totalCost += source.costPerMWh * share
totalReliability += source.reliability * share
if (source.renewable) renewableShare += share * 100
}
// Normalisieren falls Summe ≠ 100
if (totalShare > 0 && Math.abs(totalShare - 1) > 0.01) {
totalCO2 /= totalShare
totalCost /= totalShare
totalReliability /= totalShare
renewableShare /= totalShare
}
// Score: Multi-Kriterien-Bewertung
const co2Score = Math.max(0, 100 - totalCO2 / 8) // 0 CO₂ = 100, 800 = 0
const costScore = Math.max(0, 100 - (totalCost - 30) / 0.7) // 30€ = 100, 100€ = 0
const reliScore = totalReliability * 100
const score = Math.round((co2Score * 0.4 + costScore * 0.3 + reliScore * 0.3))
let rating = 'Experimentell'
if (score >= 80) rating = 'Exzellent! 🌟'
else if (score >= 65) rating = 'Gut balanciert 👍'
else if (score >= 50) rating = 'Solide Basis'
else if (score >= 35) rating = 'Verbesserungswürdig'
return {
totalCO2: Math.round(totalCO2),
totalCost: Math.round(totalCost),
totalReliability: Math.round(totalReliability * 100) / 100,
renewableShare: Math.round(renewableShare),
score,
rating,
}
}
export class EnergiemixSimulation extends Simulation {
constructor() {
super(META)
// Startwerte: aktueller europäischer Mix (circa)
this.state.variables = {
coal: 15, gas: 20, nuclear: 20,
wind: 18, solar: 12, hydro: 15,
}
}
getVariableRanges() {
const ranges: Record<string, { min: number; max: number; default: number; unit: string; label: string }> = {}
for (const source of ENERGY_SOURCES) {
ranges[source.id] = {
min: 0,
max: Math.round(source.maxShare * 100),
default: this.state.variables[source.id] || 0,
unit: '%',
label: `${source.emoji} ${source.name}`,
}
}
return ranges
}
/** Normalisiert den Mix auf 100% */
normalizeMix(): void {
const total = ENERGY_SOURCES.reduce((s, src) => s + this.getVariable(src.id), 0)
if (total > 0) {
for (const src of ENERGY_SOURCES) {
this.state.variables[src.id] = Math.round(this.getVariable(src.id) / total * 100)
}
}
}
compute() {
const mix: Record<string, number> = {}
for (const src of ENERGY_SOURCES) {
mix[src.id] = this.getVariable(src.id)
}
const result = computeMixResult(mix)
this.state.results = result
return result as unknown as Record<string, number>
}
protected onVariableChange(): void {
this.compute()
}
}