Skip to content

Commit

Permalink
Add Sustainable Web Design rating scale
Browse files Browse the repository at this point in the history
Adds the Sustainable Web Design Rating Scale for Version 3 of the Sustainable Web Design Model.
  • Loading branch information
fershad committed May 27, 2024
2 parents 49b3bc1 + d16724d commit 142c041
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 6 deletions.
1 change: 1 addition & 0 deletions src/1byte.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const KWH_PER_BYTE_FOR_DEVICES = 1.3e-10;

class OneByte {
constructor(options) {
this.allowRatings = false;
this.options = options;

this.KWH_PER_BYTE_FOR_NETWORK = KWH_PER_BYTE_FOR_NETWORK;
Expand Down
40 changes: 36 additions & 4 deletions src/co2.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
* @property {number} dataCenterCO2 - The CO2 estimate for data centers in grams
* @property {number} consumerDeviceCO2 - The CO2 estimate for consumer devices in grams
* @property {number} productionCO2 - The CO2 estimate for device production in grams
* @property {string} rating - The rating of the CO2 estimate based on the Sustainable Web Design Model
* @property {number} total - The total CO2 estimate in grams
*/

Expand All @@ -54,6 +55,7 @@
* @property {number} 'consumerDeviceCO2 - subsequent' - The CO2 estimate for consumer devices in grams on subsequent visits
* @property {number} 'productionCO2 - first' - The CO2 estimate for device production in grams on first visit
* @property {number} 'productionCO2 - subsequent' - The CO2 estimate for device production in grams on subsequent visits
* @property {string} rating - The rating of the CO2 estimate based on the Sustainable Web Design Model
* @property {number} total - The total CO2 estimate in grams
*/

Expand Down Expand Up @@ -81,8 +83,26 @@ class CO2 {
);
}

if (options?.rating && typeof options.rating !== "boolean") {
throw new Error(
`The rating option must be a boolean. Please use true or false.\nSee https://developers.thegreenwebfoundation.org/co2js/options/ to learn more about the options available in CO2.js.`
);
}

// This flag checks to see if the model itself has a rating system.
const allowRatings = !!this.model.allowRatings;

/** @private */
this._segment = options?.results === "segment";
// This flag is set by the user to enable the rating system.
this._rating = options?.rating === true;

// The rating system is only supported in the Sustainable Web Design Model.
if (!allowRatings && this._rating) {
throw new Error(
`The rating system is not supported in the model you are using. Try using the Sustainable Web Design model instead.\nSee https://developers.thegreenwebfoundation.org/co2js/models/ to learn more about the models available in CO2.js.`
);
}
}

/**
Expand All @@ -95,7 +115,7 @@ class CO2 {
* @return {number|CO2EstimateComponentsPerByte} the amount of CO2 in grammes or its separate components
*/
perByte(bytes, green = false) {
return this.model.perByte(bytes, green, this._segment);
return this.model.perByte(bytes, green, this._segment, this._rating);
}

