Dashboard: TUV křivka napojená na skutečnou telemetrii TČ
tuv_actual_c byl od vzniku grafu placeholder (null), TUV chart nikdy neukazoval data. Nové view vw_telemetry_heat_pump_15m_7d (15min agregace, R__101 + grant R__072) a plnění slotů v useDashboardData. Teploty avg přes přítomné řádky (idle-skip ok — není to výkon). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@ END $$;
|
|||||||
GRANT USAGE ON SCHEMA ems TO ems_anon;
|
GRANT USAGE ON SCHEMA ems TO ems_anon;
|
||||||
|
|
||||||
GRANT SELECT ON ems.vw_site_status TO ems_anon;
|
GRANT SELECT ON ems.vw_site_status TO ems_anon;
|
||||||
|
GRANT SELECT ON ems.vw_telemetry_heat_pump_15m_7d TO ems_anon;
|
||||||
GRANT SELECT ON ems.vw_site_effective_price TO ems_anon;
|
GRANT SELECT ON ems.vw_site_effective_price TO ems_anon;
|
||||||
GRANT SELECT ON ems.vw_latest_inverter TO ems_anon;
|
GRANT SELECT ON ems.vw_latest_inverter TO ems_anon;
|
||||||
GRANT SELECT ON ems.vw_latest_heat_pump TO ems_anon;
|
GRANT SELECT ON ems.vw_latest_heat_pump TO ems_anon;
|
||||||
|
|||||||
23
db/views/R__101_vw_telemetry_heat_pump_15m_7d.sql
Normal file
23
db/views/R__101_vw_telemetry_heat_pump_15m_7d.sql
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
-- 15min agregace telemetrie TČ pro dashboard (TUV křivka v SocTuvChart).
|
||||||
|
-- Teploty: avg přes přítomné řádky je OK (idle-skip ředí řádky, ale teplota
|
||||||
|
-- není výkon — viz telemetry.md Idle-skip; nikdy z toho nepočítat energii).
|
||||||
|
|
||||||
|
drop view if exists ems.vw_telemetry_heat_pump_15m_7d;
|
||||||
|
|
||||||
|
create view ems.vw_telemetry_heat_pump_15m_7d as
|
||||||
|
select
|
||||||
|
time_bucket(interval '15 minutes', t.measured_at) as slot_start,
|
||||||
|
t.site_id,
|
||||||
|
round(avg(t.tuv_tank_temp_c)::numeric, 1) as avg_tuv_c,
|
||||||
|
round(avg(t.water_outlet_temp_c)::numeric, 1) as avg_water_out_c,
|
||||||
|
round(avg(t.water_inlet_temp_c)::numeric, 1) as avg_water_in_c,
|
||||||
|
max(t.operating_mode) as operating_mode,
|
||||||
|
count(*) as sample_count
|
||||||
|
from ems.telemetry_heat_pump t
|
||||||
|
where t.measured_at > now() - interval '7 days'
|
||||||
|
group by 1, 2;
|
||||||
|
|
||||||
|
comment on view ems.vw_telemetry_heat_pump_15m_7d is
|
||||||
|
'15min agregace TČ telemetrie za 7 dní pro dashboard (TUV/voda křivky).';
|
||||||
|
|
||||||
|
grant select on ems.vw_telemetry_heat_pump_15m_7d to ems_anon;
|
||||||
@@ -217,6 +217,7 @@ export function useDashboardData(siteId: number | null) {
|
|||||||
telemetry15m7d,
|
telemetry15m7d,
|
||||||
auditHourly,
|
auditHourly,
|
||||||
modeLog,
|
modeLog,
|
||||||
|
hpRows,
|
||||||
priceRows,
|
priceRows,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
getCurrentPlan(siteId).catch((e: unknown) => {
|
getCurrentPlan(siteId).catch((e: unknown) => {
|
||||||
@@ -240,6 +241,15 @@ export function useDashboardData(siteId: number | null) {
|
|||||||
order: 'activated_at.asc',
|
order: 'activated_at.asc',
|
||||||
limit: '200',
|
limit: '200',
|
||||||
}),
|
}),
|
||||||
|
getJson<{ slot_start: string; avg_tuv_c: number | null }[]>(
|
||||||
|
'/vw_telemetry_heat_pump_15m_7d',
|
||||||
|
{
|
||||||
|
site_id: `eq.${siteId}`,
|
||||||
|
slot_start: `gte.${new Date(windowStart).toISOString()}`,
|
||||||
|
order: 'slot_start.asc',
|
||||||
|
limit: TELEMETRY_15M_LIMIT,
|
||||||
|
},
|
||||||
|
).catch(() => [] as { slot_start: string; avg_tuv_c: number | null }[]),
|
||||||
// Ceny bereme přes FastAPI range endpoint (PostgREST /rest je u vás chráněné → 401).
|
// Ceny bereme přes FastAPI range endpoint (PostgREST /rest je u vás chráněné → 401).
|
||||||
getSitePricesSlotsRange(
|
getSitePricesSlotsRange(
|
||||||
siteId,
|
siteId,
|
||||||
@@ -312,6 +322,14 @@ export function useDashboardData(siteId: number | null) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hpBySlot = new Map<string, number>()
|
||||||
|
if (Array.isArray(hpRows)) {
|
||||||
|
for (const r of hpRows) {
|
||||||
|
const v = r.avg_tuv_c
|
||||||
|
if (v != null) hpBySlot.set(slotTimeKey(new Date(r.slot_start).getTime()), Number(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const auditMap = new Map<string, AuditTodayHourlyRow>()
|
const auditMap = new Map<string, AuditTodayHourlyRow>()
|
||||||
if (Array.isArray(auditHourly)) {
|
if (Array.isArray(auditHourly)) {
|
||||||
for (const r of auditHourly) {
|
for (const r of auditHourly) {
|
||||||
@@ -338,6 +356,11 @@ export function useDashboardData(siteId: number | null) {
|
|||||||
base.soc_actual_pct = parseNum(tel.last_soc_pct) ?? base.soc_actual_pct
|
base.soc_actual_pct = parseNum(tel.last_soc_pct) ?? base.soc_actual_pct
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hpTuv = hpBySlot.get(k)
|
||||||
|
if (hpTuv != null) {
|
||||||
|
base.tuv_actual_c = hpTuv
|
||||||
|
}
|
||||||
|
|
||||||
const aud = auditMap.get(pragueHourKey(startMs))
|
const aud = auditMap.get(pragueHourKey(startMs))
|
||||||
if (aud) {
|
if (aud) {
|
||||||
if (base.pv_power_w == null && aud.avg_pv_kw != null) {
|
if (base.pv_power_w == null && aud.avg_pv_kw != null) {
|
||||||
|
|||||||
Reference in New Issue
Block a user