/* ============================================================ Enterprise mode — preset scenarios + portfolio dashboard ============================================================ */ /* global React, EnterpriseMapView, Icon, BarChart, Histogram, AzimuthRose, Sparkline */ const { useState: useStateE, useMemo: useMemoE, useEffect: useEffectE } = React; const utilsE = window.LEPTON_UTILS; function ScenarioCards({ scenarios, activeId, onSelect }) { return (
Portfolio scenario
Pre-warmed · zero live API calls
{scenarios.map(s => (
onSelect(s.id)}>
{s.title}
{s.subtitle} · {s.partner}
{s.count} bldgs
))}
Cached · 100%
); } function KpiStrip({ scenario }) { const buildings = scenario.buildings; const totalKwp = buildings.reduce((s, b) => s + b.kWp, 0); const totalGwh = buildings.reduce((s, b) => s + b.yearlyKwh, 0) / 1e6; const totalCo2 = buildings.reduce((s, b) => s + b.co2, 0); const totalCapex = buildings.reduce((s, b) => s + b.capex, 0); const eligible = scenario.suryaGharEligible ? buildings.filter(b => b.kWp <= 10).length : 0; const avgPayback = (buildings.reduce((s, b) => s + b.payback, 0) / buildings.length).toFixed(1); // sparklines per kpi — sorted buildings by metric const kWpSeries = [...buildings].sort((a, b) => a.kWp - b.kWp).map(b => b.kWp); const paybackSeries = [...buildings].sort((a, b) => a.payback - b.payback).map(b => b.payback); return (
Total installable
{utilsE.NUM(totalKwp, 0)}kWp
{buildings.length} buildings curated
Aggregate generation
{totalGwh.toFixed(2)}GWh/yr
{utilsE.NUM(Math.round(totalGwh * 1000), 0)} MWh · 25y
CO₂ offset / yr
{(totalCo2 / 1000).toFixed(1)}t
{utilsE.NUM(utilsE.treesEquivalent(totalCo2))} trees equiv.
CAPEX opportunity
{utilsE.INR(totalCapex)}
avg ₹{Math.round(totalCapex/buildings.length/1000)}k / site
Avg payback
{avgPayback}yrs
Surya Ghar eligible
{scenario.suryaGharEligible ? `${Math.round(eligible/buildings.length*100)}%` : "—"}
{scenario.suryaGharEligible ? `${eligible}/${buildings.length} residential` : "non-residential scenario"}
); } function RankedTable({ scenario, selectedId, onSelect, hoveredId, onHover, sort, setSort, colorMode }) { let rows = [...scenario.buildings]; rows.sort((a, b) => { const dir = sort.dir === "asc" ? 1 : -1; return (a[sort.key] - b[sort.key]) * dir; }); const maxKwp = Math.max(...rows.map(b => b.kWp)); const headerCell = (key, label, num = false) => ( setSort({ key, dir: sort.key === key && sort.dir === "desc" ? "asc" : "desc" })} > {label} {sort.key === key ? (sort.dir === "asc" ? "↑" : "↓") : "↕"} ); return (
{headerCell("kWp", "kWp", true)} {headerCell("yearlyKwh", "MWh / yr", true)} {headerCell("payback", "payback", true)} {headerCell("co2", "CO₂ t/yr", true)} {headerCell("suitability", "fit", true)} {rows.slice(0, 60).map(b => ( onHover(b.id)} onMouseLeave={() => onHover(null)} onClick={() => onSelect(b.id)} > ))}
building clusterquality
{b.id} {b.cluster} {b.clusterName.split(" ")[0]} {b.kWp.toFixed(1)} {(b.yearlyKwh / 1000).toFixed(1)} 6 ? "warn" : "")}>{b.payback} {(b.co2 / 1000).toFixed(1)} {b.suitability} {b.imageryQuality}
); } function ChartsRow({ scenario }) { // kWp histogram const kwps = scenario.buildings.map(b => b.kWp); const max = Math.max(...kwps); const binCount = 10; const bins = new Array(binCount).fill(0); const binLabels = []; for (let i = 0; i < binCount; i++) { const lo = (max / binCount) * i; const hi = (max / binCount) * (i + 1); binLabels.push(`${Math.round(lo)}`); } kwps.forEach(v => { const b = Math.min(binCount - 1, Math.floor(v / (max / binCount))); bins[b]++; }); const histData = bins.map((v, i) => ({ label: binLabels[i], value: v })); return (
kWp distribution
peak at {binLabels[bins.indexOf(Math.max(...bins))]} kWp
n={scenario.buildings.length}
Azimuth rose
south-bias = better yield (north India)
flat-roof excluded
); } /* ============================================================ Enterprise main ============================================================ */ function EnterpriseMode() { const data = window.useLeptonData(); const SCENARIOS = data.SCENARIOS; const [scenarioId, setScenarioId] = useStateE("mundra"); const [colorMode, setColorMode] = useStateE("kwp"); const [selectedId, setSelectedId] = useStateE(null); const [hoveredId, setHoveredId] = useStateE(null); const [sort, setSort] = useStateE({ key: "kWp", dir: "desc" }); // Lazy-load full scenario detail (buildings[]) on selection. useEffectE(() => { if (scenarioId && !data.SCENARIO_DETAILS[scenarioId] && data.loading.scenario !== scenarioId) { window.LEPTON_LOAD.loadScenario(scenarioId).catch(() => {}); } }, [scenarioId, data.SCENARIO_DETAILS[scenarioId], data.loading.scenario]); const summary = SCENARIOS.find(s => s.id === scenarioId); const detail = data.SCENARIO_DETAILS[scenarioId]; const scenario = detail || summary; // pick a default selected once scenario detail loads useEffectE(() => { if (detail && detail.buildings && detail.buildings.length) { const sorted = [...detail.buildings].sort((a, b) => b.kWp - a.kWp); setSelectedId(sorted[0].id); } }, [scenarioId, !!detail]); if (!SCENARIOS.length) { return (
Loading scenarios…
); } if (!scenario || !scenario.buildings) { return (
{data.loading.scenario === scenarioId ? `Pre-warming ${summary?.title} (${summary?.count || ""} buildings)… first run only.` : (data.error.scenario || "Loading scenario…")}
); } const selected = scenario.buildings.find(b => b.id === selectedId); return (
{/* color mode controls */}
Color by
{/* Legend */}
{colorMode === "kwp" ? "kWp" : colorMode === "payback" ? "Payback (yrs)" : "Suitability"}
{colorMode === "payback" ? "long" : "low"} {colorMode === "payback" ? "short" : "high"}
{scenario.buildings.length} buildings · BASE quality
{/* selected building inspector */} {selected && (
{selected.id} · {selected.clusterName}
kWp{selected.kWp}
annual{(selected.yearlyKwh/1000).toFixed(2)} MWh
azimuth · tilt{selected.azimuth}° · {selected.tilt}°
payback{selected.payback} yrs
CAPEX{utilsE.INR(selected.capex)}
area{selected.areaM2} m²
)}
{scenario.center[0].toFixed(4)}°N · {scenario.center[1].toFixed(4)}°E
{scenario.tariffNote} · ₹{scenario.tariff.toFixed(2)}/kWh
Ranked roofs · click to drop into homeowner view
{scenario.buildings.length} rows sort {sort.key} {sort.dir}
); } window.EnterpriseMode = EnterpriseMode;