/**
Expand All @@ -109,7 +129,7 @@ class CO2 {
*/
perVisit(bytes, green = false) {
if (this.model?.perVisit) {
return this.model.perVisit(bytes, green, this._segment);
return this.model.perVisit(bytes, green, this._segment, this._rating);
} else {
throw new Error(
`The perVisit() method is not supported in the model you are using. Try using perByte() instead.\nSee https://developers.thegreenwebfoundation.org/co2js/methods/ to learn more about the methods available in CO2.js.`
Expand All @@ -134,7 +154,13 @@ class CO2 {
adjustments = parseOptions(options);
}
return {
co2: this.model.perByte(bytes, green, this._segment, adjustments),
co2: this.model.perByte(
bytes,
green,
this._segment,
this._rating,
adjustments
),
green,
variables: {
description:
Expand Down Expand Up @@ -176,7 +202,13 @@ class CO2 {
}

return {
co2: this.model.perVisit(bytes, green, this._segment, adjustments),
co2: this.model.perVisit(
bytes,
green,
this._segment,
this._rating,
adjustments
),
green,
variables: {
description:
Expand Down
35 changes: 35 additions & 0 deletions src/co2.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,22 @@ describe("co2", () => {
`The perVisit() method is not supported in the model you are using. Try using perByte() instead.\nSee https://developers.thegreenwebfoundation.org/co2js/methods/ to learn more about the methods available in CO2.js.`
);
});

it("throws an error if using the rating system with OneByte", () => {
expect(() => {
co2 = new CO2({ model: "1byte", rating: true });
}).toThrowError(
`The rating system is not supported in the model you are using. Try using the Sustainable Web Design model instead.\nSee https://developers.thegreenwebfoundation.org/co2js/models/ to learn more about the models available in CO2.js.`
);
});

it("throws an error if the rating parameter is not a boolean", () => {
expect(() => {
co2 = new CO2({ rating: "false" });
}).toThrowError(
`The rating option must be a boolean. Please use true or false.\nSee https://developers.thegreenwebfoundation.org/co2js/options/ to learn more about the options available in CO2.js.`
);
});
});

// Test that grid intensity data can be imported and used
Expand Down Expand Up @@ -832,4 +848,23 @@ describe("co2", () => {
expect(co2Result["consumerDeviceCO2 - subsequent"]).toBe(0);
});
});

describe("Returning SWD results with rating", () => {
const co2NoRating = new CO2();
const co2Rating = new CO2({ rating: true });
const co2RatingSegmented = new CO2({ rating: true, results: "segment" });

it("does not return a rating when rating is false", () => {
expect(co2NoRating.perVisit(MILLION)).not.toHaveProperty("rating");
});

it("returns a rating when rating is true", () => {
expect(co2Rating.perVisit(MILLION)).toHaveProperty("rating");
});

it("returns a rating when rating is true and results are segmented", () => {
expect(co2RatingSegmented.perByte(MILLION)).toHaveProperty("rating");
expect(co2RatingSegmented.perByte(MILLION)).toHaveProperty("networkCO2");
});
});
});
10 changes: 10 additions & 0 deletions src/constants/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ const FIRST_TIME_VIEWING_PERCENTAGE = 0.75;
const RETURNING_VISITOR_PERCENTAGE = 0.25;
const PERCENTAGE_OF_DATA_LOADED_ON_SUBSEQUENT_LOAD = 0.02;

const SWDMv3Ratings = {
fifthPercentile: 0.095,
tenthPercentile: 0.186,
twentiethPercentile: 0.341,
thirtiethPercentile: 0.493,
fortiethPercentile: 0.656,
fiftiethPercentile: 0.846,
};

