├── fe
├── .eslintignore
├── src
│ ├── lib
│ │ ├── config
│ │ │ ├── time.js
│ │ │ ├── text.js
│ │ │ ├── colors.js
│ │ │ └── map.js
│ │ ├── components
│ │ │ ├── mdsvex
│ │ │ │ ├── h5.svelte
│ │ │ │ ├── p.svelte
│ │ │ │ ├── h1.svelte
│ │ │ │ ├── ol.svelte
│ │ │ │ ├── ul.svelte
│ │ │ │ ├── h3.svelte
│ │ │ │ ├── h4.svelte
│ │ │ │ ├── td.svelte
│ │ │ │ ├── h2.svelte
│ │ │ │ ├── th.svelte
│ │ │ │ ├── table.svelte
│ │ │ │ ├── blockquote.svelte
│ │ │ │ ├── code.svelte
│ │ │ │ ├── a.svelte
│ │ │ │ └── _layout.svelte
│ │ │ ├── svizzle
│ │ │ │ ├── ui
│ │ │ │ │ ├── handlers.js
│ │ │ │ │ ├── glyphs
│ │ │ │ │ │ └── Legend.svelte
│ │ │ │ │ ├── actions
│ │ │ │ │ │ └── scrollIntoView.js
│ │ │ │ │ ├── geometryObserver.js
│ │ │ │ │ └── ScrollIntoView.svelte
│ │ │ │ ├── SizeSensor.svelte
│ │ │ │ ├── GridRows.svelte
│ │ │ │ ├── GridColumns.svelte
│ │ │ │ └── legend
│ │ │ │ │ └── KeysLegend.svelte
│ │ │ ├── explorer
│ │ │ │ ├── MetricTitle.svelte
│ │ │ │ ├── SelectorInterval.svelte
│ │ │ │ ├── SelectorRegionType.svelte
│ │ │ │ ├── NoData.svelte
│ │ │ │ ├── FlexBar.svelte
│ │ │ │ ├── FilterPaneBorder.svelte
│ │ │ │ ├── medium
│ │ │ │ │ ├── unused
│ │ │ │ │ │ ├── ScrollableGrid
│ │ │ │ │ │ │ └── ColumnNames.svelte
│ │ │ │ │ │ └── LabelsGrid.svelte
│ │ │ │ │ └── ViewSelectorMedium.svelte
│ │ │ │ ├── Bullet.svelte
│ │ │ │ ├── Checkboxed.svelte
│ │ │ │ ├── small
│ │ │ │ │ ├── InfoSmall.svelte
│ │ │ │ │ └── IconBar.svelte
│ │ │ │ ├── DismissOrApply.svelte
│ │ │ │ ├── SelectionXor.svelte
│ │ │ │ └── MetricSelector.svelte
│ │ │ └── layout
│ │ │ │ ├── Nav.svelte
│ │ │ │ └── medium
│ │ │ │ └── ExplorerMedium.svelte
│ │ ├── utils
│ │ │ ├── version.js
│ │ │ ├── date.js
│ │ │ ├── theme.js
│ │ │ ├── color.js
│ │ │ ├── unescape-inlineCode.js
│ │ │ ├── svizzle
│ │ │ │ ├── url.js
│ │ │ │ ├── utils.js
│ │ │ │ └── style.js
│ │ │ ├── download.js
│ │ │ └── getters.js
│ │ ├── _content
│ │ │ ├── metrics
│ │ │ │ ├── installations.svx
│ │ │ │ ├── installers.svx
│ │ │ │ ├── installations_per_installer.svx
│ │ │ │ ├── installers_certified.svx
│ │ │ │ ├── property_feature_number_habitable_rooms.svx
│ │ │ │ ├── installers_dropped_certifications.svx
│ │ │ │ ├── property_feature_total_floor_area.svx
│ │ │ │ ├── installers_new_certifications.svx
│ │ │ │ ├── property_feature_type.svx
│ │ │ │ ├── hp_feature_scop.svx
│ │ │ │ ├── hp_id_model.svx
│ │ │ │ ├── installation_cost.svx
│ │ │ │ ├── hp_id_brand.svx
│ │ │ │ ├── hp_feature_power_capacity.svx
│ │ │ │ ├── hp_feature_power_generation.svx
│ │ │ │ ├── hp_feature_flow_temperature.svx
│ │ │ │ ├── installation_cost_sum.svx
│ │ │ │ ├── hp_feature_power_capacity_sum.svx
│ │ │ │ ├── property_tenure.svx
│ │ │ │ ├── hp_feature_power_generation_sum.svx
│ │ │ │ ├── property_feature_glazed_type.svx
│ │ │ │ ├── property_supply_mains_gas_flag.svx
│ │ │ │ ├── property_energy_rating_current.svx
│ │ │ │ ├── property_supply_photovoltaic.svx
│ │ │ │ ├── property_energy_rating_potential.svx
│ │ │ │ ├── property_feature_built_form.svx
│ │ │ │ ├── hp_feature_design.svx
│ │ │ │ ├── property_feature_glazed_area.svx
│ │ │ │ ├── property_feature_age_band.svx
│ │ │ │ ├── property_energy_efficiency_floor.svx
│ │ │ │ ├── property_energy_efficiency_roof.svx
│ │ │ │ ├── property_energy_efficiency_walls.svx
│ │ │ │ ├── property_energy_efficiency_lighting.svx
│ │ │ │ ├── property_energy_efficiency_windows.svx
│ │ │ │ ├── property_energy_efficiency_hot_water.svx
│ │ │ │ └── property_energy_efficiency_main_heat.svx
│ │ │ ├── Index.svx
│ │ │ ├── info
│ │ │ │ ├── Privacy.svx
│ │ │ │ ├── PrivacyBanner.svx
│ │ │ │ └── Disclaimer.svx
│ │ │ ├── guides
│ │ │ │ ├── A11ymenuIntro.svx
│ │ │ │ ├── AppSmall.svx
│ │ │ │ └── AppMedium.svx
│ │ │ ├── methodology
│ │ │ │ └── Geography.svx
│ │ │ └── Accessibility.svx
│ │ ├── statechart
│ │ │ ├── actions
│ │ │ │ ├── index.js
│ │ │ │ └── staticData.js
│ │ │ ├── utils.js
│ │ │ ├── context.js
│ │ │ ├── index.js
│ │ │ ├── guards.js
│ │ │ └── actors.js
│ │ ├── stores
│ │ │ ├── data.js
│ │ │ ├── tooltip.js
│ │ │ ├── geometry.js
│ │ │ ├── layout.js
│ │ │ ├── regions.js
│ │ │ └── navigation.js
│ │ ├── env.js
│ │ └── data
│ │ │ └── regions.js
│ ├── routes
│ │ ├── guides
│ │ │ ├── +page.js
│ │ │ ├── a11ymenu
│ │ │ │ └── +page.svelte
│ │ │ └── app
│ │ │ │ └── +page.svelte
│ │ ├── info
│ │ │ ├── +page.js
│ │ │ ├── privacy
│ │ │ │ └── +page.svelte
│ │ │ └── disclaimer
│ │ │ │ └── +page.svelte
│ │ ├── feedback
│ │ │ ├── +page.js
│ │ │ ├── survey
│ │ │ │ └── +page.svelte
│ │ │ └── survey2
│ │ │ │ └── +page.svelte
│ │ ├── methodology
│ │ │ ├── +page.js
│ │ │ ├── geography
│ │ │ │ └── +page.svelte
│ │ │ └── dataProcessing
│ │ │ │ └── +page.svelte
│ │ ├── explorer
│ │ │ ├── +page.js
│ │ │ ├── count
│ │ │ │ ├── +page.js
│ │ │ │ └── geo
│ │ │ │ │ └── [slug]
│ │ │ │ │ └── +page.svelte
│ │ │ ├── number
│ │ │ │ ├── +page.js
│ │ │ │ └── geo
│ │ │ │ │ └── [slug]
│ │ │ │ │ └── +page.svelte
│ │ │ ├── string
│ │ │ │ ├── +page.js
│ │ │ │ └── geo
│ │ │ │ │ └── [slug]
│ │ │ │ │ └── +page.svelte
│ │ │ ├── category
│ │ │ │ ├── +page.js
│ │ │ │ └── geo
│ │ │ │ │ └── [slug]
│ │ │ │ │ └── +page.svelte
│ │ │ └── +layout.svelte
│ │ ├── +page.svelte
│ │ └── +error.svelte
│ ├── app.html
│ └── bin
│ │ └── regions
│ │ └── README.md
├── .gitignore
├── static
│ ├── favicon.png
│ ├── logo-192.png
│ ├── logo-512.png
│ ├── font
│ │ ├── OpenDyslexic
│ │ │ ├── Bold.otf
│ │ │ ├── Italic.otf
│ │ │ ├── Regular.otf
│ │ │ └── BoldItalic.otf
│ │ ├── AvenirNext
│ │ │ └── Variable.ttf
│ │ ├── NobotoFlex
│ │ │ └── Variable.woff2
│ │ └── Archivo
│ │ │ ├── VariableFont_wdth,wght.ttf
│ │ │ └── Italic-VariableFont_wdth,wght.ttf
│ ├── manifest.json
│ ├── logos
│ │ ├── Nesta-dark.svg
│ │ └── Nesta-light.svg
│ └── audits
│ │ └── pa11y
│ │ ├── Home_themeDark.html
│ │ ├── Home_themeLight.html
│ │ ├── Guides_themeDark.html
│ │ └── Guides_themeLight.html
├── vite.config.js
├── netlify.toml
├── jsconfig.json
└── svelte.config.js
├── .npmrc
├── netlify.toml
├── shared
├── index.js
└── package.json
├── .dockerignore
├── .gitignore
├── README.md
├── be
├── src
│ ├── bin
│ │ ├── uploadTiles.sh
│ │ ├── generateMbTiles.sh
│ │ ├── saveNuts0.js
│ │ ├── updateRouteTestData.js
│ │ ├── ingest.sh
│ │ └── ingestServerSide.sh
│ ├── schemas
│ │ ├── cardinality.js
│ │ ├── certified.js
│ │ ├── date_histogram1_certified2.js
│ │ ├── stats.js
│ │ ├── date_histogram.js
│ │ ├── histogram.js
│ │ ├── terms1_certified2.js
│ │ ├── date_histogram1_cardinality2.js
│ │ ├── date_histogram1_histogram2.js
│ │ ├── date_histogram1_stats2.js
│ │ ├── terms1_histogram2.js
│ │ ├── terms.js
│ │ ├── terms1_stats2.js
│ │ ├── terms1_cardinality2.js
│ │ ├── date_histogram1_terms2.js
│ │ ├── index.js
│ │ └── terms1_terms2.js
│ ├── routes
│ │ ├── count.js
│ │ ├── cardinality.js
│ │ ├── stats.js
│ │ ├── date_histogram.js
│ │ ├── histogram.js
│ │ ├── date_histogram1_cardinality2.js
│ │ ├── terms1_histogram2.js
│ │ ├── date_histogram1_histogram2.js
│ │ ├── index.js
│ │ ├── terms1_stats2.js
│ │ ├── terms.js
│ │ ├── date_histogram1_stats2.js
│ │ ├── terms1_cardinality2.js
│ │ ├── date_histogram1_terms2.js
│ │ ├── terms1_terms2.js
│ │ └── certified.js
│ ├── server.js
│ ├── app.js
│ ├── conf.js
│ ├── filter.js
│ ├── util.js
│ ├── es.js
│ ├── utils
│ │ └── certifiedLogic.js
│ └── routes.js
├── test
│ ├── utils.js
│ ├── routes
│ │ ├── api
│ │ │ ├── count
│ │ │ │ └── 01.json
│ │ │ ├── certified
│ │ │ │ ├── 01.json
│ │ │ │ ├── 02.json
│ │ │ │ ├── 03.json
│ │ │ │ ├── 05.json
│ │ │ │ ├── 04.json
│ │ │ │ └── 06.json
│ │ │ ├── cardinality
│ │ │ │ ├── 01.json
│ │ │ │ └── 02.json
│ │ │ ├── stats
│ │ │ │ ├── 01.json
│ │ │ │ └── 02.json
│ │ │ ├── terms
│ │ │ │ ├── 03.json
│ │ │ │ ├── 01.json
│ │ │ │ ├── 02.json
│ │ │ │ ├── 05.json
│ │ │ │ └── 04.json
│ │ │ ├── terms1_certified2
│ │ │ │ ├── 01.json
│ │ │ │ ├── 04.json
│ │ │ │ ├── 05.json
│ │ │ │ ├── 02.json
│ │ │ │ └── 03.json
│ │ │ ├── terms1_cardinality2
│ │ │ │ └── 05.json
│ │ │ ├── histogram
│ │ │ │ └── 01.json
│ │ │ ├── terms1_terms2
│ │ │ │ └── 03.json
│ │ │ └── terms1_stats2
│ │ │ │ └── 01.json
│ │ └── routes.test.js
│ └── filter
│ │ ├── requests
│ │ ├── 01.json
│ │ ├── 03.json
│ │ ├── 06.json
│ │ ├── 02.json
│ │ ├── 05.json
│ │ ├── 04.json
│ │ └── 07.json
│ │ └── filter.test.js
├── es
│ └── copy_pipeline.conf
├── githooks
│ ├── post-receive
│ └── README.md
├── tiles
│ ├── config.json
│ └── README.md
└── package.json
├── Dockerfile
├── compose.yaml
├── appTest
├── src
│ ├── config.js
│ └── pa11y
│ │ └── validate.js
├── package.json
└── README.md
├── package.json
└── LICENSE
/fe/.eslintignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | base = "fe"
3 |
--------------------------------------------------------------------------------
/fe/src/lib/config/time.js:
--------------------------------------------------------------------------------
1 | export const intervals = ['1M', '1q', '1y'];
2 |
--------------------------------------------------------------------------------
/fe/.gitignore:
--------------------------------------------------------------------------------
1 | /.svelte-kit
2 | /.netlify
3 | /test/browserstack/.config.mjs
4 |
--------------------------------------------------------------------------------
/fe/src/lib/components/mdsvex/h5.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fe/src/lib/utils/version.js:
--------------------------------------------------------------------------------
1 | export {version} from '../../../../package.json';
2 |
--------------------------------------------------------------------------------
/shared/index.js:
--------------------------------------------------------------------------------
1 | export * from './counts.js';
2 | export * from './fields.js';
3 |
--------------------------------------------------------------------------------
/fe/src/lib/config/text.js:
--------------------------------------------------------------------------------
1 | export const noDataMessage = 'No results for the current selection';
2 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | **/node_modules
2 | **/test
3 | be/tiles
4 | be/es
5 | fe
6 | mapbox
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .git
3 | node_modules
4 | be/**/*.geojson
5 | be/**/*.mbtiles
6 | be/**/*.pmtiles
7 | _
8 |
--------------------------------------------------------------------------------
/fe/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mindrones/nestauk-asf_hp_market_tracker/dev/fe/static/favicon.png
--------------------------------------------------------------------------------
/fe/static/logo-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mindrones/nestauk-asf_hp_market_tracker/dev/fe/static/logo-192.png
--------------------------------------------------------------------------------
/fe/static/logo-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mindrones/nestauk-asf_hp_market_tracker/dev/fe/static/logo-512.png
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/installations.svx:
--------------------------------------------------------------------------------
1 | #### What's visualised
2 |
3 | The number of heat pump installations.
4 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/Index.svx:
--------------------------------------------------------------------------------
1 |
4 |
5 | # {toolName}
6 |
7 | TODO
8 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/installers.svx:
--------------------------------------------------------------------------------
1 | #### What's visualised
2 |
3 | The total number of installers that installed a heat pump.
4 |
--------------------------------------------------------------------------------
/fe/src/lib/statechart/actions/index.js:
--------------------------------------------------------------------------------
1 | export * from './navigation.js';
2 | export * from './staticData.js';
3 | export * from './view.js';
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Nesta Heat Pump Market Tracker
2 |
3 | The Nesta Heat Pump Market Tracker is a web app to track heat pump installations in the UK.
4 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/installations_per_installer.svx:
--------------------------------------------------------------------------------
1 | #### What's visualised
2 |
3 | The number of installations per active installer.
4 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/installers_certified.svx:
--------------------------------------------------------------------------------
1 | #### What's visualised
2 |
3 | The total number of installers with a valid certification.
4 |
--------------------------------------------------------------------------------
/fe/static/font/OpenDyslexic/Bold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mindrones/nestauk-asf_hp_market_tracker/dev/fe/static/font/OpenDyslexic/Bold.otf
--------------------------------------------------------------------------------
/fe/src/lib/statechart/utils.js:
--------------------------------------------------------------------------------
1 | import * as _ from 'lamb';
2 |
3 | export const isViewReady = _.hasPathValue('PageInteractive.ViewData', 'Ready');
4 |
--------------------------------------------------------------------------------
/fe/static/font/AvenirNext/Variable.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mindrones/nestauk-asf_hp_market_tracker/dev/fe/static/font/AvenirNext/Variable.ttf
--------------------------------------------------------------------------------
/fe/static/font/OpenDyslexic/Italic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mindrones/nestauk-asf_hp_market_tracker/dev/fe/static/font/OpenDyslexic/Italic.otf
--------------------------------------------------------------------------------
/fe/static/font/OpenDyslexic/Regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mindrones/nestauk-asf_hp_market_tracker/dev/fe/static/font/OpenDyslexic/Regular.otf
--------------------------------------------------------------------------------
/fe/src/routes/guides/+page.js:
--------------------------------------------------------------------------------
1 | import {redirect} from '@sveltejs/kit';
2 |
3 | export function load () {
4 | throw redirect(301, '/guides/app');
5 | }
6 |
--------------------------------------------------------------------------------
/fe/src/routes/info/+page.js:
--------------------------------------------------------------------------------
1 | import {redirect} from '@sveltejs/kit';
2 |
3 | export function load () {
4 | throw redirect(301, '/info/privacy');
5 | }
6 |
--------------------------------------------------------------------------------
/fe/static/font/NobotoFlex/Variable.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mindrones/nestauk-asf_hp_market_tracker/dev/fe/static/font/NobotoFlex/Variable.woff2
--------------------------------------------------------------------------------
/fe/static/font/OpenDyslexic/BoldItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mindrones/nestauk-asf_hp_market_tracker/dev/fe/static/font/OpenDyslexic/BoldItalic.otf
--------------------------------------------------------------------------------
/shared/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "App shared modules",
3 | "main": "index.js",
4 | "name": "nesta_hpmt_shared",
5 | "type": "module"
6 | }
7 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/property_feature_number_habitable_rooms.svx:
--------------------------------------------------------------------------------
1 | #### What's visualised
2 |
3 | The total number of habitable rooms in the property.
4 |
--------------------------------------------------------------------------------
/fe/src/lib/components/mdsvex/p.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
--------------------------------------------------------------------------------
/fe/src/routes/feedback/+page.js:
--------------------------------------------------------------------------------
1 | import {redirect} from '@sveltejs/kit';
2 |
3 | export function load () {
4 | throw redirect(301, '/feedback/survey');
5 | }
6 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/installers_dropped_certifications.svx:
--------------------------------------------------------------------------------
1 | #### What's visualised
2 |
3 | The total number of installers whose certifications have expired.
4 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/property_feature_total_floor_area.svx:
--------------------------------------------------------------------------------
1 | #### What's visualised
2 |
3 | The total floor area of the property, measured in square meters.
4 |
--------------------------------------------------------------------------------
/fe/src/lib/components/mdsvex/h1.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
--------------------------------------------------------------------------------
/fe/src/lib/stores/data.js:
--------------------------------------------------------------------------------
1 | import {writable} from 'svelte/store';
2 |
3 | export const _viewCache = writable({});
4 |
5 | export const _staticData = writable();
6 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/installers_new_certifications.svx:
--------------------------------------------------------------------------------
1 | #### What's visualised
2 |
3 | The total number of new or renewed certifications obtained by installers.
4 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/property_feature_type.svx:
--------------------------------------------------------------------------------
1 | #### What's visualised
2 |
3 | The number of installations by property type (e.g. bungalow, flat, house, etc).
4 |
--------------------------------------------------------------------------------
/fe/static/font/Archivo/VariableFont_wdth,wght.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mindrones/nestauk-asf_hp_market_tracker/dev/fe/static/font/Archivo/VariableFont_wdth,wght.ttf
--------------------------------------------------------------------------------
/be/src/bin/uploadTiles.sh:
--------------------------------------------------------------------------------
1 | name="nuts21_0_country21_itl21_1_itl21_2_itl21_3_lad21_msoa11_lsoa11"
2 | pmtiles upload --bucket=s3://dap-protomaps tiles/$name.pmtiles $name.pmtiles
--------------------------------------------------------------------------------
/fe/src/lib/components/mdsvex/ol.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
--------------------------------------------------------------------------------
/fe/src/lib/components/mdsvex/ul.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
10 |
--------------------------------------------------------------------------------
/fe/src/routes/methodology/+page.js:
--------------------------------------------------------------------------------
1 | import {redirect} from '@sveltejs/kit';
2 |
3 | export function load () {
4 | throw redirect(301, '/methodology/dataProcessing');
5 | }
6 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/hp_feature_scop.svx:
--------------------------------------------------------------------------------
1 | #### What's visualised
2 |
3 | SCOP values.
4 |
5 | #### SCOP
6 |
7 | Manufacturer-provided Seasonal Coefficient of Performance.
8 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/info/Privacy.svx:
--------------------------------------------------------------------------------
1 |
4 |
5 | ## Cookies and data privacy policy
6 |
7 |
8 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/hp_id_model.svx:
--------------------------------------------------------------------------------
1 | #### What's visualised
2 |
3 | The number of installations by heat pump model.
4 |
5 | #### Model
6 |
7 | The heat pump product name.
8 |
--------------------------------------------------------------------------------
/fe/static/font/Archivo/Italic-VariableFont_wdth,wght.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mindrones/nestauk-asf_hp_market_tracker/dev/fe/static/font/Archivo/Italic-VariableFont_wdth,wght.ttf
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/installation_cost.svx:
--------------------------------------------------------------------------------
1 | #### What's visualised
2 |
3 | Installation cost (in GBP).
4 |
5 | #### Cost
6 |
7 | The cost of installation of a heat pump (in GBP).
8 |
--------------------------------------------------------------------------------
/fe/src/lib/stores/tooltip.js:
--------------------------------------------------------------------------------
1 | import {writable} from 'svelte/store';
2 |
3 | export const _tooltip = writable();
4 |
5 | export const clearTooltip = () => {
6 | _tooltip.set(null);
7 | }
8 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/hp_id_brand.svx:
--------------------------------------------------------------------------------
1 | #### What's visualised
2 |
3 | The number of installations by heat pump brand.
4 |
5 | #### Brand
6 |
7 | Brand of the manufacturer of heat pumps.
8 |
--------------------------------------------------------------------------------
/fe/src/lib/components/mdsvex/h3.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
--------------------------------------------------------------------------------
/fe/src/lib/components/mdsvex/h4.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
--------------------------------------------------------------------------------
/fe/src/lib/components/svizzle/ui/handlers.js:
--------------------------------------------------------------------------------
1 | export const makeOnKeyDown = handlerFn => event => {
2 | if (event.key === 'Enter') {
3 | event.preventDefault();
4 | handlerFn(event);
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/fe/src/lib/components/mdsvex/td.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/hp_feature_power_capacity.svx:
--------------------------------------------------------------------------------
1 | #### What's visualised
2 |
3 | Power capacity values (in kW).
4 |
5 | #### Power capacity
6 |
7 | Total installed capacity of the heat pump (in kW).
8 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM --platform=linux/amd64 node:18-alpine
2 | WORKDIR /app
3 | COPY . .
4 | RUN npm install
5 | WORKDIR be
6 | RUN npm install
7 | ENV HOST=0.0.0.0 PORT=3000
8 | CMD ["npm", "run", "dev"]
9 | EXPOSE 3000
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/hp_feature_power_generation.svx:
--------------------------------------------------------------------------------
1 | #### What's visualised
2 |
3 | Energy values (in kWh).
4 |
5 | #### Power generation
6 |
7 | Estimated energy produced by the heat pump in a year (in kWh).
8 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/hp_feature_flow_temperature.svx:
--------------------------------------------------------------------------------
1 | #### What's visualised
2 |
3 | Flow temperature values (in °C).
4 |
5 | #### Flow temperature
6 |
7 | The flow temperature at which the heat pump is set (in °C).
8 |
--------------------------------------------------------------------------------
/fe/src/lib/components/mdsvex/h2.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/installation_cost_sum.svx:
--------------------------------------------------------------------------------
1 | #### What's visualised
2 |
3 | Heat pumps market value (in GBP).
4 |
5 | #### Market value
6 |
7 | The sum of the installation cost of all selected heat pumps (in GBP).
8 |
--------------------------------------------------------------------------------
/fe/src/lib/components/mdsvex/th.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
--------------------------------------------------------------------------------
/fe/src/lib/config/colors.js:
--------------------------------------------------------------------------------
1 | import {interpolateHclLong} from 'd3-interpolate';
2 |
3 | export const interpolateColor = interpolateHclLong(
4 | 'rgb(189,113,189)', // brighter purple
5 | 'rgb(255,209,124)' // brighter orange
6 | );
7 |
--------------------------------------------------------------------------------
/compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | app:
3 | image: hpmt:latest
4 | command: npm run dev
5 | ports:
6 | - "3000:3000"
7 | environment:
8 | - ELASTICSEARCH_PASSWORD
9 | volumes:
10 | - .be/src:/app/be/src
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/hp_feature_power_capacity_sum.svx:
--------------------------------------------------------------------------------
1 | #### What's visualised
2 |
3 | Power capacity values (in kW).
4 |
5 | #### Total power capacity
6 |
7 | The sum of the power capacity for all selected heat pumps (in kW).
8 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/property_tenure.svx:
--------------------------------------------------------------------------------
1 | #### What's visualised
2 |
3 | The number of installations by tenure.
4 |
5 | #### Tenure
6 |
7 | The type of legal rights (tenure) the occupant has over the property (e.g. ownership, rental).
8 |
--------------------------------------------------------------------------------
/appTest/src/config.js:
--------------------------------------------------------------------------------
1 | /* testing */
2 |
3 | export const urlBases = {
4 | development: 'http://localhost:4173',
5 | preview: 'https://skeleton.netlify.app', // FIXME update
6 | production: 'https://hpmt-prod.temp-domain', // TODO netlify
7 | };
8 |
--------------------------------------------------------------------------------
/fe/src/lib/components/mdsvex/table.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/hp_feature_power_generation_sum.svx:
--------------------------------------------------------------------------------
1 | #### What's visualised
2 |
3 | Energy values (in kWh).
4 |
5 | #### Total power generation
6 |
7 | The sum of the estimated energy produced in a year by all selected heat pumps (in kWh).
8 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/property_feature_glazed_type.svx:
--------------------------------------------------------------------------------
1 | #### What's visualised
2 |
3 | The number of installations by glazed type.
4 |
5 | #### Glazed type
6 |
7 | The type of glazing used in the property (double-glazing, triple-glazing, etc.).
8 |
--------------------------------------------------------------------------------
/fe/src/lib/utils/date.js:
--------------------------------------------------------------------------------
1 | import * as _ from 'lamb';
2 |
3 | export const formatDateY2 =
4 | date => date.getFullYear().toString().slice(2);
5 |
6 | export const formatDateY4M2 =
7 | date => `${date.getFullYear()}-${_.padLeft(1 + date.getMonth(), 0, 2)}`;
8 |
--------------------------------------------------------------------------------
/fe/src/lib/utils/theme.js:
--------------------------------------------------------------------------------
1 | export const makeSegmentToCssVar =
2 | (themeVars, activeSegmentCssVar, inactiveSegmentCssVar) =>
3 | (segment, string) =>
4 | segment === string
5 | ? themeVars[activeSegmentCssVar]
6 | : themeVars[inactiveSegmentCssVar];
7 |
--------------------------------------------------------------------------------
/be/test/utils.js:
--------------------------------------------------------------------------------
1 | import { buildServer } from '../src/app.js';
2 |
3 | export const buildTestServer = () => buildServer({
4 | disableRequestLogging: true,
5 | logger: {
6 | level: 'info',
7 | transport: {
8 | target: 'pino-pretty'
9 | }
10 | }
11 | });
12 |
--------------------------------------------------------------------------------
/be/src/schemas/cardinality.js:
--------------------------------------------------------------------------------
1 | export const schema = {
2 | schema: {
3 | querystring: {
4 | type: 'object',
5 | required: ['field'],
6 | properties: {
7 | field: { type: 'string' },
8 | missing: { type: 'string' },
9 | }
10 | }
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/property_supply_mains_gas_flag.svx:
--------------------------------------------------------------------------------
1 | #### What's visualised
2 |
3 | The number of installations by presence of heating mains gas.
4 |
5 | #### Heating mains gas?
6 |
7 | Whether the property has a mains gas supply:
8 | - Y: Yes
9 | - N: No
10 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/property_energy_rating_current.svx:
--------------------------------------------------------------------------------
1 | #### What's visualised
2 |
3 | The number of installations by current energy rating.
4 |
5 | #### Current energy rating
6 |
7 | The current energy rating of the property.
8 |
9 | One of: A, B, C, D, E, F, G.
10 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/property_supply_photovoltaic.svx:
--------------------------------------------------------------------------------
1 | #### What's visualised
2 |
3 | The photovoltaic supply value.
4 |
5 | #### Photovoltaic supply
6 |
7 | The photovoltaic area as a percentage of total roof area: 0% indicates that a Photovoltaic Supply is not present in the property.
8 |
--------------------------------------------------------------------------------
/fe/vite.config.js:
--------------------------------------------------------------------------------
1 | import { sveltekit } from '@sveltejs/kit/vite';
2 |
3 | const config = {
4 | plugins: [sveltekit()],
5 | server: {
6 | fs: {
7 | // Allow serving files from one level up to the project root
8 | allow: ['..']
9 | }
10 | }
11 | };
12 |
13 | export default config;
14 |
--------------------------------------------------------------------------------
/be/src/schemas/certified.js:
--------------------------------------------------------------------------------
1 | export const schema = {
2 | schema: {
3 | querystring: {
4 | type: 'object',
5 | properties: {
6 | logic: {
7 | default: 'overlaps',
8 | type: 'string',
9 | enum: ['overlaps', 'engulfs', 'dropped', 'new']
10 | },
11 | }
12 | }
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/be/src/schemas/date_histogram1_certified2.js:
--------------------------------------------------------------------------------
1 | export const schema = {
2 | schema: {
3 | querystring: {
4 | type: 'object',
5 | properties: {
6 | calendar_interval1: {
7 | default: '1y',
8 | type: 'string',
9 | enum: ['1M', '1q', '1y']
10 | },
11 | }
12 | }
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/fe/src/lib/components/svizzle/ui/glyphs/Legend.svelte:
--------------------------------------------------------------------------------
1 |
2 |
7 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/fe/src/routes/explorer/+page.js:
--------------------------------------------------------------------------------
1 | import {redirect} from '@sveltejs/kit';
2 | import {get} from 'svelte/store';
3 |
4 | import {_expectedRoute} from '$lib/stores/navigation.js';
5 |
6 | export function load () {
7 | const expectedRoute = get(_expectedRoute);
8 | throw redirect(301, expectedRoute);
9 | }
10 |
--------------------------------------------------------------------------------
/fe/src/routes/explorer/count/+page.js:
--------------------------------------------------------------------------------
1 | import {redirect} from '@sveltejs/kit';
2 | import {get} from 'svelte/store';
3 |
4 | import {_expectedRoute} from '$lib/stores/navigation.js';
5 |
6 | export function load () {
7 | const expectedRoute = get(_expectedRoute);
8 | throw redirect(301, expectedRoute);
9 | }
10 |
--------------------------------------------------------------------------------
/fe/src/routes/explorer/number/+page.js:
--------------------------------------------------------------------------------
1 | import {redirect} from '@sveltejs/kit';
2 | import {get} from 'svelte/store';
3 |
4 | import {_expectedRoute} from '$lib/stores/navigation.js';
5 |
6 | export function load () {
7 | const expectedRoute = get(_expectedRoute);
8 | throw redirect(301, expectedRoute);
9 | }
10 |
--------------------------------------------------------------------------------
/fe/src/routes/explorer/string/+page.js:
--------------------------------------------------------------------------------
1 | import {redirect} from '@sveltejs/kit';
2 | import {get} from 'svelte/store';
3 |
4 | import {_expectedRoute} from '$lib/stores/navigation.js';
5 |
6 | export function load () {
7 | const expectedRoute = get(_expectedRoute);
8 | throw redirect(301, expectedRoute);
9 | }
10 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/property_energy_rating_potential.svx:
--------------------------------------------------------------------------------
1 | #### What's visualised
2 |
3 | The number of installations by potential energy rating.
4 |
5 | #### Potential energy rating
6 |
7 | The potential energy rating that the property could achieve with improvements.
8 |
9 | One of: A, B, C, D, E, F, G.
10 |
--------------------------------------------------------------------------------
/fe/src/lib/components/mdsvex/blockquote.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
--------------------------------------------------------------------------------
/fe/src/routes/explorer/category/+page.js:
--------------------------------------------------------------------------------
1 | import {redirect} from '@sveltejs/kit';
2 | import {get} from 'svelte/store';
3 |
4 | import {_expectedRoute} from '$lib/stores/navigation.js';
5 |
6 | export function load () {
7 | const expectedRoute = get(_expectedRoute);
8 | throw redirect(301, expectedRoute);
9 | }
10 |
--------------------------------------------------------------------------------
/be/src/schemas/stats.js:
--------------------------------------------------------------------------------
1 |
2 | export const schema = {
3 | schema: {
4 | querystring: {
5 | type: 'object',
6 | required: ['field'],
7 | properties: {
8 | field: { type: 'string' },
9 | use_extended_stats: {
10 | type: 'boolean',
11 | default: false
12 | }
13 | }
14 | }
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/fe/src/lib/components/explorer/MetricTitle.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 | {$_currentMetricTitle}
6 |
7 |
15 |
--------------------------------------------------------------------------------
/be/src/routes/count.js:
--------------------------------------------------------------------------------
1 | import { index } from '../conf.js';
2 | import { getXCompatibleCount } from '../es.js';
3 |
4 | export const getCount = async (request, reply) => {
5 |
6 | const body = {
7 | ...request.filter,
8 | };
9 | const result = await getXCompatibleCount({ body, index });
10 |
11 | reply.send(result);
12 | };
13 |
--------------------------------------------------------------------------------
/fe/src/lib/components/explorer/SelectorInterval.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
11 |
--------------------------------------------------------------------------------
/fe/src/lib/components/mdsvex/code.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
--------------------------------------------------------------------------------
/be/src/schemas/date_histogram.js:
--------------------------------------------------------------------------------
1 | export const schema = {
2 | schema: {
3 | querystring: {
4 | type: 'object',
5 | required: ['field'],
6 | properties: {
7 | calendar_interval: {
8 | default: '1y',
9 | type: 'string',
10 | enum: ['1M', '1q', '1y']
11 | },
12 | field: { type: 'string' },
13 | }
14 | }
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/property_feature_built_form.svx:
--------------------------------------------------------------------------------
1 | #### What's visualised
2 |
3 | The number of installations by property built form.
4 |
5 | #### Built form
6 |
7 | The building type of the Property e.g. Detached, Semi-Detached, Terrace etc.
8 |
9 | Together with the Property Type, the Build Form produces a structured description of the property.
10 |
--------------------------------------------------------------------------------
/fe/src/routes/info/privacy/+page.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 | Privacy - {toolName}
8 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/be/src/bin/generateMbTiles.sh:
--------------------------------------------------------------------------------
1 | cd tiles
2 | for file in `ls geojson`; do
3 | echo $file
4 | name=${file%.geojson}
5 | tippecanoe -o mbtiles/$name.mbtiles -z14 geojson/$file --force
6 | done
7 |
8 | name=nuts21_0_country21_itl21_1_itl21_2_itl21_3_lad21_msoa11_lsoa11
9 |
10 | tile-join -o $name.mbtiles mbtiles/* --force -pk
11 | pmtiles convert $name.mbtiles $name.pmtiles
--------------------------------------------------------------------------------
/fe/src/lib/utils/color.js:
--------------------------------------------------------------------------------
1 | import * as _ from 'lamb';
2 |
3 | import {interpolateColor} from '$lib/config/colors.js';
4 |
5 | export const getItemsColorScheme = items => {
6 | const range = items.length === 1
7 | ? [0]
8 | : _.range(0, 1, 1 / (items.length - 1)).concat(1);
9 | const colorScheme = _.map(range, interpolateColor);
10 |
11 | return colorScheme;
12 | }
13 |
--------------------------------------------------------------------------------
/fe/src/routes/info/disclaimer/+page.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 | Disclaimer - {toolName}
9 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/hp_feature_design.svx:
--------------------------------------------------------------------------------
1 | #### What's visualised
2 |
3 | The number of installations by heat pump design.
4 |
5 | #### Design
6 |
7 | What the heat pump has been designed and installed to provide.
8 |
9 | This can be:
10 | - Domestic Hot Water (here abbreviated as `DHW`)
11 | - Space Heat
12 | - Another purpose
13 | - a combination of the above purposes
14 |
--------------------------------------------------------------------------------
/be/src/server.js:
--------------------------------------------------------------------------------
1 | import { buildServer } from './app.js';
2 |
3 | const { HOST='localhost' } = process.env;
4 |
5 | const start = async () => {
6 | const server = await buildServer({ logger: true });
7 |
8 | try {
9 | await server.listen({ host: HOST, port: 3000 });
10 | } catch (err) {
11 | server.log.error(err);
12 | throw new Error(err);
13 | }
14 | };
15 |
16 | start();
17 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/guides/A11ymenuIntro.svx:
--------------------------------------------------------------------------------
1 | ### Toggling the accessibility menu
2 |
3 | Clicking the accessibility icon should:
4 | * open the menu if it was closed,
5 | * close it if it was open.
6 |
7 | When the menu is open, it should appear at the centre of the screen slightly
8 | toward the bottom.
9 |
10 | To know how to use it, please check [this guide](/guides/a11ymenu).
11 |
--------------------------------------------------------------------------------
/fe/src/lib/components/svizzle/ui/actions/scrollIntoView.js:
--------------------------------------------------------------------------------
1 | import {tick} from 'svelte';
2 |
3 | export const scrollIntoViewIfTrue = async (node, doScroll) => {
4 | await tick();
5 |
6 | if (node && doScroll) {
7 | if (node.scrollIntoViewIfNeeded) {
8 | node.scrollIntoViewIfNeeded(true); // Chrome/Safari/Edge
9 | } else {
10 | node.scrollIntoView(true); // FF
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/fe/src/routes/guides/a11ymenu/+page.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 | Accessibility menu guide - {toolName}
9 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/be/src/schemas/histogram.js:
--------------------------------------------------------------------------------
1 | import { maxBins } from '../conf.js';
2 |
3 | export const schema = {
4 | schema: {
5 | querystring: {
6 | type: 'object',
7 | required: ['field'],
8 | properties: {
9 | bins: {
10 | default: 10,
11 | type: 'integer',
12 | minimum: 1,
13 | maximum: maxBins
14 | },
15 | field: { type: 'string' },
16 | }
17 | }
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/be/src/schemas/terms1_certified2.js:
--------------------------------------------------------------------------------
1 | export const schema = {
2 | schema: {
3 | querystring: {
4 | type: 'object',
5 | required: ['field1'],
6 | properties: {
7 | field1: {
8 | type: 'string'
9 | },
10 | logic2: {
11 | default: 'overlaps',
12 | type: 'string',
13 | enum: ['overlaps', 'engulfs', 'new', 'dropped']
14 | }
15 | }
16 | }
17 | }
18 | };
19 |
--------------------------------------------------------------------------------
/fe/src/lib/components/mdsvex/a.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 |
18 |
--------------------------------------------------------------------------------
/appTest/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Nesta HPMT frontend tests",
3 | "devDependencies": {
4 | "lighthouse": "^11.1.0",
5 | "pa11y": "^6.2.3",
6 | "pa11y-reporter-html": "^2.0.0"
7 | },
8 | "license": "MIT",
9 | "name": "nesta_hpmt_frontend_tests",
10 | "scripts": {
11 | "lighthouse": "node src/lighthouse/validate.js",
12 | "pa11y": "node src/pa11y/validate.js"
13 | },
14 | "type": "module"
15 | }
16 |
--------------------------------------------------------------------------------
/fe/src/routes/methodology/geography/+page.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 | Methodology - {toolName}
9 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/property_feature_glazed_area.svx:
--------------------------------------------------------------------------------
1 | #### What's visualised
2 |
3 | The value of the property glazed area.
4 |
5 | #### Glazed area
6 |
7 | Ranged estimate of the total glazed area of the Habitable Area.
8 |
9 | It's a category with values between 1 and 5, representing:
10 |
11 | - 1: Much Less Than Typical
12 | - 2: Less Than Typical
13 | - 3: Normal
14 | - 4: More Than Typical
15 | - 5: Much More Than Typical
16 |
--------------------------------------------------------------------------------
/fe/static/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "background_color": "#ffffff",
3 | "theme_color": "#0189c6",
4 | "name": "app_skeleton",
5 | "short_name": "app_skeleton",
6 | "display": "minimal-ui",
7 | "start_url": "/",
8 | "icons": [
9 | {
10 | "src": "logo-192.png",
11 | "sizes": "192x192",
12 | "type": "image/png"
13 | },
14 | {
15 | "src": "logo-512.png",
16 | "sizes": "512x512",
17 | "type": "image/png"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/fe/src/routes/methodology/dataProcessing/+page.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 | Data processing methodology - {toolName}
9 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/fe/src/lib/stores/geometry.js:
--------------------------------------------------------------------------------
1 | import {derived} from 'svelte/store';
2 |
3 | import {_glyph} from '$lib/stores/layout.js';
4 |
5 | export const _glyphGeometry = derived(
6 | _glyph,
7 | glyph => ({
8 | glyphHeight: glyph?.height,
9 | glyphWidth: glyph?.width,
10 | })
11 | );
12 |
13 | export const _barchartGeometry = derived(
14 | _glyph,
15 | glyph => ({
16 | glyphHeight: glyph?.height,
17 | glyphWidth: glyph?.width,
18 | padding: 0,
19 | })
20 | );
21 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/property_feature_age_band.svx:
--------------------------------------------------------------------------------
1 |
5 |
6 | #### What's visualised
7 |
8 | The number of installations by property age band.
9 |
10 | #### Age band
11 |
12 | Age band when building part constructed.
13 |
14 | One of:
15 |
16 |
17 | {#each ageBandOrder as key}
18 | {key}
19 | {/each}
20 |
21 |
--------------------------------------------------------------------------------
/fe/src/lib/components/explorer/SelectorRegionType.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 |
15 |
--------------------------------------------------------------------------------
/fe/src/lib/utils/unescape-inlineCode.js:
--------------------------------------------------------------------------------
1 | import {visit} from 'unist-util-visit';
2 |
3 | const entites = [
4 | [/{/gu, '{' ],
5 | [/}/gu, '}' ],
6 | ];
7 |
8 | export function unescape_code () {
9 | return function (tree) {
10 | function unescape (node) {
11 | for (let i = 0; i < entites.length; i += 1) {
12 | node.value = node.value.replace(entites[i][0], entites[i][1]);
13 | }
14 | }
15 | visit(tree, 'inlineCode', unescape);
16 | };
17 | }
18 |
--------------------------------------------------------------------------------
/fe/src/lib/components/svizzle/ui/geometryObserver.js:
--------------------------------------------------------------------------------
1 | import {writable} from 'svelte/store';
2 |
3 | export const setupGeometryObserver = () => {
4 | const _geometry = writable();
5 |
6 | const geometryObserver = node => {
7 | const observer = new ResizeObserver(() => {
8 | _geometry.set(node.getBoundingClientRect())
9 | });
10 | observer.observe(node);
11 |
12 | return () => observer.disconnect();
13 | }
14 |
15 | return {_geometry, geometryObserver}
16 | }
17 |
--------------------------------------------------------------------------------
/fe/src/lib/components/explorer/NoData.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
18 |
19 |
--------------------------------------------------------------------------------
/be/src/schemas/date_histogram1_cardinality2.js:
--------------------------------------------------------------------------------
1 | export const schema = {
2 | schema: {
3 | querystring: {
4 | type: 'object',
5 | required: ['field1', 'field2'],
6 | properties: {
7 | // 1
8 | calendar_interval1: {
9 | default: '1y',
10 | type: 'string',
11 | enum: ['1M', '1q', '1y']
12 | },
13 | field1: { type: 'string' },
14 | // 2
15 | field2: { type: 'string' },
16 | missing2: { type: 'string' },
17 | }
18 | }
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/be/src/app.js:
--------------------------------------------------------------------------------
1 | import cors from '@fastify/cors';
2 | import fastify from 'fastify';
3 |
4 | import { onRequest, formatPayload } from './hooks.js';
5 | import { routes } from './routes.js';
6 |
7 | export const buildServer = async (opts = {}) => {
8 | const server = fastify(opts);
9 | await server.register(cors, { origin: '*' });
10 | server.addHook('onRequest', onRequest);
11 | server.addHook('preSerialization', formatPayload);
12 | server.register(routes);
13 | return server;
14 | };
15 |
--------------------------------------------------------------------------------
/fe/src/lib/components/svizzle/SizeSensor.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/fe/src/lib/stores/layout.js:
--------------------------------------------------------------------------------
1 | import {_screen} from '@svizzle/ui';
2 | import {derived} from 'svelte/store';
3 |
4 | /* responsive */
5 |
6 | export const _screenId = derived(
7 | _screen,
8 | s => s && s.sizes.medium ? 'medium': 'small'
9 | );
10 |
11 | export const _isSmallScreen = derived(
12 | _screen,
13 | s => s && (s.sizes.small && !s.sizes.medium)
14 | );
15 | export const _screenClasses = derived(_screen, s => s?.classes);
16 | export const _glyph = derived(_screen, s => s?.glyph);
17 |
--------------------------------------------------------------------------------
/fe/src/lib/env.js:
--------------------------------------------------------------------------------
1 | export const isDev = import.meta.env.DEV;
2 |
3 | const backendEnv = import.meta.env?.VITE_BE_ENV || 'dev'; // see `fe/netlify.toml`
4 | const beURLs = {
5 | local: 'http://localhost:3000',
6 | dev: 'https://hpmt.be.dev.dap-tools.uk',
7 | staging: 'https://hpmt.be.staging.dap-tools.uk',
8 | production: 'https://hpmt.be.production.dap-tools.uk'
9 | };
10 | export const selectedBeURL = beURLs[backendEnv];
11 |
12 | export const themeOverride = import.meta.env?.VITE_THEME_OVERRIDE;
13 |
--------------------------------------------------------------------------------
/fe/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | %sveltekit.head%
11 |
12 |
13 | %sveltekit.body%
14 |
15 |
16 |
--------------------------------------------------------------------------------
/fe/src/lib/components/svizzle/ui/ScrollIntoView.svelte:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/be/src/bin/saveNuts0.js:
--------------------------------------------------------------------------------
1 | import NUTS_RG_03M_2021_4326_LEVL_0 from '@svizzle/atlas/data/dist/NUTS/topojson/NUTS_RG_03M_2021_4326_LEVL_0.js'
2 | import { saveObj } from '@svizzle/file';
3 | import { topoToGeo } from '@svizzle/geo';
4 |
5 |
6 | try {
7 | const geojson = topoToGeo(NUTS_RG_03M_2021_4326_LEVL_0, 'NUTS');
8 | const save = saveObj('tiles/geojson/nuts21_0.geojson');
9 | await save(geojson);
10 | console.log("Saved nuts0 successfully")
11 | } catch (e) {
12 | console.error(e);
13 | throw new Error('Saving nuts0 failed');
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/fe/src/lib/utils/svizzle/url.js:
--------------------------------------------------------------------------------
1 | import {isArray, isObject} from '@svizzle/utils';
2 | import * as _ from 'lamb';
3 | import {RISON} from 'rison2';
4 |
5 | export const objectToSearchParams = _.pipe([
6 | _.pairs,
7 | _.mapWith(_.joinWith('=')),
8 | _.joinWith('&')
9 | ]);
10 |
11 | const escapeAmpersand = string => string.replace(/&/gu, '%26');
12 |
13 | export const risonifyValues = _.mapValuesWith(
14 | _.when(
15 | _.anyOf([isArray, isObject]),
16 | _.pipe([
17 | RISON.stringify,
18 | escapeAmpersand
19 | ])
20 | )
21 | );
22 |
--------------------------------------------------------------------------------
/be/test/routes/api/count/01.json:
--------------------------------------------------------------------------------
1 | {
2 | "query": {
3 | "method": "GET",
4 | "url": "count"
5 | },
6 | "response": {
7 | "code": 200,
8 | "data": {
9 | "count": 240509,
10 | "_shards": {
11 | "total": 1,
12 | "successful": 1,
13 | "skipped": 0,
14 | "failed": 0
15 | }
16 | },
17 | "message": "aggregation successful",
18 | "request": {
19 | "agg": {
20 | "id": "count",
21 | "params": {}
22 | },
23 | "filter": {
24 | "query": {
25 | "match_all": {}
26 | }
27 | }
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/property_energy_efficiency_floor.svx:
--------------------------------------------------------------------------------
1 |
5 |
6 | #### What's visualised
7 |
8 | The number of installations by floor energy efficiency.
9 |
10 | #### Floor energy efficiency
11 |
12 | A qualitative description of the energy efficiency of the floor of the property.
13 |
14 | One of:
15 |
16 |
17 | {#each energyEfficiencyOrder as key}
18 | {key}
19 | {/each}
20 |
21 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/property_energy_efficiency_roof.svx:
--------------------------------------------------------------------------------
1 |
5 |
6 | #### What's visualised
7 |
8 | The number of installations by roof heat energy efficiency.
9 |
10 | #### Roof heat energy efficiency
11 |
12 | A qualitative description of the energy efficiency of the roof of the property.
13 |
14 | One of:
15 |
16 |
17 | {#each energyEfficiencyOrder as key}
18 | {key}
19 | {/each}
20 |
21 |
--------------------------------------------------------------------------------
/fe/src/routes/guides/app/+page.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 | App guide - {toolName}
12 |
16 |
17 |
18 | {#if $_screen?.sizes.medium}
19 |
20 | {:else}
21 |
22 | {/if}
23 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/property_energy_efficiency_walls.svx:
--------------------------------------------------------------------------------
1 |
5 |
6 | #### What's visualised
7 |
8 | The number of installations by walls heat energy efficiency.
9 |
10 | #### Walls heat energy efficiency
11 |
12 | A qualitative description of the energy efficiency of the walls of the property.
13 |
14 | One of:
15 |
16 |
17 | {#each energyEfficiencyOrder as key}
18 | {key}
19 | {/each}
20 |
21 |
--------------------------------------------------------------------------------
/be/test/routes/api/certified/01.json:
--------------------------------------------------------------------------------
1 | {
2 | "query": {
3 | "method": "GET",
4 | "url": "certified",
5 | "query": {
6 | "calendar_interval": "1y"
7 | }
8 | },
9 | "response": {
10 | "code": 200,
11 | "data": {
12 | "count": 2300
13 | },
14 | "message": "aggregation successful",
15 | "request": {
16 | "agg": {
17 | "id": "certified",
18 | "params": {
19 | "calendar_interval": "1y",
20 | "logic": "overlaps"
21 | }
22 | },
23 | "filter": {
24 | "query": {
25 | "match_all": {}
26 | }
27 | }
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/be/test/routes/api/certified/02.json:
--------------------------------------------------------------------------------
1 | {
2 | "query": {
3 | "method": "GET",
4 | "url": "certified",
5 | "query": {
6 | "calendar_interval": "1q"
7 | }
8 | },
9 | "response": {
10 | "code": 200,
11 | "data": {
12 | "count": 2300
13 | },
14 | "message": "aggregation successful",
15 | "request": {
16 | "agg": {
17 | "id": "certified",
18 | "params": {
19 | "calendar_interval": "1q",
20 | "logic": "overlaps"
21 | }
22 | },
23 | "filter": {
24 | "query": {
25 | "match_all": {}
26 | }
27 | }
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/be/test/routes/api/certified/03.json:
--------------------------------------------------------------------------------
1 | {
2 | "query": {
3 | "method": "GET",
4 | "url": "certified",
5 | "query": {
6 | "calendar_interval": "1M"
7 | }
8 | },
9 | "response": {
10 | "code": 200,
11 | "data": {
12 | "count": 2300
13 | },
14 | "message": "aggregation successful",
15 | "request": {
16 | "agg": {
17 | "id": "certified",
18 | "params": {
19 | "calendar_interval": "1M",
20 | "logic": "overlaps"
21 | }
22 | },
23 | "filter": {
24 | "query": {
25 | "match_all": {}
26 | }
27 | }
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/property_energy_efficiency_lighting.svx:
--------------------------------------------------------------------------------
1 |
5 |
6 | #### What's visualised
7 |
8 | The number of installations by lighting energy efficiency.
9 |
10 | #### Lighting energy efficiency
11 |
12 | A qualitative description of the energy efficiency of the lighting used in the property.
13 |
14 | One of:
15 |
16 |
17 | {#each energyEfficiencyOrder as key}
18 | {key}
19 | {/each}
20 |
21 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/property_energy_efficiency_windows.svx:
--------------------------------------------------------------------------------
1 |
5 |
6 | #### What's visualised
7 |
8 | The number of installations by windows heat energy efficiency.
9 |
10 | #### Windows heat energy efficiency
11 |
12 | A qualitative description of the energy efficiency of the windows of the property.
13 |
14 | One of:
15 |
16 |
17 | {#each energyEfficiencyOrder as key}
18 | {key}
19 | {/each}
20 |
21 |
--------------------------------------------------------------------------------
/be/test/filter/requests/01.json:
--------------------------------------------------------------------------------
1 | {
2 | "request": {
3 | "property_feature_type": [
4 | "House"
5 | ]
6 | },
7 | "query": {
8 | "query": {
9 | "bool": {
10 | "filter": [
11 | {
12 | "terms": {
13 | "property_feature_type.keyword": [
14 | "House"
15 | ]
16 | }
17 | }
18 | ]
19 | }
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/property_energy_efficiency_hot_water.svx:
--------------------------------------------------------------------------------
1 |
5 |
6 | #### What's visualised
7 |
8 | The number of installations by hot water energy efficiency.
9 |
10 | #### Hot water energy efficiency
11 |
12 | A qualitative description of the energy efficiency of the hot water system of the property.
13 |
14 | One of:
15 |
16 |
17 | {#each energyEfficiencyOrder as key}
18 | {key}
19 | {/each}
20 |
21 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/metrics/property_energy_efficiency_main_heat.svx:
--------------------------------------------------------------------------------
1 |
5 |
6 | #### What's visualised
7 |
8 | The number of installations by main heat energy efficiency.
9 |
10 | #### Main heat energy efficiency
11 |
12 | A qualitative description of the energy efficiency of the main heating system of the property.
13 |
14 | One of:
15 |
16 |
17 | {#each energyEfficiencyOrder as key}
18 | {key}
19 | {/each}
20 |
21 |
--------------------------------------------------------------------------------
/fe/src/lib/components/explorer/FlexBar.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 |
18 |
19 |
20 |
21 |
29 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "nestauk",
3 | "description": "Nesta Heat Pump Market tracker",
4 | "license": "MIT",
5 | "name": "nesta_hpmt",
6 | "private": true,
7 | "scripts": {
8 | "clearmodules": "rm -rf node_modules be/node_modules fe/node_modules mapbox/node_modules shared/node_modules",
9 | "clearlockfile": "rm -rf package-lock.json",
10 | "clearall": "npm run clearmodules && npm run clearlockfile",
11 | "reinstall": "npm run clearall && npm install"
12 | },
13 | "version": "0.8.0",
14 | "workspaces": [
15 | "be",
16 | "fe",
17 | "shared"
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/fe/src/lib/utils/svizzle/utils.js:
--------------------------------------------------------------------------------
1 | import {
2 | areAllTruthy,
3 | areEqual,
4 | getLength,
5 | pluckKey,
6 | trim,
7 | } from '@svizzle/utils';
8 | import * as _ from 'lamb';
9 |
10 | export const pluckKeySorted = _.pipe([pluckKey, _.sortWith()]);
11 |
12 | export const areAllFalsyWith = accessor => _.every(_.not(accessor));
13 |
14 | export const doPairItemsContainSameValues = _.allOf([
15 | _.pipe([_.mapWith(getLength), _.apply(_.areSame)]),
16 | _.pipe([
17 | _.mapWith(_.sortWith([])),
18 | _.apply(_.zip),
19 | _.mapWith(areEqual),
20 | areAllTruthy
21 | ])
22 | ]);
23 |
--------------------------------------------------------------------------------
/be/es/copy_pipeline.conf:
--------------------------------------------------------------------------------
1 | input {
2 | elasticsearch {
3 | hosts => "${LOGSTASH_INPUT_DOMAIN}:9200"
4 | user => "elastic"
5 | password => "${LOGSTASH_INPUT_PASSWORD}"
6 | index=> "${LOGSTASH_INPUT_INDEX}"
7 | ssl => true
8 | ca_file => "${LOGSTASH_INPUT_CERT}"
9 | }
10 | }
11 |
12 | output {
13 | opensearch {
14 | hosts => ["${LOGSTASH_OUTPUT_DOMAIN}:443"]
15 | auth_type => {
16 | type => 'basic'
17 | user => 'elastic'
18 | password => "${LOGSTASH_OUTPUT_PASSWORD}"
19 | }
20 | index=> "${LOGSTASH_OUTPUT_INDEX}"
21 | cs_compatibility => disabled
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/info/PrivacyBanner.svx:
--------------------------------------------------------------------------------
1 | Cookies are files saved on your phone, tablet or computer when you visit a
2 | website.
3 |
4 | We only use essential cookies on this website:
5 |
6 | * the tool itself does not collect any personal data.
7 | * the feedback form is using
8 | [Google Forms](https://www.google.com/forms/about/),
9 | which installs cookies in your browser.
10 |
11 | By submitting data with Google Forms you accept the
12 | [Terms of Service](https://policies.google.com/terms) and the
13 | [Data Privacy Policy of Google Inc](https://policies.google.com/privacy).
14 |
--------------------------------------------------------------------------------
/fe/src/lib/utils/download.js:
--------------------------------------------------------------------------------
1 | import {saveAs} from 'file-saver';
2 | import JSZip from 'jszip';
3 | import * as _ from 'lamb';
4 |
5 | /* zip */
6 |
7 | export const getZippedFiles = async files => {
8 | const zipper = new JSZip();
9 |
10 | _.pairs(files)
11 | .forEach(([name, content]) => zipper.file(name, content));
12 |
13 | const zipBlob = await zipper.generateAsync({type: 'blob'});
14 |
15 | return zipBlob;
16 | }
17 |
18 | export const initiateZippedDownload = async (zipName, files) => {
19 | const content = await getZippedFiles(files);
20 | saveAs(content, zipName);
21 | }
22 |
--------------------------------------------------------------------------------
/be/src/routes/cardinality.js:
--------------------------------------------------------------------------------
1 | import { index } from '../conf.js';
2 | import { client } from '../es.js';
3 |
4 | export const getCardinality = async (request, reply) => {
5 | const {
6 | field,
7 | missing = null,
8 | } = request.query;
9 |
10 | const body = {
11 | ...request.filter,
12 | size: 0,
13 | aggs: {
14 | cardinality: {
15 | cardinality: {
16 | field,
17 | ...missing && { missing }
18 | }
19 | }
20 | }
21 | };
22 |
23 | const result = await client.search({
24 | body,
25 | index
26 | });
27 |
28 | reply.send(result.aggregations);
29 | };
30 |
--------------------------------------------------------------------------------
/be/src/schemas/date_histogram1_histogram2.js:
--------------------------------------------------------------------------------
1 | import { maxBins } from '../conf.js';
2 |
3 | export const schema = {
4 | schema: {
5 | querystring: {
6 | type: 'object',
7 | required: ['field1', 'field2'],
8 | properties: {
9 | // 1
10 | calendar_interval1: {
11 | default: '1y',
12 | type: 'string',
13 | enum: ['1M', '1q', '1y']
14 | },
15 | field1: { type: 'string' },
16 | // 2
17 | bins2: {
18 | default: 10,
19 | type: 'integer',
20 | minimum: 1,
21 | maximum: maxBins
22 | },
23 | field2: { type: 'string' },
24 | }
25 | }
26 | }
27 | };
28 |
--------------------------------------------------------------------------------
/be/src/schemas/date_histogram1_stats2.js:
--------------------------------------------------------------------------------
1 | export const schema = {
2 | schema: {
3 | querystring: {
4 | type: 'object',
5 | required: ['field1', 'field2'],
6 | properties: {
7 | // 1
8 | calendar_interval1: {
9 | default: '1y',
10 | type: 'string',
11 | enum: ['1M', '1q', '1y']
12 | },
13 | field1: { type: 'string' },
14 | // 2
15 | field2: { type: 'string' },
16 | use_extended_stats2: {
17 | default: false,
18 | type: 'boolean'
19 | },
20 | use_percentiles2: {
21 | default: false,
22 | type: 'boolean'
23 | }
24 | }
25 | }
26 | }
27 | };
28 |
--------------------------------------------------------------------------------
/fe/src/lib/components/explorer/FilterPaneBorder.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
11 |
12 |
13 |
14 |
29 |
--------------------------------------------------------------------------------
/be/src/routes/stats.js:
--------------------------------------------------------------------------------
1 | import { index } from '../conf.js';
2 | import { client } from '../es.js';
3 |
4 | export const getStats = async (request, reply) => {
5 | const {
6 | field,
7 | use_extended_stats = false,
8 | } = request.query;
9 |
10 | const statQuery = use_extended_stats ? 'extended_stats' : 'stats';
11 |
12 | const body = {
13 | ...request.filter,
14 | size: 0,
15 | aggs: {
16 | stats: {
17 | [statQuery]: {
18 | field,
19 | }
20 | }
21 | }
22 | };
23 |
24 | const result = await client.search({
25 | body,
26 | index
27 | });
28 |
29 | reply.send(result.aggregations);
30 | };
31 |
--------------------------------------------------------------------------------
/fe/netlify.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | command = "cd .. && npm install && cd fe && npm run build"
3 | publish = ".svelte-kit/netlify"
4 |
5 | [build.environment]
6 | ADAPTER = "netlify"
7 | NODE_VERSION = "18.13.0"
8 | VITE_BE_ENV = "dev"
9 |
10 | [functions]
11 | # temporary workaround for https://github.com/sveltejs/kit/issues/6462
12 | node_bundler = "esbuild"
13 |
14 | [context.dev.environment]
15 | VITE_BE_ENV = "dev"
16 |
17 | [context.staging.environment]
18 | VITE_BE_ENV = "staging"
19 |
20 | [context.production.environment]
21 | VITE_BE_ENV = "production"
22 |
23 | # re: `VITE_BE_ENV` please see `fe/src/lib/env.js`
24 |
--------------------------------------------------------------------------------
/be/src/routes/date_histogram.js:
--------------------------------------------------------------------------------
1 | import { index } from '../conf.js';
2 | import { client } from '../es.js';
3 |
4 | export const getDateHistogram = async (request, reply) => {
5 | const {
6 | calendar_interval = '1y',
7 | field,
8 | } = request.query;
9 |
10 | const body = {
11 | ...request.filter,
12 | size: 0,
13 | aggs: {
14 | date_histogram: {
15 | date_histogram: {
16 | field,
17 | calendar_interval,
18 | format: 'yyyy-MM'
19 | }
20 | }
21 | }
22 | };
23 |
24 | const result = await client.search({
25 | body,
26 | index
27 | });
28 |
29 | reply.send(result.aggregations);
30 | };
31 |
--------------------------------------------------------------------------------
/be/src/schemas/terms1_histogram2.js:
--------------------------------------------------------------------------------
1 | import { maxBuckets, maxBins } from '../conf.js';
2 |
3 | export const schema = {
4 | schema: {
5 | querystring: {
6 | type: 'object',
7 | required: ['field1', 'field2'],
8 | properties: {
9 | // 1
10 | field1: { type: 'string' },
11 | size1: {
12 | default: maxBuckets,
13 | type: 'integer',
14 | minimum: 1,
15 | maximum: maxBuckets
16 | },
17 | // 2
18 | bins2: {
19 | default: 10,
20 | type: 'integer',
21 | minimum: 1,
22 | maximum: maxBins
23 | },
24 | field2: { type: 'string' },
25 | }
26 | }
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/be/test/routes/api/cardinality/01.json:
--------------------------------------------------------------------------------
1 | {
2 | "query": {
3 | "method": "GET",
4 | "url": "cardinality",
5 | "query": {
6 | "field": "property_energy_efficiency_windows.keyword"
7 | }
8 | },
9 | "response": {
10 | "code": 200,
11 | "data": {
12 | "cardinality": {
13 | "value": 5
14 | }
15 | },
16 | "message": "aggregation successful",
17 | "request": {
18 | "agg": {
19 | "id": "cardinality",
20 | "params": {
21 | "field": "property_energy_efficiency_windows.keyword"
22 | }
23 | },
24 | "filter": {
25 | "query": {
26 | "match_all": {}
27 | }
28 | }
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/be/test/filter/requests/03.json:
--------------------------------------------------------------------------------
1 | {
2 | "request": {
3 | "hp_feature_flow_temperature": {
4 | "gt": 10,
5 | "lt": 50
6 | }
7 | },
8 | "query": {
9 | "query": {
10 | "bool": {
11 | "filter": [
12 | {
13 | "range": {
14 | "hp_feature_flow_temperature": {
15 | "gt": 10,
16 | "lt": 50
17 | }
18 | }
19 | }
20 | ]
21 | }
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/fe/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "checkJs": false,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "strict": false
12 | }
13 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias and https://kit.svelte.dev/docs/configuration#files
14 | //
15 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
16 | // from the referenced tsconfig.json - TypeScript does not merge them in
17 | }
18 |
--------------------------------------------------------------------------------
/fe/src/lib/components/layout/Nav.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 | {#if $_isSmallScreen || isServerSide}
15 |
21 | {:else}
22 |
23 | {/if}
24 |
--------------------------------------------------------------------------------
/fe/src/routes/feedback/survey/+page.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 | Survey - {toolName}
7 |
11 |
12 |
13 |
14 |
21 |
22 |
23 |
32 |
--------------------------------------------------------------------------------
/fe/src/routes/feedback/survey2/+page.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 | Add your org - {toolName}
7 |
11 |
12 |
13 |
14 |
21 |
22 |
23 |
32 |
--------------------------------------------------------------------------------
/be/test/routes/api/cardinality/02.json:
--------------------------------------------------------------------------------
1 | {
2 | "query": {
3 | "method": "GET",
4 | "url": "cardinality",
5 | "query": {
6 | "field": "property_energy_efficiency_windows.keyword",
7 | "missing": "missing"
8 | }
9 | },
10 | "response": {
11 | "code": 200,
12 | "data": {
13 | "cardinality": {
14 | "value": 6
15 | }
16 | },
17 | "message": "aggregation successful",
18 | "request": {
19 | "agg": {
20 | "id": "cardinality",
21 | "params": {
22 | "field": "property_energy_efficiency_windows.keyword",
23 | "missing": "missing"
24 | }
25 | },
26 | "filter": {
27 | "query": {
28 | "match_all": {}
29 | }
30 | }
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/fe/src/lib/_content/guides/AppSmall.svx:
--------------------------------------------------------------------------------
1 |
4 |
5 | ## Navigating the app
6 |
7 | ### Structure of the page
8 |
9 | In the lower part of the screen you have a navigation bar with, from the right:
10 | * an icon with three lines (a so-called "hamburger") to open
11 | the navigation menu
12 | * the accessibility icon, to toggle the accessibility menu
13 | * a theme switcher button to select between light and dark colour themes
14 | * the name of the current page
15 |
16 | ### Navigating to a new page
17 |
18 | Clicking on the hamburger should show a list of links to navigate to other
19 | pages.
20 |
21 |
22 |
--------------------------------------------------------------------------------
/be/src/schemas/terms.js:
--------------------------------------------------------------------------------
1 | import { maxBuckets } from '../conf.js';
2 |
3 | export const schema = {
4 | schema: {
5 | querystring: {
6 | type: 'object',
7 | required: ['field'],
8 | properties: {
9 | field: { type: 'string' },
10 | missing: { type: 'string' },
11 | size: {
12 | default: maxBuckets,
13 | type: 'integer',
14 | minimum: 1,
15 | maximum: maxBuckets
16 | },
17 | use_extended_stats: {
18 | default: false,
19 | type: 'boolean'
20 | },
21 | with_percentiles: {
22 | default: false,
23 | type: 'boolean'
24 | },
25 | with_stats: {
26 | default: false,
27 | type: 'boolean'
28 | },
29 | }
30 | }
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/be/src/schemas/terms1_stats2.js:
--------------------------------------------------------------------------------
1 | import { maxBuckets } from '../conf.js';
2 |
3 | export const schema = {
4 | schema: {
5 | querystring: {
6 | type: 'object',
7 | required: ['field1', 'field2'],
8 | properties: {
9 | // 1
10 | field1: { type: 'string' },
11 | missing1: { type: 'string' },
12 | size1: {
13 | default: maxBuckets,
14 | type: 'integer',
15 | minimum: 1,
16 | maximum: maxBuckets
17 | },
18 | // 2
19 | field2: { type: 'string' },
20 | use_extended_stats2: {
21 | default: false,
22 | type: 'boolean'
23 | },
24 | use_percentiles2: {
25 | default: false,
26 | type: 'boolean'
27 | }
28 | }
29 | }
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/be/test/routes/api/stats/01.json:
--------------------------------------------------------------------------------
1 | {
2 | "query": {
3 | "method": "GET",
4 | "url": "stats",
5 | "query": {
6 | "field": "installation_cost"
7 | }
8 | },
9 | "response": {
10 | "code": 200,
11 | "data": {
12 | "stats": {
13 | "count": 116218,
14 | "min": 1,
15 | "max": 200000,
16 | "avg": 11871.213866879221,
17 | "sum": 1379648733.1809692
18 | }
19 | },
20 | "message": "aggregation successful",
21 | "request": {
22 | "agg": {
23 | "id": "stats",
24 | "params": {
25 | "field": "installation_cost",
26 | "use_extended_stats": false
27 | }
28 | },
29 | "filter": {
30 | "query": {
31 | "match_all": {}
32 | }
33 | }
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/be/test/filter/requests/06.json:
--------------------------------------------------------------------------------
1 | {
2 | "request": {
3 | "installation_date": {
4 | "gt": "2016-01-01T00:00:00.000Z",
5 | "lt": "2018-01-01T00:00:00.000Z"
6 | }
7 | },
8 | "query": {
9 | "query": {
10 | "bool": {
11 | "filter": [
12 | {
13 | "range": {
14 | "installation_date": {
15 | "gt": "2016-01-01T00:00:00.000Z",
16 | "lt": "2018-01-01T00:00:00.000Z"
17 | }
18 | }
19 | }
20 | ]
21 | }
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/be/src/routes/histogram.js:
--------------------------------------------------------------------------------
1 | import { index } from '../conf.js';
2 | import { client } from '../es.js';
3 | import { getIntervalForBins } from '../util.js';
4 |
5 | export const getHistogram = async (request, reply) => {
6 | const {
7 | bins = 10,
8 | field,
9 | } = request.query;
10 |
11 | const interval = await getIntervalForBins(
12 | field,
13 | bins,
14 | request.filter
15 | );
16 | request.meta = { interval };
17 |
18 | const body = {
19 | ...request.filter,
20 | size: 0,
21 | aggs: {
22 | histogram: {
23 | histogram: {
24 | field,
25 | interval,
26 | }
27 | }
28 | }
29 | };
30 |
31 | const result = await client.search({body, index});
32 |
33 | reply.send(result.aggregations);
34 | };
35 |
--------------------------------------------------------------------------------
/fe/src/routes/+page.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 | Home - {toolName}
9 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
38 |
--------------------------------------------------------------------------------
/fe/src/lib/utils/getters.js:
--------------------------------------------------------------------------------
1 | import * as _ from 'lamb';
2 |
3 | /* ES responses */
4 |
5 | export const getCardinalityValue = _.getPath('cardinality.value');
6 | export const getCertifiedValue = _.getPath('certified.value');
7 | export const getCount = _.getKey('count');
8 | export const getDocCount = _.getKey('doc_count');
9 | export const getKeyAsString = _.getKey('key_as_string');
10 | export const getStatsAvg = _.getPath('stats.avg');
11 | export const getStatsSum = _.getPath('stats.sum');
12 | export const getTermsBuckets = _.getPath('terms.buckets');
13 |
14 | /* rest */
15 |
16 | export const getEntity = _.getKey('entity');
17 | export const getField = _.getKey('field');
18 | export const getName = _.getKey('name');
19 | export const getSelected = _.getKey('selected');
20 |
--------------------------------------------------------------------------------
/fe/src/lib/config/map.js:
--------------------------------------------------------------------------------
1 | /* map bounding box (UK) */
2 |
3 | export const DEFAULT_BBOX_WSEN = [-8.61752, 49.90774, 1.76229, 60.84585];
4 |
5 | /* mapbox */
6 |
7 | export const MAPBOXGL_ACCESSTOKEN = 'pk.eyJ1IjoibmVzdGEtdWsiLCJhIjoiY2ozbjUzY2drMDAwNzJxbnl6a21uM253cSJ9.3RTMySEVk0LC4gQvGoG-Zw';
8 |
9 | export const MAPBOXGL_STYLEURLs = {
10 | themeLight: 'mapbox://styles/nesta-uk/cl8olrzo200ci16pim0h4c1pn',
11 | themeDark: 'mapbox://styles/nesta-uk/cl8ilzuy3001214qyg06jwsoy'
12 | }
13 |
14 | export const regionTypeToFeatureNameId = {
15 | country21: 'CTRY21NM',
16 | itl21_1: 'ITL121NM',
17 | itl21_2: 'ITL221NM',
18 | itl21_3: 'ITL321NM',
19 | lad21: 'LAD21NM',
20 | lsoa11: 'LSOA11NM',
21 | msoa11: 'MSOA11NM',
22 | }
23 |
24 | export const heroRegionStrokeWidth = 1.5;
25 |
--------------------------------------------------------------------------------
/fe/src/lib/components/explorer/medium/unused/ScrollableGrid/ColumnNames.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 | {#each categories as category}
14 |
19 | {/each}
20 |
21 |
22 |
23 |
32 |
--------------------------------------------------------------------------------
/fe/src/lib/components/explorer/Bullet.svelte:
--------------------------------------------------------------------------------
1 |
14 |
15 |
19 | {text}
20 |
21 |
22 |
34 |
--------------------------------------------------------------------------------
/fe/src/lib/components/svizzle/GridRows.svelte:
--------------------------------------------------------------------------------
1 |
17 |
18 |
22 |
23 |
24 |
25 |
39 |
--------------------------------------------------------------------------------
/fe/src/lib/components/explorer/Checkboxed.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
15 |
19 |
20 | {label}
21 |
22 |
23 |
24 |
40 |
--------------------------------------------------------------------------------
/fe/src/lib/utils/svizzle/style.js:
--------------------------------------------------------------------------------
1 | import {
2 | joinWithBlank,
3 | makePostfixed,
4 | makePrefixed,
5 | } from '@svizzle/utils';
6 | import {rgb} from 'd3-color';
7 | import * as _ from 'lamb';
8 |
9 | export const getHexColor = _.pipe([rgb, _.invoke('formatHex')]);
10 |
11 | const getPropDef = _.pipe([
12 | _.joinWith(': '),
13 | makePostfixed(';\n')
14 | ]);
15 | const getRuleDefs = _.pipe([
16 | _.pairs,
17 | _.mapWith(getPropDef),
18 | _.joinWith('\t'),
19 | makePrefixed('{\n\t'),
20 | makePostfixed('}')
21 | ]);
22 | const getClassDef = _.pipe([
23 | _.collect([
24 | _.head,
25 | _.pipe([
26 | _.getAt(1),
27 | getRuleDefs
28 | ])
29 | ]),
30 | joinWithBlank
31 | ]);
32 | export const getThemeClassDefsText = _.pipe([
33 | _.pairs,
34 | _.mapWith(getClassDef),
35 | _.joinWith('\n'),
36 | ]);
37 |
--------------------------------------------------------------------------------
/be/src/conf.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-process-env
2 | export const esEnv = process.env.BE_ENV || 'dev'; // dev, staging, production
3 | const domains = {
4 | dev: 'https://hpmt.es.dev.dap-tools.uk:9200',
5 | staging: 'https://hpmt.es.staging.dap-tools.uk:9200',
6 | production: 'https://hpmt.es.production.dap-tools.uk:443'
7 | };
8 | export const domain = domains[esEnv];
9 | export const index = 'hpmt_gold_interim_v7';
10 | // fingerprint generated from cert file created during ES installation
11 | export const fingerprint = '8D:AC:F2:E3:92:C9:59:FB:E5:77:3A:82:3B:2E:11:C9:68:43:21:2E:CF:57:05:CB:5A:C6:84:12:E9:22:69:E4';
12 |
13 | export const maxBins = 50;
14 | export const maxBuckets = 65536;
15 | export const maxEndDate='2024-02-01';
16 | export const minDocCount = 5;
17 | export const minStartDate='2008-10-24';
18 |
--------------------------------------------------------------------------------
/fe/src/lib/components/svizzle/GridColumns.svelte:
--------------------------------------------------------------------------------
1 |
17 |
18 |
22 |
23 |
24 |
25 |
39 |
--------------------------------------------------------------------------------
/be/src/filter.js:
--------------------------------------------------------------------------------
1 | import {getId} from '@svizzle/utils';
2 | import * as _ from 'lamb';
3 |
4 | import {fields} from 'nesta_hpmt_shared/fields.js';
5 |
6 | const schema = _.index(fields, getId);
7 |
8 | export const getFilterQuery = _.pipe([
9 | _.pairs,
10 | _.mapWith(([key, value]) => ({
11 | [schema[key].esType]: {
12 | [schema[key].esKey || key]: value
13 | }
14 | })
15 | )
16 | ]);
17 |
18 | const clauseToFilter = {
19 | include: 'should',
20 | exclude: 'must_not'
21 | }
22 | export const getStringsFiltersQuery = _.pipe([
23 | _.mapWith(({clause, field, values}) => ({
24 | bool: {
25 | [clauseToFilter[clause]]: _.map(values, value => ({
26 | wildcard: {
27 | [field]: {
28 | value: `*${value}*`,
29 | case_insensitive: true
30 | }
31 | }
32 | }))
33 | }
34 | }))
35 | ]);
36 |
--------------------------------------------------------------------------------
/be/src/bin/updateRouteTestData.js:
--------------------------------------------------------------------------------
1 |
2 | import { readDir, readJson, saveObj } from '@svizzle/file';
3 | import * as _ from 'lamb';
4 |
5 | import { buildServer } from '../app.js';
6 |
7 | const path = 'test/routes/api';
8 |
9 | const updateRoute = async route => {
10 |
11 | const server = await buildServer();
12 |
13 | const testPath = `${path}/${route}`;
14 | const tests = await readDir(testPath);
15 |
16 | for await (const testFile of tests) {
17 | const { query } = await readJson(`${testPath}/${testFile}`);
18 | const response = await server.inject(query);
19 | const save = saveObj(`${testPath}/${testFile}`, '\t');
20 | save({ query, response: response.json() });
21 | }
22 | };
23 |
24 | const main = async () => {
25 | const routes = await readDir(path);
26 | _.forEach(routes, updateRoute);
27 | };
28 |
29 | main();
30 |
--------------------------------------------------------------------------------
/be/githooks/post-receive:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Change this to staging on the staging server
4 | branch="dev"
5 |
6 | while read oldrev newrev ref
7 | do
8 | if [[ $ref =~ .*/$branch$ ]]; then
9 | echo "$branch ref received. Deploying $branch branch..."
10 | git --work-tree=$HOME/asf_hp_market_tracker --git-dir=$HOME/asf_hp_market_tracker.git checkout $branch -f
11 |
12 |
13 | export PATH=$PATH:/home/ubuntu/.nvm/versions/node/v18.15.0/bin/
14 | export ELASTICSEARCH_PASSWORD=
15 |
16 | cd $HOME/asf_hp_market_tracker/be
17 | npm install
18 |
19 | pm2 delete all
20 | pm2 start src/server.js
21 | else
22 | echo "Ref $ref successfully received. Doing nothing: only the $branch branch may be deployed on this server."
23 | fi
24 | done
25 |
--------------------------------------------------------------------------------
/be/src/schemas/terms1_cardinality2.js:
--------------------------------------------------------------------------------
1 | import { maxBuckets } from '../conf.js';
2 |
3 | export const schema = {
4 | schema: {
5 | querystring: {
6 | type: 'object',
7 | required: ['field1', 'field2'],
8 | properties: {
9 | // 1
10 | field1: { type: 'string' },
11 | missing1: { type: 'string' },
12 | size1: {
13 | default: maxBuckets,
14 | type: 'integer',
15 | minimum: 1,
16 | maximum: maxBuckets
17 | },
18 | // 2
19 | field2: { type: 'string' },
20 | missing2: { type: 'string' },
21 | use_extended_stats2: {
22 | default: false,
23 | type: 'boolean'
24 | },
25 | with_percentiles2: {
26 | default: false,
27 | type: 'boolean'
28 | },
29 | with_stats2: {
30 | default: false,
31 | type: 'boolean'
32 | },
33 | }
34 | }
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/fe/src/lib/statechart/context.js:
--------------------------------------------------------------------------------
1 | import {allRegionTypes} from '$lib/data/regions.js';
2 |
3 | export const context = {
4 | nextSearchParams: '',
5 | nextSelection: {},
6 | selection: {
7 | categsGeoSortBy: 'total',
8 | categsStreamgraphsSorting: 'off',
9 | categsTimeGraph: 'trends',
10 | filters: {
11 | installerRegionNames: [],
12 | installerRegionType: 'country21',
13 | propertyRegionNames: [],
14 | propertyRegionType: 'country21',
15 | },
16 | interval: '1y',
17 | numTimeGraph: 'percentiles',
18 | regionType: 'country21',
19 | regionTypes: allRegionTypes,
20 | stackedBarsExtents: 'absolute',
21 | stringsFilters: [],
22 | stringsGeoSortBy: 'total',
23 | stringsStreamgraphsSorting: 'off',
24 | stringsTimeGraph: 'trends',
25 | stringsTopCount: 10,
26 | trendType: 'progressive',
27 | viewId: '',
28 | },
29 | viewQueryPath: '',
30 | };
31 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/methodology/Geography.svx:
--------------------------------------------------------------------------------
1 | ## Geography
2 |
3 | ### Geolocation
4 |
5 | To map installations to standardised geographies, we use two sources from the Office for National Statistics (ONS) [Open Geography Portal](https://geoportal.statistics.gov.uk/).
6 |
7 | Firstly we use the National Statistics Postcode Lookup (NSPL). This allows us to map a postcode to a range of geographical hierarchies, including countries, Output Areas (OAs) and Local Authority Districts (LADs).
8 |
9 | We also use a lookup file provided by the ONS, the `Local Authority District (April 2021) to LAU1 to ITL3 to ITL2 to ITL1` file, which allows us to further map the installations onto International Territorial Level geographies.
10 |
11 | Through this, we are able to aggregate installations to these different geographies, and through the use of shapefiles, display these in the explorer as choropleths.
12 |
--------------------------------------------------------------------------------
/fe/src/routes/explorer/string/geo/[slug]/+page.svelte:
--------------------------------------------------------------------------------
1 |
24 |
25 |
32 |
--------------------------------------------------------------------------------
/be/src/routes/date_histogram1_cardinality2.js:
--------------------------------------------------------------------------------
1 | import { index } from '../conf.js';
2 | import { client } from '../es.js';
3 |
4 | export const getDateHistogram1Cardinality2 = async (request, reply) => {
5 | const {
6 | calendar_interval1 = '1y',
7 | field1,
8 | field2,
9 | missing2 = null,
10 | } = request.query;
11 |
12 | const body = {
13 | ...request.filter,
14 | size: 0,
15 | aggs: {
16 | date_histogram: {
17 | date_histogram: {
18 | field: field1,
19 | calendar_interval: calendar_interval1,
20 | format: 'yyyy-MM'
21 | },
22 | aggs: {
23 | cardinality: {
24 | cardinality: {
25 | field: field2,
26 | ...missing2 && { missing: missing2 }
27 | }
28 | }
29 | }
30 | }
31 | }
32 | };
33 |
34 | const result = await client.search({
35 | body,
36 | index
37 | });
38 |
39 | reply.send(result.aggregations);
40 | };
41 |
--------------------------------------------------------------------------------
/be/src/schemas/date_histogram1_terms2.js:
--------------------------------------------------------------------------------
1 | import { maxBuckets } from '../conf.js';
2 |
3 | export const schema = {
4 | schema: {
5 | querystring: {
6 | type: 'object',
7 | required: ['field1', 'field2'],
8 | properties: {
9 | // 1
10 | calendar_interval1: {
11 | default: '1y',
12 | type: 'string',
13 | enum: ['1M', '1q', '1y']
14 | },
15 | field1: { type: 'string' },
16 | // 2
17 | field2: { type: 'string' },
18 | missing2: { type: 'string' },
19 | size2: {
20 | type: 'integer',
21 | minimum: 1,
22 | maximum: maxBuckets
23 | },
24 | use_extended_stats2: {
25 | default: false,
26 | type: 'boolean'
27 | },
28 | with_percentiles2: {
29 | default: false,
30 | type: 'boolean'
31 | },
32 | with_stats2: {
33 | default: false,
34 | type: 'boolean'
35 | },
36 | }
37 | }
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/fe/src/lib/components/mdsvex/_layout.svelte:
--------------------------------------------------------------------------------
1 |
20 |
21 |
24 |
25 |
32 |
--------------------------------------------------------------------------------
/fe/src/bin/regions/README.md:
--------------------------------------------------------------------------------
1 | # Regions
2 |
3 | ## Bounding boxes
4 |
5 | Step to run:
6 | - `cd be`: enter the backend dir
7 | - `npm run getBoundaries`: this will fetch the boundaries we use to generate tiles in `be/tiles/geojson` (a gitignored dir)
8 | - `cd ../fe`: enter the frontend dir
9 | - `npm run makeRegions`: this saves into `fe/src/lib/data/regions.json` which we'll import from the frontend
10 |
11 | ## Hierarchy
12 |
13 | We're using [this lookup table](https://geoportal.statistics.gov.uk/datasets/ons::local-authority-district-april-2021-to-lau1-to-itl3-to-itl2-to-itl1-january-2021-lookup-in-united-kingdom/explore)
14 |
15 | Regions currently in use form a hierarchy:
16 |
17 | - country
18 | - ITL1
19 | - ITL2
20 | - ITL3
21 | - LAD
22 |
23 | Step to run:
24 | - `cd fe`: enter the frontend dir
25 | - `npm run makeHierarchy`: this saves into `fe/src/lib/data/hierarchy.json` which we'll import from the frontend
26 |
--------------------------------------------------------------------------------
/be/src/bin/ingest.sh:
--------------------------------------------------------------------------------
1 | if [ -z "$1" ]
2 | then
3 | echo "No index name supplied"
4 | exit 1
5 | fi
6 |
7 | export AWS_PAGER=""
8 |
9 | instance="i-0c069e95aaa193572"
10 |
11 | aws ec2 start-instances --instance-ids $instance --output=text
12 | aws ec2 wait instance-running --instance-ids=$instance
13 | sleep 10 # need to wait a little longer before being able to ssh
14 | public_ip=$(aws ec2 describe-instances --instance-ids $instance --query 'Reservations[*].Instances[*].PublicIpAddress' --output text)
15 |
16 | scp -o StrictHostKeyChecking=no es/hosted_pipeline.conf ubuntu@$public_ip:/home/ubuntu/hpmt_pipeline/hosted_pipeline.conf
17 | scp -o StrictHostKeyChecking=no es/managed_pipeline.conf ubuntu@$public_ip:/home/ubuntu/hpmt_pipeline/managed_pipeline.confm1ss10n5
18 | ssh -o StrictHostKeyChecking=no ubuntu@$public_ip 'bash -s' < src/bin/ingestServerSide.sh $*
19 |
20 | aws ec2 stop-instances --instance-ids $instance --output=text
--------------------------------------------------------------------------------
/fe/src/lib/components/svizzle/legend/KeysLegend.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 | {#each keys as key}
9 |
10 |
14 | {key}
15 |
16 | {/each}
17 |
18 |
19 |
20 |
46 |
--------------------------------------------------------------------------------
/be/test/filter/requests/02.json:
--------------------------------------------------------------------------------
1 | {
2 | "request": {
3 | "property_feature_type": [
4 | "House"
5 | ],
6 | "property_supply_heating_system": [
7 | "heat pump"
8 | ]
9 | },
10 | "query": {
11 | "query": {
12 | "bool": {
13 | "filter": [
14 | {
15 | "terms": {
16 | "property_feature_type.keyword": [
17 | "House"
18 | ]
19 | }
20 | },
21 | {
22 | "terms": {
23 | "property_supply_heating_system.keyword": [
24 | "heat pump"
25 | ]
26 | }
27 | }
28 | ]
29 | }
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/be/test/filter/requests/05.json:
--------------------------------------------------------------------------------
1 | {
2 | "request": {
3 | "property_feature_type": [
4 | "House"
5 | ],
6 | "hp_feature_power_generation": {
7 | "gt": 12000,
8 | "lt": 15000
9 | }
10 | },
11 | "query": {
12 | "query": {
13 | "bool": {
14 | "filter": [
15 | {
16 | "terms": {
17 | "property_feature_type.keyword": [
18 | "House"
19 | ]
20 | }
21 | },
22 | {
23 | "range": {
24 | "hp_feature_power_generation": {
25 | "gt": 12000,
26 | "lt": 15000
27 | }
28 | }
29 | }
30 | ]
31 | }
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/be/test/routes/api/terms/03.json:
--------------------------------------------------------------------------------
1 | {
2 | "query": {
3 | "method": "GET",
4 | "url": "terms",
5 | "query": {
6 | "field": "hp_feature_heat_system.keyword",
7 | "size": 2
8 | }
9 | },
10 | "response": {
11 | "code": 200,
12 | "data": {
13 | "terms": {
14 | "doc_count_error_upper_bound": 0,
15 | "sum_other_doc_count": 1457,
16 | "buckets": [
17 | {
18 | "key": "Air Source Heat Pump",
19 | "doc_count": 207891
20 | },
21 | {
22 | "key": "Ground/Water Source Heat Pump",
23 | "doc_count": 31161
24 | }
25 | ]
26 | }
27 | },
28 | "message": "aggregation successful",
29 | "request": {
30 | "agg": {
31 | "id": "terms",
32 | "params": {
33 | "field": "hp_feature_heat_system.keyword",
34 | "size": 2,
35 | "use_extended_stats": false,
36 | "with_percentiles": false,
37 | "with_stats": false
38 | }
39 | },
40 | "filter": {
41 | "query": {
42 | "match_all": {}
43 | }
44 | }
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/fe/src/lib/components/explorer/medium/unused/LabelsGrid.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
16 | {#each categories as category}
17 |
18 |
23 | {category}
24 |
25 | {/each}
26 |
27 |
28 |
43 |
--------------------------------------------------------------------------------
/be/test/routes/api/terms1_certified2/01.json:
--------------------------------------------------------------------------------
1 | {
2 | "query": {
3 | "method": "GET",
4 | "url": "terms1_certified2",
5 | "query": {
6 | "field1": "property_geo_region_country21_name.keyword"
7 | }
8 | },
9 | "response": {
10 | "code": 200,
11 | "data": {
12 | "terms": {
13 | "buckets": [
14 | {
15 | "key": "England",
16 | "certified": {
17 | "value": 2001
18 | }
19 | },
20 | {
21 | "key": "Scotland",
22 | "certified": {
23 | "value": 355
24 | }
25 | },
26 | {
27 | "key": "Wales",
28 | "certified": {
29 | "value": 448
30 | }
31 | }
32 | ]
33 | }
34 | },
35 | "message": "aggregation successful",
36 | "request": {
37 | "agg": {
38 | "id": "terms1_certified2",
39 | "params": {
40 | "field1": "property_geo_region_country21_name.keyword",
41 | "logic2": "overlaps"
42 | }
43 | },
44 | "filter": {
45 | "query": {
46 | "match_all": {}
47 | }
48 | }
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/be/src/routes/terms1_histogram2.js:
--------------------------------------------------------------------------------
1 | import { index, maxBuckets } from '../conf.js';
2 | import { client } from '../es.js';
3 | import { getIntervalForBins } from '../util.js';
4 |
5 | export const getTerms1Histogram2 = async (request, reply) => {
6 | const {
7 | field1,
8 | missing1 = null,
9 | size1 = maxBuckets,
10 | bins2 = 10,
11 | field2,
12 | } = request.query;
13 |
14 | const interval = await getIntervalForBins(
15 | field2,
16 | bins2,
17 | request.filter
18 | );
19 | request.meta = { interval2: interval };
20 |
21 | const body = {
22 | ...request.filter,
23 | size: 0,
24 | aggs: {
25 | terms: {
26 | terms: {
27 | field: field1,
28 | size: size1,
29 | ...missing1 && { missing: missing1 }
30 | },
31 | aggs: {
32 | histogram: {
33 | histogram: {
34 | field: field2,
35 | interval,
36 | }
37 | }
38 | }
39 | }
40 | }
41 | };
42 |
43 | const result = await client.search({index, body});
44 |
45 | reply.send(result.aggregations);
46 | };
47 |
--------------------------------------------------------------------------------
/be/src/routes/date_histogram1_histogram2.js:
--------------------------------------------------------------------------------
1 | import { index } from '../conf.js';
2 | import { client } from '../es.js';
3 | import { getIntervalForBins } from '../util.js';
4 |
5 | export const getDateHistogram1Histogram2 = async (request, reply) => {
6 | const {
7 | calendar_interval1 = '1y',
8 | field1,
9 | bins2 = 10,
10 | field2,
11 | } = request.query;
12 |
13 | const interval = await getIntervalForBins(
14 | field2,
15 | bins2,
16 | request.filter
17 | );
18 | request.meta = { interval2: interval };
19 |
20 | const body = {
21 | ...request.filter,
22 | size: 0,
23 | aggs: {
24 | date_histogram: {
25 | date_histogram: {
26 | field: field1,
27 | calendar_interval: calendar_interval1,
28 | format: 'yyyy-MM'
29 | },
30 | aggs: {
31 | histogram: {
32 | histogram: {
33 | field: field2,
34 | interval,
35 | }
36 | }
37 | }
38 | }
39 | }
40 | };
41 |
42 | const result = await client.search({body, index});
43 |
44 | reply.send(result.aggregations);
45 | };
46 |
--------------------------------------------------------------------------------
/fe/src/lib/data/regions.js:
--------------------------------------------------------------------------------
1 | import * as _ from 'lamb';
2 |
3 | // FIXME can't assert json in svelte files
4 | // this module might soon not be needed as it seems it will included in Vite 5:
5 | // https://github.com/vitejs/vite/issues/4934
6 | // https://github.com/vitejs/vite/blob/v5.0.0-beta.14/packages/vite/CHANGELOG.md#500-beta2-2023-09-15
7 | import hierarchyJson from '$lib/data/hierarchy.json' assert {type: 'json'};
8 | import regionsByTypeJson from '$lib/data/regions.json' assert {type: 'json'};
9 | import {getName} from '$lib/utils/getters.js';
10 |
11 | export const hierarchy = hierarchyJson;
12 | export const regionsByType = regionsByTypeJson;
13 |
14 | // could be derived from `regionsByType`
15 | export const allRegionTypes = [
16 | 'country21',
17 | 'itl21_1',
18 | 'itl21_2',
19 | 'itl21_3',
20 | 'lad21',
21 | // 'lsoa11',
22 | // 'msoa11',
23 | ];
24 |
25 | export const allRegionsByType = _.group(_.values(hierarchy), _.getKey('type'));
26 | export const allRegionsByNameByType =
27 | _.mapValues(allRegionsByType, _.indexBy(getName));
28 |
--------------------------------------------------------------------------------
/be/test/routes/api/terms1_certified2/04.json:
--------------------------------------------------------------------------------
1 | {
2 | "query": {
3 | "method": "GET",
4 | "url": "terms1_certified2",
5 | "query": {
6 | "field1": "property_geo_region_country21_name.keyword",
7 | "logic2": "new"
8 | }
9 | },
10 | "response": {
11 | "code": 200,
12 | "data": {
13 | "terms": {
14 | "buckets": [
15 | {
16 | "key": "England",
17 | "certified": {
18 | "value": 2001
19 | }
20 | },
21 | {
22 | "key": "Scotland",
23 | "certified": {
24 | "value": 355
25 | }
26 | },
27 | {
28 | "key": "Wales",
29 | "certified": {
30 | "value": 448
31 | }
32 | }
33 | ]
34 | }
35 | },
36 | "message": "aggregation successful",
37 | "request": {
38 | "agg": {
39 | "id": "terms1_certified2",
40 | "params": {
41 | "field1": "property_geo_region_country21_name.keyword",
42 | "logic2": "new"
43 | }
44 | },
45 | "filter": {
46 | "query": {
47 | "match_all": {}
48 | }
49 | }
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/be/test/routes/api/terms1_certified2/05.json:
--------------------------------------------------------------------------------
1 | {
2 | "query": {
3 | "method": "GET",
4 | "url": "terms1_certified2",
5 | "query": {
6 | "field1": "property_geo_region_country21_name.keyword",
7 | "logic2": "dropped"
8 | }
9 | },
10 | "response": {
11 | "code": 200,
12 | "data": {
13 | "terms": {
14 | "buckets": [
15 | {
16 | "key": "England",
17 | "certified": {
18 | "value": 2005
19 | }
20 | },
21 | {
22 | "key": "Scotland",
23 | "certified": {
24 | "value": 356
25 | }
26 | },
27 | {
28 | "key": "Wales",
29 | "certified": {
30 | "value": 448
31 | }
32 | }
33 | ]
34 | }
35 | },
36 | "message": "aggregation successful",
37 | "request": {
38 | "agg": {
39 | "id": "terms1_certified2",
40 | "params": {
41 | "field1": "property_geo_region_country21_name.keyword",
42 | "logic2": "dropped"
43 | }
44 | },
45 | "filter": {
46 | "query": {
47 | "match_all": {}
48 | }
49 | }
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/be/src/routes/index.js:
--------------------------------------------------------------------------------
1 | export { getCardinality } from './cardinality.js';
2 | export { getCertified } from './certified.js';
3 | export { getCount } from './count.js';
4 | export { getDateHistogram } from './date_histogram.js';
5 | export { getDateHistogram1Cardinality2 } from './date_histogram1_cardinality2.js';
6 | export { getDateHistogram1Certified2 } from './date_histogram1_certified2.js';
7 | export { getDateHistogram1Histogram2 } from './date_histogram1_histogram2.js';
8 | export { getDateHistogram1Stats2 } from './date_histogram1_stats2.js';
9 | export { getDateHistogram1Terms2 } from './date_histogram1_terms2.js';
10 | export { getHistogram } from './histogram.js';
11 | export { getStats } from './stats.js';
12 | export { getTerms } from './terms.js';
13 | export { getTerms1Cardinality2 } from './terms1_cardinality2.js';
14 | export { getTerms1Certified2 } from './terms1_certified2.js';
15 | export { getTerms1Histogram2 } from './terms1_histogram2.js';
16 | export { getTerms1Stats2 } from './terms1_stats2.js';
17 | export { getTerms1Terms2 } from './terms1_terms2.js';
18 |
--------------------------------------------------------------------------------
/fe/svelte.config.js:
--------------------------------------------------------------------------------
1 | import {dirname, join} from 'path';
2 | import {fileURLToPath} from 'url';
3 |
4 | import adapterAuto from '@sveltejs/adapter-auto';
5 | import adapterNetlify from '@sveltejs/adapter-netlify';
6 | import {mdsvex} from 'mdsvex';
7 |
8 | import {unescape_code} from './src/lib/utils/unescape-inlineCode.js';
9 |
10 | // https://github.com/sveltejs/language-tools/issues/1665
11 | const __dirname = dirname(fileURLToPath(import.meta.url));
12 | const mdsvexLayout = join(__dirname, 'src/lib/components/mdsvex/_layout.svelte');
13 |
14 | // eslint-disable-next-line no-process-env
15 | const adapter = process.env.ADAPTER === 'netlify'
16 | ? adapterNetlify({
17 | edge: false,
18 | split: false
19 | })
20 | : adapterAuto();
21 |
22 | /** @type {import('@sveltejs/kit').Config} */
23 | const config = {
24 | extensions: ['.svelte', '.svx', '.md'],
25 | kit: {adapter},
26 | preprocess: [
27 | mdsvex({
28 | layout: mdsvexLayout,
29 | extensions: ['.svx', '.md'],
30 | remarkPlugins: [unescape_code]
31 | })
32 | ]
33 | };
34 |
35 | export default config;
36 |
--------------------------------------------------------------------------------
/be/test/filter/requests/04.json:
--------------------------------------------------------------------------------
1 | {
2 | "request": {
3 | "hp_feature_flow_temperature": {
4 | "gt": 10,
5 | "lt": 50
6 | },
7 | "hp_feature_power_generation": {
8 | "gt": 12000,
9 | "lt": 15000
10 | }
11 | },
12 | "query": {
13 | "query": {
14 | "bool": {
15 | "filter": [
16 | {
17 | "range": {
18 | "hp_feature_flow_temperature": {
19 | "gt": 10,
20 | "lt": 50
21 | }
22 | }
23 | },
24 | {
25 | "range": {
26 | "hp_feature_power_generation": {
27 | "gt": 12000,
28 | "lt": 15000
29 | }
30 | }
31 | }
32 | ]
33 | }
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/be/src/routes/terms1_stats2.js:
--------------------------------------------------------------------------------
1 | import { index, maxBuckets } from '../conf.js';
2 | import { client } from '../es.js';
3 |
4 | export const getTerms1Stats2 = async (request, reply) => {
5 | const {
6 | field1,
7 | missing1 = null,
8 | size1 = maxBuckets,
9 | field2,
10 | use_extended_stats2 = false,
11 | use_percentiles2 = false,
12 | } = request.query;
13 |
14 | const statQuery = use_extended_stats2 ? 'extended_stats' : 'stats';
15 |
16 | const body = {
17 | ...request.filter,
18 | size: 0,
19 | aggs: {
20 | terms: {
21 | terms: {
22 | field: field1,
23 | size: size1,
24 | ...missing1 && { missing: missing1 }
25 | },
26 | aggs: {
27 | stats: {
28 | [statQuery]: {
29 | field: field2,
30 | }
31 | },
32 | ...use_percentiles2 && {
33 | percentiles: {
34 | percentiles: {
35 | field: field2
36 | }
37 | }
38 | }
39 | }
40 | }
41 | }
42 | };
43 |
44 | const result = await client.search({
45 | body,
46 | index
47 | });
48 |
49 | reply.send(result.aggregations);
50 | };
51 |
--------------------------------------------------------------------------------
/be/src/routes/terms.js:
--------------------------------------------------------------------------------
1 | import { index, maxBuckets } from '../conf.js';
2 | import { client } from '../es.js';
3 |
4 | export const getTerms = async (request, reply) => {
5 | const {
6 | field,
7 | missing = null,
8 | size = maxBuckets,
9 | use_extended_stats = false,
10 | with_percentiles = false,
11 | with_stats = false,
12 | } = request.query;
13 |
14 | const stats_type = use_extended_stats ? 'extended_stats_bucket' : 'stats_bucket';
15 |
16 | const body = {
17 | ...request.filter,
18 | size: 0,
19 | aggs: {
20 | terms: {
21 | terms: {
22 | field,
23 | size,
24 | ...missing && { missing }
25 | }
26 | },
27 | ...with_stats && {
28 | stats: {
29 | [stats_type]: {
30 | buckets_path: 'terms>_count'
31 | }
32 | }
33 | },
34 | ...with_percentiles && {
35 | percentiles: {
36 | percentiles_bucket: {
37 | buckets_path: 'terms>_count'
38 | }
39 | }
40 | }
41 | }
42 | };
43 |
44 | const result = await client.search({
45 | body,
46 | index
47 | });
48 |
49 | reply.send(result.aggregations);
50 | };
51 |
--------------------------------------------------------------------------------
/be/src/routes/date_histogram1_stats2.js:
--------------------------------------------------------------------------------
1 | import { index } from '../conf.js';
2 | import { client } from '../es.js';
3 |
4 | export const getDateHistogram1Stats2 = async (request, reply) => {
5 | const {
6 | calendar_interval1 = '1y',
7 | field1,
8 | field2,
9 | use_extended_stats2 = false,
10 | use_percentiles2 = false
11 | } = request.query;
12 |
13 | const statQuery = use_extended_stats2 ? 'extended_stats' : 'stats';
14 |
15 | const body = {
16 | ...request.filter,
17 | size: 0,
18 | aggs: {
19 | date_histogram: {
20 | date_histogram: {
21 | field: field1,
22 | calendar_interval: calendar_interval1,
23 | format: 'yyyy-MM'
24 | },
25 | aggs: {
26 | stats: {
27 | [statQuery]: {
28 | field: field2,
29 | }
30 | },
31 | ...use_percentiles2 && {
32 | percentiles: {
33 | percentiles: {
34 | field: field2
35 | }
36 | }
37 | }
38 | }
39 | }
40 | }
41 | };
42 |
43 | const result = await client.search({
44 | body,
45 | index
46 | });
47 |
48 | reply.send(result.aggregations);
49 | };
50 |
--------------------------------------------------------------------------------
/fe/src/lib/stores/regions.js:
--------------------------------------------------------------------------------
1 | import {isIterableEmpty} from '@svizzle/utils';
2 | import * as _ from 'lamb';
3 | import {derived} from 'svelte/store';
4 |
5 | import {allRegionsByNameByType} from '$lib/data/regions.js';
6 | import {_currentMetric, _selection} from '$lib/stores/navigation.js';
7 | import {getName} from '$lib/utils/getters.js';
8 | import {getDescendantsRegions} from '$lib/utils/regions.js';
9 |
10 | export const _selectedRegionNamesIndex = derived(
11 | [_currentMetric, _selection],
12 | ([currentMetric, {filters, regionType}]) => {
13 | const {geoPrefix} = currentMetric;
14 | const parentRegionsNames = filters[`${geoPrefix}RegionNames`];
15 |
16 | let regionsByName;
17 | if (isIterableEmpty(parentRegionsNames)) {
18 | regionsByName = allRegionsByNameByType[regionType];
19 | } else {
20 | const selectedRegions = getDescendantsRegions({
21 | parentRegionsType: filters[`${geoPrefix}RegionType`],
22 | parentRegionsNames,
23 | targetRegionType: regionType
24 | });
25 | regionsByName = _.index(selectedRegions, getName);
26 | }
27 |
28 | return regionsByName;
29 | }
30 | );
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Nesta
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/be/test/routes/api/certified/05.json:
--------------------------------------------------------------------------------
1 | {
2 | "query": {
3 | "method": "GET",
4 | "url": "certified",
5 | "query": {
6 | "filter": "(installer_certificate_date_start:(gte:'2016-01-01'),installer_certificate_date_end:(lte:'2020-01-01'))",
7 | "logic": "new"
8 | }
9 | },
10 | "response": {
11 | "code": 200,
12 | "data": {
13 | "count": 815
14 | },
15 | "message": "aggregation successful",
16 | "request": {
17 | "agg": {
18 | "id": "certified",
19 | "params": {
20 | "filter": "(installer_certificate_date_start:(gte:'2016-01-01'),installer_certificate_date_end:(lte:'2020-01-01'))",
21 | "logic": "new"
22 | }
23 | },
24 | "filter": {
25 | "query": {
26 | "bool": {
27 | "filter": [
28 | {
29 | "range": {
30 | "installer_certificate_date_start": {
31 | "gte": "2016-01-01"
32 | }
33 | }
34 | },
35 | {
36 | "range": {
37 | "installer_certificate_date_end": {
38 | "lte": "2020-01-01"
39 | }
40 | }
41 | }
42 | ]
43 | }
44 | }
45 | }
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/be/src/bin/ingestServerSide.sh:
--------------------------------------------------------------------------------
1 | cd hpmt_pipeline
2 | source input.rc
3 |
4 | export LOGSTASH_OUTPUT_INDEX=$1
5 | result=$(mysql --login-path=missions -e "use production_asf; SELECT COUNT(*) FROM hpmt_gold_interim;")
6 | count=$(echo $result | awk '{print $2}')
7 | if [ "$2" ]
8 | then
9 | domains=$2
10 | else
11 | declare -a domains=("dev" "staging" "production")
12 | fi
13 |
14 | for domain in "${domains[@]}"
15 | do
16 | source $domain.rc
17 | export LOGSTASH_OUTPUT_DOMAIN="https://hpmt.es.$domain.dap-tools.uk"
18 | echo "--- 0" > last_run.yml # set value read by configuration to start at 0
19 | last_sql_value=0
20 | while [ $last_sql_value -le $count ]
21 | do
22 | if [[ "$domain" == "production" ]]
23 | then
24 | ./logstash -f /home/ubuntu/hpmt_pipeline/managed_pipeline.conf
25 | else
26 | ./logstash -f /home/ubuntu/hpmt_pipeline/hosted_pipeline.conf
27 | fi
28 | last_sql_value=$(($last_sql_value + 50000)) # increase the number of rows already read
29 | echo "--- $last_sql_value" > last_run.yml
30 | done
31 |
32 | echo "Finished"
33 | done
--------------------------------------------------------------------------------
/be/test/routes/api/certified/04.json:
--------------------------------------------------------------------------------
1 | {
2 | "query": {
3 | "method": "GET",
4 | "url": "certified",
5 | "query": {
6 | "filter": "(installer_certificate_date_start:(gte:'2016-01-01'),installer_certificate_date_end:(lte:'2020-01-01'))",
7 | "logic": "engulfs"
8 | }
9 | },
10 | "response": {
11 | "code": 200,
12 | "data": {
13 | "count": 636
14 | },
15 | "message": "aggregation successful",
16 | "request": {
17 | "agg": {
18 | "id": "certified",
19 | "params": {
20 | "filter": "(installer_certificate_date_start:(gte:'2016-01-01'),installer_certificate_date_end:(lte:'2020-01-01'))",
21 | "logic": "engulfs"
22 | }
23 | },
24 | "filter": {
25 | "query": {
26 | "bool": {
27 | "filter": [
28 | {
29 | "range": {
30 | "installer_certificate_date_start": {
31 | "gte": "2016-01-01"
32 | }
33 | }
34 | },
35 | {
36 | "range": {
37 | "installer_certificate_date_end": {
38 | "lte": "2020-01-01"
39 | }
40 | }
41 | }
42 | ]
43 | }
44 | }
45 | }
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/be/test/routes/api/certified/06.json:
--------------------------------------------------------------------------------
1 | {
2 | "query": {
3 | "method": "GET",
4 | "url": "certified",
5 | "query": {
6 | "filter": "(installer_certificate_date_start:(gte:'2016-01-01'),installer_certificate_date_end:(lte:'2020-01-01'))",
7 | "logic": "dropped"
8 | }
9 | },
10 | "response": {
11 | "code": 200,
12 | "data": {
13 | "count": 901
14 | },
15 | "message": "aggregation successful",
16 | "request": {
17 | "agg": {
18 | "id": "certified",
19 | "params": {
20 | "filter": "(installer_certificate_date_start:(gte:'2016-01-01'),installer_certificate_date_end:(lte:'2020-01-01'))",
21 | "logic": "dropped"
22 | }
23 | },
24 | "filter": {
25 | "query": {
26 | "bool": {
27 | "filter": [
28 | {
29 | "range": {
30 | "installer_certificate_date_start": {
31 | "gte": "2016-01-01"
32 | }
33 | }
34 | },
35 | {
36 | "range": {
37 | "installer_certificate_date_end": {
38 | "lte": "2020-01-01"
39 | }
40 | }
41 | }
42 | ]
43 | }
44 | }
45 | }
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/be/test/routes/api/terms/01.json:
--------------------------------------------------------------------------------
1 | {
2 | "query": {
3 | "method": "GET",
4 | "url": "terms",
5 | "query": {
6 | "field": "hp_feature_heat_system.keyword"
7 | }
8 | },
9 | "response": {
10 | "code": 200,
11 | "data": {
12 | "terms": {
13 | "doc_count_error_upper_bound": 0,
14 | "sum_other_doc_count": 0,
15 | "buckets": [
16 | {
17 | "key": "Air Source Heat Pump",
18 | "doc_count": 207891
19 | },
20 | {
21 | "key": "Ground/Water Source Heat Pump",
22 | "doc_count": 31161
23 | },
24 | {
25 | "key": "Undefined or Other Heat Pump Type",
26 | "doc_count": 1409
27 | },
28 | {
29 | "key": "No HP",
30 | "doc_count": 48
31 | }
32 | ]
33 | }
34 | },
35 | "message": "aggregation successful",
36 | "request": {
37 | "agg": {
38 | "id": "terms",
39 | "params": {
40 | "field": "hp_feature_heat_system.keyword",
41 | "size": 65536,
42 | "use_extended_stats": false,
43 | "with_percentiles": false,
44 | "with_stats": false
45 | }
46 | },
47 | "filter": {
48 | "query": {
49 | "match_all": {}
50 | }
51 | }
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/fe/src/lib/components/explorer/small/InfoSmall.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
16 |
17 |
18 |
19 |
20 |
Coverage for the current selection
21 |
22 |
23 |
This metric
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
48 |
--------------------------------------------------------------------------------
/fe/src/routes/explorer/category/geo/[slug]/+page.svelte:
--------------------------------------------------------------------------------
1 |
28 |
29 |
38 |
--------------------------------------------------------------------------------
/fe/src/lib/components/explorer/DismissOrApply.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 | Dismiss
13 |
14 |
19 | Apply
20 |
21 |
22 |
23 |
52 |
--------------------------------------------------------------------------------
/fe/src/routes/+error.svelte:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 | {toolName}: {status}
17 |
21 |
22 |
23 |
27 | {status}: {error?.message || 'Message not defined'}
28 |
29 |
30 | {#if status === 404}
31 | The page you navigated to doesn't seem to exist.
32 | {/if}
33 |
34 | {#if isDev && error?.stack}
35 |
36 |
37 | {error?.stack}
38 |
39 |
40 | {/if}
41 |
42 |
43 |
48 |
--------------------------------------------------------------------------------
/be/src/schemas/index.js:
--------------------------------------------------------------------------------
1 | export { schema as cardinalitySchema } from './cardinality.js';
2 | export { schema as certifiedSchema } from './certified.js';
3 | export { schema as dateHistogram1Cardinality2 } from './date_histogram1_cardinality2.js';
4 | export { schema as dateHistogram1Certified2 } from './date_histogram1_certified2.js';
5 | export { schema as dateHistogram1Histogram2Schema } from './date_histogram1_histogram2.js';
6 | export { schema as dateHistogram1Stats2Schema } from './date_histogram1_stats2.js';
7 | export { schema as dateHistogram1Terms2Schema } from './date_histogram1_terms2.js';
8 | export { schema as dateHistogramSchema } from './date_histogram.js';
9 | export { schema as histogramSchema } from './histogram.js';
10 | export { schema as statsSchema } from './stats.js';
11 | export { schema as terms1Cardinality2Schema } from './terms1_cardinality2.js';
12 | export { schema as terms1Certified2Schema } from './terms1_certified2.js';
13 | export { schema as terms1Histogram2Schema } from './terms1_histogram2.js';
14 | export { schema as terms1Stats2Schema } from './terms1_stats2.js';
15 | export { schema as terms1Terms2Schema } from './terms1_terms2.js';
16 | export { schema as termsSchema } from './terms.js';
17 |
--------------------------------------------------------------------------------
/be/src/schemas/terms1_terms2.js:
--------------------------------------------------------------------------------
1 | import { maxBuckets } from '../conf.js';
2 |
3 | export const schema = {
4 | schema: {
5 | querystring: {
6 | type: 'object',
7 | required: ['field1', 'field2'],
8 | properties: {
9 | // 1
10 | field1: { type: 'string' },
11 | missing1: { type: 'string' },
12 | size1: {
13 | default: maxBuckets,
14 | type: 'integer',
15 | minimum: 1,
16 | maximum: maxBuckets
17 | },
18 | use_extended_stats1: {
19 | default: false,
20 | type: 'boolean'
21 | },
22 | with_percentiles1: {
23 | default: false,
24 | type: 'boolean'
25 | },
26 | with_stats1: {
27 | default: false,
28 | type: 'boolean'
29 | },
30 | // 2
31 | field2: { type: 'string' },
32 | missing2: { type: 'string' },
33 | size2: {
34 | default: maxBuckets,
35 | type: 'integer',
36 | minimum: 1,
37 | maximum: maxBuckets
38 | },
39 | use_extended_stats2: {
40 | default: false,
41 | type: 'boolean'
42 | },
43 | with_percentiles2: {
44 | default: false,
45 | type: 'boolean'
46 | },
47 | with_stats2: {
48 | default: false,
49 | type: 'boolean'
50 | },
51 | }
52 | }
53 | }
54 | };
55 |
--------------------------------------------------------------------------------
/be/test/routes/api/terms/02.json:
--------------------------------------------------------------------------------
1 | {
2 | "query": {
3 | "method": "GET",
4 | "url": "terms",
5 | "query": {
6 | "field": "hp_feature_heat_system.keyword",
7 | "missing": "missing"
8 | }
9 | },
10 | "response": {
11 | "code": 200,
12 | "data": {
13 | "terms": {
14 | "doc_count_error_upper_bound": 0,
15 | "sum_other_doc_count": 0,
16 | "buckets": [
17 | {
18 | "key": "Air Source Heat Pump",
19 | "doc_count": 207891
20 | },
21 | {
22 | "key": "Ground/Water Source Heat Pump",
23 | "doc_count": 31161
24 | },
25 | {
26 | "key": "Undefined or Other Heat Pump Type",
27 | "doc_count": 1409
28 | },
29 | {
30 | "key": "No HP",
31 | "doc_count": 48
32 | }
33 | ]
34 | }
35 | },
36 | "message": "aggregation successful",
37 | "request": {
38 | "agg": {
39 | "id": "terms",
40 | "params": {
41 | "field": "hp_feature_heat_system.keyword",
42 | "missing": "missing",
43 | "size": 65536,
44 | "use_extended_stats": false,
45 | "with_percentiles": false,
46 | "with_stats": false
47 | }
48 | },
49 | "filter": {
50 | "query": {
51 | "match_all": {}
52 | }
53 | }
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/fe/src/lib/statechart/index.js:
--------------------------------------------------------------------------------
1 | import {createMachine, interpret} from 'xstate';
2 |
3 | import {browser} from '$app/environment';
4 | import {isDev} from '$lib/env.js';
5 | import {_isViewReady} from '$lib/stores/view.js';
6 |
7 | import * as actions from './actions/index.js';
8 | import * as services from './actors.js';
9 | import {config} from './config.js';
10 | import {context} from './context.js';
11 | import * as guards from './guards.js';
12 | import {isViewReady} from './utils.js';
13 |
14 | export const machine = createMachine(
15 | {...config, context},
16 | {actions, guards, services}
17 | );
18 | export const explorerActor = interpret(machine).start();
19 |
20 | if (browser) {
21 | // eslint-disable-next-line no-unused-vars
22 | explorerActor.subscribe((state, event) => {
23 | const isViewStateReady = isViewReady(state.value);
24 | _isViewReady.set(isViewStateReady);
25 |
26 | if (isDev) {
27 | console.log(
28 | '_isViewReady',
29 | isViewStateReady ? '🟩' : '⬛️',
30 | JSON.stringify(state.value)
31 | );
32 |
33 | // console.log('🤖🤖🤖', JSON.stringify(state.value));
34 | // if (event) {console.log('🎈🎈🎈', event);
35 | }
36 | });
37 |
38 | explorerActor.send({type: 'CLIENT_DETECTED'});
39 | }
40 |
--------------------------------------------------------------------------------
/fe/src/routes/explorer/+layout.svelte:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 | Explorer - {toolName}
21 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/be/tiles/config.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "boundary": "country21",
4 | "endpoint": "https://services1.arcgis.com/ESMARspQHYMw9BZ9/ArcGIS/rest/services/Countries_December_2021_UK_BGC_2022/FeatureServer"
5 | },
6 | {
7 | "boundary": "itl21_1",
8 | "endpoint": "https://services1.arcgis.com/ESMARspQHYMw9BZ9/arcgis/rest/services/International_Territorial_Level_1_January_2021_UK_BGC_2022/FeatureServer"
9 | },
10 | {
11 | "boundary": "itl21_2",
12 | "endpoint": "https://services1.arcgis.com/ESMARspQHYMw9BZ9/ArcGIS/rest/services/International_Territorial_Level_2_January_2021_UK_BGC_V2_2022/FeatureServer"
13 | },
14 | {
15 | "boundary": "itl21_3",
16 | "endpoint": "https://services1.arcgis.com/ESMARspQHYMw9BZ9/ArcGIS/rest/services/International_Territorial_Level_3_January_2021_UK_BGC_V3_2022/FeatureServer"
17 | },
18 | {
19 | "boundary": "lad21",
20 | "endpoint": "https://services1.arcgis.com/ESMARspQHYMw9BZ9/ArcGIS/rest/services/LAD_Dec_2021_GB_BFC_2022/FeatureServer"
21 | },
22 | {
23 | "boundary": "msoa11",
24 | "endpoint": "https://services1.arcgis.com/ESMARspQHYMw9BZ9/arcgis/rest/services/msoa/FeatureServer"
25 | },
26 | {
27 | "boundary": "lsoa11",
28 | "endpoint": "https://services1.arcgis.com/ESMARspQHYMw9BZ9/arcgis/rest/services/lsoa/FeatureServer"
29 | }
30 | ]
--------------------------------------------------------------------------------
/fe/src/lib/_content/guides/AppMedium.svx:
--------------------------------------------------------------------------------
1 |
4 |
5 | ## Navigating the app
6 |
7 | ### Structure of the page
8 |
9 | The header is a navigation bar divided in two sections:
10 | * on the left you have various links to navigate the app
11 | * on the right you have:
12 | * a link to the raw data in JSON format.
13 | * a link to a set of pages where you can provide feedback:
14 | * a survey to provide general feedback
15 | * a form to request adding your organisation to the app
16 | * a form to request the removal of your organisation from the dataset
17 | * a link to a set of pages where you can find app info like privacy
18 | and disclaimers
19 |
20 | In the middle of the page there's the content area, where content depends
21 | on the page you're in.
22 |
23 | The footer is another navigation bar divided in three parts showing:
24 | * the list of sponsor logos, linked to their website
25 | * the app version, linked to its change log on GitHub
26 | * the accessibility section, with:
27 | * a theme switcher button to select between light and dark colour themes
28 | * a link to the app accessibility statement
29 | * a button to toggle the accessibility menu
30 |
31 |
32 |
--------------------------------------------------------------------------------
/be/test/filter/filter.test.js:
--------------------------------------------------------------------------------
1 | import { readDirFiles } from '@svizzle/file';
2 | import rison from 'rison';
3 | import { test } from 'tap';
4 |
5 | import { buildTestServer } from '../utils.js';
6 | import { index } from '../../src/conf.js';
7 | import { client } from '../../src/es.js';
8 | import { makeQuery } from '../../src/filter.js';
9 |
10 | const buildQuery = request => {
11 | return {
12 | method: 'GET',
13 | url: 'terms',
14 | query: {
15 | filter: rison.encode(request),
16 | field: 'property_geo_region_country.keyword'
17 | }
18 | };
19 | };
20 |
21 | test('#makeQuery', async tap => {
22 | const server = await buildTestServer();
23 | tap.teardown(() => server.close());
24 | const tests = await readDirFiles('test/filter/requests', '', JSON.parse);
25 | for await (const { request, query } of tests) {
26 | const q = makeQuery(request);
27 | tap.same(q, query, 'generated query and expected query should be the same');
28 | const response = await client.search({ index, body: q }, { meta: true });
29 | tap.equal(response.statusCode, 200, 'ES should return a 200 status code');
30 | const beResponse = await server.inject(buildQuery(request));
31 | tap.equal(beResponse.statusCode, 200, 'BE should return a 200 status code');
32 |
33 | }
34 | tap.end();
35 | });
36 |
--------------------------------------------------------------------------------
/be/test/routes/api/terms1_cardinality2/05.json:
--------------------------------------------------------------------------------
1 | {
2 | "query": {
3 | "method": "GET",
4 | "url": "terms1_cardinality2",
5 | "query": {
6 | "field1": "property_geo_region_itl21_1_id.keyword",
7 | "field2": "property_energy_efficiency_windows.keyword",
8 | "size1": 2
9 | }
10 | },
11 | "response": {
12 | "code": 200,
13 | "data": {
14 | "terms": {
15 | "doc_count_error_upper_bound": 0,
16 | "sum_other_doc_count": 162244,
17 | "buckets": [
18 | {
19 | "key": "TLK",
20 | "doc_count": 39952,
21 | "cardinality": {
22 | "value": 5
23 | }
24 | },
25 | {
26 | "key": "TLH",
27 | "doc_count": 37882,
28 | "cardinality": {
29 | "value": 5
30 | }
31 | }
32 | ]
33 | }
34 | },
35 | "message": "aggregation successful",
36 | "request": {
37 | "agg": {
38 | "id": "terms1_cardinality2",
39 | "params": {
40 | "field1": "property_geo_region_itl21_1_id.keyword",
41 | "field2": "property_energy_efficiency_windows.keyword",
42 | "size1": 2,
43 | "use_extended_stats2": false,
44 | "with_percentiles2": false,
45 | "with_stats2": false
46 | }
47 | },
48 | "filter": {
49 | "query": {
50 | "match_all": {}
51 | }
52 | }
53 | }
54 | }
55 | }
--------------------------------------------------------------------------------
/fe/src/lib/_content/Accessibility.svx:
--------------------------------------------------------------------------------
1 |
4 |
5 | ## Web accessibility statement
6 |
7 | ### General
8 |
9 | We are committed to improving accessibility and usability for all
10 | users of this tool, including those relying on screen readers where possible.
11 |
12 | However, it's important to note that many components of our site are visual
13 | (e.g. charts and maps), and as such are not fully accessible.
14 |
15 | We look forward to implementing measures to address these limitations as soon as
16 | time and resources will allow.
17 |
18 | ### Disclaimer
19 |
20 | Automated tests indicate the tool is AAA compliant, but the aforementioned
21 | limitations in visual components may affect full accessibility.
22 |
23 | ### Contact Us
24 |
25 | If, at any time, you have specific questions or concerns about the accessibility
26 | of any particular web page on the Website, then please contact us by e-mail at
27 | [{contactEmail}](mailto:{contactEmail}).
28 |
29 | If you do encounter an accessibility issue, then please be sure to specify the
30 | web page and nature of the issue, and we will make all reasonable efforts to
31 | make that page or the information contained therein accessible for you.
32 |
33 | Thank you for your cooperation!
34 |
--------------------------------------------------------------------------------
/be/src/routes/terms1_cardinality2.js:
--------------------------------------------------------------------------------
1 | import { index, maxBuckets } from '../conf.js';
2 | import { client } from '../es.js';
3 |
4 | export const getTerms1Cardinality2 = async (request, reply) => {
5 | const {
6 | field1,
7 | missing1 = null,
8 | size1 = maxBuckets,
9 | field2,
10 | missing2 = null,
11 | use_extended_stats2 = false,
12 | with_percentiles2 = false,
13 | with_stats2 = false,
14 | } = request.query;
15 |
16 | const stats_type2 = use_extended_stats2 ? 'extended_stats_bucket' : 'stats_bucket';
17 |
18 | const body = {
19 | ...request.filter,
20 | size: 0,
21 | aggs: {
22 | terms: {
23 | terms: {
24 | field: field1,
25 | size: size1,
26 | ...missing1 && { missing: missing1 }
27 | },
28 | aggs: {
29 | cardinality: {
30 | cardinality: {
31 | field: field2,
32 | ...missing2 && { missing: missing2 }
33 | }
34 | },
35 | }
36 | },
37 | ...with_stats2 && {
38 | stats: {
39 | [stats_type2]: {
40 | buckets_path: 'terms>cardinality'
41 | }
42 | }
43 | },
44 | ...with_percentiles2 && {
45 | percentiles: {
46 | percentiles_bucket: {
47 | buckets_path: 'terms>cardinality'
48 | }
49 | }
50 | }
51 | }
52 | };
53 |
54 | const result = await client.search({
55 | body,
56 | index
57 | });
58 |
59 | reply.send(result.aggregations);
60 | };
61 |
--------------------------------------------------------------------------------
/be/test/routes/api/stats/02.json:
--------------------------------------------------------------------------------
1 | {
2 | "query": {
3 | "method": "GET",
4 | "url": "stats",
5 | "query": {
6 | "field": "installation_cost",
7 | "use_extended_stats": true
8 | }
9 | },
10 | "response": {
11 | "code": 200,
12 | "data": {
13 | "stats": {
14 | "count": 116218,
15 | "min": 1,
16 | "max": 200000,
17 | "avg": 11871.213866879221,
18 | "sum": 1379648733.1809692,
19 | "sum_of_squares": 24648776268653.113,
20 | "variance": 71165147.36007194,
21 | "variance_population": 71165147.36007194,
22 | "variance_sampling": 71165759.70721015,
23 | "std_deviation": 8435.943774117508,
24 | "std_deviation_population": 8435.943774117508,
25 | "std_deviation_sampling": 8435.980067971364,
26 | "std_deviation_bounds": {
27 | "upper": 28743.101415114237,
28 | "lower": -5000.6736813557945,
29 | "upper_population": 28743.101415114237,
30 | "lower_population": -5000.6736813557945,
31 | "upper_sampling": 28743.17400282195,
32 | "lower_sampling": -5000.7462690635075
33 | }
34 | }
35 | },
36 | "message": "aggregation successful",
37 | "request": {
38 | "agg": {
39 | "id": "stats",
40 | "params": {
41 | "field": "installation_cost",
42 | "use_extended_stats": true
43 | }
44 | },
45 | "filter": {
46 | "query": {
47 | "match_all": {}
48 | }
49 | }
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/be/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "@elastic/elasticsearch": "^8.7.0",
4 | "@fastify/cors": "^8.2.1",
5 | "@opensearch-project/opensearch": "^2.3.1",
6 | "@svizzle/atlas": "^0.9.1",
7 | "@svizzle/file": "^0.14.2",
8 | "@svizzle/geo": "^0.9.2",
9 | "@svizzle/utils": "^0.20.0",
10 | "dap_dv_backends_utils": "github:nestauk/dap_dv_backends_utils#v0.0.13",
11 | "fastify": "^4.14.1",
12 | "just-compare": "^2.3.0",
13 | "lamb": "^0.61.0",
14 | "luxon": "^3.4.3",
15 | "mkdirp": "^3.0.1",
16 | "nodemon": "^2.0.21",
17 | "pm2": "^5.3.0",
18 | "rison": "^0.1.1"
19 | },
20 | "description": "Nesta HPMT backend",
21 | "devDependencies": {
22 | "eslint": "^8.36.0",
23 | "mocha": "^10.2.0",
24 | "pino-pretty": "^10.0.0",
25 | "tap": "^16.3.4"
26 | },
27 | "license": "MIT",
28 | "name": "nesta_hpmt_backend",
29 | "scripts": {
30 | "dev": "nodemon src/server.js | pino-pretty",
31 | "getBoundaries": "mkdirp tiles/geojson && npx downloadBoundaries -i tiles/config.json -o tiles/geojson && node src/bin/saveNuts0.js",
32 | "getTiles": "mkdirp tiles/mbtiles && sh src/bin/generateMbTiles.sh",
33 | "runLogstashPipeline": "sh src/bin/ingest.sh",
34 | "lint": "eslint .",
35 | "test": "tap --no-coverage",
36 | "stop": "pm2 stop server",
37 | "updateRouteTests": "node src/bin/updateRouteTestData.js",
38 | "uploadTiles": "sh src/bin/uploadTiles.sh"
39 | },
40 | "type": "module"
41 | }
42 |
--------------------------------------------------------------------------------
/be/test/routes/api/histogram/01.json:
--------------------------------------------------------------------------------
1 | {
2 | "query": {
3 | "method": "GET",
4 | "url": "histogram",
5 | "query": {
6 | "field": "installation_cost",
7 | "bins": 10
8 | }
9 | },
10 | "response": {
11 | "code": 200,
12 | "data": {
13 | "histogram": {
14 | "buckets": [
15 | {
16 | "key": 0,
17 | "doc_count": 109545
18 | },
19 | {
20 | "key": 22222,
21 | "doc_count": 5508
22 | },
23 | {
24 | "key": 44444,
25 | "doc_count": 812
26 | },
27 | {
28 | "key": 66666,
29 | "doc_count": 178
30 | },
31 | {
32 | "key": 88888,
33 | "doc_count": 81
34 | },
35 | {
36 | "key": 111110,
37 | "doc_count": 44
38 | },
39 | {
40 | "key": 133332,
41 | "doc_count": 34
42 | },
43 | {
44 | "key": 155554,
45 | "doc_count": 4
46 | },
47 | {
48 | "key": 177776,
49 | "doc_count": 6
50 | },
51 | {
52 | "key": 199998,
53 | "doc_count": 6
54 | }
55 | ]
56 | }
57 | },
58 | "message": "aggregation successful",
59 | "request": {
60 | "agg": {
61 | "id": "histogram",
62 | "params": {
63 | "field": "installation_cost",
64 | "bins": 10
65 | }
66 | },
67 | "filter": {
68 | "query": {
69 | "match_all": {}
70 | }
71 | }
72 | },
73 | "meta": {
74 | "interval": 22222
75 | }
76 | }
77 | }
--------------------------------------------------------------------------------
/be/test/routes/api/terms/05.json:
--------------------------------------------------------------------------------
1 | {
2 | "query": {
3 | "method": "GET",
4 | "url": "terms",
5 | "query": {
6 | "field": "hp_feature_heat_system.keyword",
7 | "with_percentiles": true
8 | }
9 | },
10 | "response": {
11 | "code": 200,
12 | "data": {
13 | "terms": {
14 | "doc_count_error_upper_bound": 0,
15 | "sum_other_doc_count": 0,
16 | "buckets": [
17 | {
18 | "key": "Air Source Heat Pump",
19 | "doc_count": 207891
20 | },
21 | {
22 | "key": "Ground/Water Source Heat Pump",
23 | "doc_count": 31161
24 | },
25 | {
26 | "key": "Undefined or Other Heat Pump Type",
27 | "doc_count": 1409
28 | },
29 | {
30 | "key": "No HP",
31 | "doc_count": 48
32 | }
33 | ]
34 | },
35 | "percentiles": {
36 | "values": {
37 | "1.0": 48,
38 | "5.0": 48,
39 | "25.0": 1409,
40 | "50.0": 31161,
41 | "75.0": 31161,
42 | "95.0": 207891,
43 | "99.0": 207891
44 | }
45 | }
46 | },
47 | "message": "aggregation successful",
48 | "request": {
49 | "agg": {
50 | "id": "terms",
51 | "params": {
52 | "field": "hp_feature_heat_system.keyword",
53 | "with_percentiles": true,
54 | "size": 65536,
55 | "use_extended_stats": false,
56 | "with_stats": false
57 | }
58 | },
59 | "filter": {
60 | "query": {
61 | "match_all": {}
62 | }
63 | }
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/be/src/routes/date_histogram1_terms2.js:
--------------------------------------------------------------------------------
1 | import { index, maxBuckets } from '../conf.js';
2 | import { client } from '../es.js';
3 |
4 | export const getDateHistogram1Terms2 = async (request, reply) => {
5 | const {
6 | calendar_interval1 = '1y',
7 | field1,
8 | field2,
9 | missing2 = null,
10 | size2 = maxBuckets,
11 | use_extended_stats2 = false,
12 | with_percentiles2 = false,
13 | with_stats2 = false,
14 | } = request.query;
15 |
16 | const stats_type2 = use_extended_stats2 ? 'extended_stats_bucket' : 'stats_bucket';
17 |
18 | const body = {
19 | ...request.filter,
20 | size: 0,
21 | aggs: {
22 | date_histogram: {
23 | date_histogram: {
24 | field: field1,
25 | calendar_interval: calendar_interval1,
26 | format: 'yyyy-MM'
27 | },
28 | aggs: {
29 | terms: {
30 | terms: {
31 | field: field2,
32 | size: size2,
33 | ...missing2 && { missing: missing2 }
34 | }
35 | },
36 | ...with_stats2 && {
37 | stats: {
38 | [stats_type2]: {
39 | buckets_path: 'terms>_count'
40 | }
41 | }
42 | },
43 | ...with_percentiles2 && {
44 | percentiles: {
45 | percentiles_bucket: {
46 | buckets_path: 'terms>_count'
47 | }
48 | }
49 | }
50 | }
51 | }
52 | }
53 | };
54 |
55 | const result = await client.search({
56 | body,
57 | index
58 | });
59 |
60 | reply.send(result.aggregations);
61 | };
62 |
--------------------------------------------------------------------------------
/fe/src/routes/explorer/number/geo/[slug]/+page.svelte:
--------------------------------------------------------------------------------
1 |
35 |
36 |
45 |
--------------------------------------------------------------------------------
/fe/src/lib/components/explorer/SelectionXor.svelte:
--------------------------------------------------------------------------------
1 |
38 |
39 |
49 |
--------------------------------------------------------------------------------
/be/test/filter/requests/07.json:
--------------------------------------------------------------------------------
1 | {
2 | "request": {
3 | "property_feature_type": [
4 | "House"
5 | ],
6 | "hp_feature_power_generation": {
7 | "gt": 12000,
8 | "lt": 15000
9 | },
10 | "installation_date": {
11 | "gt": "2016-01-01T00:00:00.000Z",
12 | "lt": "2018-01-01T00:00:00.000Z"
13 | }
14 | },
15 | "query": {
16 | "query": {
17 | "bool": {
18 | "filter": [
19 | {
20 | "terms": {
21 | "property_feature_type.keyword": [
22 | "House"
23 | ]
24 | }
25 | },
26 | {
27 | "range": {
28 | "hp_feature_power_generation": {
29 | "gt": 12000,
30 | "lt": 15000
31 | }
32 | }
33 | },
34 | {
35 | "range": {
36 | "installation_date": {
37 | "gt": "2016-01-01T00:00:00.000Z",
38 | "lt": "2018-01-01T00:00:00.000Z"
39 | }
40 | }
41 | }
42 | ]
43 | }
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/appTest/src/pa11y/validate.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import * as _ from 'lamb';
3 | import pa11y from 'pa11y';
4 | import htmlReporter from 'pa11y-reporter-html';
5 | import Queue from 'queue-promise';
6 |
7 | import {lighthouseUrls} from '../../../fe/src/lib/config.js';
8 | import {urlBases} from '../config.js';
9 |
10 | const themeOverride = process.env?.VITE_THEME_OVERRIDE;
11 | const fileSuffix = themeOverride ? `_${themeOverride}` : '';
12 |
13 | const queue = new Queue({
14 | concurrent: 1
15 | });
16 |
17 | queue.on('end', () => {
18 | console.log('Done!');
19 | });
20 |
21 | const auditURL = async (id, url) => {
22 | const options = {
23 | standard: 'WCAG2AAA'
24 | };
25 | console.log('Auditing', url);
26 | const runnerResult = await pa11y(
27 | urlBases.development + url,
28 | options
29 | );
30 | console.log('Auditing done for', url);
31 | const reportHtml = await htmlReporter.results(runnerResult, url);
32 |
33 | // eslint-disable-next-line no-sync
34 | fs.writeFileSync(`../fe/static/audits/pa11y/${id}${fileSuffix}.html`, reportHtml);
35 |
36 | // `.lhr` is the Lighthouse Result as a JS object
37 | console.log(
38 | 'Report is done for',
39 | runnerResult.pageUrl
40 | );
41 | console.log(
42 | 'Issues found:',
43 | runnerResult.issues.length
44 | );
45 | }
46 |
47 | const enqueueTask = ([id, url]) =>
48 | queue.enqueue(async () => await auditURL(id, url));
49 |
50 | const auditUrls = _.pipe([
51 | _.pairs,
52 | _.mapWith(enqueueTask)
53 | ]);
54 |
55 | auditUrls(lighthouseUrls);
56 |
--------------------------------------------------------------------------------
/be/githooks/README.md:
--------------------------------------------------------------------------------
1 | # Creating the hook on the remote server
2 |
3 | Create a bare git repo:
4 |
5 | ```sh
6 | mkdir asf_hp_market_tracker.git
7 | cd asf_hp_market_tracker.git
8 | git init --bare
9 | ```
10 |
11 | Create post-receive hook file
12 |
13 | ```sh
14 | cd hooks
15 | touch post-receive
16 | ```
17 |
18 | Copy contents of [post-receive](./post-receive) to this file.
19 |
20 | Please make sure that you:
21 | - Update the `branch` variable to reflect which one the remote is, i.e. either
22 | `dev` or `staging`
23 |
24 | Then make executable:
25 |
26 | ```
27 | sudo chmod +x post-receive
28 | ```
29 |
30 | Clone the bare repo:
31 |
32 | ```
33 | cd $HOME
34 | git clone asf_hp_market_tracker.git
35 | ```
36 |
37 | # On the client
38 |
39 | Create the dev remote and push the dev/staging branch to the server.
40 | Example for dev server:
41 |
42 | ```sh
43 | git remote add dev ubuntu@hpmt.be.dev.dap-tools.uk:/home/ubuntu/asf_hp_market_tracker.git
44 | git push dev dev
45 | ```
46 |
47 | # Back on the server
48 |
49 | cd to the repository (not the bare one, i.e. not the directory ending in `.git`)
50 |
51 | `cd asf_hp_market_tracker`
52 |
53 | Pull, then switch to dev
54 |
55 | ```sh
56 | git pull
57 | git checkout dev --force
58 | ```
59 |
60 | # Install pm2 globally
61 |
62 | **N.B** Make sure npm and node are installed on the server before the next
63 | step. The easiest way is to use [nvm](https://github.com/nvm-sh/nvm#installing-and-updating)
64 |
65 | `npm install -g pm2`
66 |
67 | The G.H. action should take care of the rest.
68 |
69 |
70 |
--------------------------------------------------------------------------------
/be/src/util.js:
--------------------------------------------------------------------------------
1 | import * as _ from 'lamb';
2 |
3 | import { index } from './conf.js';
4 | import { client, getDocumentCount } from './es.js';
5 |
6 | export const getIntervalForBins = async (field, bins, filter) => {
7 | const stats = await client.search({
8 | index,
9 | body: {
10 | ...(filter ?? {}),
11 | aggs: {
12 | stats_: {
13 | stats: {
14 | field
15 | }
16 | }
17 | }
18 | },
19 | });
20 |
21 | const { min, max } = stats.aggregations.stats_;
22 |
23 | return Math.max(Math.round((max - min) / (bins - 1)), 1);
24 | };
25 |
26 | export const calculateCoverage = async (filter, fieldsInvolved, field1, field2) => {
27 |
28 | // 100 docs
29 | // 50 docs have design prop
30 | // no filter: coverage is 50%
31 | // apply filter -> 40 docs returned
32 |
33 | // Number of retrievable documents: 50 docs -> 50% of all data points
34 | // Number of documents after filtering: 40 docs
35 |
36 | const retrievableFilter = [
37 | { exists: { field: field1 } },
38 | ...(field2 ? [{ exists: { field: field2 } }] : []),
39 | ...(_.map(fieldsInvolved, f => ({ exists: { field: f }}))),
40 | ]
41 |
42 | const retreivableQuery = {
43 | query: {
44 | bool: { filter: retrievableFilter }
45 | }
46 | }
47 |
48 | const filteredQuery = {
49 | query: {
50 | bool: { filter: [...filter, ...retrievableFilter] }
51 | }
52 | }
53 |
54 | const retreivable = await getDocumentCount(retreivableQuery);
55 | const filtered = await getDocumentCount(filteredQuery);
56 | const coverage = {
57 | retreivable,
58 | filtered
59 | }
60 | return coverage;
61 | };
62 |
--------------------------------------------------------------------------------
/be/test/routes/routes.test.js:
--------------------------------------------------------------------------------
1 | import { readDir, readJson } from '@svizzle/file';
2 | import { test } from 'tap';
3 |
4 | import { buildTestServer } from '../utils.js';
5 |
6 | const path = 'test/routes/api';
7 |
8 | const testRoute = async route => {
9 | test(`/${route}`, async t => {
10 | const server = await buildTestServer();
11 | t.teardown(() => server.close());
12 |
13 | const testPath = `${path}/${route}`;
14 | const tests = await readDir(testPath);
15 |
16 | for await (const testFile of tests) {
17 | const { query, response: expectedResponse } = await readJson(`${testPath}/${testFile}`);
18 | const response = await server.inject(query);
19 | t.equal(response.statusCode, 200, `${route} ${testFile} returns a 200 status code`);
20 | t.same(response.json(), expectedResponse, `${route} ${testFile} returns the expected response body`);
21 | }
22 | t.end();
23 | });
24 | };
25 |
26 | const histogramTests = async (tap, query, result) => {
27 | const q = query.query;
28 | const expectedBins = 'missing' in q ? q.bins + 1 : q.bins;
29 | tap.equal(expectedBins, result.data.agg1.buckets.length);
30 | };
31 |
32 | testRoute('cardinality');
33 | testRoute('certified');
34 | testRoute('date_histogram');
35 | testRoute('date_histogram1_cardinality2');
36 | testRoute('date_histogram1_terms2');
37 | testRoute('date_histogram1_stats2');
38 | testRoute('date_histogram1_histogram2');
39 | testRoute('histogram', histogramTests);
40 | testRoute('terms');
41 | testRoute('terms1_cardinality2');
42 | testRoute('terms1_histogram2');
43 | testRoute('terms1_terms2');
44 | testRoute('terms1_stats2');
45 |
--------------------------------------------------------------------------------
/be/tiles/README.md:
--------------------------------------------------------------------------------
1 | ## Tiles
2 |
3 | This directory stores the protomaps tiles used to render the maps used in the frontend.
4 |
5 | ### Requirements
6 |
7 | The system was tested using the following versions:
8 |
9 | - [pmtiles v1.7.1](https://github.com/protomaps/go-pmtiles/releases)
10 | - [tippecanoe v1.36.0](https://github.com/mapbox/tippecanoe)
11 |
12 | ### Getting the source Geojson
13 |
14 | To populate this directory, first run `npm run getBoundaries`. This uses the `config.json`
15 | file along with the `downloadBoundaries` script provided by `dap_dv_backends_utils` to source
16 | the relevant geojson boundaries from the ONS Arcgis servers. It also uses `@svizzle/atlas`
17 | to save the `NUTS0` boundaries. All resulting geojson files are saved in the `geojson`
18 | directory.
19 |
20 | ### Converting to Mapbox and Protomaps tilesets
21 |
22 | Once the geojson is saved, use `npm run getTiles` to generate the corresponding `mbtiles`
23 | and `pmtiles`. This works by first generating a Mapbox tileset (mbtile) for each geojson
24 | downloaded in the previous script, then using `tile-join` to create a single tileset where
25 | each layer is a tileset corresponding to the boundaries listed in `config.json`. Finally,
26 | using `pmtiles`, we convert the joined Mapbox tileset to a Protomaps tileset.
27 |
28 | ### Uploading to s3
29 |
30 | To upload to an s3 bucket, use `npm run uploadTiles`. You will probably need to
31 | specify your own bucket on which you have the correct access rights. To set up
32 | a CDN using AWS Cloudront and Lambda functions, you can follow
33 | [this documentation](https://protomaps.com/docs/cdn/aws).
--------------------------------------------------------------------------------
/fe/src/lib/stores/navigation.js:
--------------------------------------------------------------------------------
1 | import {decapitalize} from '@svizzle/utils';
2 | import {derived, writable} from 'svelte/store';
3 |
4 | import {
5 | defaultMetricId,
6 | metricById,
7 | metricTitleById,
8 | } from '$lib/data/metrics.js';
9 | import {context} from '$lib/statechart/context.js';
10 | import {objectToSearchParams, risonifyValues} from '$lib/utils/svizzle/url.js';
11 |
12 | export const _activeViewType = writable('geo');
13 |
14 | export const _currentMetricId = writable(defaultMetricId);
15 | export const _currentMetric = writable(metricById[defaultMetricId]);
16 | export const _currentMetricTitle = derived(
17 | [_activeViewType, _currentMetric],
18 | ([activeViewType, {id, type, unitOfMeasure}]) => {
19 | const metricTitle = metricTitleById[id];
20 | const title = activeViewType === 'geo' && type === 'number'
21 | ? `Average ${decapitalize(metricTitle)}`
22 | : metricTitle;
23 | const unitOfMeasureLabel = unitOfMeasure ? ` [${unitOfMeasure}]` : '';
24 |
25 | return `${title}${unitOfMeasureLabel}`;
26 | }
27 | );
28 |
29 | export const _currentPage = writable();
30 |
31 | export const _expectedRoute = derived(
32 | [_activeViewType, _currentMetric],
33 | ([activeViewType, currentMetric]) => {
34 | const {id, type} = currentMetric;
35 | return `/explorer/${type}/${activeViewType}/${id}`;
36 | }
37 | );
38 |
39 | export const _selection = writable(context.selection);
40 |
41 | export const _searchParams = derived(
42 | _selection,
43 | selection => {
44 | const serialisedSelection = risonifyValues(selection);
45 | const searchParams = objectToSearchParams(serialisedSelection);
46 |
47 | return searchParams;
48 | }
49 | );
50 |
--------------------------------------------------------------------------------
/be/test/routes/api/terms1_certified2/02.json:
--------------------------------------------------------------------------------
1 | {
2 | "query": {
3 | "method": "GET",
4 | "url": "terms1_certified2",
5 | "query": {
6 | "field1": "property_geo_region_country21_name.keyword",
7 | "filter": "(installer_certificate_date_start:(gte:'2016-01-01'),installer_certificate_date_end:(lte:'2020-01-01'))",
8 | "logic2": "engulfs"
9 | }
10 | },
11 | "response": {
12 | "code": 200,
13 | "data": {
14 | "terms": {
15 | "buckets": [
16 | {
17 | "key": "England",
18 | "certified": {
19 | "value": 544
20 | }
21 | },
22 | {
23 | "key": "Scotland",
24 | "certified": {
25 | "value": 117
26 | }
27 | },
28 | {
29 | "key": "Wales",
30 | "certified": {
31 | "value": 143
32 | }
33 | }
34 | ]
35 | }
36 | },
37 | "message": "aggregation successful",
38 | "request": {
39 | "agg": {
40 | "id": "terms1_certified2",
41 | "params": {
42 | "field1": "property_geo_region_country21_name.keyword",
43 | "filter": "(installer_certificate_date_start:(gte:'2016-01-01'),installer_certificate_date_end:(lte:'2020-01-01'))",
44 | "logic2": "engulfs"
45 | }
46 | },
47 | "filter": {
48 | "query": {
49 | "bool": {
50 | "filter": [
51 | {
52 | "range": {
53 | "installer_certificate_date_start": {
54 | "gte": "2016-01-01"
55 | }
56 | }
57 | },
58 | {
59 | "range": {
60 | "installer_certificate_date_end": {
61 | "lte": "2020-01-01"
62 | }
63 | }
64 | }
65 | ]
66 | }
67 | }
68 | }
69 | }
70 | }
71 | }
--------------------------------------------------------------------------------
/be/src/es.js:
--------------------------------------------------------------------------------
1 | import { Client as EsClient } from '@elastic/elasticsearch';
2 | import { Client as OsClient } from '@opensearch-project/opensearch'
3 |
4 | import { esEnv, domain, index, fingerprint } from './conf.js';
5 |
6 | // eslint-disable-next-line no-process-env
7 | const password = process.env.ELASTICSEARCH_PASSWORD || false;
8 | if (!password) {
9 | throw new Error('Elasticsearch password environment variable not set');
10 | }
11 |
12 | export const client = esEnv === 'production'
13 | ? new OsClient({
14 | node: domain,
15 | auth: {
16 | username: 'elastic',
17 | password
18 | }
19 | })
20 | : new EsClient({
21 | node: domain,
22 | auth: {
23 | username: 'elastic',
24 | password
25 | },
26 | tls: {
27 | caFingerprint: fingerprint,
28 | rejectUnauthorized: false
29 | }
30 | });
31 |
32 | export const isOpenSearch = esEnv === 'production';
33 |
34 | // override search function to match the ES client
35 | if (esEnv === 'production') {
36 | client.search = (function(super_) {
37 | return async function() {
38 | const result = await super_.apply(this, arguments);
39 |
40 | if ('body' in result) {
41 | return result.body;
42 | }
43 | if ('aggregations' in result) {
44 | return result.aggregations;
45 | }
46 |
47 | throw new Error('Unexpected result from ES client');
48 | };
49 |
50 | }(client.search));
51 | }
52 | export const getXCompatibleCount = async options => {
53 | const result = await client.count(options);
54 | return isOpenSearch ? result.body : result;
55 | };
56 |
57 | export const getDocumentCount = async body => {
58 | const { count } = await getXCompatibleCount({ body, index });
59 | return count;
60 | };
61 |
--------------------------------------------------------------------------------
/fe/src/lib/components/explorer/medium/ViewSelectorMedium.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
19 |
24 | Geo
25 |
26 |
27 |
31 |
36 | Time
37 |
38 |
39 |
43 |
48 | Stats
49 |
50 |
51 |
52 |
53 |
72 |
--------------------------------------------------------------------------------
/fe/src/lib/components/explorer/small/IconBar.svelte:
--------------------------------------------------------------------------------
1 |
20 |
21 |
25 | {#each icons as {glyph, id, transform}}
26 |
41 | {/each}
42 |
43 |
44 |
73 |
--------------------------------------------------------------------------------
/appTest/README.md:
--------------------------------------------------------------------------------
1 | # Status
2 |
3 | This directory contains script to test the app developed in `fe/` and `be/`.
4 | We haven't made it into a workspace to speed up installation of the workspaces
5 | (e.g. `puppeteer` requires binaries that might take a long time to be downloaded).
6 |
7 | # Setting up and running the tests
8 |
9 | The server should be running in the background for the tests to run.
10 |
11 | The following script shows the procedure but is intended to serve as a
12 | guideline as it, for example, doesn't describe how to stop the preview server
13 | after testing.
14 |
15 | It is OS specific and up to the tester to decide if launching the server in
16 | parallel from a single terminal console or use two terminals.
17 |
18 | The `VITE_THEME_OVERRIDE` environment variable must be set to
19 | `themeLight` or `themeDark` in order to test both color themes when launching
20 | the `build` and `lighthouse`|`pa11y` npm scripts so as to write the reports with
21 | the proper filenames.
22 |
23 | ```
24 | npm i
25 |
26 | export VITE_THEME_OVERRIDE=themeLight
27 | # `$env:VITE_THEME_OVERRIDE = "themeLight"` for Windows PowerShell
28 | # `let VITE_THEME_OVERRIDE=themeLight`` for Windows Command line
29 |
30 | cd fe
31 | npm run build
32 | npm run preview &
33 |
34 | cd ../appTest
35 | npm i
36 | npm run lighthouse
37 | npm run pa11y
38 |
39 | # terminate preview server to switch themes
40 |
41 | export VITE_THEME_OVERRIDE=themeDark
42 | # see above for other OS
43 |
44 | cd ../
45 | npm run build
46 | npm run preview &
47 |
48 | cd ../appTest
49 | npm i
50 | npm run lighthouse
51 | npm run pa11y
52 | ```
53 |
54 | After running the tests you'll find the reports in `fe/static/audits/`. These
55 | reports will be linked from the accessibility statement page in the app.
56 |
--------------------------------------------------------------------------------
/be/test/routes/api/terms1_terms2/03.json:
--------------------------------------------------------------------------------
1 | {
2 | "query": {
3 | "method": "GET",
4 | "url": "terms1_terms2",
5 | "query": {
6 | "field1": "hp_feature_heat_system.keyword",
7 | "field2": "property_feature_type.keyword",
8 | "size1": 2,
9 | "size2": 1
10 | }
11 | },
12 | "response": {
13 | "code": 200,
14 | "data": {
15 | "terms": {
16 | "doc_count_error_upper_bound": 0,
17 | "sum_other_doc_count": 1457,
18 | "buckets": [
19 | {
20 | "key": "Air Source Heat Pump",
21 | "doc_count": 207891,
22 | "terms": {
23 | "doc_count_error_upper_bound": 0,
24 | "sum_other_doc_count": 71504,
25 | "buckets": [
26 | {
27 | "key": "House",
28 | "doc_count": 136387
29 | }
30 | ]
31 | }
32 | },
33 | {
34 | "key": "Ground/Water Source Heat Pump",
35 | "doc_count": 31161,
36 | "terms": {
37 | "doc_count_error_upper_bound": 0,
38 | "sum_other_doc_count": 9640,
39 | "buckets": [
40 | {
41 | "key": "House",
42 | "doc_count": 21520
43 | }
44 | ]
45 | }
46 | }
47 | ]
48 | }
49 | },
50 | "message": "aggregation successful",
51 | "request": {
52 | "agg": {
53 | "id": "terms1_terms2",
54 | "params": {
55 | "field1": "hp_feature_heat_system.keyword",
56 | "field2": "property_feature_type.keyword",
57 | "size1": 2,
58 | "size2": 1,
59 | "use_extended_stats1": false,
60 | "with_percentiles1": false,
61 | "with_stats1": false,
62 | "use_extended_stats2": false,
63 | "with_percentiles2": false,
64 | "with_stats2": false
65 | }
66 | },
67 | "filter": {
68 | "query": {
69 | "match_all": {}
70 | }
71 | }
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/fe/src/lib/statechart/actions/staticData.js:
--------------------------------------------------------------------------------
1 | import * as _ from 'lamb';
2 |
3 | import {_staticData} from '$lib/stores/data.js';
4 |
5 | export const logStaticData = (ctx, {data}) => {
6 | console.log('[logStaticData] response:', data);
7 | }
8 |
9 | const getDataTermsBuckets = _.getPath('data.terms.buckets');
10 |
11 | const getBrandsModels = _.pipe([
12 | getDataTermsBuckets,
13 | _.mapWith(({doc_count, key, terms: {buckets}}) =>
14 | ({
15 | key,
16 | value: doc_count,
17 | values: _.map(buckets,
18 | ({doc_count: value, key: subKey}) => ({key: subKey, value})
19 | ),
20 | })
21 | ),
22 | ]);
23 |
24 | const indexCatStats = _.pipe([
25 | _.indexBy(_.pipe([
26 | _.getPath('request.agg.params.field'),
27 | _.replace('.keyword', '')
28 | ])),
29 | _.mapValuesWith(getDataTermsBuckets),
30 | ]);
31 |
32 | const getCount = _.getPath('data.count');
33 |
34 | const indexNumHists = _.pipe([
35 | _.indexBy(_.getPath('request.agg.params.field')),
36 | _.mapValuesWith(_.getPath('data.histogram.buckets'))
37 | ]);
38 |
39 | const indexNumStats = _.pipe([
40 | _.indexBy(_.getPath('request.agg.params.field')),
41 | _.mapValuesWith(_.getPath('data.stats')),
42 | ]);
43 |
44 | const indexTimelines = _.pipe([
45 | _.indexBy(_.getPath('request.agg.params.calendar_interval')),
46 | _.mapValuesWith(_.getPath('data.date_histogram.buckets'))
47 | ]);
48 |
49 | export const updateStaticDataStore = (
50 | ctx,
51 | {data: {
52 | brandsModelsTerms,
53 | catStats,
54 | count,
55 | numHists,
56 | numStats,
57 | timelines,
58 | }}
59 | ) => _staticData.set({
60 | brandsModels: getBrandsModels(brandsModelsTerms),
61 | catStatsById: indexCatStats(catStats),
62 | count: getCount(count),
63 | numHistsById: indexNumHists(numHists),
64 | numStatsById: indexNumStats(numStats),
65 | timelines: indexTimelines(timelines),
66 | });
67 |
--------------------------------------------------------------------------------
/fe/static/logos/Nesta-dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/be/src/routes/terms1_terms2.js:
--------------------------------------------------------------------------------
1 | import { index, maxBuckets } from '../conf.js';
2 | import { client } from '../es.js';
3 |
4 | export const getTerms1Terms2 = async (request, reply) => {
5 | const {
6 | field1,
7 | missing1 = null,
8 | size1 = maxBuckets,
9 | use_extended_stats1,
10 | with_percentiles1,
11 | with_stats1 = false,
12 | field2,
13 | missing2 = null,
14 | size2 = maxBuckets,
15 | use_extended_stats2,
16 | with_percentiles2,
17 | with_stats2 = false,
18 | } = request.query;
19 |
20 | const stats_type1 = use_extended_stats1 ? 'extended_stats_bucket' : 'stats_bucket';
21 | const stats_type2 = use_extended_stats2 ? 'extended_stats_bucket' : 'stats_bucket';
22 |
23 | const body = {
24 | ...request.filter,
25 | size: 0,
26 | aggs: {
27 | terms: {
28 | terms: {
29 | field: field1,
30 | size: size1,
31 | ...missing1 && { missing: missing1 }
32 | },
33 | aggs: {
34 | terms: {
35 | terms: {
36 | field: field2,
37 | size: size2,
38 | ...missing2 && { missing: missing2 }
39 | }
40 | },
41 | ...with_stats2 && {
42 | stats: {
43 | [stats_type2]: {
44 | buckets_path: 'terms>_count'
45 | }
46 | }
47 | },
48 | ...with_percentiles2 && {
49 | percentiles: {
50 | percentiles_bucket: {
51 | buckets_path: 'terms>_count'
52 | }
53 | }
54 | }
55 | }
56 | },
57 | ...with_stats1 && {
58 | stats1: {
59 | [stats_type1]: {
60 | buckets_path: 'terms>_count'
61 | }
62 | }
63 | },
64 | ...with_percentiles1 && {
65 | percentiles1: {
66 | percentiles_bucket: {
67 | buckets_path: 'terms>_count'
68 | }
69 | }
70 | }
71 | }
72 | };
73 |
74 | const result = await client.search({
75 | body,
76 | index
77 | });
78 |
79 | reply.send(result.aggregations);
80 | };
81 |
--------------------------------------------------------------------------------
/fe/static/logos/Nesta-light.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/fe/src/lib/components/layout/medium/ExplorerMedium.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
20 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
77 |
--------------------------------------------------------------------------------
/be/src/routes/certified.js:
--------------------------------------------------------------------------------
1 | import { DateTime, Interval } from 'luxon';
2 |
3 | import { index, maxBuckets } from '../conf.js';
4 | import { client } from '../es.js';
5 | import { calculateCounts } from '../utils/certifiedLogic.js';
6 |
7 | export const getCertified = async (request, reply) => {
8 | const {
9 | logic='overlaps'
10 | } = request.query;
11 |
12 | const maxMinDateResponse = await client.search({
13 | body: {
14 | aggs: {
15 | max: { max: { field: 'installer_certificate_date_end'} },
16 | min: { min: { field: 'installer_certificate_date_start' } }
17 | }
18 | },
19 | index
20 | });
21 | const maxEndDate = maxMinDateResponse.aggregations.max.value_as_string;
22 | const minStartDate = maxMinDateResponse.aggregations.min.value_as_string;
23 |
24 | const start =
25 | request.originalFilter.installer_certificate_date_start?.gte || minStartDate;
26 | const end =
27 | request.originalFilter.installer_certificate_date_end?.lte || maxEndDate;
28 | const missingEndDate = DateTime.fromISO(maxEndDate).toMillis();
29 |
30 | const body = {
31 | ...request.filter,
32 | size: 0,
33 | aggs: {
34 | terms: {
35 | terms: {
36 | field: 'installer_id_hash.keyword',
37 | size: maxBuckets
38 | },
39 | aggs: {
40 | min: {
41 | min: {
42 | field: 'installer_certificate_date_start'
43 | }
44 | },
45 | max: {
46 | max: {
47 | field: 'installer_certificate_date_end',
48 | missing: missingEndDate
49 | }
50 | }
51 | }
52 | }
53 | }
54 | };
55 |
56 | const result = await client.search({
57 | body,
58 | index
59 | });
60 |
61 | const timelineWindow = Interval.fromDateTimes(
62 | DateTime.fromISO(start),
63 | DateTime.fromISO(end)
64 | );
65 |
66 | const calculate = calculateCounts(timelineWindow, logic);
67 | const count = calculate(result.aggregations.terms.buckets);
68 |
69 | const response = { count };
70 | reply.send(response);
71 | };
72 |
--------------------------------------------------------------------------------
/be/test/routes/api/terms/04.json:
--------------------------------------------------------------------------------
1 | {
2 | "query": {
3 | "method": "GET",
4 | "url": "terms",
5 | "query": {
6 | "field": "hp_feature_heat_system.keyword",
7 | "use_extended_stats": true,
8 | "with_stats": true
9 | }
10 | },
11 | "response": {
12 | "code": 200,
13 | "data": {
14 | "terms": {
15 | "doc_count_error_upper_bound": 0,
16 | "sum_other_doc_count": 0,
17 | "buckets": [
18 | {
19 | "key": "Air Source Heat Pump",
20 | "doc_count": 207891
21 | },
22 | {
23 | "key": "Ground/Water Source Heat Pump",
24 | "doc_count": 31161
25 | },
26 | {
27 | "key": "Undefined or Other Heat Pump Type",
28 | "doc_count": 1409
29 | },
30 | {
31 | "key": "No HP",
32 | "doc_count": 48
33 | }
34 | ]
35 | },
36 | "stats": {
37 | "count": 4,
38 | "min": 48,
39 | "max": 207891,
40 | "avg": 60127.25,
41 | "sum": 240509,
42 | "sum_of_squares": 44191663387,
43 | "variance": 7432629654.1875,
44 | "variance_population": 7432629654.1875,
45 | "variance_sampling": 9910172872.25,
46 | "std_deviation": 86212.70007480046,
47 | "std_deviation_population": 86212.70007480046,
48 | "std_deviation_sampling": 99549.85119150103,
49 | "std_deviation_bounds": {
50 | "upper": 232552.65014960093,
51 | "lower": -112298.15014960093,
52 | "upper_population": 232552.65014960093,
53 | "lower_population": -112298.15014960093,
54 | "upper_sampling": 259226.95238300206,
55 | "lower_sampling": -138972.45238300206
56 | }
57 | }
58 | },
59 | "message": "aggregation successful",
60 | "request": {
61 | "agg": {
62 | "id": "terms",
63 | "params": {
64 | "field": "hp_feature_heat_system.keyword",
65 | "use_extended_stats": true,
66 | "with_stats": true,
67 | "size": 65536,
68 | "with_percentiles": false
69 | }
70 | },
71 | "filter": {
72 | "query": {
73 | "match_all": {}
74 | }
75 | }
76 | }
77 | }
78 | }
--------------------------------------------------------------------------------
/be/test/routes/api/terms1_certified2/03.json:
--------------------------------------------------------------------------------
1 | {
2 | "query": {
3 | "method": "GET",
4 | "url": "terms1_certified2",
5 | "query": {
6 | "field1": "property_geo_region_itl21_1_name.keyword"
7 | }
8 | },
9 | "response": {
10 | "code": 200,
11 | "data": {
12 | "terms": {
13 | "buckets": [
14 | {
15 | "key": "South West (England)",
16 | "certified": {
17 | "value": 658
18 | }
19 | },
20 | {
21 | "key": "East",
22 | "certified": {
23 | "value": 608
24 | }
25 | },
26 | {
27 | "key": "South East (England)",
28 | "certified": {
29 | "value": 728
30 | }
31 | },
32 | {
33 | "key": "Scotland",
34 | "certified": {
35 | "value": 355
36 | }
37 | },
38 | {
39 | "key": "East Midlands (England)",
40 | "certified": {
41 | "value": 533
42 | }
43 | },
44 | {
45 | "key": "Yorkshire and The Humber",
46 | "certified": {
47 | "value": 503
48 | }
49 | },
50 | {
51 | "key": "West Midlands (England)",
52 | "certified": {
53 | "value": 559
54 | }
55 | },
56 | {
57 | "key": "North West (England)",
58 | "certified": {
59 | "value": 488
60 | }
61 | },
62 | {
63 | "key": "Wales",
64 | "certified": {
65 | "value": 448
66 | }
67 | },
68 | {
69 | "key": "London",
70 | "certified": {
71 | "value": 315
72 | }
73 | },
74 | {
75 | "key": "North East (England)",
76 | "certified": {
77 | "value": 226
78 | }
79 | }
80 | ]
81 | }
82 | },
83 | "message": "aggregation successful",
84 | "request": {
85 | "agg": {
86 | "id": "terms1_certified2",
87 | "params": {
88 | "field1": "property_geo_region_itl21_1_name.keyword",
89 | "logic2": "overlaps"
90 | }
91 | },
92 | "filter": {
93 | "query": {
94 | "match_all": {}
95 | }
96 | }
97 | }
98 | }
99 | }
--------------------------------------------------------------------------------
/fe/src/lib/statechart/guards.js:
--------------------------------------------------------------------------------
1 | import {isNotNaN, isNotNil} from '@svizzle/utils';
2 | import * as _ from 'lamb';
3 | import {RISON} from 'rison2';
4 | import {get} from 'svelte/store';
5 |
6 | import {_viewCache, _staticData} from '$lib/stores/data.js';
7 | import {_currentPage} from '$lib/stores/navigation.js';
8 | import {doPairItemsContainSameValues} from '$lib/utils/svizzle/utils.js';
9 |
10 | /* view data */
11 |
12 | export const isViewDataCached = ctx => {
13 | const page = get(_currentPage);
14 | const viewCache = get(_viewCache);
15 | const key = `${page.route.id}-${ctx.viewQueryPath}`;
16 |
17 | return _.has(viewCache, key);
18 | }
19 |
20 | /* navigation */
21 |
22 | export const hasFullSearchParams = ctx => {
23 | const searchParams = _.fromPairs(Array.from(ctx.page.url.searchParams.entries()));
24 |
25 | // do `ctx.selection` and `ctx.page` have the same keys?
26 |
27 | const ctxSelectionKeys = _.keys(ctx.selection);
28 | const searchParamsKeys = _.keys(searchParams);
29 | let pass = doPairItemsContainSameValues([ctxSelectionKeys, searchParamsKeys]);
30 |
31 | if (pass) {
32 | // if so, does the URL `filters` have region criteria?
33 |
34 | const parsedSearchParams = _.mapValues(
35 | searchParams,
36 | value => {
37 | const dontParseIt = value === '' || isNotNaN(parseInt(value[0], 10));
38 | const result = dontParseIt ? value : RISON.parse(value);
39 |
40 | return result;
41 | }
42 | );
43 |
44 | pass =
45 | _.has(parsedSearchParams, 'filters') &&
46 | _.has(parsedSearchParams.filters, 'installerRegionNames') &&
47 | _.has(parsedSearchParams.filters, 'installerRegionType') &&
48 | _.has(parsedSearchParams.filters, 'propertyRegionNames') &&
49 | _.has(parsedSearchParams.filters, 'propertyRegionType') &&
50 | _.has(parsedSearchParams.filters, 'installation_date');
51 | }
52 |
53 | return pass;
54 | }
55 |
56 | /* static data */
57 |
58 | const checkStaticData = _.allOf([
59 | isNotNil,
60 | _.hasKey('timelines')
61 | ]);
62 |
63 | // eslint-disable-next-line no-unused-vars
64 | export const hasStaticData = (ctx, {data}) => {
65 | const staticData = get(_staticData);
66 | const pass = checkStaticData(staticData);
67 |
68 | return pass;
69 | };
70 |
--------------------------------------------------------------------------------
/be/test/routes/api/terms1_stats2/01.json:
--------------------------------------------------------------------------------
1 | {
2 | "query": {
3 | "method": "GET",
4 | "url": "terms1_stats2",
5 | "query": {
6 | "field1": "property_energy_efficiency_walls.keyword",
7 | "field2": "installation_cost"
8 | }
9 | },
10 | "response": {
11 | "code": 200,
12 | "data": {
13 | "terms": {
14 | "doc_count_error_upper_bound": 0,
15 | "sum_other_doc_count": 0,
16 | "buckets": [
17 | {
18 | "key": "Very Good",
19 | "doc_count": 101487,
20 | "stats": {
21 | "count": 24936,
22 | "min": 1,
23 | "max": 200000,
24 | "avg": 11678.729104266322,
25 | "sum": 291220788.943985
26 | }
27 | },
28 | {
29 | "key": "Good",
30 | "doc_count": 78915,
31 | "stats": {
32 | "count": 45254,
33 | "min": 1,
34 | "max": 200000,
35 | "avg": 11138.48231488005,
36 | "sum": 504060878.6775818
37 | }
38 | },
39 | {
40 | "key": "Average",
41 | "doc_count": 28782,
42 | "stats": {
43 | "count": 23138,
44 | "min": 1,
45 | "max": 167139.765625,
46 | "avg": 10907.402371311839,
47 | "sum": 252375476.06741333
48 | }
49 | },
50 | {
51 | "key": "Very Poor",
52 | "doc_count": 18685,
53 | "stats": {
54 | "count": 14352,
55 | "min": 1,
56 | "max": 200000,
57 | "avg": 15334.702359703473,
58 | "sum": 220083648.26646423
59 | }
60 | },
61 | {
62 | "key": "Poor",
63 | "doc_count": 12615,
64 | "stats": {
65 | "count": 8518,
66 | "min": 1,
67 | "max": 150000,
68 | "avg": 13111.904694406045,
69 | "sum": 111687204.18695068
70 | }
71 | }
72 | ]
73 | }
74 | },
75 | "message": "aggregation successful",
76 | "request": {
77 | "agg": {
78 | "id": "terms1_stats2",
79 | "params": {
80 | "field1": "property_energy_efficiency_walls.keyword",
81 | "field2": "installation_cost",
82 | "size1": 65536,
83 | "use_extended_stats2": false,
84 | "use_percentiles2": false
85 | }
86 | },
87 | "filter": {
88 | "query": {
89 | "match_all": {}
90 | }
91 | }
92 | }
93 | }
94 | }
--------------------------------------------------------------------------------
/fe/src/lib/statechart/actors.js:
--------------------------------------------------------------------------------
1 | import {applyFnMap} from '@svizzle/utils';
2 | import * as _ from 'lamb';
3 |
4 | import {intervals} from '$lib/config/time.js';
5 | import {categoricalMetrics, numericMetrics} from '$lib/data/metrics.js';
6 | import {selectedBeURL} from '$lib/env.js';
7 |
8 | const query = queryPath =>
9 | fetch(`${selectedBeURL}/${queryPath}`)
10 | .then(response => response.json());
11 |
12 | export const queryViewData = ({viewQueryPath}) => query(viewQueryPath);
13 |
14 | const getQueryPromise = ({endpoint, params}) => {
15 | const queryPath = `${endpoint}?${new URLSearchParams(params)}`;
16 |
17 | return query(queryPath);
18 | }
19 |
20 | export const queryStaticData = () => {
21 | const brandsModelsTermsPromise = getQueryPromise({
22 | endpoint: 'terms1_terms2',
23 | params: {
24 | field1: 'hp_id_brand.keyword',
25 | field2: 'hp_id_model.keyword',
26 | }
27 | });
28 |
29 | const catTermsPromises = _.map(categoricalMetrics, ({id}) =>
30 | getQueryPromise({
31 | endpoint: 'terms',
32 | params: {
33 | field: `${id}.keyword` // use `esKey`?
34 | }
35 | })
36 | );
37 |
38 | const countPromise = query('count');
39 |
40 | const numHistPromises = _.map(numericMetrics, ({id: field}) =>
41 | getQueryPromise({
42 | endpoint: 'histogram',
43 | params: {
44 | bins: 20,
45 | field
46 | }
47 | })
48 | );
49 |
50 | const numStatsPromises = _.map(numericMetrics, ({id: field}) =>
51 | getQueryPromise({
52 | endpoint: 'stats',
53 | params: {field}
54 | })
55 | );
56 |
57 | const timelinesPromises = _.map(intervals, calendar_interval =>
58 | getQueryPromise({
59 | endpoint: 'date_histogram',
60 | params: {
61 | calendar_interval,
62 | field: 'installation_date'
63 | }
64 | })
65 | );
66 |
67 | return Promise.all([
68 | brandsModelsTermsPromise,
69 | Promise.all(catTermsPromises),
70 | countPromise,
71 | Promise.all(numHistPromises),
72 | Promise.all(numStatsPromises),
73 | Promise.all(timelinesPromises),
74 | ]).then(applyFnMap({
75 | brandsModelsTerms: _.getAt(0),
76 | catStats: _.getAt(1),
77 | count: _.getAt(2),
78 | numHists: _.getAt(3),
79 | numStats: _.getAt(4),
80 | timelines: _.getAt(5),
81 | }));
82 | };
83 |
--------------------------------------------------------------------------------
/fe/src/lib/_content/info/Disclaimer.svx:
--------------------------------------------------------------------------------
1 | ## Disclaimer
2 |
3 | You should not rely on this data and accordingly we are not liable for any
4 | damages suffered. Whilst we take all reasonable care in the provision of our
5 | services and make every effort to keep our content up to date, we do not accept
6 | any responsibility for errors or omissions including, but not limited to, how
7 | the information is used, how it is interpreted or what reliance is placed on it.
8 |
9 | We do not guarantee the accuracy of the content or provide any conditions or
10 | warranties that the information will be:
11 | * current
12 | * secure
13 | * accurate
14 | * complete
15 | * free from bugs or viruses
16 | * comprehensive
17 |
18 | We do not publish or offer any advice. Where appropriate, you should seek
19 | independent professional or specialist advice before acting on any of the
20 | information contained within our content.
21 |
22 | We do not accept any liability for any loss or damage that may arise from using
23 | the content published on this website. This includes, but is not limited to:
24 | * any direct, indirect or consequential losses
25 | * any loss or damage caused by civil wrongs ("tort", including negligence), breach of contract or otherwise
26 | * the use of this website and the content or reliability of any websites that are linked to or from it
27 | * the inability to use this website and any websites that are linked to or from it
28 |
29 | This applies if the loss or damage was foreseeable, arose in the normal course
30 | of things or you advised us that it might happen.
31 |
32 | This includes (but is not limited to) the loss of your:
33 | * income or revenue
34 | * salary, benefits or other payments
35 | * business
36 | * profits or contracts
37 | * opportunity
38 | * anticipated savings
39 | * data
40 | * goodwill or reputation
41 | * tangible property
42 | * intangible property, including loss, corruption or damage to data or any
43 | computer system
44 | * wasted management or office time
45 |
46 | We may still be liable for:
47 | * death or personal injury arising from our negligence
48 | * fraudulent misrepresentation
49 | * any other liability that cannot be excluded or limited under applicable
50 | law
51 |
--------------------------------------------------------------------------------
/fe/src/routes/explorer/count/geo/[slug]/+page.svelte:
--------------------------------------------------------------------------------
1 |
60 |
61 |
70 |
--------------------------------------------------------------------------------
/fe/static/audits/pa11y/Home_themeDark.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Accessibility Report For "http://localhost:4173/" (Fri Oct 27 2023 12:04:10 GMT-0300 (Argentina Standard Time))
7 |
8 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
Accessibility Report For "http://localhost:4173/"
91 |
Generated at: Fri Oct 27 2023 12:04:10 GMT-0300 (Argentina Standard Time)
92 |
93 |
94 | 0 errors
95 | 0 warnings
96 | 0 notices
97 |
98 |
99 |
101 |
102 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/fe/static/audits/pa11y/Home_themeLight.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Accessibility Report For "http://localhost:4173/" (Fri Oct 27 2023 12:02:12 GMT-0300 (Argentina Standard Time))
7 |
8 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
Accessibility Report For "http://localhost:4173/"
91 |
Generated at: Fri Oct 27 2023 12:02:12 GMT-0300 (Argentina Standard Time)
92 |
93 |
94 | 0 errors
95 | 0 warnings
96 | 0 notices
97 |
98 |
99 |
101 |
102 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/be/src/utils/certifiedLogic.js:
--------------------------------------------------------------------------------
1 | import * as _ from 'lamb';
2 | import { DateTime, Interval } from 'luxon';
3 |
4 | /*
5 | each bucket is an aggregation for a single installer
6 | - date_histogram1 = date_histogram for certificate_date_start
7 | - date_histogram2 = date_histogram for certificate_date_end
8 | */
9 | export const getValidDateRanges = _.pipe([
10 | _.mapWith(bucket => [bucket.min.value_as_string, bucket.max.value_as_string]),
11 | _.filterWith(range => range[0] && range[1])
12 | ]);
13 |
14 | /*
15 | - interval: ['2000-01', '2010-01']
16 |
17 | - logic = 'overlaps' (does `range` overlap `interval`?)
18 | - range: ['2005-01', '2015-01'] => true
19 | - range: ['1990-01', '1995-01'] => false
20 |
21 | - logic = 'engulfs' (does `range` engulf `interval`?)
22 | - range: ['1990-01', '2015-01'] => true
23 | - range: ['2005-01', '2015-01'] => false
24 | */
25 | const intersectsWithInterval = (range, interval, logic) => {
26 | const rangeInterval = Interval.fromDateTimes(
27 | DateTime.fromISO(range[0]), // certified_start
28 | DateTime.fromISO(range[1]) // certified_end
29 | );
30 |
31 | let intersects = false;
32 |
33 | switch (logic) {
34 | case 'overlaps':
35 | intersects = rangeInterval.overlaps(interval);
36 | break;
37 | case 'engulfs':
38 | intersects = rangeInterval.engulfs(interval);
39 | break;
40 | case 'new':
41 | intersects = rangeInterval.start >= interval.start;
42 | break;
43 | case 'dropped':
44 | intersects = rangeInterval.end <= interval.end;
45 | break;
46 | default:
47 | break;
48 | }
49 |
50 | return intersects;
51 | };
52 |
53 | export const calculateCounts = (window, logic) => _.pipe([
54 | getValidDateRanges,
55 | _.reduceWith(
56 | (count, range) => count + intersectsWithInterval(range, window, logic),
57 | 0
58 | )
59 | ]);
60 |
61 | export const roundDateDown = (date, calendar_interval) => {
62 | let result = date;
63 |
64 | switch (calendar_interval) {
65 | case '1y':
66 | result = DateTime.fromISO(`${date.year}`);
67 | break;
68 | case '1M':
69 | result = DateTime.fromISO(`${date.year}-${date.month}-01`);
70 | break;
71 | case '1q': {
72 | const quarter = Math.floor(date.month / 3);
73 | result = DateTime.fromISO(`${date.year}-${3 * quarter + 1}-01`);
74 | break;
75 | }
76 | default:
77 | break;
78 | }
79 |
80 | return result;
81 | };
82 |
--------------------------------------------------------------------------------
/be/src/routes.js:
--------------------------------------------------------------------------------
1 | import {
2 | getCardinality,
3 | getCount,
4 | getDateHistogram,
5 | getDateHistogram1Cardinality2,
6 | getDateHistogram1Certified2,
7 | getDateHistogram1Histogram2,
8 | getDateHistogram1Stats2,
9 | getDateHistogram1Terms2,
10 | getHistogram,
11 | getStats,
12 | getTerms,
13 | getTerms1Certified2,
14 | getTerms1Cardinality2,
15 | getTerms1Histogram2,
16 | getTerms1Stats2,
17 | getTerms1Terms2,
18 | getCertified,
19 | } from './routes/index.js';
20 | import {
21 | cardinalitySchema,
22 | certifiedSchema,
23 | dateHistogram1Cardinality2,
24 | dateHistogram1Certified2,
25 | dateHistogram1Histogram2Schema,
26 | dateHistogram1Stats2Schema,
27 | dateHistogram1Terms2Schema,
28 | dateHistogramSchema,
29 | histogramSchema,
30 | statsSchema,
31 | terms1Cardinality2Schema,
32 | terms1Certified2Schema,
33 | terms1Histogram2Schema,
34 | terms1Stats2Schema,
35 | terms1Terms2Schema,
36 | termsSchema,
37 | } from './schemas/index.js';
38 |
39 | export const routes = (fastify, options, done) => {
40 | fastify.get('/cardinality', cardinalitySchema, getCardinality);
41 | fastify.get('/certified', certifiedSchema, getCertified);
42 | fastify.get('/count', getCount);
43 | fastify.get('/date_histogram', dateHistogramSchema, getDateHistogram);
44 | fastify.get('/date_histogram1_cardinality2', dateHistogram1Cardinality2, getDateHistogram1Cardinality2);
45 | fastify.get('/date_histogram1_certified2', dateHistogram1Certified2, getDateHistogram1Certified2);
46 | fastify.get('/date_histogram1_histogram2', dateHistogram1Histogram2Schema, getDateHistogram1Histogram2);
47 | fastify.get('/date_histogram1_stats2', dateHistogram1Stats2Schema, getDateHistogram1Stats2);
48 | fastify.get('/date_histogram1_terms2', dateHistogram1Terms2Schema, getDateHistogram1Terms2);
49 | fastify.get('/histogram', histogramSchema, getHistogram);
50 | fastify.get('/stats', statsSchema, getStats);
51 | fastify.get('/terms', termsSchema, getTerms);
52 | fastify.get('/terms1_cardinality2', terms1Cardinality2Schema, getTerms1Cardinality2);
53 | fastify.get('/terms1_certified2', terms1Certified2Schema, getTerms1Certified2);
54 | fastify.get('/terms1_histogram2', terms1Histogram2Schema, getTerms1Histogram2);
55 | fastify.get('/terms1_stats2', terms1Stats2Schema, getTerms1Stats2);
56 | fastify.get('/terms1_terms2', terms1Terms2Schema, getTerms1Terms2);
57 | done();
58 | };
59 |
--------------------------------------------------------------------------------
/fe/static/audits/pa11y/Guides_themeDark.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Accessibility Report For "http://localhost:4173/guides/app" (Fri Oct 27 2023 12:04:12 GMT-0300 (Argentina Standard Time))
7 |
8 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
Accessibility Report For "http://localhost:4173/guides/app"
91 |
Generated at: Fri Oct 27 2023 12:04:12 GMT-0300 (Argentina Standard Time)
92 |
93 |
94 | 0 errors
95 | 0 warnings
96 | 0 notices
97 |
98 |
99 |
101 |
102 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/fe/static/audits/pa11y/Guides_themeLight.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Accessibility Report For "http://localhost:4173/guides/app" (Fri Oct 27 2023 12:02:13 GMT-0300 (Argentina Standard Time))
7 |
8 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
Accessibility Report For "http://localhost:4173/guides/app"
91 |
Generated at: Fri Oct 27 2023 12:02:13 GMT-0300 (Argentina Standard Time)
92 |
93 |
94 | 0 errors
95 | 0 warnings
96 | 0 notices
97 |
98 |
99 |
101 |
102 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/fe/src/lib/components/explorer/MetricSelector.svelte:
--------------------------------------------------------------------------------
1 |
29 |
30 |
31 | {#each metricGroups as {key: entity, value}}
32 | {entity}
33 |
34 | {#each value as {id, label, type}}
35 |
36 |
40 |
41 |
49 | {label}
50 |
51 |
52 |
53 | {/each}
54 |
55 | {/each}
56 |
57 |
58 |
84 |
--------------------------------------------------------------------------------