fix telemtrie na dahsbaordu (15min misto 1h)
Some checks failed
deploy / deploy (push) Failing after 1m42s
test / smoke-test (push) Successful in 2s

This commit is contained in:
Dusan Vojacek
2026-04-10 20:48:41 +02:00
parent 920d9ff40c
commit eb8dd0368f
11 changed files with 131 additions and 23 deletions

View File

@@ -13,9 +13,11 @@ import {
type Props = {
slots: SlotData[]
nowIndex: number
/** Stejný zdroj jako horní karta SOC (živá telemetrie); bod v aktuálním slotu. */
liveBatSoc?: number | null
}
export function SocTuvChart({ slots, nowIndex }: Props) {
export function SocTuvChart({ slots, nowIndex, liveBatSoc = null }: Props) {
const canvasRef = useRef<HTMLCanvasElement>(null)
const chartRef = useRef<Chart | null>(null)
@@ -50,12 +52,22 @@ export function SocTuvChart({ slots, nowIndex }: Props) {
)
const series = useMemo(() => {
const socReal = slots.map((s, i) => (i <= nowIndex ? s.soc_actual_pct : null))
const socReal = slots.map((s, i) => {
if (i > nowIndex) return null
if (
i === nowIndex &&
liveBatSoc != null &&
Number.isFinite(liveBatSoc)
) {
return liveBatSoc
}
return s.soc_actual_pct
})
const socPlan = slots.map((s) => s.soc_plan_pct)
const tuvReal = slots.map((s, i) => (i <= nowIndex ? s.tuv_actual_c : null))
const tuvPlan = slots.map((s) => s.tuv_plan_c)
return { socReal, socPlan, tuvReal, tuvPlan }
}, [slots, nowIndex])
}, [slots, nowIndex, liveBatSoc])
const bgPlugin = useMemo(
() => createSlotBackgroundPluginRefs(slotsRef, negRangesRef),

View File

@@ -22,7 +22,7 @@ import type {
HeatPumpLatestRow,
ModeLogRecentRow,
SiteStatusRow,
TelemetryHourly7dRow,
Telemetry15m7dRow,
} from '../types/ems'
import type { PlanningIntervalDto } from '../types/plan'
@@ -56,13 +56,6 @@ function buildLiveMetrics(
}
}
function hourFloorUtcMs(ms: number): number {
const d = new Date(ms)
d.setUTCMinutes(0, 0, 0)
d.setUTCSeconds(0, 0)
return d.getTime()
}
/** Klíč hodiny v Europe/Prague (pro shodu s vw_audit_today_hourly.hour_local). */
function pragueHourKey(ms: number): string {
return new Intl.DateTimeFormat('sv-SE', {
@@ -198,7 +191,7 @@ export function useDashboardData(siteId: number | null) {
const [
planMaybe,
statusArr,
hourly7d,
telemetry15m7d,
auditHourly,
modeLog,
hpArr,
@@ -211,10 +204,10 @@ export function useDashboardData(siteId: number | null) {
throw e
}),
getJson<SiteStatusRow[]>('/vw_site_status', { site_id: `eq.${siteId}` }),
getJson<TelemetryHourly7dRow[]>('/vw_telemetry_hourly_7d', {
getJson<Telemetry15m7dRow[]>('/vw_telemetry_15m_7d', {
site_id: `eq.${siteId}`,
order: 'hour.asc',
limit: '500',
order: 'slot_start.asc',
limit: '1000',
}),
getJson<AuditTodayHourlyRow[]>('/vw_audit_today_hourly', {
site_id: `eq.${siteId}`,
@@ -297,10 +290,10 @@ export function useDashboardData(siteId: number | null) {
}
setForecastWeek(forecastDays)
const hourlyMap = new Map<number, TelemetryHourly7dRow>()
if (Array.isArray(hourly7d)) {
for (const r of hourly7d) {
hourlyMap.set(new Date(r.hour).getTime(), r)
const telemetryBySlot = new Map<string, Telemetry15m7dRow>()
if (Array.isArray(telemetry15m7d)) {
for (const r of telemetry15m7d) {
telemetryBySlot.set(slotTimeKey(new Date(r.slot_start).getTime()), r)
}
}
@@ -321,7 +314,7 @@ export function useDashboardData(siteId: number | null) {
const base = emptySlot(iso)
const k = slotTimeKey(startMs)
const tel = hourlyMap.get(hourFloorUtcMs(startMs))
const tel = telemetryBySlot.get(k)
if (tel) {
base.pv_power_w = tel.avg_pv_w ?? base.pv_power_w
base.battery_power_w = tel.avg_battery_w ?? base.battery_power_w
@@ -373,6 +366,12 @@ export function useDashboardData(siteId: number | null) {
})
}
const liveSoc = parseNum(status?.battery_soc_percent)
if (liveSoc != null && nIdx >= 0 && nIdx < built.length) {
const cur = built[nIdx]!
built[nIdx] = { ...cur, soc_actual_pct: liveSoc }
}
const neg: NegPriceItem[] = []
const nowMs = Date.now()
for (const r of flatPrices) {

View File

@@ -35,7 +35,9 @@ const MemoRegimeBar = memo(RegimeBar, (prev, next) =>
)
const MemoSocTuvChart = memo(SocTuvChart, (prev, next) =>
prev.slots === next.slots && prev.nowIndex === next.nowIndex,
prev.slots === next.slots &&
prev.nowIndex === next.nowIndex &&
prev.liveBatSoc === next.liveBatSoc,
)
function fmtKw2(w: number | null | undefined): string {
@@ -324,7 +326,11 @@ export function Dashboard() {
chartArea={chartArea}
/>
<div className="border-t border-slate-800 px-2 py-2">
<MemoSocTuvChart slots={data.slots} nowIndex={data.nowIndex} />
<MemoSocTuvChart
slots={data.slots}
nowIndex={data.nowIndex}
liveBatSoc={data.liveMetrics?.bat_soc ?? null}
/>
</div>
</div>
)}

View File

@@ -48,6 +48,18 @@ export type TelemetryHourly7dRow = {
sample_count: number | null
}
/** ems.vw_telemetry_15m_7d (řádky z telemetry_inverter_15m) */
export type Telemetry15m7dRow = {
slot_start: string
site_id: number
avg_pv_w: number | null
avg_battery_w: number | null
avg_grid_w: number | null
avg_load_w: number | null
last_soc_pct: string | number | null
sample_count: number | null
}
/** ems.vw_latest_heat_pump */
export type HeatPumpLatestRow = {
site_id: number