fix: BigScreen data display, carbon, energy history (v1.6.2)
BigScreen fixes: - Fix NaN in 今日用电 (normalize energy_today structure) - Fix 总设备=0 (compute from online+offline) - Fix energy flow zeros (map total_load→total_power) - Fix 今日发电=0 (extract from nested energy_today) Backend fixes (synced from ems-core): - Carbon overview fallback from energy_data × emission_factors - Energy history: datetime parsing (was 500) - Dashboard generation: station-level dedup (93K→14.8K kWh) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,10 +20,10 @@ export default function EnergyFlowDiagram({ realtime, overview }: Props) {
|
||||
const particlesRef = useRef<Particle[]>([]);
|
||||
const rafRef = useRef<number>(0);
|
||||
|
||||
const gridPower = realtime?.grid_power ?? 0;
|
||||
const pvPower = realtime?.pv_power ?? 0;
|
||||
const totalPower = realtime?.total_power ?? 0;
|
||||
const hpPower = realtime?.heatpump_power ?? 0;
|
||||
const gridPower = Number(realtime?.grid_power) || 0;
|
||||
const pvPower = Number(realtime?.pv_power) || 0;
|
||||
const totalPower = Number(realtime?.total_power) || Number(realtime?.total_load) || 0;
|
||||
const hpPower = Number(realtime?.heatpump_power) || 0;
|
||||
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
|
||||
@@ -8,7 +8,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export default function EnergyOverviewCard({ data, realtime }: Props) {
|
||||
const selfUseRate = data?.self_consumption_rate ?? 0;
|
||||
const selfUseRate = Number(data?.self_consumption_rate) || 0;
|
||||
|
||||
const gaugeOption = {
|
||||
series: [{
|
||||
@@ -46,14 +46,14 @@ export default function EnergyOverviewCard({ data, realtime }: Props) {
|
||||
<div className={styles.statItem}>
|
||||
<span className={styles.statLabel}>今日用电</span>
|
||||
<span className={styles.statValueCyan}>
|
||||
<AnimatedNumber value={data?.energy_today ?? 0} decimals={1} />
|
||||
<AnimatedNumber value={Number(data?.energy_today) || 0} decimals={1} />
|
||||
<span className={styles.unit}> kWh</span>
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.statItem}>
|
||||
<span className={styles.statLabel}>光伏发电</span>
|
||||
<span className={styles.statValueGreen}>
|
||||
<AnimatedNumber value={data?.pv_generation_today ?? 0} decimals={1} />
|
||||
<AnimatedNumber value={Number(data?.pv_generation_today) || 0} decimals={1} />
|
||||
<span className={styles.unit}> kWh</span>
|
||||
</span>
|
||||
</div>
|
||||
@@ -62,7 +62,7 @@ export default function EnergyOverviewCard({ data, realtime }: Props) {
|
||||
<div className={styles.statItem}>
|
||||
<span className={styles.statLabel}>电网购电</span>
|
||||
<span className={styles.statValueOrange}>
|
||||
<AnimatedNumber value={data?.grid_import_today ?? 0} decimals={1} />
|
||||
<AnimatedNumber value={Number(data?.grid_import_today) || 0} decimals={1} />
|
||||
<span className={styles.unit}> kWh</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -9,8 +9,9 @@ export default function LoadCurveCard({ loadData }: Props) {
|
||||
const hours = loadData?.hours ?? Array.from({ length: 24 }, (_, i) => `${i}:00`);
|
||||
const values = loadData?.values ?? new Array(24).fill(0);
|
||||
const peak = values.length ? Math.max(...values) : 0;
|
||||
const valley = values.length ? Math.min(...values.filter((v: number) => v > 0)) || 0 : 0;
|
||||
const avg = values.length ? values.reduce((a: number, b: number) => a + b, 0) / values.filter((v: number) => v > 0).length || 0 : 0;
|
||||
const positiveValues = values.filter((v: number) => v > 0);
|
||||
const valley = positiveValues.length ? Math.min(...positiveValues) : 0;
|
||||
const avg = positiveValues.length ? positiveValues.reduce((a: number, b: number) => a + b, 0) / positiveValues.length : 0;
|
||||
|
||||
const option = {
|
||||
grid: { left: 40, right: 12, top: 30, bottom: 24 },
|
||||
|
||||
@@ -8,10 +8,10 @@ interface Props {
|
||||
}
|
||||
|
||||
export default function PVCard({ realtime, overview }: Props) {
|
||||
const pvPower = realtime?.pv_power ?? 0;
|
||||
const todayGen = overview?.pv_generation_today ?? 0;
|
||||
const monthlyGen = overview?.pv_monthly_generation ?? 0;
|
||||
const selfUseRate = overview?.self_consumption_rate ?? 0;
|
||||
const pvPower = Number(realtime?.pv_power) || 0;
|
||||
const todayGen = Number(overview?.pv_generation_today) || 0;
|
||||
const monthlyGen = Number(overview?.pv_monthly_generation) || 0;
|
||||
const selfUseRate = Number(overview?.self_consumption_rate) || 0;
|
||||
|
||||
// Donut for self-use ratio
|
||||
const donutOption = {
|
||||
|
||||
@@ -84,9 +84,50 @@ export default function BigScreen() {
|
||||
return null;
|
||||
};
|
||||
|
||||
if (get(0)) setOverview(get(0));
|
||||
if (get(1)) setRealtime(get(1));
|
||||
if (get(2)) setLoadData(get(2));
|
||||
if (get(0)) {
|
||||
const ov = get(0);
|
||||
// Normalize overview: API returns nested energy_today.electricity structure
|
||||
// but components expect flat fields like energy_today, pv_generation_today, etc.
|
||||
const elec = ov?.energy_today?.electricity;
|
||||
const normalized = {
|
||||
...ov,
|
||||
energy_today: elec?.consumption ?? ov?.energy_today ?? 0,
|
||||
pv_generation_today: elec?.generation ?? ov?.pv_generation_today ?? 0,
|
||||
// Flatten device_stats.total if present
|
||||
device_total: ov?.device_stats?.total ?? 0,
|
||||
self_consumption_rate: ov?.self_consumption_rate ?? 0,
|
||||
};
|
||||
setOverview(normalized);
|
||||
// Also update deviceStats from overview if it has device_stats with total
|
||||
if (ov?.device_stats) {
|
||||
setDeviceStats((prev: any) => ({ ...prev, ...ov.device_stats }));
|
||||
}
|
||||
}
|
||||
if (get(1)) {
|
||||
const rt = get(1);
|
||||
// Normalize: API returns total_load but components use total_power
|
||||
setRealtime({
|
||||
...rt,
|
||||
total_power: rt?.total_power ?? rt?.total_load ?? 0,
|
||||
});
|
||||
}
|
||||
if (get(2)) {
|
||||
const raw = get(2);
|
||||
// Normalize load curve: API returns [{time, power}] array
|
||||
// but LoadCurveCard expects {hours: [...], values: [...]}
|
||||
if (Array.isArray(raw)) {
|
||||
const hours = raw.map((item: any) => {
|
||||
const t = item.time || item.timestamp || '';
|
||||
// Extract HH:00 from timestamp
|
||||
const match = t.match(/(\d{2}:\d{2})/);
|
||||
return match ? match[1] : t;
|
||||
});
|
||||
const values = raw.map((item: any) => item.power ?? item.value ?? 0);
|
||||
setLoadData({ hours, values });
|
||||
} else {
|
||||
setLoadData(raw);
|
||||
}
|
||||
}
|
||||
if (get(3)) {
|
||||
const alarms = get(3);
|
||||
setAlarmEvents(Array.isArray(alarms) ? alarms : alarms?.items ?? alarms?.events ?? []);
|
||||
@@ -121,10 +162,11 @@ export default function BigScreen() {
|
||||
return d.toLocaleTimeString('zh-CN', { hour12: false });
|
||||
};
|
||||
|
||||
const totalDevices = deviceStats?.total ?? 0;
|
||||
const onlineDevices = deviceStats?.online ?? 0;
|
||||
const offlineDevices = deviceStats?.offline ?? 0;
|
||||
const alarmDevices = deviceStats?.alarm_count ?? alarmStats?.active_count ?? 0;
|
||||
const alarmDevices = deviceStats?.alarm_count ?? deviceStats?.alarm ?? alarmStats?.active_count ?? 0;
|
||||
const maintenanceDevices = deviceStats?.maintenance ?? 0;
|
||||
const totalDevices = deviceStats?.total ?? (onlineDevices + offlineDevices + alarmDevices + maintenanceDevices) || 0;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
|
||||
Reference in New Issue
Block a user