Skip to content

Commit 077a4ea

Browse files
committed
feat: Use the data from Lindas to display the dynamic tariffs
1 parent 409b499 commit 077a4ea

File tree

6 files changed

+106
-51
lines changed

6 files changed

+106
-51
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ and this project adheres to
99
You can also check the
1010
[release page](https://github.com/visualize-admin/electricity-prices-switzerland/releases)
1111

12+
# Unreleased
13+
14+
- Feat
15+
- Dynamic tariffs coming from Lindas
16+
1217
# 2.11.3 (2025-08-26)
1318

1419
- Feat

src/components/charts-generic/bars/bars-grouped.tsx

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { t } from "@lingui/macro";
2-
31
import { Bar } from "src/components/charts-generic/bars/bars-simple";
42
import {
53
GroupedBarsState,
@@ -8,7 +6,6 @@ import {
86
import { useChartTheme } from "src/components/charts-generic/use-chart-theme";
97
import { EXPANDED_TAG } from "src/components/detail-page/price-components-bars";
108
import { useFormatCurrency } from "src/domain/helpers";
11-
import { useFlag } from "src/utils/flags";
129

1310
import { BAR_HEIGHT, LABEL_PADDING } from "../constants";
1411

@@ -110,8 +107,6 @@ export const BarsGroupedLabels = () => {
110107
const { labelFontSize } = useChartTheme();
111108
const formatCurrency = useFormatCurrency();
112109

113-
const dynamicTariffsFlag = useFlag("dynamicElectricityTariffs");
114-
115110
return (
116111
<g transform={`translate(${margins.left} ${margins.top})`}>
117112
{sortedData.map((d, i) => {
@@ -121,16 +116,6 @@ export const BarsGroupedLabels = () => {
121116
const value = formatCurrency(getX(d));
122117
const label = getLabel(d);
123118

124-
const isDynamic = dynamicTariffsFlag;
125-
const dynamicText = isDynamic
126-
? `(${formatCurrency(d.min as number)} - ${formatCurrency(
127-
d.max as number
128-
)}, ${t({
129-
id: "dynamic.tariff",
130-
message: "dynamic",
131-
})})`
132-
: "";
133-
134119
return (
135120
<text
136121
key={`label-${i}`}
@@ -142,7 +127,7 @@ export const BarsGroupedLabels = () => {
142127
>
143128
{!segment.includes(EXPANDED_TAG) && (
144129
<tspan fontWeight={700}>
145-
{value} {xAxisLabel} {dynamicText}
130+
{value} {xAxisLabel}
146131
</tspan>
147132
)}{" "}
148133
{label}

src/components/detail-page/price-components-bars.tsx

Lines changed: 97 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { t, Trans } from "@lingui/macro";
22
import { Box } from "@mui/material";
33
import { ascending, group, groups, max, min } from "d3";
44
import * as React from "react";
5+
import { useMemo } from "react";
56

67
import { ButtonGroup } from "src/components/button-group";
78
import {
@@ -35,19 +36,26 @@ import {
3536
ObservationValue,
3637
detailsPriceComponents,
3738
} from "src/domain/data";
38-
import { mkNumber, pivot_longer } from "src/domain/helpers";
39+
import {
40+
FormatCurrency,
41+
mkNumber,
42+
pivot_longer,
43+
useFormatCurrency,
44+
} from "src/domain/helpers";
3945
import { RP_PER_KWH } from "src/domain/metrics";
4046
import { useQueryStateEnergyPricesDetails } from "src/domain/query-states";
4147
import { getLocalizedLabel } from "src/domain/translation";
42-
import { FlagValue } from "src/flags";
4348
import {
4449
ObservationKind,
4550
PriceComponent,
4651
useObservationsWithAllPriceComponentsQuery,
4752
} from "src/graphql/queries";
53+
import {
54+
ResolvedObservation,
55+
ResolvedOperatorObservation,
56+
} from "src/graphql/resolver-mapped-types";
4857
import { EMPTY_ARRAY } from "src/lib/empty-array";
4958
import { useLocale } from "src/lib/use-locale";
50-
import { useFlag } from "src/utils/flags";
5159

5260
import { FilterSetDescription } from "./filter-set-description";
5361

@@ -101,7 +109,9 @@ export const PriceComponentsBarChart = ({ id, entity }: SectionProps) => {
101109
const cantonObservations = observationsQuery.fetching
102110
? EMPTY_ARRAY
103111
: observationsQuery.data?.cantonMedianObservations ?? EMPTY_ARRAY;
104-
const observations = [...operatorObservations, ...cantonObservations];
112+
const observations = useMemo(() => {
113+
return [...operatorObservations, ...cantonObservations];
114+
}, [operatorObservations, cantonObservations]);
105115

106116
const withUniqueEntityId = observations.map((obs) => ({
107117
uniqueId:
@@ -136,7 +146,17 @@ export const PriceComponentsBarChart = ({ id, entity }: SectionProps) => {
136146
product: product[0],
137147
};
138148

139-
const dynamicTariffsFlag = useFlag("dynamicElectricityTariffs");
149+
const formatCurrency = useFormatCurrency();
150+
151+
const shouldShowDynamicTariffsHelp = useMemo(() => {
152+
return observations.some(
153+
(obs) =>
154+
obs &&
155+
obs.__typename === "OperatorObservation" &&
156+
// I do not know why we need the as here
157+
hasDynamicTariffs(obs as ResolvedOperatorObservation)
158+
);
159+
}, [observations]);
140160

141161
return (
142162
<Card downloadId={DOWNLOAD_ID}>
@@ -219,7 +239,7 @@ export const PriceComponentsBarChart = ({ id, entity }: SectionProps) => {
219239
</Box>
220240
</>
221241
)}
222-
{dynamicTariffsFlag && (
242+
{shouldShowDynamicTariffsHelp && (
223243
<HintBlue iconName="infocircle">
224244
<Trans id="dynamic-tariffs.hint">
225245
Prices shown with values in brackets are dynamic. You can find more
@@ -249,7 +269,7 @@ export const PriceComponentsBarChart = ({ id, entity }: SectionProps) => {
249269
view: view[0],
250270
priceComponent: priceComponent[0] as PriceComponent,
251271
entity,
252-
dynamicTariffsFlag,
272+
formatCurrency,
253273
});
254274

255275
return (
@@ -322,12 +342,56 @@ export const PriceComponentsBarChart = ({ id, entity }: SectionProps) => {
322342
);
323343
};
324344

345+
const dynamicTariffs = (
346+
min: number,
347+
max: number,
348+
formatCurrency: FormatCurrency
349+
) => {
350+
return `(${formatCurrency(min)} - ${formatCurrency(max)}, ${t({
351+
id: "dynamic.tariff",
352+
message: "dynamic",
353+
})})`;
354+
};
355+
356+
const hasDynamicTariffs = (observation: ResolvedOperatorObservation) => {
357+
return (
358+
"lowestrate" in observation &&
359+
typeof observation["lowestrate"] !== "undefined" &&
360+
observation["lowestrate"] !== null &&
361+
"highestrate" in observation &&
362+
typeof observation["highestrate"] !== "undefined" &&
363+
observation["highestrate"] !== null
364+
);
365+
};
366+
367+
const getLabelFromObservation = (
368+
priceComponent: PriceComponent,
369+
formatCurrency: FormatCurrency,
370+
obs: ResolvedObservation
371+
) => {
372+
if (obs.__typename === "CantonMedianObservation") {
373+
return `${obs.period}, ${obs.cantonLabel}`;
374+
} else {
375+
return `${
376+
priceComponent === "total" &&
377+
obs.__typename === "OperatorObservation" &&
378+
hasDynamicTariffs(obs)
379+
? `${dynamicTariffs(
380+
obs["lowestrate"],
381+
obs["highestrate"],
382+
formatCurrency
383+
)}, `
384+
: ""
385+
}${obs.period}, ${obs.operatorLabel}, ${obs.municipalityLabel}`;
386+
}
387+
};
388+
325389
const prepareObservations = ({
326390
groupedObservations,
327391
priceComponent,
328392
entity,
329393
view,
330-
dynamicTariffsFlag,
394+
formatCurrency,
331395
}: {
332396
groupedObservations: [
333397
ObservationValue,
@@ -339,14 +403,13 @@ const prepareObservations = ({
339403
priceComponent: PriceComponent;
340404
entity: Entity;
341405
view: string;
342-
dynamicTariffsFlag: FlagValue;
406+
formatCurrency: FormatCurrency;
343407
}) => {
344408
if (entity === "canton") {
345409
return groupedObservations.flatMap((year) =>
346410
year[1].flatMap((ent) =>
347411
ent[1].flatMap((value) => ({
348412
...value[1][0],
349-
...(dynamicTariffsFlag ? { max: 65.45, min: 12.89 } : {}),
350413
label: value[1][0].uniqueId,
351414
}))
352415
)
@@ -355,39 +418,42 @@ const prepareObservations = ({
355418
return view === "collapsed"
356419
? groupedObservations.flatMap((year) =>
357420
year[1].flatMap((ent) =>
358-
ent[1].flatMap((value) =>
359-
value[1].length === 1
421+
ent[1].flatMap((value) => {
422+
const first = value[1][0];
423+
return value[1].length === 1
360424
? {
361-
...value[1][0],
362-
label: value[1][0].uniqueId,
363-
...(dynamicTariffsFlag ? { max: 65.45, min: 12.89 } : {}),
425+
...first,
426+
label: getLabelFromObservation(
427+
priceComponent,
428+
formatCurrency,
429+
first as ResolvedObservation
430+
),
364431
}
365432
: {
366433
priceComponent,
367434
value: value[0],
368-
...(dynamicTariffsFlag ? { max: 65.45, min: 12.89 } : {}),
369-
[entity]: value[1][0][entity],
370-
period: value[1][0].period,
371-
uniqueId: `${priceComponent}${value[1][0].period}${value[1][0].operatorLabel}${value[1][0].municipalityLabel}${value[1].length}`,
372-
label: `${value[1][0].period}, ${
373-
value[1][0].operatorLabel
374-
}, ${value[1].length} ${getLocalizedLabel({
435+
[entity]: first[entity],
436+
period: first.period,
437+
uniqueId: `${priceComponent}${first.period}${first.operatorLabel}${first.municipalityLabel}${value[1].length}`,
438+
label: `${first.period}, ${first.operatorLabel}, ${
439+
value[1].length
440+
} ${getLocalizedLabel({
375441
id:
376442
entity === "operator" ? "municipalities" : "operators",
377443
})}`,
378444
entities: value[1],
379-
}
380-
)
445+
};
446+
})
381447
)
382448
)
383449
: groupedObservations.flatMap((year) =>
384450
year[1].flatMap((ent) =>
385451
ent[1].flatMap((value) => {
452+
const firstValue = value[1][0];
386453
const singleEntities = value[1]
387454
.flatMap((d) => ({
388455
priceComponent,
389456
value: d.value,
390-
...(dynamicTariffsFlag ? { max: 65.45, min: 12.89 } : {}),
391457
[entity]: d[entity],
392458
period: d.period,
393459
uniqueId: `${priceComponent}${d.period}${d.operatorLabel}${d.municipalityLabel}${value[1].length}${EXPANDED_TAG}`,
@@ -401,13 +467,12 @@ const prepareObservations = ({
401467
{
402468
priceComponent,
403469
value: value[0],
404-
...(dynamicTariffsFlag ? { max: 65.45, min: 12.89 } : {}),
405-
[entity]: value[1][0][entity],
406-
period: value[1][0].period,
407-
uniqueId: `${priceComponent}${value[1][0].period}${value[1][0].operatorLabel}${value[1][0].municipalityLabel}${value[1].length}`,
408-
label: `${value[1][0].period}, ${
409-
value[1][0].operatorLabel
410-
}, ${value[1].length} ${getLocalizedLabel({
470+
[entity]: firstValue[entity],
471+
period: firstValue.period,
472+
uniqueId: `${priceComponent}${firstValue.period}${firstValue.operatorLabel}${firstValue.municipalityLabel}${value[1].length}`,
473+
label: `${firstValue.period}, ${firstValue.operatorLabel}, ${
474+
value[1].length
475+
} ${getLocalizedLabel({
411476
id: entity === "operator" ? "municipalities" : "operators",
412477
})}`,
413478
entities: value[1],

src/domain/helpers.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ export const useFormatCurrency = (alwaysLeaveDecimals: boolean = false) => {
4747
return formatter;
4848
};
4949

50+
export type FormatCurrency = ReturnType<typeof useFormatCurrency>;
51+
5052
const useD3Format = (fmtString: string) => {
5153
const locale = useLocale();
5254
const formatter = React.useMemo(() => {

src/flags/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export { default as createHooks } from "./hooks";
22
/** @knipignore */
33
export { default as createAPI } from "./api";
44
export { default as createComponents } from "./components";
5+
/** @knipignore */
56
export type { FlagValue } from "./types";
67
/** @knipignore */
78
export type { FlagName } from "./types";

src/utils/flags.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,6 @@ const specs = {
2828
/** Show sunshine specific UI */
2929
sunshine: {},
3030

31-
/** Show dynamic electricity tariffs */
32-
dynamicElectricityTariffs: {},
33-
3431
/** Show mock operational standards chart */
3532
mockOperationalStandardsChart: {},
3633

0 commit comments

Comments
 (0)