/* settings-mode.jsx — Driftläge: "Automatisk drift" (fka Gravio-Mode) vs "Egna inställningar" (fka User-mode). SPEC_GRAVIO_USER_MODE_UI 2026-07-30. Namnförslag till André (arbetsnamnen Gravio-Mode/User-mode är inte husägar- språk): AUTOMATISK DRIFT / EGNA INSTÄLLNINGAR. Byt strängarna i MODE_OPTS. self_sufficiency-läget är RIVET — operating_mode läses/skrivs inte längre någonstans i UI:t (se settings.jsx toBackend). HÅRDA KRAV som koden bär (§3.3): - null = "ojusterad" ≠ 0 = "av". Ojusterade rattar visar flottans värde som GHOST (placeholder), aldrig som satt värde, och skickas ALDRIG i payloaden. - Master-gaten (user_mode_enabled) raderar inga rattvärden när den slås av — backend ignorerar dem, UI:t minns dem. - Ratt 5 (batterislitage) och EV-prioriteringen mode-gatas INTE: de gäller alltid och ligger utanför gaten i renderingen. */ // Flottans kalibrerade defaults — visas som ghost, skickas aldrig av UI:t. // Ändrar André flottans värden ska bara dessa siffror uppdateras. const FLEET_DEFAULTS = { traj_premie_ore_per_kwh_h: 3, sellsolar_min_price_ore: 30, maxprofit_import_aversion_kr: 0.25, soc_planning_buffer_pct: 2, cycle_cost_ore: 10, }; // soc_planning_buffer_pct: LÅST UPP 2026-07-30 (André: "morgonbufferten ska // vara klar att använda"). Ratten är fullt justerbar och POSTas som de andra. const SOC_BUFFER_READY = true; // Rattarna som får POSTas som null vid "återställ" (mode-gatade + fria). // cycle_cost_ore ligger INTE här: det är ett vanligt alltid-fält (cycle_cost_kwh). const MODE_KNOB_FIELDS = [ 'traj_premie_ore_per_kwh_h', 'sellsolar_min_price_ore', 'maxprofit_import_aversion_kr', ...(SOC_BUFFER_READY ? ['soc_planning_buffer_pct'] : []), ]; const MODE_OPTS = [ { value:false, label:'Automatisk drift', help:'HEMS kör Gravios kalibrerade inställningar — samma som hela flottan. Du behöver inte ställa in något, och förbättrar vi inställningarna följer ditt hus med automatiskt.' }, { value:true, label:'Egna inställningar', help:'Samma motor, men du får själv skruva på ett antal värden — hur tidigt batteriet fylls, när solen säljs och hur ogärna huset köper el. Allt står på Gravios standard tills du ändrar det.' }, ]; const KNOBS = [ { // Enheten "öre" är BORTTAGEN (utvecklaren 2026-07-30): parametern är en // viktning, inte ett pris — att visa öre fick den att läsas som en kostnad. // Skalan är enhetslös 0–15. Fältnamnet behåller sitt backend-suffix. field:'traj_premie_ore_per_kwh_h', label:'Fyll batteriet tidigt', min:0, max:15, step:1, unit:'', dec:0, scale:true, help:'Högre värde = batteriet laddas fullt tidigare på dagen som säkerhet mot prognosfel. Lägre = ren prisoptimering, som kan vänta med laddningen till de billigaste timmarna.', zero:'Av — ren prisoptimering, ingen premie för tidig laddning.', }, { field:'sellsolar_min_price_ore', label:'Lägsta säljpris för sol', min:0, max:100, step:1, unit:'öre', dec:0, help:'Under detta pris säljs solen aldrig — den lagras i batteriet i stället. Är batteriet fullt flödar överskottet ändå ut, så ingen sol slösas.', zero:'Av — solen säljs vid alla priser.', }, { // Visas i ÖRE, lagras i KRONOR. Blandade enheter i samma panel var // förvirrande (André 2026-07-30) — allt som är pengar är nu öre. // factor:100 ⇒ UI-värde = backend-värde × 100. Backend-fältet är oförändrat. field:'maxprofit_import_aversion_kr', label:'Undvik nätköp', min:0, max:50, step:1, unit:'öre', dec:0, factor:100, help:'Högre värde = batteriet täcker huset i stället för att köpa el, även när en liten handelsvinst går förlorad. Vid höga värden avstår systemet också billiga nattladdningar med liten marginal — det är avsiktligt.', zero:'Av — ren arbitrage, HEMS köper när det är lönsamt.', }, { // Hette "Kvällsbuffert" — döpt till Morgonbuffert (utvecklaren 2026-07-30): // bufferten SPARAS på kvällen men finns TILL för morgonen, och namnet ska // beskriva vad den skyddar. Mekaniken enligt utvecklaren: kvällens planering // lägger bufferten OVANPÅ min-SoC (min 3 % + 2 % ⇒ planerar mot 5 %); kl 00:00 // släpps spärren och batteriet får gå ned till min-SoC igen; bufferten ska // vara återfylld till 23:45 så nästa dygns planering har sina 2 % extra. field:'soc_planning_buffer_pct', label:'Morgonbuffert', min:0, max:20, step:1, unit:'%', dec:0, help:'Extra reserv OVANPÅ din lägsta batterinivå, som kvällens planering sparar till morgonen. Har du lägsta nivå 3 % planerar HEMS mot 5 %. Vid midnatt släpps spärren och batteriet får användas ned till 3 % igen, och bufferten är återfylld till kl 23:45 inför nästa dygn. Syftet är att natten inte ska behöva köpa el om förbrukningen blir högre än planerat.', zero:'Av — ingen extra reserv över natten, planeringen går ned till din lägsta batterinivå.', pending: !SOC_BUFFER_READY, // false sedan 2026-07-30 }, ]; // ── En ratt: slider + siffra, ghost när ojusterad ───────────────────────── function Knob({ k, value, onChange, onReset, always }){ // f = UI-enhet per backend-enhet (100 för öre-visning av ett kr-fält). // Allt i den här komponenten räknas i UI-enhet; bara onChange går tillbaka. const f = k.factor || 1; const toUi = (v) => Number(v) * f; const toApi = (v) => f === 1 ? v : Math.round(Number(v) * 1e6 / f) / 1e6; const def = toUi(FLEET_DEFAULTS[k.field]); const unset = value === null || value === undefined || value === ''; const shown = unset ? def : toUi(value); const fmt = (n) => Number(n).toFixed(k.dec).replace('.', ',').replace(/,0+$/, ''); // Enhetslösa rattar (t.ex. "Fyll batteriet tidigt") får ingen enhet alls — // ett hängande mellanslag före punkten ser ut som en bugg. const val = (n) => fmt(n) + (k.unit ? ' ' + k.unit : ''); const commit = (raw) => { if (raw === '' ) { onReset(); return; } let n = parseFloat(String(raw).replace(',', '.')); if (isNaN(n)) return; n = Math.min(k.max, Math.max(k.min, n)); onChange(toApi(n)); }; return (