export {
fileSize,
KWH_PER_GB,
Expand All @@ -36,4 +45,5 @@ export {
FIRST_TIME_VIEWING_PERCENTAGE,
RETURNING_VISITOR_PERCENTAGE,
PERCENTAGE_OF_DATA_LOADED_ON_SUBSEQUENT_LOAD,
SWDMv3Ratings,
};
4 changes: 3 additions & 1 deletion src/helpers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {

const formatNumber = (num) => parseFloat(num.toFixed(2));

const lessThanEqualTo = (num, limit) => num <= limit;

function parseOptions(options) {
// CHeck that it is an object
if (typeof options !== "object") {
Expand Down Expand Up @@ -192,4 +194,4 @@ function getApiRequestHeaders(comment = "") {
return { "User-Agent": `co2js/${process.env.CO2JS_VERSION} ${comment}` };
}

export { formatNumber, parseOptions, getApiRequestHeaders };
export { formatNumber, parseOptions, getApiRequestHeaders, lessThanEqualTo };
74 changes: 73 additions & 1 deletion src/sustainable-web-design.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,22 @@ import {
FIRST_TIME_VIEWING_PERCENTAGE,
RETURNING_VISITOR_PERCENTAGE,
PERCENTAGE_OF_DATA_LOADED_ON_SUBSEQUENT_LOAD,
SWDMv3Ratings,
} from "./constants/index.js";
import { formatNumber } from "./helpers/index.js";
import { formatNumber, lessThanEqualTo } from "./helpers/index.js";

const {
fifthPercentile,
tenthPercentile,
twentiethPercentile,
thirtiethPercentile,
fortiethPercentile,
fiftiethPercentile,
} = SWDMv3Ratings;

class SustainableWebDesign {
constructor(options) {
this.allowRatings = true;
this.options = options;
}

Expand Down Expand Up @@ -119,13 +130,15 @@ class SustainableWebDesign {
* @param {number} bytes - the data transferred in bytes
* @param {boolean} carbonIntensity - a boolean indicating whether the data center is green or not
* @param {boolean} segmentResults - a boolean indicating whether to return the results broken down by component
* @param {boolean} ratingResults - a boolean indicating whether to return the rating based on the Sustainable Web Design Model
* @param {object} options - an object containing the grid intensity and first/return visitor values
* @return {number|object} the total number in grams of CO2 equivalent emissions, or an object containing the breakdown by component
*/
perByte(
bytes,
carbonIntensity = false,
segmentResults = false,
ratingResults = false,
options = {}
) {
if (bytes < 1) {
Expand Down Expand Up @@ -153,10 +166,27 @@ class SustainableWebDesign {
(prevValue, currentValue) => prevValue + currentValue
);

let rating = null;
if (ratingResults) {
rating = this.ratingScale(co2ValuesSum);
}

if (segmentResults) {
if (ratingResults) {
return {
...co2ValuesbyComponent,
total: co2ValuesSum,
rating: rating,
};
}

return { ...co2ValuesbyComponent, total: co2ValuesSum };
}

if (ratingResults) {
return { total: co2ValuesSum, rating: rating };
}

return co2ValuesSum;
}

Expand All @@ -167,13 +197,15 @@ class SustainableWebDesign {
* @param {number} bytes - the data transferred in bytes
* @param {boolean} carbonIntensity - a boolean indicating whether the data center is green or not
* @param {boolean} segmentResults - a boolean indicating whether to return the results broken down by component
* @param {boolean} ratingResults - a boolean indicating whether to return the rating based on the Sustainable Web Design Model
* @param {object} options - an object containing the grid intensity and first/return visitor values
* @return {number|object} the total number in grams of CO2 equivalent emissions, or an object containing the breakdown by component
*/
perVisit(
bytes,
carbonIntensity = false,
segmentResults = false,
ratingResults = false,
options = {}
) {
const energyBycomponent = this.energyPerVisitByComponent(bytes, options);
Expand All @@ -197,10 +229,26 @@ class SustainableWebDesign {
(prevValue, currentValue) => prevValue + currentValue
);

let rating = null;
if (ratingResults) {
rating = this.ratingScale(co2ValuesSum);
}

if (segmentResults) {
if (ratingResults) {
return {
...co2ValuesbyComponent,
total: co2ValuesSum,
rating: rating,
};
}
return { ...co2ValuesbyComponent, total: co2ValuesSum };
}

if (ratingResults) {
return { total: co2ValuesSum, rating: rating };
}

// so we can return their sum
return co2ValuesSum;
}
Expand Down Expand Up @@ -331,6 +379,30 @@ class SustainableWebDesign {
productionEnergy: formatNumber(annualEnergy * PRODUCTION_ENERGY),
};
}

/**
* Determines the rating of a website's sustainability based on its CO2 emissions.
*
* @param {number} co2e - The CO2 emissions of the website in grams.
* @returns {string} The sustainability rating, ranging from "A+" (best) to "F" (worst).
*/
ratingScale(co2e) {
if (lessThanEqualTo(co2e, fifthPercentile)) {
return "A+";
} else if (lessThanEqualTo(co2e, tenthPercentile)) {
return "A";
} else if (lessThanEqualTo(co2e, twentiethPercentile)) {
return "B";
} else if (lessThanEqualTo(co2e, thirtiethPercentile)) {
return "C";
} else if (lessThanEqualTo(co2e, fortiethPercentile)) {
return "D";
} else if (lessThanEqualTo(co2e, fiftiethPercentile)) {
return "E";
} else {
return "F";
}
}
}

export { SustainableWebDesign };
Expand Down
31 changes: 31 additions & 0 deletions src/sustainable-web-design.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import SustainableWebDesign from "./sustainable-web-design.js";
import { MILLION, SWD } from "./constants/test-constants.js";
import { SWDMv3Ratings } from "./constants/index.js";

const {
fifthPercentile,
tenthPercentile,
twentiethPercentile,
thirtiethPercentile,
fortiethPercentile,
fiftiethPercentile,
} = SWDMv3Ratings;

describe("sustainable web design model", () => {
const swd = new SustainableWebDesign();
Expand Down Expand Up @@ -113,4 +123,25 @@ describe("sustainable web design model", () => {
});
});
});

describe("SWD Rating Scale", () => {
it("should return a string", () => {
expect(typeof swd.ratingScale(averageWebsiteInBytes)).toBe("string");
});

it("should return a rating", () => {
// Check a 3MB file size
expect(swd.ratingScale(3000000)).toBe("F");
});

it("returns ratings as expected", () => {
expect(swd.ratingScale(fifthPercentile)).toBe("A+");
expect(swd.ratingScale(tenthPercentile)).toBe("A");
expect(swd.ratingScale(twentiethPercentile)).toBe("B");
expect(swd.ratingScale(thirtiethPercentile)).toBe("C");
expect(swd.ratingScale(fortiethPercentile)).toBe("D");
expect(swd.ratingScale(fiftiethPercentile)).toBe("E");
expect(swd.ratingScale(0.9)).toBe("F");
});
});
});

0 comments on commit 142c041

Please sign in to comment.