From 5149ed8ca30709793583a3fe0bd1fd8984052d13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20K=C3=B6ves?= <3187531+vkoves@users.noreply.github.com> Date: Tue, 7 Jan 2025 21:15:31 -0600 Subject: [PATCH 01/11] Install Prettier --- .prettierignore | 2 ++ .prettierrc | 1 + package.json | 1 + yarn.lock | 5 +++++ 4 files changed, 9 insertions(+) create mode 100644 .prettierignore create mode 100644 .prettierrc diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..c6d72715 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +# Ignore built files +dist diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/.prettierrc @@ -0,0 +1 @@ +{} diff --git a/package.json b/package.json index 78fa9f69..649598df 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "eslint-plugin-promise": "^6.1.1", "eslint-plugin-vue": "^9.9.0", "gridsome-plugin-typescript": "^0.4.0", + "prettier": "3.4.2", "sass": "^1.59.3", "sass-loader": "^10.1.1", "style-resources-loader": "^1.5.0", diff --git a/yarn.lock b/yarn.lock index f632067e..a71b135c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8621,6 +8621,11 @@ prettier@1.16.3: resolved "https://registry.npmjs.org/prettier/-/prettier-1.16.3.tgz" integrity sha512-kn/GU6SMRYPxUakNXhpP0EedT/KmaPzr0H5lIsDogrykbaxOpOfAFfk5XA7DZrJyMAv1wlMV3CPcZruGXVVUZw== +prettier@3.4.2: + version "3.4.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.4.2.tgz#a5ce1fb522a588bf2b78ca44c6e6fe5aa5a2b13f" + integrity sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ== + "prettier@^1.18.2 || ^2.0.0": version "2.8.8" resolved "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz" From 09047522cdcc1bf63d8420dd91333957bcd9debe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20K=C3=B6ves?= <3187531+vkoves@users.noreply.github.com> Date: Tue, 7 Jan 2025 21:15:48 -0600 Subject: [PATCH 02/11] Run Prettier and disable ESLint rules that interfere --- .eslintrc.js | 152 +- .github/workflows/eslint.yml | 4 +- .github/workflows/pytest.yml | 8 +- README.md | 28 +- docker-compose.yml | 2 +- gridsome.config.js | 34 +- gridsome.server.js | 72 +- src/common-functions.vue | 82 +- src/components/BuildingImage.vue | 45 +- src/components/BuildingsTable.vue | 160 +- src/components/DataDisclaimer.vue | 47 +- src/components/DataSourceFootnote.vue | 9 +- src/components/EmailBuildingModal.vue | 142 +- src/components/EmissionsBreakdownGraph.vue | 12 +- .../HistoricalBuildingDataTable.vue | 73 +- src/components/NewTabIcon.vue | 8 +- src/components/OverallRankEmoji.vue | 33 +- src/components/OwnerLogo.vue | 41 +- src/components/RankText.vue | 75 +- src/components/ReportingTile.vue | 23 +- src/components/StatTile.vue | 335 ++-- src/components/graphs/BarGraph.vue | 55 +- src/components/graphs/PieChart.vue | 79 +- src/components/graphs/SparkLine.vue | 271 +-- src/components/layout/AppFooter.vue | 25 +- src/components/layout/AppHeader.vue | 145 +- src/components/layout/Popup.vue | 18 +- src/constants/building-images.constant.vue | 508 ++--- .../buildings-custom-info.constant.vue | 1664 +++++++++-------- src/data/source/Benchmark DataExplainer.md | 143 +- src/layouts/Default.vue | 10 +- src/main.js | 41 +- src/pages/About.vue | 119 +- src/pages/BiggestBuildings.vue | 31 +- src/pages/BiggestGasFreeBuildings.vue | 36 +- src/pages/Blog.vue | 33 +- src/pages/CleanestBuildings.vue | 45 +- src/pages/Index.vue | 603 +++--- src/pages/LargeOwners.vue | 56 +- src/pages/Map.vue | 346 ++-- src/pages/ReleaseNotes.vue | 93 +- src/pages/RetrofitChicagoParticipants.vue | 69 +- src/pages/Search.vue | 148 +- src/pages/TakeActionTips.vue | 140 +- src/pages/TopElectricityUsers.vue | 23 +- src/pages/TopEmitters.vue | 46 +- src/pages/TopGasUsers.vue | 30 +- src/pages/blog/MillionsInMissedFines.vue | 170 +- src/scss/colors.scss | 8 +- src/scss/global.scss | 209 ++- src/templates/BuildingDetails.vue | 282 +-- src/templates/BuildingIDRedirect.vue | 22 +- src/templates/BuildingOwner.vue | 124 +- static/README.md | 2 +- tsconfig.json | 36 +- 55 files changed, 3610 insertions(+), 3405 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 0c8fb30d..e2d61e50 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,64 +1,116 @@ module.exports = { - 'env': { - 'browser': true, - 'node': true, - 'es2021': true, + env: { + browser: true, + node: true, + es2021: true, }, - 'extends': [ - 'eslint:recommended', - 'plugin:@typescript-eslint/eslint-recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:vue/recommended', + extends: [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "plugin:vue/recommended", ], - 'parserOptions': { - "parser": "@typescript-eslint/parser", - "project": "./tsconfig.json", - 'extraFileExtensions': [ '.vue' ] + parserOptions: { + parser: "@typescript-eslint/parser", + project: "./tsconfig.json", + extraFileExtensions: [".vue"], }, - 'plugins': [ - 'vue', - ], - 'rules': { - 'max-len': ['error', { - 'code': 100, - // Fixes errors in HTML files with long links - 'ignoreUrls': true, - }], + plugins: ["vue"], + rules: { + "max-len": [ + "error", + { + code: 100, + // Fixes errors in HTML files with long links + ignoreUrls: true, + }, + ], + + // Disable stylistic rules that interfere with Prettier + "vue/html-self-closing": "off", + "vue/singleline-html-element-content-newline": "off", + "vue/max-attributes-per-line": "off", - 'vue/multi-word-component-names': ['off'], + "vue/multi-word-component-names": ["off"], // This rule is for Vue3, and Gridsome uses Vue2 - 'vue/no-deprecated-filter': ['off'], + "vue/no-deprecated-filter": ["off"], // Don't make multiline, it adds spaces - "vue/multiline-html-element-content-newline": ["error", { - "ignores": [ - "g-link", - // Original inline elements from: https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/utils/inline-non-void-elements.json - "a", "abbr", "audio", "b", "bdi", "bdo", "canvas", "cite", "code", "data", "del", "dfn", "em", "i", "iframe", "ins", "kbd", "label", "map", "mark", "noscript", "object", "output", "picture", "q", "ruby", "s", "samp", "small", "span", "strong", "sub", "sup", "svg", "time", "u", "var", "video" - ], - }], - "vue/singleline-html-element-content-newline": ["error", { - "externalIgnores": ["g-link"] - }], + "vue/multiline-html-element-content-newline": [ + "error", + { + ignores: [ + "g-link", + // Original inline elements from: https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/utils/inline-non-void-elements.json + "a", + "abbr", + "audio", + "b", + "bdi", + "bdo", + "canvas", + "cite", + "code", + "data", + "del", + "dfn", + "em", + "i", + "iframe", + "ins", + "kbd", + "label", + "map", + "mark", + "noscript", + "object", + "output", + "picture", + "q", + "ruby", + "s", + "samp", + "small", + "span", + "strong", + "sub", + "sup", + "svg", + "time", + "u", + "var", + "video", + ], + }, + ], + "vue/singleline-html-element-content-newline": [ + "error", + { + externalIgnores: ["g-link"], + }, + ], - '@typescript-eslint/indent': ['error', 2], - "semi": ["error", "always"], + "@typescript-eslint/indent": ["error", 2], + semi: ["error", "always"], "arrow-parens": ["error", "always"], "comma-dangle": ["error", "always-multiline"], - "@typescript-eslint/explicit-function-return-type": ["error", { - "allowExpressions": true, - "allowHigherOrderFunctions": true, - "allowTypedFunctionExpressions": true - }] + "@typescript-eslint/explicit-function-return-type": [ + "error", + { + allowExpressions: true, + allowHigherOrderFunctions: true, + allowTypedFunctionExpressions: true, + }, + ], }, - 'overrides': [ + overrides: [ { - 'files': ['*.vue', 'declarations/*.ts'], - 'rules': { - 'indent': 'off', - '@typescript-eslint/indent': 'off', - '@typescript-eslint/no-var-requires': 0, - } - } - ] + files: ["*.vue", "declarations/*.ts"], + rules: { + indent: "off", + "@typescript-eslint/indent": "off", + "@typescript-eslint/no-var-requires": 0, + }, + }, + ], }; diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml index eca608b7..94c9a41c 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/eslint.yml @@ -7,10 +7,10 @@ name: ESLint on: push: - branches: [ "main" ] + branches: ["main"] pull_request: # The branches below must be a subset of the branches above - branches: [ "main" ] + branches: ["main"] jobs: eslint: diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index ebea6994..b9a9953e 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -4,21 +4,21 @@ name: Pytest Data Tests on: push: - branches: [ "main" ] + branches: ["main"] pull_request: # The branches below must be a subset of the branches above - branches: [ "main" ] + branches: ["main"] jobs: pytest: - name: 'Pytest' + name: "Pytest" runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: "3.9" - name: Install dependencies run: | cd src/data diff --git a/README.md b/README.md index 4692f18d..245d82e9 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ docker-compose run --rm electrify-chicago yarn lint-fix ### Run Data Processing 1. If you update the raw data CSVs or the data scripts that post-process them (like if you are adding -a new statistical analysis), you need to re-run the data processing. + a new statistical analysis), you need to re-run the data processing. 2. To then process a new CSV file (at `src/data/source/ChicagoEnergyBenchmarking.csv`), you need to run the following command: @@ -93,8 +93,9 @@ docker-compose run --rm electrify-chicago bash create_test_data.sh ```bash docker-compose run --rm electrify-chicago python -m pytest ``` + 3. Run the following command for individual unit test suite (where YOUR_FILE_NAME is something like -`test_clean_all_years`): + `test_clean_all_years`): ```bash docker-compose run --rm electrify-chicago python -m pytest tests/data/scripts/unit/YOUR_FILE_NAME.py @@ -107,7 +108,7 @@ docker-compose run --rm electrify-chicago python -m pytest tests/data/scripts/un If there's a new large building owner to add, simply: 1. **Add the building owner in the `BuildingOwners` constant** in `buildings-custom-info.constant.vue` - -this defines metadata about the owner like their name and logo URLs + this defines metadata about the owner like their name and logo URLs Example: @@ -122,8 +123,8 @@ iit: { ``` 2. **Tag buildings they own in the `BuildingsCustomInfo` constant** (in the same -`buildings-custom-info.constant.vue` file) - this associates a given building (by its numeric unique -ID, found under its address on its details page), with a given owner. + `buildings-custom-info.constant.vue` file) - this associates a given building (by its numeric unique + ID, found under its address on its details page), with a given owner. Example: @@ -133,15 +134,15 @@ Example: ``` 3. **Setup their route by adding the new owner's ID (key) to `BuildingOwnerIds`** (in -`gridsome.server.js`) - this tells Gridsome to create a route for this given slug + `gridsome.server.js`) - this tells Gridsome to create a route for this given slug Example: ```ts const BuildingOwnerIds = [ - 'iit', + "iit", // ... -] +]; ``` **Note:** You'll have to restart your `yarn develop` after step 3 to see changes, since @@ -149,8 +150,8 @@ const BuildingOwnerIds = [ ### Adding Building Images -1. **Find A Suitable Image* -- Building images can be sourced from Google Maps or a source that allows redistribution, like -Wikimedia. +1. \*_Find A Suitable Image_ -- Building images can be sourced from Google Maps or a source that allows redistribution, like + Wikimedia. 2 **Process the Image** @@ -162,12 +163,11 @@ We should reasonably crop images if needed and then scale them to be EITHER: Make sure to export it as a `.jpg` image at a quality level of 70, which should ensure a reasonable file size under 200 kB. -**Store the image in `/static/building-imgs/`. +\*\*Store the image in `/static/building-imgs/`. 3. **Tell The Site There's a Building Image** - Follow the pattern of other buildings in the -`building-images.constant.vue`, providing an attribution URL, the image file name, and specify -whether it's a tall (portrait) image and whether it's from Google Maps. - + `building-images.constant.vue`, providing an attribution URL, the image file name, and specify + whether it's a tall (portrait) image and whether it's from Google Maps. 4. **Confirm the image is visible and looks good** - and that's all there is to it! diff --git a/docker-compose.yml b/docker-compose.yml index fae95a02..4d2d1474 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -version: '3.8' +version: "3.8" services: electrify-chicago: diff --git a/gridsome.config.js b/gridsome.config.js index fb7ce1f5..b07fe0a7 100644 --- a/gridsome.config.js +++ b/gridsome.config.js @@ -4,7 +4,7 @@ // Changes here require a server restart. // To restart press CTRL + C in terminal and run `gridsome develop` -const path = require('path'); +const path = require("path"); /** * A function that loads in all our global SCSS files @@ -12,21 +12,19 @@ const path = require('path'); * @param {string} rule A rule? */ function addStyleResource(rule) { - rule.use('style-resource') - .loader('style-resources-loader') - .options({ - patterns: [ - path.resolve(__dirname, './src/scss/*.scss'), - ], - }); + rule + .use("style-resource") + .loader("style-resources-loader") + .options({ + patterns: [path.resolve(__dirname, "./src/scss/*.scss")], + }); } - module.exports = { - siteName: 'Electrify Chicago', + siteName: "Electrify Chicago", plugins: [ { - use: 'gridsome-plugin-typescript', + use: "gridsome-plugin-typescript", }, ], @@ -34,14 +32,14 @@ module.exports = { // Register building details path Building: [ { - path: '/building/:slugSource', - component: './src/templates/BuildingDetails.vue', + path: "/building/:slugSource", + component: "./src/templates/BuildingDetails.vue", }, { // A path that redirects from a building ID to the canonical slug URL - name: 'building-id-redirect', - path: '/building-id/:ID', - component: './src/templates/BuildingIDRedirect.vue', + name: "building-id-redirect", + path: "/building-id/:ID", + component: "./src/templates/BuildingIDRedirect.vue", }, ], }, @@ -49,11 +47,11 @@ module.exports = { // Ensure /scss folder is globally available chainWebpack(config) { // Load variables for all vue-files - const types = ['vue-modules', 'vue', 'normal-modules', 'normal']; + const types = ["vue-modules", "vue", "normal-modules", "normal"]; // or if you use scss types.forEach((type) => { - addStyleResource(config.module.rule('scss').oneOf(type)); + addStyleResource(config.module.rule("scss").oneOf(type)); }); }, }; diff --git a/gridsome.server.js b/gridsome.server.js index c41c74c8..0b274d7e 100644 --- a/gridsome.server.js +++ b/gridsome.server.js @@ -9,34 +9,34 @@ * From fetching CSV data: * https://gridsome.org/docs/fetching-data/#csv */ -const {readFileSync} = require('fs'); -const parse = require('csv-parse/sync').parse; +const { readFileSync } = require("fs"); +const parse = require("csv-parse/sync").parse; -const DataDirectory = './src/data/dist/'; +const DataDirectory = "./src/data/dist/"; -const BuildingEmissionsDataFile = 'building-benchmarks.csv'; -const HistoricBenchmarkingDataFile = 'benchmarking-all-years.csv'; +const BuildingEmissionsDataFile = "building-benchmarks.csv"; +const HistoricBenchmarkingDataFile = "benchmarking-all-years.csv"; // This is an array equivalent of Object.keys(BuildingOwners) but this file can't use Typescript and // import that file const BuildingOwnerIds = [ - 'depaul', - 'uchicago', - 'uic', - 'iit', - 'northwestern', - 'loyola', - 'cps', - 'cha', - 'cityofchicago', - 'columbia', - 'ccc', - 'moody', - 'saic', - 'npu', + "depaul", + "uchicago", + "uic", + "iit", + "northwestern", + "loyola", + "cps", + "cha", + "cityofchicago", + "columbia", + "ccc", + "moody", + "saic", + "npu", ]; -module.exports = function(api) { +module.exports = function (api) { // Use the Data Store API here: https://gridsome.org/docs/data-store-api/ api.loadSource(async (actions) => { loadBuildingBenchmarkData(actions); @@ -47,13 +47,13 @@ module.exports = function(api) { api.createPages(({ createPage }) => { // Create pages for building owners. This could be a dynamic route, but making it this way // should let them get statically built as expected - BuildingOwnerIds.forEach(ownerId => { + BuildingOwnerIds.forEach((ownerId) => { createPage({ path: `/owner/${ownerId}`, - component: './src/templates/BuildingOwner.vue', + component: "./src/templates/BuildingOwner.vue", context: { ownerId }, - }) - }) + }); + }); }); }; @@ -63,7 +63,10 @@ module.exports = function(api) { * @param {unknown} actions The actions class? */ function loadBuildingBenchmarkData(actions) { - const latestBenchmarksRaw = readFileSync(`${DataDirectory}${BuildingEmissionsDataFile}`, 'utf8'); + const latestBenchmarksRaw = readFileSync( + `${DataDirectory}${BuildingEmissionsDataFile}`, + "utf8", + ); /** * Load in building benchmarks and expose as Buildings collection @@ -73,29 +76,34 @@ function loadBuildingBenchmarkData(actions) { skip_empty_lines: true, }); - const collection = actions.addCollection({typeName: 'Building'}); + const collection = actions.addCollection({ typeName: "Building" }); for (const building of LatestBenchmarksData) { // Make a slugSource that is the property name or the address as a fallback (skip one letter // names, e.g. '-) - building.slugSource = building.PropertyName.length > 1 ? building.PropertyName : building.Address; + building.slugSource = + building.PropertyName.length > 1 + ? building.PropertyName + : building.Address; - if (!building.slugSource || typeof building.slugSource !== 'string') { - throw new Error('No building slug source (name or address)!', building); + if (!building.slugSource || typeof building.slugSource !== "string") { + throw new Error("No building slug source (name or address)!", building); } collection.addNode(building); } } - /** * Load in the historic benchmark data * * @param {unknown} actions The actions class? */ function loadHistoricBenchmarkDat(actions) { - const historicBenchmarksRaw = readFileSync(`${DataDirectory}${HistoricBenchmarkingDataFile}`, 'utf8'); + const historicBenchmarksRaw = readFileSync( + `${DataDirectory}${HistoricBenchmarkingDataFile}`, + "utf8", + ); /** * Load in building benchmarks and expose as Buildings collection @@ -105,7 +113,7 @@ function loadHistoricBenchmarkDat(actions) { skip_empty_lines: true, }); - const collection = actions.addCollection({ typeName: 'Benchmark' }); + const collection = actions.addCollection({ typeName: "Benchmark" }); for (const benchmark of HistoricBenchmarksData) { collection.addNode(benchmark); diff --git a/src/common-functions.vue b/src/common-functions.vue index 051b09f7..ba78a0b5 100644 --- a/src/common-functions.vue +++ b/src/common-functions.vue @@ -1,5 +1,5 @@ diff --git a/src/components/BuildingsTable.vue b/src/components/BuildingsTable.vue index 3194008d..97165e04 100644 --- a/src/components/BuildingsTable.vue +++ b/src/components/BuildingsTable.vue @@ -1,15 +1,14 @@ - + - + @@ -214,7 +170,9 @@ export default class BuildingsTable extends Vue { min-width: 88rem; // Wide columns shouldn't be as wide if we have more of them - .wide-col { width: 17%; } + .wide-col { + width: 17%; + } } a { @@ -228,7 +186,9 @@ export default class BuildingsTable extends Vue { position: sticky; top: 0; - tr { background-color: $grey-dark; } + tr { + background-color: $grey-dark; + } th { text-align: left; @@ -244,18 +204,31 @@ export default class BuildingsTable extends Vue { } } - th, td { + th, + td { padding: 0.75rem; line-height: 1.25; - &:first-of-type { padding-left: 1rem; } - &:last-of-type { padding-right: 1rem; } - &.numeric { text-align: right; } - &.wide-col { width: 20%; } - &.prop-type { width: 12rem; } + &:first-of-type { + padding-left: 1rem; + } + &:last-of-type { + padding-right: 1rem; + } + &.numeric { + text-align: right; + } + &.wide-col { + width: 20%; + } + &.prop-type { + width: 12rem; + } } - tr:nth-of-type(2n + 2) { background-color: $grey; } + tr:nth-of-type(2n + 2) { + background-color: $grey; + } .prop-address { font-size: 0.75em; @@ -277,7 +250,10 @@ export default class BuildingsTable extends Vue { font-size: 0.825rem; padding: 0.5rem 0.25rem; } - td.property-name, td.property-address { width: 10rem; } + td.property-name, + td.property-address { + width: 10rem; + } } } } diff --git a/src/components/DataDisclaimer.vue b/src/components/DataDisclaimer.vue index c2a981eb..8ab209e2 100644 --- a/src/components/DataDisclaimer.vue +++ b/src/components/DataDisclaimer.vue @@ -1,29 +1,27 @@ - + diff --git a/src/components/EmailBuildingModal.vue b/src/components/EmailBuildingModal.vue index d51eeb17..9540dc29 100644 --- a/src/components/EmailBuildingModal.vue +++ b/src/components/EmailBuildingModal.vue @@ -1,92 +1,55 @@ - + diff --git a/src/components/HistoricalBuildingDataTable.vue b/src/components/HistoricalBuildingDataTable.vue index 8b211ccf..42341514 100644 --- a/src/components/HistoricalBuildingDataTable.vue +++ b/src/components/HistoricalBuildingDataTable.vue @@ -3,26 +3,20 @@ - - + - - + - - - + + - + @@ -80,9 +62,9 @@ diff --git a/src/components/OverallRankEmoji.vue b/src/components/OverallRankEmoji.vue index b6a6b151..4ea5e069 100644 --- a/src/components/OverallRankEmoji.vue +++ b/src/components/OverallRankEmoji.vue @@ -1,8 +1,5 @@
- Year - + Year Floor Area sqft - Chicago Energy
Rating + Chicago Energy
+ Rating
- Energy Star
Score +
+ Energy Star
+ Score
GHG Intensity kg CO2e / sqft @@ -30,38 +24,26 @@ GHG Emissions metric tons CO2e - Source EUI kBTU / sqft - Source EUI kBTU / sqft - Electricity Use kBTU - - Fossil Gas Use kBTU - + Electricity Use kBTUFossil Gas Use kBTU District Steam Use kBTU
{{ benchmark.DataYear }} {{ benchmark.GrossFloorArea | optionalInt }} - {{ benchmark.ChicagoEnergyRating || '-' }} + {{ benchmark.ChicagoEnergyRating || "-" }} - {{ benchmark.ENERGYSTARScore || '-' }} + {{ benchmark.ENERGYSTARScore || "-" }} {{ benchmark.GHGIntensity }} {{ benchmark.TotalGHGEmissions | optionalFloat }}