Skip to content

Commit

Permalink
Merge pull request #431 from Klimatbyran/staging
Browse files Browse the repository at this point in the history
KPI 7: climate requirements in procurements
  • Loading branch information
elvbom authored Mar 24, 2024
2 parents 810cf9f + 964a5f8 commit 2b2806c
Show file tree
Hide file tree
Showing 28 changed files with 476 additions and 30,670 deletions.
2 changes: 1 addition & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
"singleQuote": true,
"printWidth": 90,
"tabWidth": 2,
"bracketSameLine": true,
"bracketSameLine": false,
"endOfLine": "auto"
}
5 changes: 0 additions & 5 deletions Atoms.tsx

This file was deleted.

27 changes: 18 additions & 9 deletions components/Map/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import NextNProgress from 'nextjs-progressbar'
import { colorTheme } from '../../Theme'
import { mapColors } from '../shared'
import { replaceLetters } from '../../utils/shared'
import { CurrentDataPoints } from '../../utils/types'

const INITIAL_VIEW_STATE = {
longitude: 17.062927,
Expand Down Expand Up @@ -41,6 +42,17 @@ const getColor = (
return dataPoint === boundaries[0] ? colors[0] : colors[colors.length - 1]
}

// Special case for KPIs with three cases
if (boundaries.length === 3) {
if (dataPoint > boundaries[1]) {
return colors[colors.length - 1]
}
if (dataPoint > boundaries[0]) {
return colors[4]
}
return colors[0]
}

// Special case for invalid dates
const invalidDate = (possibleDate: unknown) => possibleDate instanceof Date && Number.isNaN(possibleDate.getTime())
if (invalidDate(dataPoint)) {
Expand All @@ -66,17 +78,13 @@ const getColor = (
return colors[5]
}

type Props = {
data: Array<{
name: string
dataPoint: number | string | Date
formattedDataPoint: number | string
}>
type MapProps = {
data: Array<CurrentDataPoints>
boundaries: number[] | string[] | Date[]
children?: ReactNode
}

function Map({ data, boundaries, children }: Props) {
function Map({ data, boundaries, children }: MapProps) {
const [municipalityData, setMunicipalityData] = useState<any>({})
const router = useRouter()

Expand Down Expand Up @@ -117,8 +125,9 @@ function Map({ data, boundaries, children }: Props) {
const municipalityLines = municipalityData?.features?.flatMap(
({ geometry, properties }: { geometry: any; properties: any }) => {
const name = replaceLetters(properties.name)
const dataPoint = data.find((e) => e.name === name)?.dataPoint
const formattedDataPoint = data.find((e) => e.name === name)?.formattedDataPoint
const currentMunicipality = data.find((e) => e.name === name)
const dataPoint = currentMunicipality?.primaryDataPoint
const formattedDataPoint = currentMunicipality?.formattedPrimaryDataPoint

if (geometry.type === 'MultiPolygon') {
return geometry.coordinates.map((coords: any) => ({
Expand Down
10 changes: 9 additions & 1 deletion components/Map/MapLabels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,15 @@ type MapLabelsProps = {
}

function MapLabels({ labels, rotations }: MapLabelsProps) {
const labelColors = labels.length === 2 ? [mapColors[0], mapColors[mapColors.length - 1]] : mapColors
let labelColors = mapColors

// Special cases for binary KPIs and KPIs with three cases
if (labels.length === 2) {
labelColors = [mapColors[0], mapColors[mapColors.length - 1]]
}
if (labels.length === 3) {
labelColors = [mapColors[0], mapColors[4], mapColors[mapColors.length - 1]]
}

return (
<Container>
Expand Down
7 changes: 4 additions & 3 deletions components/Municipality/MunicipalityScorecard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { H4, H5, ParagraphItalic } from '../Typography'
import Icon from '../../public/icons/boxedArrow.svg'
import PlanIcon from '../../public/icons/climatePlan.svg'
import FactSection from '../FactSection'
import { climatePlanMissing } from '../../utils/datasetDefinitions'

const StyledDiv = styled.div`
display: flex;
Expand Down Expand Up @@ -128,14 +129,14 @@ function Scorecard({
politicalRule,
climatePlan,
}: Props) {
const climatePlanYearFormatted = climatePlan.YearAdapted !== 'Saknas'
const climatePlanYearFormatted = climatePlan.YearAdapted !== climatePlanMissing
? `Antagen ${climatePlan.YearAdapted}`
: climatePlan.YearAdapted
const rankFormatted = `${rank} av 290`
const politicalRuleFormatted = politicalRule ? politicalRule.join(', ') : 'Data saknas'

const handleButtonClick = () => {
if (climatePlan.Link !== 'Saknas') {
if (climatePlan.Link !== climatePlanMissing) {
window.open(climatePlan.Link, '_blank')
}
}
Expand All @@ -156,7 +157,7 @@ function Scorecard({
<SectionRight>
<LinkButton
onClick={handleButtonClick}
disabled={climatePlan.Link === 'Saknas'}
disabled={climatePlan.Link === climatePlanMissing}
>
Öppna
<Square>
Expand Down
51 changes: 36 additions & 15 deletions components/Municipality/MunicipalitySolutions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import EVCar from '../../public/icons/evcars_32.svg'
import Bike from '../../public/icons/bikelanes_32.svg'
import Basket from '../../public/icons/consumtion_32.svg'
import Charger from '../../public/icons/charger.svg'
import Procurements from '../../public/icons/kpis/procurements_32.svg'
import { requirementsInProcurement } from '../../utils/datasetDefinitions'

const StyledH2 = styled(H2)`
margin-top: 32px;
Expand All @@ -27,7 +29,7 @@ type SolutionSectionProps = {
title: string
heading: string
data: string
info: string
info: string | JSX.Element
}

function SolutionSection({
Expand Down Expand Up @@ -55,6 +57,32 @@ function MunicipalitySolutions({ municipality }: SolutionsProps) {
<Paragraph>
Här visas nyckeltal för hur det går med klimatomställningen i kommunerna.
</Paragraph>
<SolutionSection
icon={<Basket />}
title="Hushållens konsumtionsutsläpp"
heading="CO₂e per person och år"
data={`${municipality.TotalConsumptionEmission.toFixed(1)} ton`}
info="Hushållens konsumtionsutsläpp (CO₂e) i ton per invånare år 2019."
/>
<SolutionSection
icon={<Procurements />}
title="Klimatkrav i upphandlingar"
heading="Ställer kommunen klimatkrav i sina upphandlingar?"
data={requirementsInProcurement(municipality.ProcurementScore)}
info={(
<>
Kommuner som ställer klimatkrav vid offentliga upphandlingar.
“Ja” innebär principbeslut och underlag som tillstyrker.
“Kanske” innebär ja-svar i enkätundersökning eller via mejl, men utan underlag som tillstyrker.
{' '}
<a href="mailto:[email protected]">
Mejla oss
</a>
{' '}
för att redigera informationen.
</>
)}
/>
<SolutionSection
icon={<EVCar />}
title="Elbilarna"
Expand All @@ -63,27 +91,20 @@ function MunicipalitySolutions({ municipality }: SolutionsProps) {
info="Ökningstakten för andelen nyregistrerade laddbara bilar sedan Parisavtalet
2015 i procentenheter per år."
/>
<SolutionSection
icon={<Bike />}
title="Cyklarna"
heading="Antal meter cykelväg per invånare"
data={`${municipality.BicycleMetrePerCapita.toFixed(1)} meter`}
info="Antal meter cykelväg per invånare år 2022 totalt för alla väghållare (statlig, kommunal, enskild)."
/>
<SolutionSection
icon={<Basket />}
title="Hushållens konsumtionsutsläpp"
heading="CO₂e per person och år"
data={`${municipality.TotalConsumptionEmission.toFixed(1)} ton`}
info="Hushållens konsumtionsutsläpp (CO₂e) i ton per invånare år 2019."
/>
<SolutionSection
icon={<Charger />}
title="Laddarna"
heading="Antal elbilar per laddare"
data={`${municipality.ElectricVehiclePerChargePoints < 1e10 ? municipality.ElectricVehiclePerChargePoints.toFixed(1) : 'Laddare saknas'}`}
info="Antal registrerade laddbara bilar per offentliga laddpunkter år 2023. EU rekommenderar max 10 bilar per laddare."
/>
<SolutionSection
icon={<Bike />}
title="Cyklarna"
heading="Antal meter cykelväg per invånare"
data={`${municipality.BicycleMetrePerCapita.toFixed(1)} meter`}
info="Antal meter cykelväg per invånare år 2022 totalt för alla väghållare (statlig, kommunal, enskild)."
/>
</>
)
}
Expand Down
4 changes: 2 additions & 2 deletions components/RadioButtonMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Fragment } from 'react'
import styled from 'styled-components'
import { datasetDescriptions } from '../utils/datasetDescriptions'
import { dataDescriptions } from '../utils/datasetDefinitions'
import { SelectedData } from '../utils/types'
import { devices } from '../utils/devices'

Expand Down Expand Up @@ -47,7 +47,7 @@ const RadioInput = styled.input`
}
`

const datasetKeys = Object.keys(datasetDescriptions)
const datasetKeys = Object.keys(dataDescriptions)

type MenuProps = {
selectedData: SelectedData
Expand Down
2 changes: 1 addition & 1 deletion data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ To recalculate and refresh the site's data, navigate to the `/data` folder and e
`python3 climate_data_calculations.py`
The results will be saved in the `/data/output` folder, primarily in the `climate-data.json` file. The climate data is sourced using a TypeScript utility service located at `utils/climateDataService.tsx`. This service is responsible for fetching and manipulating the data found in `climate-data.json` for use throughout the website. To add or edit the descriptions of datasets that appear in the national overview on the website's homepage, make the necessary changes in `utils/datasetDescriptions.tsx`.
The results will be saved in the `/data/output` folder, primarily in the `climate-data.json` file. The climate data is sourced using a TypeScript utility service located at `utils/climateDataService.tsx`. This service is responsible for fetching and manipulating the data found in `climate-data.json` for use throughout the website. To add or edit the descriptions of datasets that appear in the national overview on the website's homepage, make the necessary changes in `utils/datasetDefinitions.tsx`.
#### Handling Data Inconsistencies for Municipalities
Expand Down
39 changes: 24 additions & 15 deletions data/climate_data_calculations.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
# -*- coding: utf-8 -*-

import json
import numpy as np
import pandas as pd

from solutions.cars.electric_car_change_rate import get_electric_car_change_rate
from solutions.cars.electric_vehicle_per_charge_points import get_electric_vehicle_per_charge_points
from solutions.bicycles.bicycle_data_calculations import bicycle_calculations
from facts.plans.plans_data_prep import get_climate_plans
from facts.municipalities_counties import get_municipalities
from facts.procurements.climate_requirements_in_procurements import get_procurement_data
from issues.emissions.emission_data_calculations import emission_calculations
from issues.consumption.consumption_data_calculations import get_consumption_emissions

Expand All @@ -21,10 +20,10 @@
print('1. Municipalities loaded and prepped')

df = emission_calculations(df)
print('2. Climate data and calculations all done')
print('2. Climate data and calculations added')

df = get_electric_car_change_rate(df)
print('3. Hybrid car data and calculations finished')
print('3. Hybrid car data and calculations added')

df = get_climate_plans(df)
print('4. Climate plans added')
Expand All @@ -34,16 +33,17 @@

df = get_consumption_emissions(df)
print('6. Consumption emission data added')

df_evpc = get_electric_vehicle_per_charge_points()
df = df.merge(df_evpc, on='Kommun', how='left')
print('7. Add CPEV for December 2023')
print('7. CPEV for December 2023 added')

# MERGE ALL DATA IN ONE LIST TO RULE THEM ALL
df_procurements = get_procurement_data()
df = df.merge(df_procurements, on='Kommun', how='left')
print('8. Climate requirements in procurements added')

temp = [] # remane the columns
for i in range(len(df)):
temp.append({
temp = [
{
'kommun': df.iloc[i]['Kommun'],
'län': df.iloc[i]['Län'],
'emissions': {
Expand All @@ -57,16 +57,20 @@
'2018': df.iloc[i][2018],
'2019': df.iloc[i][2019],
'2020': df.iloc[i][2020],
'2021': df.iloc[i][2021]
'2021': df.iloc[i][2021],
},
'budget': df.iloc[i]['Budget'],
'emissionBudget': df.iloc[i]['parisPath'],
'approximatedHistoricalEmission': df.iloc[i]['approximatedHistorical'],
'totalApproximatedHistoricalEmission': df.iloc[i]['totalApproximatedHistorical'],
'trend': df.iloc[i]['trend'],
'trendEmission': df.iloc[i]['trendEmission'],
'historicalEmissionChangePercent': df.iloc[i]['historicalEmissionChangePercent'],
'neededEmissionChangePercent': df.iloc[i]['neededEmissionChangePercent'],
'historicalEmissionChangePercent': df.iloc[i][
'historicalEmissionChangePercent'
],
'neededEmissionChangePercent': df.iloc[i][
'neededEmissionChangePercent'
],
'hitNetZero': df.iloc[i]['hitNetZero'],
'budgetRunsOut': df.iloc[i]['budgetRunsOut'],
'electricCarChangePercent': df.iloc[i]['electricCarChangePercent'],
Expand All @@ -77,9 +81,14 @@
'bicycleMetrePerCapita': df.iloc[i]['metrePerCapita'],
'totalConsumptionEmission': df.iloc[i]['Total emissions'],
'electricVehiclePerChargePoints': df.iloc[i]['EVPC'],
})
'procurementScore': df.iloc[i]['procurementScore'],
'procurementLink': df.iloc[i]['procurementLink'],
}
for i in range(len(df))
]

with open('output/climate-data.json', 'w', encoding='utf8') as json_file: # save dataframe as json file
with open('output/climate-data.json', 'w', encoding='utf8') as json_file:
# save dataframe as json file
json.dump(temp, json_file, ensure_ascii=False, default=str)

print('Climate data JSON file created and saved')
32 changes: 3 additions & 29 deletions data/facts/plans/plans_data_prep.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,10 @@
# -*- coding: utf-8 -*-

import numpy as np
import pandas as pd
import re
from helpers import clean_kommun

PATH_PLANS_DATA = 'facts/plans/klimatplaner.xlsx'

MUNICIPALITIES_W_S = ['Alingsås kommun', 'Bengtsfors kommun', 'Bollnäs kommun', 'Borås stad', 'Degerfors kommun', 'Grums kommun',
'Hagfors kommun', 'Hofors kommun', 'Hällefors kommun', 'Höganäs kommun', 'Kramfors kommun', 'Munkfors kommun',
'Mönsterås kommun', 'Robertsfors kommun', 'Sotenäs kommun', 'Storfors kommun', 'Strängnäs kommun', 'Torsås kommun',
'Tranås kommun', 'Vännäs kommun', 'Västerås stad']


def clean_kommun(kommun):
# Remove any whitespace
kommun = kommun.strip()

# Replace 'Falu kommun' with 'Falun'
if kommun == 'Falu kommun':
return 'Falun'

if kommun == 'Region Gotland (kommun)':
return 'Gotland'

# Remove 'kommun' or 'stad' from municipalities in the list 'municipalities_w_s'
if kommun in MUNICIPALITIES_W_S:
kommun = re.sub(r'( kommun| stad)', '', kommun)

# Remove 'kommun', 'stad', 's kommun', or 's stad' from all other municipalities
kommun = re.sub(r'( kommun| stad|s kommun|s stad)', '', kommun)

return kommun
PATH_PLANS_DATA = 'facts/plans/klimatplaner.xlsx'


def get_climate_plans(df):
Expand All @@ -48,7 +22,7 @@ def get_climate_plans(df):
df_plans = df_plans.rename(
columns={df_plans.columns[6]: 'cred'})

df_plans = df_plans.where(pd.notnull(df_plans), 'Saknas')
df_plans = df_plans.where(pd.notnull(df_plans), 'Saknar plan')

df = df.merge(df_plans, on='Kommun', how='left')

Expand Down
Binary file not shown.
Loading

0 comments on commit 2b2806c

Please sign in to comment.