Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ and this project adheres to
You can also check the
[release page](https://github.com/visualize-admin/electricity-prices-switzerland/releases)

# Unreleased

- Feat
- Dynamic tariffs coming from Lindas

# 2.11.3 (2025-08-26)

- Feat
Expand Down
17 changes: 1 addition & 16 deletions src/components/charts-generic/bars/bars-grouped.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { t } from "@lingui/macro";

import { Bar } from "src/components/charts-generic/bars/bars-simple";
import {
GroupedBarsState,
Expand All @@ -8,7 +6,6 @@ import {
import { useChartTheme } from "src/components/charts-generic/use-chart-theme";
import { EXPANDED_TAG } from "src/components/detail-page/price-components-bars";
import { useFormatCurrency } from "src/domain/helpers";
import { useFlag } from "src/utils/flags";

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

Expand Down Expand Up @@ -110,8 +107,6 @@ export const BarsGroupedLabels = () => {
const { labelFontSize } = useChartTheme();
const formatCurrency = useFormatCurrency();

const dynamicTariffsFlag = useFlag("dynamicElectricityTariffs");

return (
<g transform={`translate(${margins.left} ${margins.top})`}>
{sortedData.map((d, i) => {
Expand All @@ -121,16 +116,6 @@ export const BarsGroupedLabels = () => {
const value = formatCurrency(getX(d));
const label = getLabel(d);

const isDynamic = dynamicTariffsFlag;
const dynamicText = isDynamic
? `(${formatCurrency(d.min as number)} - ${formatCurrency(
d.max as number
)}, ${t({
id: "dynamic.tariff",
message: "dynamic",
})})`
: "";

return (
<text
key={`label-${i}`}
Expand All @@ -142,7 +127,7 @@ export const BarsGroupedLabels = () => {
>
{!segment.includes(EXPANDED_TAG) && (
<tspan fontWeight={700}>
{value} {xAxisLabel} {dynamicText}
{value} {xAxisLabel}
</tspan>
)}{" "}
{label}
Expand Down
129 changes: 97 additions & 32 deletions src/components/detail-page/price-components-bars.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { t, Trans } from "@lingui/macro";
import { Box } from "@mui/material";
import { ascending, group, groups, max, min } from "d3";
import * as React from "react";
import { useMemo } from "react";

import { ButtonGroup } from "src/components/button-group";
import {
Expand Down Expand Up @@ -35,19 +36,26 @@ import {
ObservationValue,
detailsPriceComponents,
} from "src/domain/data";
import { mkNumber, pivot_longer } from "src/domain/helpers";
import {
FormatCurrency,
mkNumber,
pivot_longer,
useFormatCurrency,
} from "src/domain/helpers";
import { RP_PER_KWH } from "src/domain/metrics";
import { useQueryStateEnergyPricesDetails } from "src/domain/query-states";
import { getLocalizedLabel } from "src/domain/translation";
import { FlagValue } from "src/flags";
import {
ObservationKind,
PriceComponent,
useObservationsWithAllPriceComponentsQuery,
} from "src/graphql/queries";
import {
ResolvedObservation,
ResolvedOperatorObservation,
} from "src/graphql/resolver-mapped-types";
import { EMPTY_ARRAY } from "src/lib/empty-array";
import { useLocale } from "src/lib/use-locale";
import { useFlag } from "src/utils/flags";

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

Expand Down Expand Up @@ -101,7 +109,9 @@ export const PriceComponentsBarChart = ({ id, entity }: SectionProps) => {
const cantonObservations = observationsQuery.fetching
? EMPTY_ARRAY
: observationsQuery.data?.cantonMedianObservations ?? EMPTY_ARRAY;
const observations = [...operatorObservations, ...cantonObservations];
const observations = useMemo(() => {
return [...operatorObservations, ...cantonObservations];
}, [operatorObservations, cantonObservations]);

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

const dynamicTariffsFlag = useFlag("dynamicElectricityTariffs");
const formatCurrency = useFormatCurrency();

const shouldShowDynamicTariffsHelp = useMemo(() => {
return observations.some(
(obs) =>
obs &&
obs.__typename === "OperatorObservation" &&
// I do not know why we need the as here
hasDynamicTariffs(obs as ResolvedOperatorObservation)
);
}, [observations]);

return (
<Card downloadId={DOWNLOAD_ID}>
Expand Down Expand Up @@ -219,7 +239,7 @@ export const PriceComponentsBarChart = ({ id, entity }: SectionProps) => {
</Box>
</>
)}
{dynamicTariffsFlag && (
{shouldShowDynamicTariffsHelp && (
<HintBlue iconName="infocircle">
<Trans id="dynamic-tariffs.hint">
Prices shown with values in brackets are dynamic. You can find more
Expand Down Expand Up @@ -249,7 +269,7 @@ export const PriceComponentsBarChart = ({ id, entity }: SectionProps) => {
view: view[0],
priceComponent: priceComponent[0] as PriceComponent,
entity,
dynamicTariffsFlag,
formatCurrency,
});

return (
Expand Down Expand Up @@ -322,12 +342,56 @@ export const PriceComponentsBarChart = ({ id, entity }: SectionProps) => {
);
};

const dynamicTariffs = (
min: number,
max: number,
formatCurrency: FormatCurrency
) => {
return `(${formatCurrency(min)} - ${formatCurrency(max)}, ${t({
id: "dynamic.tariff",
message: "dynamic",
})})`;
};

const hasDynamicTariffs = (observation: ResolvedOperatorObservation) => {
return (
"lowestrate" in observation &&
typeof observation["lowestrate"] !== "undefined" &&
observation["lowestrate"] !== null &&
"highestrate" in observation &&
typeof observation["highestrate"] !== "undefined" &&
observation["highestrate"] !== null
);
};

const getLabelFromObservation = (
priceComponent: PriceComponent,
formatCurrency: FormatCurrency,
obs: ResolvedObservation
) => {
if (obs.__typename === "CantonMedianObservation") {
return `${obs.period}, ${obs.cantonLabel}`;
} else {
return `${
priceComponent === "total" &&
obs.__typename === "OperatorObservation" &&
hasDynamicTariffs(obs)
? `${dynamicTariffs(
obs["lowestrate"],
obs["highestrate"],
formatCurrency
)}, `
: ""
}${obs.period}, ${obs.operatorLabel}, ${obs.municipalityLabel}`;
}
};

const prepareObservations = ({
groupedObservations,
priceComponent,
entity,
view,
dynamicTariffsFlag,
formatCurrency,
}: {
groupedObservations: [
ObservationValue,
Expand All @@ -339,14 +403,13 @@ const prepareObservations = ({
priceComponent: PriceComponent;
entity: Entity;
view: string;
dynamicTariffsFlag: FlagValue;
formatCurrency: FormatCurrency;
}) => {
if (entity === "canton") {
return groupedObservations.flatMap((year) =>
year[1].flatMap((ent) =>
ent[1].flatMap((value) => ({
...value[1][0],
...(dynamicTariffsFlag ? { max: 65.45, min: 12.89 } : {}),
label: value[1][0].uniqueId,
}))
)
Expand All @@ -355,39 +418,42 @@ const prepareObservations = ({
return view === "collapsed"
? groupedObservations.flatMap((year) =>
year[1].flatMap((ent) =>
ent[1].flatMap((value) =>
value[1].length === 1
ent[1].flatMap((value) => {
const first = value[1][0];
return value[1].length === 1
? {
...value[1][0],
label: value[1][0].uniqueId,
...(dynamicTariffsFlag ? { max: 65.45, min: 12.89 } : {}),
...first,
label: getLabelFromObservation(
priceComponent,
formatCurrency,
first as ResolvedObservation
),
}
: {
priceComponent,
value: value[0],
...(dynamicTariffsFlag ? { max: 65.45, min: 12.89 } : {}),
[entity]: value[1][0][entity],
period: value[1][0].period,
uniqueId: `${priceComponent}${value[1][0].period}${value[1][0].operatorLabel}${value[1][0].municipalityLabel}${value[1].length}`,
label: `${value[1][0].period}, ${
value[1][0].operatorLabel
}, ${value[1].length} ${getLocalizedLabel({
[entity]: first[entity],
period: first.period,
uniqueId: `${priceComponent}${first.period}${first.operatorLabel}${first.municipalityLabel}${value[1].length}`,
label: `${first.period}, ${first.operatorLabel}, ${
value[1].length
} ${getLocalizedLabel({
id:
entity === "operator" ? "municipalities" : "operators",
})}`,
entities: value[1],
}
)
};
})
)
)
: groupedObservations.flatMap((year) =>
year[1].flatMap((ent) =>
ent[1].flatMap((value) => {
const firstValue = value[1][0];
const singleEntities = value[1]
.flatMap((d) => ({
priceComponent,
value: d.value,
...(dynamicTariffsFlag ? { max: 65.45, min: 12.89 } : {}),
[entity]: d[entity],
period: d.period,
uniqueId: `${priceComponent}${d.period}${d.operatorLabel}${d.municipalityLabel}${value[1].length}${EXPANDED_TAG}`,
Expand All @@ -401,13 +467,12 @@ const prepareObservations = ({
{
priceComponent,
value: value[0],
...(dynamicTariffsFlag ? { max: 65.45, min: 12.89 } : {}),
[entity]: value[1][0][entity],
period: value[1][0].period,
uniqueId: `${priceComponent}${value[1][0].period}${value[1][0].operatorLabel}${value[1][0].municipalityLabel}${value[1].length}`,
label: `${value[1][0].period}, ${
value[1][0].operatorLabel
}, ${value[1].length} ${getLocalizedLabel({
[entity]: firstValue[entity],
period: firstValue.period,
uniqueId: `${priceComponent}${firstValue.period}${firstValue.operatorLabel}${firstValue.municipalityLabel}${value[1].length}`,
label: `${firstValue.period}, ${firstValue.operatorLabel}, ${
value[1].length
} ${getLocalizedLabel({
id: entity === "operator" ? "municipalities" : "operators",
})}`,
entities: value[1],
Expand Down
2 changes: 2 additions & 0 deletions src/domain/helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export const useFormatCurrency = (alwaysLeaveDecimals: boolean = false) => {
return formatter;
};

export type FormatCurrency = ReturnType<typeof useFormatCurrency>;

const useD3Format = (fmtString: string) => {
const locale = useLocale();
const formatter = React.useMemo(() => {
Expand Down
1 change: 1 addition & 0 deletions src/flags/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { default as createHooks } from "./hooks";
/** @knipignore */
export { default as createAPI } from "./api";
export { default as createComponents } from "./components";
/** @knipignore */
export type { FlagValue } from "./types";
/** @knipignore */
export type { FlagName } from "./types";
4 changes: 4 additions & 0 deletions src/graphql/queries.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ fragment operatorObservationWithAllPriceComponentsFields on OperatorObservation
energy: value(priceComponent: energy)
fixcostspercent: value(priceComponent: fixcostspercent)
total: value(priceComponent: total)

highestrate
lowestrate
urltr
}

fragment cantonMedianObservationWithAllPriceComponentsFields on CantonMedianObservation {
Expand Down
9 changes: 9 additions & 0 deletions src/graphql/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,11 +230,14 @@ export type OperatorObservation = {
cantonLabel?: Maybe<Scalars["String"]["output"]>;
category: Scalars["String"]["output"];
coverageRatio: Scalars["Float"]["output"];
highestrate?: Maybe<Scalars["Float"]["output"]>;
lowestrate?: Maybe<Scalars["Float"]["output"]>;
municipality: Scalars["String"]["output"];
municipalityLabel?: Maybe<Scalars["String"]["output"]>;
operator: Scalars["String"]["output"];
operatorLabel?: Maybe<Scalars["String"]["output"]>;
period: Scalars["String"]["output"];
urltr?: Maybe<Scalars["String"]["output"]>;
value?: Maybe<Scalars["Float"]["output"]>;
};

Expand Down Expand Up @@ -706,6 +709,8 @@ export type OperatorObservationWithAllPriceComponentsFieldsFragment = {
operator: string;
operatorLabel?: string | null;
category: string;
highestrate?: number | null;
lowestrate?: number | null;
aidfee?: number | null;
annualmeteringcost?: number | null;
meteringrate?: number | null;
Expand Down Expand Up @@ -746,6 +751,8 @@ export type ObservationsWithAllPriceComponentsQuery = {
operator: string;
operatorLabel?: string | null;
category: string;
highestrate?: number | null;
lowestrate?: number | null;
aidfee?: number | null;
annualmeteringcost?: number | null;
meteringrate?: number | null;
Expand Down Expand Up @@ -1163,6 +1170,8 @@ export const OperatorObservationWithAllPriceComponentsFieldsFragmentDoc = gql`
energy: value(priceComponent: energy)
fixcostspercent: value(priceComponent: fixcostspercent)
total: value(priceComponent: total)
highestrate
lowestrate
}
`;
export const CantonMedianObservationWithAllPriceComponentsFieldsFragmentDoc = gql`
Expand Down
Loading
Loading