Appearance
Chart Engine — typové dotazy a odpovědi
Referenční dvojice request → response pro GET /v1/charts/series podle kontraktu v API Analysis a pravidel ve feature docu. Čísla jsou spočítaná ručně z fixtur, ne vygenerovaná — proto se dají použít jako test. Deset případů, U1 až U10, slouží třem místům najednou:
- fixtury mocku obrazovky se berou odsud, verbatim;
- testovací korpus pro testovou analýzu epicu;
- referenční odpověď, proti které se píše backend.
Odpovědi jsou zkrácené v tom smyslu, že z dlouhých polí points je vypsaný začátek a konec; pravidlo pro dopočet zbytku je vždy uvedené. Nic jiného zkrácené není.
Fixtury, ze kterých se počítá
| Fixtura | Co to je | Data |
|---|---|---|
CHART-E1 | elektroměr odběru, objekt CHART-B1 | 2,5 kWh v každé čtvrthodině března 2026, mimo výpadek 10. 3. 08:00–12:00 (16 intervalů chybí) |
CHART-B1 | objekt se systémovým součtem | elektřina: odběr CHART-E1 + výroba FVE 3 000 − dodávka do sítě 1 200 · plyn v kWh · teplo v GJ · voda v m³ · podružný elektroměr kuchyně (reportOnly, jen se zobrazuje) |
CHART-B1-KITCHEN | podružný elektroměr kuchyně pod CHART-E1, outputMode = reportOnly (výchozí pro nové podružné měřidlo) | 2 250 kWh — o příspěvku rozhoduje vlastní nastavení měřidla, ne poloha pod rodičem (rozhodnutí 28. 9., U2 a U9) |
CHART-S1 | sektor školství | objekty CHART-B1, B2, B3, B4, B5 |
CHART-C1 | areál | sektory CHART-S1 a CHART-S2 |
CHART-B9 | objekt bez systémového součtu pro teplo | slouží stavu noSystemTotal |
CHART-B1-PV / -EXPORT / -CHECK | výroba FVE, dodávka do sítě, kontrolní měřidlo objektu CHART-B1 | 3 000 kWh (include+import) · 1 200 kWh (include+export) · 1 400 kWh (reportOnly) — znaménka pro U9 |
CHART-B2-SHOP | nájemní prodejna v objektu CHART-B2, outputMode = subtract (přefakturovaná nájemci), v legacy navíc připočtená do CHART-B1 | 1 800 kWh — jediný subtract v sadě a případ přefakturace přes objekty (U9) |
CHART-B2-OLD | vyměněný elektroměr objektu CHART-B2, ponechaný kvůli historii, outputMode = exclude | 900 kWh — jediný exclude v sadě; do žádného součtu nevstupuje |
Kalendář. Březen 2026 má 2 972 čtvrthodin (29. 3. má 92 místo 96), říjen 2026 má 2 980 (25. 10. má 100). Běžný den má 96 intervalů = 240 kWh při 2,5 kWh/interval.
U1 — Jedno měřidlo, měsíčně, kalendářní rok
Základní tvar odpovědi. Nejmenší dotaz, který má smysl ukázat testerovi.
GET /v1/charts/series
?metric=consumption
&medium=electricity
&scopeType=gauge&scopeId=CHART-E1
&granularity=month
&from=2026-01-01T00:00:00%2B01:00&to=2027-01-01T00:00:00%2B01:00
&unit=kWhjson
{
"data": {
"query": { "metric": "consumption", "medium": ["electricity"], "scopeType": "gauge", "scopeId": ["CHART-E1"],
"breakdown": "subject", "granularity": "month", "unit": "kWh", "sourceMode": "effective",
"sortKey": "label", "sortDirection": "asc",
"from": "2026-01-01T00:00:00+01:00", "to": "2027-01-01T00:00:00+01:00" },
"served": { "from": "2026-01-01T00:00:00+01:00", "to": "2027-01-01T00:00:00+01:00",
"timezone": "Europe/Prague", "clippedEdges": false },
"provisional": false,
"series": [
{
"id": "gauge:CHART-E1|electricity",
"subject": { "type": "gauge", "id": "CHART-E1", "label": "ZŠ Komenského — hlavní elektroměr" },
"medium": "electricity",
"unit": "kWh",
"total": 7390.000000,
"sortValue": 7390.000000,
"coverage": { "intervalsInPeriod": 35036, "intervalsCovered": 2956, "ratio": 0.0844 },
"points": [
{ "from": "2026-01-01T00:00:00+01:00", "to": "2026-02-01T00:00:00+01:00", "value": null,
"coverage": { "intervalsInPeriod": 2976, "intervalsCovered": 0, "ratio": 0.0 } },
{ "from": "2026-03-01T00:00:00+01:00", "to": "2026-04-01T00:00:00+02:00", "value": 7390.000000,
"coverage": { "intervalsInPeriod": 2972, "intervalsCovered": 2956, "ratio": 0.9946 } }
]
}
],
"total": {
"value": 7390.000000, "unit": "kWh", "rowSum": 7390.000000, "excludedSubjects": [],
"byMedium": [ { "medium": "electricity", "value": 7390.000000 } ],
"coverage": { "intervalsInPeriod": 35036, "intervalsCovered": 2956, "ratio": 0.0844 },
"points": [
{ "from": "2026-03-01T00:00:00+01:00", "to": "2026-04-01T00:00:00+02:00", "value": 7390.000000,
"coverage": { "intervalsInPeriod": 2972, "intervalsCovered": 2956, "ratio": 0.9946 } }
]
},
"unavailable": []
},
"status": 200, "message": "OK", "requestId": "018f9c4d-2c11-7a3e-9b77-4f0d3a5e6b21"
}Ruční kontrola: 2 972 intervalů března − 16 chybějících = 2 956 × 2,5 kWh = 7 390 kWh. Ostatních jedenáct měsíců fixtura nemá, takže jsou null s nulovým pokrytím — ne nula. Roční pokrytí 2 956 / 35 036 je nízké právem: rok opravdu není pokrytý.
Co tím testujeme: tvar odpovědi, series.id, měsíční kbelík přes změnu času (1. 3. +01:00 → 1. 4. +02:00), rozdíl mezi null a nulou, pokrytí počítané ve čtvrthodinách i na měsíční podrobnosti.
U2 — Pět měřidel, jedno podružné pod jiným
Proč rowSum a value nejsou totéž. Čísla schválená v analýze: prostý součet 19 000, skutečný 16 750.
GET /v1/charts/series
?medium=electricity
&scopeType=gauge&scopeId=B1-MAIN&scopeId=B1-KITCHEN&scopeId=B2-MAIN&scopeId=B3-MAIN&scopeId=B4-MAIN
&granularity=month&from=2025-01-01T00:00:00%2B01:00&to=2025-04-01T00:00:00%2B02:00
&unit=kWh&sortKey=total&sortDirection=descOdpověď (vypsané jen součty řad a rozhodující části):
json
{
"series": [
{ "id": "gauge:B1-MAIN|electricity", "subject": { "type": "gauge", "id": "B1-MAIN", "label": "B1 — hlavní" }, "total": 6000.000000, "sortValue": 6000.000000, "contribution": "add" },
{ "id": "gauge:B2-MAIN|electricity", "subject": { "type": "gauge", "id": "B2-MAIN", "label": "B2 — hlavní" }, "total": 5000.000000, "sortValue": 5000.000000, "contribution": "add" },
{ "id": "gauge:B3-MAIN|electricity", "subject": { "type": "gauge", "id": "B3-MAIN", "label": "B3 — hlavní" }, "total": 3500.000000, "sortValue": 3500.000000, "contribution": "add" },
{ "id": "gauge:B4-MAIN|electricity", "subject": { "type": "gauge", "id": "B4-MAIN", "label": "B4 — hlavní" }, "total": 2250.000000, "sortValue": 2250.000000, "contribution": "add" },
{ "id": "gauge:B1-KITCHEN|electricity", "subject": { "type": "gauge", "id": "B1-KITCHEN", "label": "B1 — kuchyně" }, "total": 2250.000000, "sortValue": 2250.000000, "contribution": "none" }
],
"total": {
"value": 16750.000000,
"rowSum": 19000.000000,
"unit": "kWh",
"excludedSubjects": [
{ "seriesId": "gauge:B1-KITCHEN|electricity", "medium": "electricity",
"reason": "excludedByOutputMode", "outputMode": "reportOnly", "value": 2250.000000 }
],
"byMedium": [ { "medium": "electricity", "value": 16750.000000 } ],
"points": [
{ "from": "2025-01-01T00:00:00+01:00", "to": "2025-02-01T00:00:00+01:00", "value": 6000.000000 },
{ "from": "2025-02-01T00:00:00+01:00", "to": "2025-03-01T00:00:00+01:00", "value": 5500.000000 },
{ "from": "2025-03-01T00:00:00+01:00", "to": "2025-04-01T00:00:00+02:00", "value": 5250.000000 }
]
}
}Ruční kontrola: 6 000 + 5 000 + 3 500 + 2 250 + 2 250 = 19 000 (rowSum). Kuchyně je podružná pod B1-MAIN a má outputMode = reportOnly (výchozí pro podružné měřidlo — Nezapočítávat), takže se do součtu nezapočítá dvakrát: 19 000 − 2 250 = 16 750 (value). Rozhoduje její vlastní nastavení, ne to, že je pod rodičem: kdyby ji uživatel přepnul na Přičíst (include), value by bylo 19 000 a engine by to nepřepsal — varování o dvojím započtení patří do formuláře měřidla, ne do grafu. Součet total.points: 6 000 + 5 500 + 5 250 = 16 750 — sedí.
Co tím testujeme: řazení podle součtu, dvojí započtení, excludedSubjects s důvodem excludedByOutputMode, a hlavně to, že řádek Celkem v tabulce nesmí být součet sloupce — je o 2 250 kWh nižší.
U3 — Tři energetická média na jednom grafu
GET /v1/charts/series
?medium=electricity&medium=gas&medium=heat
&scopeType=building&scopeId=CHART-B1
&granularity=month&from=2025-01-01T00:00:00%2B01:00&to=2025-04-01T00:00:00%2B02:00
&unit=MWhjson
{
"series": [
{ "id": "building:CHART-B1|electricity", "medium": "electricity", "unit": "MWh", "total": 9.800000,
"points": [ { "value": 3.600000 }, { "value": 3.300000 }, { "value": 2.900000 } ] },
{ "id": "building:CHART-B1|gas", "medium": "gas", "unit": "MWh", "total": 24.000000,
"points": [ { "value": 11.000000 }, { "value": 8.000000 }, { "value": 5.000000 } ] },
{ "id": "building:CHART-B1|heat", "medium": "heat", "unit": "MWh", "total": 15.000000,
"points": [ { "value": 7.000000 }, { "value": 5.000000 }, { "value": 3.000000 } ] }
],
"total": {
"value": 48.800000, "unit": "MWh", "rowSum": 48.800000, "excludedSubjects": [],
"byMedium": [
{ "medium": "electricity", "value": 9.800000 },
{ "medium": "gas", "value": 24.000000 },
{ "medium": "heat", "value": 15.000000 }
],
"points": [ { "value": 21.600000 }, { "value": 16.300000 }, { "value": 10.900000 } ]
}
}Ruční kontrola: elektřina 9,8 = odběr 8,0 + FVE 3,0 − dodávka 1,2 (systémový součet objektu, ne součet měřidel). Teplo je ve fixtuře uložené v GJ: 54 GJ = 15 MWh (54 ÷ 3,6). Celkem 9,8 + 24 + 15 = 48,8 MWh.
Co tím testujeme: tři samostatné řady na jedné ose — nikdy jeden sloupec —, převod GJ → MWh v enginu, byMedium, a systémový součet objektu.
U4 — Voda k tomu: druhá odpověď, druhý graf
Tentýž výběr s vodou navíc. Obrazovka nepošle jeden dotaz — pošle druhý.
GET /v1/charts/series?medium=water&scopeType=building&scopeId=CHART-B1
&granularity=month&from=2025-01-01T00:00:00%2B01:00&to=2025-04-01T00:00:00%2B02:00&unit=m3json
{
"series": [
{ "id": "building:CHART-B1|water", "medium": "water", "unit": "m³", "total": 312.000000,
"points": [ { "value": 104.000000 }, { "value": 98.000000 }, { "value": 110.000000 } ] }
],
"total": { "value": 312.000000, "unit": "m³", "rowSum": 312.000000, "byMedium": [ { "medium": "water", "value": 312.000000 } ] }
}A kdyby to obrazovka zkusila jedním dotazem:
GET /v1/charts/series?medium=electricity&medium=water&… → 400
{ "errorCode": "ERR_CHART_SERIES_MEDIUM_MIX",
"errorMessage": "Media must belong to one quantity family: electricity is energy, water is volume.",
"status": 400 }Co tím testujeme: dvě odpovědi vedle sebe, každá s vlastní jednotkou a vlastním součtem, a odmítnutí kombinace, ke které se uživatel přes UI nesmí dostat.
U5 — Čtvrthodiny přes den se změnou času
GET /v1/charts/series?medium=electricity&scopeType=gauge&scopeId=CHART-E1
&granularity=15min&from=2026-03-28T00:00:00%2B01:00&to=2026-03-31T00:00:00%2B02:00&unit=kWhjson
{
"served": { "from": "2026-03-28T00:00:00+01:00", "to": "2026-03-31T00:00:00+02:00",
"timezone": "Europe/Prague", "clippedEdges": false },
"series": [
{
"id": "gauge:CHART-E1|electricity", "medium": "electricity", "unit": "kWh",
"total": 710.000000,
"coverage": { "intervalsInPeriod": 284, "intervalsCovered": 284, "ratio": 1.0 },
"points": [
{ "from": "2026-03-28T00:00:00+01:00", "to": "2026-03-28T00:15:00+01:00", "value": 2.500000 },
{ "from": "2026-03-29T01:30:00+01:00", "to": "2026-03-29T01:45:00+01:00", "value": 2.500000 },
{ "from": "2026-03-29T01:45:00+01:00", "to": "2026-03-29T03:00:00+02:00", "value": 2.500000 },
{ "from": "2026-03-29T03:00:00+02:00", "to": "2026-03-29T03:15:00+02:00", "value": 2.500000 },
{ "from": "2026-03-30T23:45:00+02:00", "to": "2026-03-31T00:00:00+02:00", "value": 2.500000 }
]
}
],
"total": { "value": 710.000000, "unit": "kWh", "rowSum": 710.000000 }
}Ruční kontrola: 28. 3. má 96 intervalů, 29. 3. jen 92 (hodina 02:00–03:00 neexistuje), 30. 3. má 96 → 284 × 2,5 = 710 kWh. Kdyby engine počítal 3 × 96, vyšlo by 720 — a právě to je chyba, kterou tenhle případ chytá.
Týž rozsah po dnech vrátí tři kbelíky: 240, 230, 240 kWh.
U5b — výpadek a částečně pokrytý kbelík (denní podrobnost)
GET /v1/charts/series?…&granularity=day&from=2026-03-09T00:00:00%2B01:00&to=2026-03-12T00:00:00%2B01:00json
"points": [
{ "from": "2026-03-09T00:00:00+01:00", "to": "2026-03-10T00:00:00+01:00", "value": 240.000000,
"coverage": { "intervalsInPeriod": 96, "intervalsCovered": 96, "ratio": 1.0 } },
{ "from": "2026-03-10T00:00:00+01:00", "to": "2026-03-11T00:00:00+01:00", "value": 200.000000,
"coverage": { "intervalsInPeriod": 96, "intervalsCovered": 80, "ratio": 0.8333 } },
{ "from": "2026-03-11T00:00:00+01:00", "to": "2026-03-12T00:00:00+01:00", "value": 240.000000,
"coverage": { "intervalsInPeriod": 96, "intervalsCovered": 96, "ratio": 1.0 } }
]Ruční kontrola: 10. 3. chybí 16 intervalů (08:00–12:00), zbylo 80 × 2,5 = 200 kWh při pokrytí 83 %. Sloupec je nižší, ale není to úspora — a odpověď to říká číslem, ne poznámkou pod grafem.
U6 — Vlastní období 15. 1. – 15. 2., měsíčně: ořezané okraje
Fixtura B2-MAIN má rovnoměrných 10 kWh/den v lednu i únoru 2025.
GET /v1/charts/series?medium=electricity&scopeType=gauge&scopeId=B2-MAIN
&granularity=month&from=2025-01-15T00:00:00%2B01:00&to=2025-02-16T00:00:00%2B01:00&unit=kWhjson
{
"served": { "from": "2025-01-15T00:00:00+01:00", "to": "2025-02-16T00:00:00+01:00",
"timezone": "Europe/Prague", "clippedEdges": true },
"series": [
{
"id": "gauge:B2-MAIN|electricity", "unit": "kWh", "total": 320.000000,
"points": [
{ "from": "2025-01-15T00:00:00+01:00", "to": "2025-02-01T00:00:00+01:00", "value": 170.000000,
"clipped": true,
"coverage": { "intervalsInPeriod": 1632, "intervalsCovered": 1632, "ratio": 1.0 } },
{ "from": "2025-02-01T00:00:00+01:00", "to": "2025-02-16T00:00:00+01:00", "value": 150.000000,
"clipped": true,
"coverage": { "intervalsInPeriod": 1440, "intervalsCovered": 1440, "ratio": 1.0 } }
]
}
],
"total": { "value": 320.000000, "unit": "kWh" }
}Ruční kontrola: 15.–31. 1. je 17 dní = 170 kWh, 1.–15. 2. je 15 dní = 150 kWh, celkem 320 kWh. Starý SQL podle začátku kbelíku by vrátil celý leden (310) a celý únor (280) = 590 kWh za období, které tolik nemá. Týž dotaz na GET /v1/consumption/summary musí vrátit 320.
Co tím testujeme: ořez na obou koncích, clippedEdges, skutečné hranice v bodech, a shodu s agregačním endpointem.
U7 — Areál: rozpad po objektech × jeden součet
GET /v1/charts/series?medium=electricity&scopeType=campusNode&scopeId=CHART-C1
&breakdown=building&granularity=month&from=2025-01-01T00:00:00%2B01:00&to=2025-02-01T00:00:00%2B01:00&unit=kWhjson
"series": [
{ "id": "building:CHART-B1|electricity", "subject": { "type": "building", "id": "CHART-B1", "label": "ZŠ Komenského" }, "total": 9800.000000 },
{ "id": "building:CHART-B2|electricity", "subject": { "type": "building", "id": "CHART-B2", "label": "ZŠ Nádražní" }, "total": 5000.000000 },
{ "id": "building:CHART-B3|electricity", "subject": { "type": "building", "id": "CHART-B3", "label": "MŠ Sluníčko" }, "total": 3500.000000 },
{ "id": "building:CHART-B4|electricity", "subject": { "type": "building", "id": "CHART-B4", "label": "Sokolovna" }, "total": 2250.000000 },
{ "id": "building:CHART-B5|electricity", "subject": { "type": "building", "id": "CHART-B5", "label": "Knihovna" }, "total": 1450.000000 }
],
"total": { "value": 22000.000000, "rowSum": 22000.000000, "unit": "kWh", "excludedSubjects": [] }Týž dotaz s breakdown=none:
json
"series": [
{ "id": "campusNode:CHART-C1|electricity", "subject": { "type": "campusNode", "id": "CHART-C1", "label": "Areál Jih" },
"medium": "electricity", "total": 22000.000000 }
],
"total": { "value": 22000.000000, "rowSum": 22000.000000, "unit": "kWh" }Ruční kontrola: 9 800 + 5 000 + 3 500 + 2 250 + 1 450 = 22 000 kWh; jeden součet i pět řad dávají totéž číslo. Kdyby byl CHART-B3 zároveň pod druhým vybraným uzlem, objeví se jednou a druhá cesta je v excludedSubjects s důvodem alreadyCounted.
Co tím testujeme: rozsah není totéž co řada (4.1 OR-2), a překryv se hlídá i mezi podstromy.
U8 — Tři stavy, které nejsou prázdný graf
Jeden dotaz přes objekty, kde každý selže jinak.
GET /v1/charts/series?medium=heat&scopeType=building&scopeId=CHART-B1&scopeId=CHART-B9&scopeId=CHART-B7
&granularity=month&from=2025-01-01T00:00:00%2B01:00&to=2025-02-01T00:00:00%2B01:00&unit=kWhjson
{
"series": [
{ "id": "building:CHART-B1|heat", "subject": { "type": "building", "id": "CHART-B1", "label": "ZŠ Komenského" },
"medium": "heat", "unit": "kWh", "total": 7000.000000 },
{ "id": "building:CHART-B7|heat", "subject": { "type": "building", "id": "CHART-B7", "label": "Hasičská zbrojnice" },
"medium": "heat", "unit": "kWh", "total": null,
"coverage": { "intervalsInPeriod": 2976, "intervalsCovered": 0, "ratio": 0.0 },
"points": [ { "from": "2025-01-01T00:00:00+01:00", "to": "2025-02-01T00:00:00+01:00", "value": null } ] }
],
"unavailable": [
{ "subject": { "type": "building", "id": "CHART-B9", "label": "Sklad Průmyslová" }, "medium": "heat",
"reason": "noSystemTotal", "detail": "objekt nemá systémový součet pro teplo" }
],
"total": { "value": 7000.000000, "rowSum": 7000.000000, "unit": "kWh" }
}Dvě varianty téhož principu:
json
// metric=cost
"unavailable": [ { "metric": "cost", "reason": "capabilityDeferred", "pendingDependency": "pricing-data-source" } ]
// plynoměr uložený v m³, dotaz v kWh, bez výhřevnosti
"unavailable": [ { "subject": { "type": "gauge", "id": "B5-GAS-M3", "label": "Knihovna — plynoměr" },
"medium": "gas", "reason": "unsupportedConversion",
"detail": "m³ → kWh vyžaduje výhřevnost, která pro toto měřidlo není zadaná" } ]Co tím testujeme: CHART-B7 je vybraný, použitelný a nemá data → zůstává v odpovědi jako řada samých null, v legendě i v tabulce. CHART-B9 je vybraný, ale otázka na něj nedává smysl → je v unavailable se svým jménem. Ani jeden prostě nezmizí. To je rozdíl mezi „nic jsme nenaměřili" a „takový objekt neznáme".
U9 — Znaménka: výroba, dodávka do sítě, kontrolní a přefakturační měřidlo
Proč rowSum a value nejsou totéž podruhé — a tentokrát to není podružným měřidlem, ale tím, jak měřidlo účtuje. Pět měřidel objektu CHART-B1, březen 2026:
| Měřidlo | outputMode | direction | Hodnota řady | contribution |
|---|---|---|---|---|
CHART-E1 — odběr ze sítě | include | import | 7 390 kWh | add |
CHART-B1-PV — výroba FVE | include | import | 3 000 kWh | add |
CHART-B1-EXPORT — dodávka do sítě | include | export | 1 200 kWh | subtract |
CHART-B1-KITCHEN — podružné pod CHART-E1 | reportOnly | import | 2 250 kWh | none |
CHART-B1-CHECK — kontrolní, paralelně k hlavnímu | reportOnly | import | 1 400 kWh | none |
GET /v1/charts/series
?medium=electricity
&scopeType=gauge&scopeId=CHART-E1&scopeId=CHART-B1-PV&scopeId=CHART-B1-EXPORT
&scopeId=CHART-B1-KITCHEN&scopeId=CHART-B1-CHECK
&granularity=month&from=2026-03-01T00:00:00%2B01:00&to=2026-04-01T00:00:00%2B02:00
&unit=kWhOdpověď (vypsané jen součty řad a součet výběru):
json
{
"series": [
{ "id": "gauge:CHART-E1|electricity", "subject": { "type": "gauge", "id": "CHART-E1", "label": "B1 — odběr ze sítě" }, "total": 7390.000000, "contribution": "add" },
{ "id": "gauge:CHART-B1-PV|electricity", "subject": { "type": "gauge", "id": "CHART-B1-PV", "label": "B1 — výroba FVE" }, "total": 3000.000000, "contribution": "add" },
{ "id": "gauge:CHART-B1-EXPORT|electricity", "subject": { "type": "gauge", "id": "CHART-B1-EXPORT", "label": "B1 — dodávka do sítě" }, "total": 1200.000000, "contribution": "subtract" },
{ "id": "gauge:CHART-B1-KITCHEN|electricity", "subject": { "type": "gauge", "id": "CHART-B1-KITCHEN", "label": "B1 — kuchyně" }, "total": 2250.000000, "contribution": "none" },
{ "id": "gauge:CHART-B1-CHECK|electricity", "subject": { "type": "gauge", "id": "CHART-B1-CHECK", "label": "B1 — kontrolní" }, "total": 1400.000000, "contribution": "none" }
],
"total": {
"value": 9190.000000,
"rowSum": 15240.000000,
"unit": "kWh",
"excludedSubjects": [
{ "seriesId": "gauge:CHART-B1-KITCHEN|electricity", "medium": "electricity",
"reason": "excludedByOutputMode", "outputMode": "reportOnly", "value": 2250.000000 },
{ "seriesId": "gauge:CHART-B1-CHECK|electricity", "medium": "electricity",
"reason": "excludedByOutputMode", "outputMode": "reportOnly", "value": 1400.000000 }
],
"byMedium": [ { "medium": "electricity", "value": 9190.000000 } ]
}
}Ruční kontrola: 7 390 + 3 000 + 1 200 + 2 250 + 1 400 = 15 240 (rowSum, prostý součet sloupce). Se znaménky a vyloučeními: 7 390 + 3 000 − 1 200 + 0 + 0 = 9 190 (value). Kuchyně i kontrolní měřidlo vypadávají ze stejného důvodu — jejich vlastní outputMode; to, že kuchyně visí pod CHART-E1, engine nezajímá. A to je přesně systémový součet objektu: dotaz scopeType=building&scopeId=CHART-B1 na stejné období vrátí jednu řadu o součtu 9 190 kWh. Dvě cesty ke stejnému číslu — to je ten test.
Co tím testujeme: že kontrakt nese znaménko, ne jen hodnotu. Kontrolní měřidlo se v grafu vykreslí (uživatel si ho vybral, chce ho vidět), ale součtem nehne. Dodávka do sítě se kreslí kladně a součet snižuje. Bez pole contribution by FE nedokázalo vysvětlit, proč řádek Celkem není součtem sloupce — a uživatel by to bral jako chybu.
Přefakturace přes dva objekty — co první řez nedělá
Legacy umí zavěsit podružné měřidlo pod hlavní měřidlo jiného objektu: energie se v místě měření odečte a v místě fakturace přičte. EM3 počítá systémový součet objektu nad měřidly téhož objektu, takže tento převod nikde nevzniká a chart engine si ho nevymýšlí.
GET /v1/charts/series
?medium=electricity
&scopeType=building&scopeId=CHART-B1&scopeId=CHART-B2
&scopeType=gauge&scopeId=CHART-B2-SHOP
&granularity=month&from=2026-03-01T00:00:00%2B01:00&to=2026-04-01T00:00:00%2B02:00&unit=kWhCHART-B2-SHOP je nájemní prodejna v objektu B2 s outputMode = subtract: v systémovém součtu B2 se její spotřeba odečítá, protože ji platí nájemce. Legacy ji navíc připočítává do B1 — a právě tuhle druhou polovinu převodu EM3 nedělá. V odpovědi:
json
"excludedSubjects": [
{ "seriesId": "gauge:CHART-B2-SHOP|electricity", "medium": "electricity",
"containedIn": { "type": "building", "id": "CHART-B2", "label": "B2" },
"reason": "containedInSubject", "value": 1800.000000 }
]Prodejna se vykreslí jako vlastní řada, ale do součtu výběru vstupuje jen přes objekt B2, v jehož systémovém součtu už je (se záporným znaménkem, které jí dává subtract). Žádná část její spotřeby se nepřesouvá do B1. Stejně tak CHART-B2-OLD — vyměněný elektroměr s outputMode = exclude — se vykreslí, když si ho uživatel vybere, a do žádného součtu nevstoupí; v excludedSubjects má důvod excludedByOutputMode. Jestli se převod mezi objekty má modelovat vůbec, je otázka na Consumption & Cost Calculation Core, ne na graf — v Chart Engine docu je to vedeno jako odložené.
U10 — Záhlaví hodnotové tabulky podle zvoleného filtru
Úrovně ve složeném záhlaví nejsou pevná šablona — jsou to ty, které uživatel v kaskádě skutečně zvolil, v jejím pořadí, plus typ hodnoty jako poslední řádek. Stejná data, tři hloubky filtru. Areál C-JIH, elektřina, leden–březen 2025, celkem 24 000 kWh ve všech třech.
a) Vybraný jen areál. Filtr: areál = Areál Jih, objekt = —, měřidlo = —.
GET /v1/charts/series?medium=electricity&scopeType=campusNode&scopeId=C-JIH
&granularity=month&from=2025-01-01T00:00:00%2B01:00&to=2025-04-01T00:00:00%2B02:00&unit=kWhJedna řada (campusNode:C-JIH|electricity, 24 000 kWh). Záhlaví má dva řádky:
| Období | Areál: Areál Jih |
|---|---|
| Jednotka: kWh | |
| leden 2025 | 9 000 |
| únor 2025 | 8 000 |
| březen 2025 | 7 000 |
| Celkem | 24 000 |
b) Areál + rozpad po objektech. &breakdown=building → dvě řady (13 300 a 10 700 kWh). Záhlaví má tři řádky: areál (spojený nad oběma sloupci) / objekt / jednotka.
c) Areál + objekt + měřidlo. &breakdown=gauge → čtyři řady (9 300 + 4 000 + 7 500 + 3 200 kWh). Záhlaví má čtyři řádky: areál / objekt (každý spojený nad svými měřidly) / měřidlo / jednotka.
Ruční kontrola: 13 300 + 10 700 = 24 000 · 9 300 + 4 000 = 13 300 · 7 500 + 3 200 = 10 700. Součet výběru je ve všech třech hloubkách stejný; mění se jen to, jak jemně je rozepsaný.
Co tím testujeme: že se v záhlaví neobjeví úroveň, kterou uživatel nezúžil — žádné prázdné „objekt" a „měřidlo" u výběru samotného areálu — a že každé zúžení přidá právě jeden řádek, v pořadí kaskády. Typ hodnoty (jednotka, později Kč) je vždy poslední řádek, protože je to to, čím to číslo je.
Napojení na Highcharts
Adapter nic nepočítá; překládá. Pravidla podle review:
| Z odpovědi | Do Highcharts |
|---|---|
series[].id | series.id — klíč viditelnosti, ne index ani popisek |
series[].subject.label + medium | series.name („ZŠ Komenského — plyn") |
points[].from, value | data: [[Date.parse(from), value], …] na datetime ose; hodnota null se posílá, nevynechává |
points[].to, coverage, clipped | sidecar podle id + časové značky pro tooltip a tabulku |
| jednotka | yAxis.title.text, tooltip.valueSuffix |
| pořadí řad | zachovat; nikdy neřadit v knihovně |
total.points | řádek Celkem v tabulce, ne série v grafu |
js
Highcharts.setOptions({ time: { timezone: 'Europe/Prague' } }); // ne prohlížečové pásmo
const options = {
chart: { type: 'column' },
xAxis: { type: 'datetime' },
yAxis: { title: { text: response.total.unit } },
plotOptions: { series: { connectNulls: false, turboThreshold: 0 } },
series: response.series.map(s => ({
id: s.id,
name: `${s.subject.label} — ${mediumLabel(s.medium)}`,
color: mediumColor(s.medium),
data: s.points.map(p => [Date.parse(p.from), p.value])
}))
};Na co si dát pozor: turboThreshold je ve výchozím stavu 1 000 bodů a formát [x, y] ho přežije, ale u delších řad ho raději vypnout; dataGrouping se nesmí zapnout (je součástí Highcharts Stock a mění hodnoty podle šířky v pixelech); accessibility modul načíst, protože tabulka je textová alternativa grafu a legenda musí jít ovládat klávesnicí.
Co který případ ověřuje
| Případ | Ověřuje | Pravidlo |
|---|---|---|
| U1 | tvar odpovědi, series.id, null × nula, měsíční pokrytí ve čtvrthodinách | FR1, FR11 |
| U2 | řazení, dvojí započtení, excludedSubjects, total.points | FR9, FR10 |
| U3 | tři řady na jedné ose, GJ → MWh, byMedium, systémový součet | FR8, FR9 |
| U4 | dvě odpovědi, dva grafy, ERR_CHART_SERIES_MEDIUM_MIX | FR9, API-A → Error Responses |
| U5 | 92 intervalů v den změny času, částečné pokrytí, 15 min bez stropu | FR2, FR11 |
| U6 | ořezané okraje, shoda se /consumption/summary | FR2, FR11 |
| U7 | scope × breakdown, překryv podstromů areálu | FR1, FR9 |
| U8 | noData × noSystemTotal × capabilityDeferred × unsupportedConversion | FR11 |
| U9 | contribution podle outputMode × direction, přefakturace přes objekty | FR9, Deferred |
| U10 | záhlaví hodnotové tabulky podle zvoleného filtru | Chart Types → FR3 |