diff --git a/dist/iife/index.js b/dist/iife/index.js index 7a0a3ca..45c7415 100644 --- a/dist/iife/index.js +++ b/dist/iife/index.js @@ -1,20 +1,22 @@ -var co2=(()=>{var k=Object.create;var y=Object.defineProperty;var H=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var Z=Object.getPrototypeOf,x=Object.prototype.hasOwnProperty;var $=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports),J=(t,e)=>{for(var r in e)y(t,r,{get:e[r],enumerable:!0})},v=(t,e,r,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of j(e))!x.call(t,n)&&n!==r&&y(t,n,{get:()=>e[n],enumerable:!(a=H(e,n))||a.enumerable});return t};var X=(t,e,r)=>(r=t!=null?k(Z(t)):{},v(e||!t||!t.__esModule?y(r,"default",{value:t,enumerable:!0}):r,t)),Q=t=>v(y({},"__esModule",{value:!0}),t);var F=$((Ve,w)=>{"use strict";async function ee(t,e){return typeof t=="string"?te(t,e):ne(t,e)}function te(t,e){return e.indexOf(t)>-1}function re(t){return Object.entries(t).filter(([a,n])=>n.green).map(([a,n])=>n.url)}function ne(t,e){let r=[];for(let a of t)e.indexOf(a)>-1&&r.push(a);return r}function ae(t,e){return typeof t=="string"?K(t,e):ie(t,e)}function K(t,e){return e.indexOf(t)>-1?t:{url:t,green:!1}}function ie(t,e){let r={};for(let a of t)r[a]=K(a,e);return r}w.exports={check:ee,greenDomainsFromResults:re,find:ae}});var ge={};J(ge,{averageIntensity:()=>E,co2:()=>D,default:()=>fe,hosting:()=>M,marginalIntensity:()=>V});var T=4883333333333333e-25;var m=class{constructor(e){this.options=e,this.KWH_PER_BYTE_FOR_NETWORK=T}perByte(e,r){if(e<1)return 0;if(r){let n=e*72e-12*0,o=e*T*475;return n+o}let a=72e-12+T;return e*a*519}};var L=m;var C={GIGABYTE:1e9};var q={AFG:132.53,AFRICA:559.42,ALB:24.29,DZA:634.61,ASM:611.11,AGO:174.73,ATG:611.11,ARG:394.62,ARM:264.54,ABW:561.22,ASEAN:554.5,ASIA:591.19,AUS:570.35,AUT:110.81,AZE:671.39,BHS:660.1,BHR:904.62,BGD:678.11,BRB:605.51,BLR:441.74,BEL:138.11,BLZ:225.81,BEN:584.07,BTN:23.33,BOL:489.14,BIH:600,BWA:847.91,BRA:105.51,BRN:893.91,BGR:335.33,BFA:467.53,BDI:250,CPV:558.14,KHM:417.71,CMR:305.42,CAN:161.43,CYM:642.86,CAF:0,TCD:628.57,CHL:353.52,CHN:585.82,COL:214.88,COM:642.86,COG:700,COD:24.46,COK:250,CRI:26.46,CIV:393.89,HRV:204.96,CUB:637.61,CYP:534.32,CZE:449.72,DNK:151.65,DJI:692.31,DMA:529.41,DOM:580.78,ECU:150.69,EGY:570.13,SLV:116.54,GNQ:591.84,ERI:631.58,EST:416.67,SWZ:172.41,ETH:24.64,EU:243.83,EUROPE:327.69,FLK:500,FRO:404.76,FJI:288.46,FIN:79.16,FRA:56.04,GUF:217.82,PYF:442.86,G20:482.92,G7:361.52,GAB:491.6,GMB:666.67,GEO:167.02,DEU:380.95,GHA:484,GRC:336.57,GRL:178.57,GRD:640,GLP:500,GUM:622.86,GTM:328.27,GIN:236.84,GNB:625,GUY:640.35,HTI:567.31,HND:282.27,HKG:699.5,HUN:204.19,ISL:27.68,IND:705.13,IDN:675.93,IRN:665.15,IRQ:688.81,IRL:290.81,ISR:582.93,ITA:330.72,JAM:555.56,JPN:512.81,JOR:540.92,KAZ:830.41,KEN:83.33,KIR:666.67,XKX:894.65,KWT:649.2,KGZ:147.29,LAO:265.51,"LATIN AMERICA AND CARIBBEAN":260.28,LVA:123.2,LBN:599.01,LSO:20,LBR:227.85,LBY:818.69,LTU:160.07,LUX:105.26,MAC:448.98,MDG:436.44,MWI:66.67,MYS:605.83,MDV:611.77,MLI:408,MLT:459.14,MTQ:523.18,MRT:464.71,MUS:632.48,MEX:475.36,"MIDDLE EAST":660.46,MDA:648.5,MNG:771.8,MNE:417.07,MSR:1e3,MAR:662.64,MOZ:135.65,MMR:483.57,NAM:59.26,NRU:750,NPL:24.44,NLD:267.62,NCL:660.58,NZL:110.89,NIC:265.12,NER:670.89,NGA:516.23,"NORTH AMERICA":356.01,PRK:389.59,MKD:565.35,NOR:30.08,OCEANIA:507.63,OECD:360.53,OMN:564.69,PAK:463.66,PSE:516.13,PAN:161.68,PNG:507.25,PRY:24.31,PER:251.74,POL:661.93,PRT:165.55,PRI:678.74,QAT:602.59,REU:572.82,ROU:240.58,RUS:436.28,RWA:316.33,KNA:636.36,LCA:666.67,SPM:600,VCT:529.41,WSM:473.68,STP:642.86,SAU:706.79,SEN:511.6,SRB:636.06,SYC:564.52,SLE:50,SGP:474,SVK:116.77,SVN:231.28,SLB:700,SOM:578.95,ZAF:729.67,KOR:441.65,SSD:629.03,ESP:174.05,LKA:509.78,SDN:263.16,SUR:349.28,SWE:40.7,CHE:34.84,SYR:701.66,TWN:639.53,TJK:116.86,TZA:339.25,THA:560.74,PHL:601.1,TGO:443.18,TON:625,TTO:681.53,TUN:564.62,TUR:464.59,TKM:1306.03,TCA:653.85,UGA:44.53,UKR:259.69,ARE:561.14,GBR:237.59,USA:385.98,URY:112.65,UZB:1167.6,VUT:571.43,VEN:185.8,VNM:409.8,VGB:647.06,VIR:632.35,WORLD:485.99,YEM:566.1,ZMB:111.97,ZWE:297.87},z="average";var E={data:q,type:z};var U=.81,O=.52,_=.14,S=.15,p=.19,i=E.data.WORLD,f=50,g=.75,N=.25,I=.02;var d=t=>parseFloat(t.toFixed(2));function B(t){if(typeof t!="object")throw new Error("Options must be an object");let e={};if(t?.gridIntensity){e.gridIntensity={};let{device:r,dataCenter:a,network:n}=t.gridIntensity;(r||r===0)&&(typeof r=="object"?(E.data[r.country?.toUpperCase()]||(console.warn(`"${r.country}" is not a valid country. Please use a valid 3 digit ISO 3166 country code. +var co2=(()=>{var te=Object.create;var P=Object.defineProperty;var re=Object.getOwnPropertyDescriptor;var ne=Object.getOwnPropertyNames;var ie=Object.getPrototypeOf,ae=Object.prototype.hasOwnProperty;var se=(r,e)=>()=>(e||r((e={exports:{}}).exports,e),e.exports),oe=(r,e)=>{for(var t in e)P(r,t,{get:e[t],enumerable:!0})},j=(r,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of ne(e))!ae.call(r,n)&&n!==t&&P(r,n,{get:()=>e[n],enumerable:!(i=re(e,n))||i.enumerable});return r};var le=(r,e,t)=>(t=r!=null?te(ie(r)):{},j(e||!r||!r.__esModule?P(t,"default",{value:r,enumerable:!0}):t,r)),ce=r=>j(P({},"__esModule",{value:!0}),r);var Q=se((gt,q)=>{"use strict";async function Be(r,e){return typeof r=="string"?pe(r,e):be(r,e)}function pe(r,e){return e.indexOf(r)>-1}function De(r){return Object.entries(r).filter(([i,n])=>n.green).map(([i,n])=>n.url)}function be(r,e){let t=[];for(let i of r)e.indexOf(i)>-1&&t.push(i);return t}function we(r,e){return typeof r=="string"?X(r,e):Ve(r,e)}function X(r,e){return e.indexOf(r)>-1?r:{url:r,green:!1}}function Ve(r,e){let t={};for(let i of r)t[i]=X(i,e);return t}q.exports={check:Be,greenDomainsFromResults:De,find:we}});var je={};oe(je,{averageIntensity:()=>g,co2:()=>K,default:()=>Ye,hosting:()=>F,marginalIntensity:()=>H});var S=4883333333333333e-25;var v=class{constructor(e){this.allowRatings=!1,this.options=e,this.KWH_PER_BYTE_FOR_NETWORK=S}perByte(e,t){if(e<1)return 0;if(t){let n=e*72e-12*0,a=e*S*475;return n+a}let i=72e-12+S;return e*i*519}};var B=v;var I={GIGABYTE:1e9};var de={AFG:132.53,AFRICA:559.42,ALB:24.29,DZA:634.61,ASM:611.11,AGO:174.73,ATG:611.11,ARG:394.62,ARM:264.54,ABW:561.22,ASEAN:554.5,ASIA:591.19,AUS:570.35,AUT:110.81,AZE:671.39,BHS:660.1,BHR:904.62,BGD:678.11,BRB:605.51,BLR:441.74,BEL:138.11,BLZ:225.81,BEN:584.07,BTN:23.33,BOL:489.14,BIH:600,BWA:847.91,BRA:105.51,BRN:893.91,BGR:335.33,BFA:467.53,BDI:250,CPV:558.14,KHM:417.71,CMR:305.42,CAN:161.43,CYM:642.86,CAF:0,TCD:628.57,CHL:353.52,CHN:585.82,COL:214.88,COM:642.86,COG:700,COD:24.46,COK:250,CRI:26.46,CIV:393.89,HRV:204.96,CUB:637.61,CYP:534.32,CZE:449.72,DNK:151.65,DJI:692.31,DMA:529.41,DOM:580.78,ECU:150.69,EGY:570.13,SLV:116.54,GNQ:591.84,ERI:631.58,EST:416.67,SWZ:172.41,ETH:24.64,EU:243.83,EUROPE:327.69,FLK:500,FRO:404.76,FJI:288.46,FIN:79.16,FRA:56.04,GUF:217.82,PYF:442.86,G20:482.92,G7:361.52,GAB:491.6,GMB:666.67,GEO:167.02,DEU:380.95,GHA:484,GRC:336.57,GRL:178.57,GRD:640,GLP:500,GUM:622.86,GTM:328.27,GIN:236.84,GNB:625,GUY:640.35,HTI:567.31,HND:282.27,HKG:699.5,HUN:204.19,ISL:27.68,IND:705.13,IDN:675.93,IRN:665.15,IRQ:688.81,IRL:290.81,ISR:582.93,ITA:330.72,JAM:555.56,JPN:512.81,JOR:540.92,KAZ:830.41,KEN:83.33,KIR:666.67,XKX:894.65,KWT:649.2,KGZ:147.29,LAO:265.51,"LATIN AMERICA AND CARIBBEAN":260.28,LVA:123.2,LBN:599.01,LSO:20,LBR:227.85,LBY:818.69,LTU:160.07,LUX:105.26,MAC:448.98,MDG:436.44,MWI:66.67,MYS:605.83,MDV:611.77,MLI:408,MLT:459.14,MTQ:523.18,MRT:464.71,MUS:632.48,MEX:475.36,"MIDDLE EAST":660.46,MDA:648.5,MNG:771.8,MNE:417.07,MSR:1e3,MAR:662.64,MOZ:135.65,MMR:483.57,NAM:59.26,NRU:750,NPL:24.44,NLD:267.62,NCL:660.58,NZL:110.89,NIC:265.12,NER:670.89,NGA:516.23,"NORTH AMERICA":356.01,PRK:389.59,MKD:565.35,NOR:30.08,OCEANIA:507.63,OECD:360.53,OMN:564.69,PAK:463.66,PSE:516.13,PAN:161.68,PNG:507.25,PRY:24.31,PER:251.74,POL:661.93,PRT:165.55,PRI:678.74,QAT:602.59,REU:572.82,ROU:240.58,RUS:436.28,RWA:316.33,KNA:636.36,LCA:666.67,SPM:600,VCT:529.41,WSM:473.68,STP:642.86,SAU:706.79,SEN:511.6,SRB:636.06,SYC:564.52,SLE:50,SGP:474,SVK:116.77,SVN:231.28,SLB:700,SOM:578.95,ZAF:729.67,KOR:441.65,SSD:629.03,ESP:174.05,LKA:509.78,SDN:263.16,SUR:349.28,SWE:40.7,CHE:34.84,SYR:701.66,TWN:639.53,TJK:116.86,TZA:339.25,THA:560.74,PHL:601.1,TGO:443.18,TON:625,TTO:681.53,TUN:564.62,TUR:464.59,TKM:1306.03,TCA:653.85,UGA:44.53,UKR:259.69,ARE:561.14,GBR:237.59,USA:385.98,URY:112.65,UZB:1167.6,VUT:571.43,VEN:185.8,VNM:409.8,VGB:647.06,VIR:632.35,WORLD:485.99,YEM:566.1,ZMB:111.97,ZWE:297.87},ue="average";var g={data:de,type:ue};var x=.81,p=.52,D=.14,b=.15,w=.19,u=g.data.WORLD,T=50,_=.75,A=.25,y=.02,Z={OPERATIONAL_KWH_PER_GB_DATACENTER:.055,OPERATIONAL_KWH_PER_GB_NETWORK:.059,OPERATIONAL_KWH_PER_GB_DEVICE:.08,EMBODIED_KWH_PER_GB_DATACENTER:.012,EMBODIED_KWH_PER_GB_NETWORK:.013,EMBODIED_KWH_PER_GB_DEVICE:.081,GLOBAL_GRID_INTENSITY:494},$={fifthPercentile:.095,tenthPercentile:.186,twentiethPercentile:.341,thirtiethPercentile:.493,fortiethPercentile:.656,fiftiethPercentile:.846},J={fifthPercentile:.04,tenthPercentile:.079,twentiethPercentile:.145,thirtiethPercentile:.209,fortiethPercentile:.278,fiftiethPercentile:.359};var N=r=>parseFloat(r.toFixed(2)),f=(r,e)=>r<=e;function V(r){if(typeof r!="object")throw new Error("Options must be an object");let e={};if(r?.gridIntensity){e.gridIntensity={};let{device:t,dataCenter:i,network:n}=r.gridIntensity;(t||t===0)&&(typeof t=="object"?(g.data[t.country?.toUpperCase()]||(console.warn(`"${t.country}" is not a valid country. Please use a valid 3 digit ISO 3166 country code. See https://developers.thegreenwebfoundation.org/co2js/data/ for more information. -Falling back to global average grid intensity.`),e.gridIntensity.device={value:i}),e.gridIntensity.device={country:r.country,value:parseFloat(E.data[r.country?.toUpperCase()])}):typeof r=="number"?e.gridIntensity.device={value:r}:(e.gridIntensity.device={value:i},console.warn(`The device grid intensity must be a number or an object. You passed in a ${typeof r}. -Falling back to global average grid intensity.`))),(a||a===0)&&(typeof a=="object"?(E.data[a.country?.toUpperCase()]||(console.warn(`"${a.country}" is not a valid country. Please use a valid 3 digit ISO 3166 country code. +Falling back to global average grid intensity.`),e.gridIntensity.device={value:u}),e.gridIntensity.device={country:t.country,value:parseFloat(g.data[t.country?.toUpperCase()])}):typeof t=="number"?e.gridIntensity.device={value:t}:(e.gridIntensity.device={value:u},console.warn(`The device grid intensity must be a number or an object. You passed in a ${typeof t}. +Falling back to global average grid intensity.`))),(i||i===0)&&(typeof i=="object"?(g.data[i.country?.toUpperCase()]||(console.warn(`"${i.country}" is not a valid country. Please use a valid 3 digit ISO 3166 country code. See https://developers.thegreenwebfoundation.org/co2js/data/ for more information. -Falling back to global average grid intensity.`),e.gridIntensity.dataCenter={value:i}),e.gridIntensity.dataCenter={country:a.country,value:parseFloat(E.data[a.country?.toUpperCase()])}):typeof a=="number"?e.gridIntensity.dataCenter={value:a}:(e.gridIntensity.dataCenter={value:i},console.warn(`The data center grid intensity must be a number or an object. You passed in a ${typeof a}. -Falling back to global average grid intensity.`))),(n||n===0)&&(typeof n=="object"?(E.data[n.country?.toUpperCase()]||(console.warn(`"${n.country}" is not a valid country. Please use a valid 3 digit ISO 3166 country code. +Falling back to global average grid intensity.`),e.gridIntensity.dataCenter={value:u}),e.gridIntensity.dataCenter={country:i.country,value:parseFloat(g.data[i.country?.toUpperCase()])}):typeof i=="number"?e.gridIntensity.dataCenter={value:i}:(e.gridIntensity.dataCenter={value:u},console.warn(`The data center grid intensity must be a number or an object. You passed in a ${typeof i}. +Falling back to global average grid intensity.`))),(n||n===0)&&(typeof n=="object"?(g.data[n.country?.toUpperCase()]||(console.warn(`"${n.country}" is not a valid country. Please use a valid 3 digit ISO 3166 country code. See https://developers.thegreenwebfoundation.org/co2js/data/ for more information. Falling back to global average grid intensity. -Falling back to global average grid intensity.`),e.gridIntensity.network={value:i}),e.gridIntensity.network={country:n.country,value:parseFloat(E.data[n.country?.toUpperCase()])}):typeof n=="number"?e.gridIntensity.network={value:n}:(e.gridIntensity.network={value:i},console.warn(`The network grid intensity must be a number or an object. You passed in a ${typeof n}. -Falling back to global average grid intensity.`)))}return(t?.dataReloadRatio||t.dataReloadRatio===0)&&(typeof t.dataReloadRatio=="number"?t.dataReloadRatio>=0&&t.dataReloadRatio<=1?e.dataReloadRatio=t.dataReloadRatio:(e.dataReloadRatio=I,console.warn(`The dataReloadRatio option must be a number between 0 and 1. You passed in ${t.dataReloadRatio}. -Falling back to default value.`)):(e.dataReloadRatio=I,console.warn(`The dataReloadRatio option must be a number. You passed in a ${typeof t.dataReloadRatio}. -Falling back to default value.`))),(t?.firstVisitPercentage||t.firstVisitPercentage===0)&&(typeof t.firstVisitPercentage=="number"?t.firstVisitPercentage>=0&&t.firstVisitPercentage<=1?e.firstVisitPercentage=t.firstVisitPercentage:(e.firstVisitPercentage=g,console.warn(`The firstVisitPercentage option must be a number between 0 and 1. You passed in ${t.firstVisitPercentage}. -Falling back to default value.`)):(e.firstVisitPercentage=g,console.warn(`The firstVisitPercentage option must be a number. You passed in a ${typeof t.firstVisitPercentage}. -Falling back to default value.`))),(t?.returnVisitPercentage||t.returnVisitPercentage===0)&&(typeof t.returnVisitPercentage=="number"?t.returnVisitPercentage>=0&&t.returnVisitPercentage<=1?e.returnVisitPercentage=t.returnVisitPercentage:(e.returnVisitPercentage=N,console.warn(`The returnVisitPercentage option must be a number between 0 and 1. You passed in ${t.returnVisitPercentage}. -Falling back to default value.`)):(e.returnVisitPercentage=N,console.warn(`The returnVisitPercentage option must be a number. You passed in a ${typeof t.returnVisitPercentage}. -Falling back to default value.`))),e}function G(t=""){return{"User-Agent":`co2js/0.15.0 ${t}`}}var P=class{constructor(e){this.options=e}energyPerByteByComponent(e){let a=e/C.GIGABYTE*U;return{consumerDeviceEnergy:a*O,networkEnergy:a*_,productionEnergy:a*p,dataCenterEnergy:a*S}}co2byComponent(e,r=i,a={}){let n=i,o=i,c=i,R=i;if(a?.gridIntensity){let{device:s,network:l,dataCenter:A}=a.gridIntensity;(s?.value||s?.value===0)&&(n=s.value),(l?.value||l?.value===0)&&(o=l.value),(A?.value||A?.value===0)&&(c=A.value)}r===!0&&(c=f);let u={};for(let[s,l]of Object.entries(e))s.startsWith("dataCenterEnergy")?u[s.replace("Energy","CO2")]=l*c:s.startsWith("consumerDeviceEnergy")?u[s.replace("Energy","CO2")]=l*n:s.startsWith("networkEnergy")?u[s.replace("Energy","CO2")]=l*o:u[s.replace("Energy","CO2")]=l*R;return u}perByte(e,r=!1,a=!1,n={}){e<1&&(e=0);let o=this.energyPerByteByComponent(e,n);if(typeof r!="boolean")throw new Error(`perByte expects a boolean for the carbon intensity value. Received: ${r}`);let c=this.co2byComponent(o,r,n),u=Object.values(c).reduce((s,l)=>s+l);return a?{...c,total:u}:u}perVisit(e,r=!1,a=!1,n={}){let o=this.energyPerVisitByComponent(e,n);if(typeof r!="boolean")throw new Error(`perVisit expects a boolean for the carbon intensity value. Received: ${r}`);let c=this.co2byComponent(o,r,n),u=Object.values(c).reduce((s,l)=>s+l);return a?{...c,total:u}:u}energyPerByte(e){let r=this.energyPerByteByComponent(e);return Object.values(r).reduce((n,o)=>n+o)}energyPerVisitByComponent(e,r={},a=g,n=N,o=I){(r.dataReloadRatio||r.dataReloadRatio===0)&&(o=r.dataReloadRatio),(r.firstVisitPercentage||r.firstVisitPercentage===0)&&(a=r.firstVisitPercentage),(r.returnVisitPercentage||r.returnVisitPercentage===0)&&(n=r.returnVisitPercentage);let c=this.energyPerByteByComponent(e),R={},u=Object.values(c);for(let[s,l]of Object.entries(c))R[`${s} - first`]=l*a,R[`${s} - subsequent`]=l*n*o;return R}energyPerVisit(e){let r=0,a=0,n=Object.entries(this.energyPerVisitByComponent(e));for(let[o,c]of n)o.indexOf("first")>0&&(r+=c);for(let[o,c]of n)o.indexOf("subsequent")>0&&(a+=c);return r+a}emissionsPerVisitInGrams(e,r=i){return d(e*r)}annualEnergyInKwh(e,r=1e3){return e*r*12}annualEmissionsInGrams(e,r=1e3){return e*r*12}annualSegmentEnergy(e){return{consumerDeviceEnergy:d(e*O),networkEnergy:d(e*_),dataCenterEnergy:d(e*S),productionEnergy:d(e*p)}}};var h=P;var b=class{constructor(e){if(this.model=new h,e?.model==="1byte")this.model=new L;else if(e?.model==="swd")this.model=new h;else if(e?.model)throw new Error(`"${e.model}" is not a valid model. Please use "1byte" for the OneByte model, and "swd" for the Sustainable Web Design model. -See https://developers.thegreenwebfoundation.org/co2js/models/ to learn more about the models available in CO2.js.`);this._segment=e?.results==="segment"}perByte(e,r=!1){return this.model.perByte(e,r,this._segment)}perVisit(e,r=!1){if(this.model?.perVisit)return this.model.perVisit(e,r,this._segment);throw new Error(`The perVisit() method is not supported in the model you are using. Try using perByte() instead. -See https://developers.thegreenwebfoundation.org/co2js/methods/ to learn more about the methods available in CO2.js.`)}perByteTrace(e,r=!1,a={}){let n={};return a&&(n=B(a)),{co2:this.model.perByte(e,r,this._segment,n),green:r,variables:{description:"Below are the variables used to calculate this CO2 estimate.",bytes:e,gridIntensity:{description:"The grid intensity (grams per kilowatt-hour) used to calculate this CO2 estimate.",network:n?.gridIntensity?.network?.value??i,dataCenter:r?f:n?.gridIntensity?.dataCenter?.value??i,production:i,device:n?.gridIntensity?.device?.value??i}}}}perVisitTrace(e,r=!1,a={}){if(this.model?.perVisit){let n={};return a&&(n=B(a)),{co2:this.model.perVisit(e,r,this._segment,n),green:r,variables:{description:"Below are the variables used to calculate this CO2 estimate.",bytes:e,gridIntensity:{description:"The grid intensity (grams per kilowatt-hour) used to calculate this CO2 estimate.",network:n?.gridIntensity?.network?.value??i,dataCenter:r?f:n?.gridIntensity?.dataCenter?.value??i,production:i,device:n?.gridIntensity?.device?.value??i},dataReloadRatio:n?.dataReloadRatio??.02,firstVisitPercentage:n?.firstVisitPercentage??.75,returnVisitPercentage:n?.returnVisitPercentage??.25}}}else throw new Error(`The perVisitDetailed() method is not supported in the model you are using. Try using perByte() instead. -See https://developers.thegreenwebfoundation.org/co2js/methods/ to learn more about the methods available in CO2.js.`)}};var D=b;var W=X(F());function oe(t,e){let r=typeof e=="string"?{userAgentIdentifier:e}:e;if(r?.db&&r.verbose)throw new Error("verbose mode cannot be used with a local lookup database");return typeof t=="string"?se(t,r):ce(t,r)}async function se(t,e={}){let r=await fetch(`https://api.thegreenwebfoundation.org/greencheck/${t}`,{headers:G(e.userAgentIdentifier)});if(e?.db)return W.default.check(t,e.db);let a=await r.json();return e.verbose?a:a.green}async function ce(t,e={}){try{let r="https://api.thegreenwebfoundation.org/v2/greencheckmulti",a=JSON.stringify(t),o=await(await fetch(`${r}/${a}`,{headers:G(e.userAgentIdentifier)})).json();return e.verbose?o:le(o)}catch{return e.verbose?{}:[]}}function le(t){return Object.entries(t).filter(([a,n])=>n.green).map(([a,n])=>n.url)}var Y={check:oe};function ue(t,e){return Y.check(t,e)}var M={check:ue};var Ee={AFG:"414",ALB:"0",DZA:"528",ASM:"753",AND:"188",AGO:"1476",AIA:"753",ATG:"753",ARG:"478",ARM:"390",ABW:"753",AUS:"808",AUT:"242",AZE:"534","AZORES (PORTUGAL)":"753",BHS:"753",BHR:"726",BGD:"528",BRB:"749",BLR:"400",BEL:"252",BLZ:"403",BEN:"745",BMU:"753",BTN:"0",BOL:"604",BES:"753",BIH:"1197",BWA:"1486",BRA:"284",VGB:"753",BRN:"681",BGR:"911",BFA:"753",BDI:"414",KHM:"1046",CMR:"659",CAN:"372",CYM:"753",CPV:"753",CAF:"188",TCD:"753","CHANNEL ISLANDS (U.K)":"753",CHL:"657",CHN:"899",COL:"410",COM:"753",COD:"0",COG:"659",COK:"753",CRI:"108",CIV:"466",HRV:"294",CUB:"559",CUW:"876",CYP:"751",CZE:"902",DNK:"362",DJI:"753",DMA:"753",DOM:"601",ECU:"560",EGY:"554",SLV:"547",GNQ:"632",ERI:"915",EST:"1057",SWZ:"0",ETH:"0",FLK:"753",FRO:"753",FJI:"640",FIN:"267",FRA:"158",GUF:"423",PYF:"753",GAB:"946",GMB:"753",GEO:"289",DEU:"650",GHA:"495",GIB:"779",GRC:"507",GRL:"264",GRD:"753",GLP:"753",GUM:"753",GTM:"798",GIN:"753",GNB:"753",GUY:"847",HTI:"1048",HND:"662",HUN:"296",ISL:"0",IND:"951",IDN:"783",IRN:"592",IRQ:"1080",IRL:"380",IMN:"436",ISR:"394",ITA:"414",JAM:"711",JPN:"471",JOR:"529",KAZ:"797",KEN:"574",KIR:"753",PRK:"754",KOR:"555",XKX:"1145",KWT:"675",KGZ:"217",LAO:"1069",LVA:"240",LBN:"794",LSO:"0",LBR:"677",LBY:"668",LIE:"151",LTU:"211",LUX:"220",MDG:"876","MADEIRA (PORTUGAL)":"663",MWI:"489",MYS:"551",MDV:"753",MLI:"1076",MLT:"520",MHL:"753",MTQ:"753",MRT:"753",MUS:"700",MYT:"753",MEX:"531",FSM:"753",MDA:"541",MCO:"158",MNG:"1366",MNE:"899",MSR:"753",MAR:"729",MOZ:"234",MMR:"719",NAM:"355",NRU:"753",NPL:"0",NLD:"326",NCL:"779",NZL:"246",NIC:"675",NER:"772",NGA:"526",NIU:"753",MKD:"851",MNP:"753",NOR:"47",OMN:"479",PAK:"592",PLW:"753",PSE:"719",PAN:"477",PNG:"597",PRY:"0",PER:"473",PHL:"672",POL:"828",PRT:"389",PRI:"596",QAT:"503",REU:"772",ROU:"489",RUS:"476",RWA:"712",SHN:"753",KNA:"753",LCA:"753",MAF:"753",SPM:"753",VCT:"753",WSM:"753",SMR:"414",STP:"753",SAU:"592",SEN:"870",SRB:"1086",SYC:"753",SLE:"489",SGP:"379",SXM:"753",SVK:"332",SVN:"620",SLB:"753",SOM:"753",ZAF:"1070",SSD:"890",ESP:"402",LKA:"731",SDN:"736",SUR:"1029",SWE:"68",CHE:"48",SYR:"713",TWN:"484",TJK:"255",TZA:"531",THA:"450",TLS:"753",TGO:"859",TON:"753",TTO:"559",TUN:"468",TUR:"376",TKM:"927",TCA:"753",TUV:"753",UGA:"279",UKR:"768",ARE:"556",GBR:"380",USA:"416",URY:"174",UZB:"612",VUT:"753",VEN:"711",VNM:"560",VIR:"650",YEM:"807",ZMB:"416",ZWE:"1575","MEMO: EU 27":"409"},Re="marginal",de="2021";var V={data:Ee,type:Re,year:de};var fe={co2:D,hosting:M,averageIntensity:E,marginalIntensity:V};return Q(ge);})(); +Falling back to global average grid intensity.`),e.gridIntensity.network={value:u}),e.gridIntensity.network={country:n.country,value:parseFloat(g.data[n.country?.toUpperCase()])}):typeof n=="number"?e.gridIntensity.network={value:n}:(e.gridIntensity.network={value:u},console.warn(`The network grid intensity must be a number or an object. You passed in a ${typeof n}. +Falling back to global average grid intensity.`)))}return(r?.dataReloadRatio||r.dataReloadRatio===0)&&(typeof r.dataReloadRatio=="number"?r.dataReloadRatio>=0&&r.dataReloadRatio<=1?e.dataReloadRatio=r.dataReloadRatio:(e.dataReloadRatio=y,console.warn(`The dataReloadRatio option must be a number between 0 and 1. You passed in ${r.dataReloadRatio}. +Falling back to default value.`)):(e.dataReloadRatio=y,console.warn(`The dataReloadRatio option must be a number. You passed in a ${typeof r.dataReloadRatio}. +Falling back to default value.`))),(r?.firstVisitPercentage||r.firstVisitPercentage===0)&&(typeof r.firstVisitPercentage=="number"?r.firstVisitPercentage>=0&&r.firstVisitPercentage<=1?e.firstVisitPercentage=r.firstVisitPercentage:(e.firstVisitPercentage=_,console.warn(`The firstVisitPercentage option must be a number between 0 and 1. You passed in ${r.firstVisitPercentage}. +Falling back to default value.`)):(e.firstVisitPercentage=_,console.warn(`The firstVisitPercentage option must be a number. You passed in a ${typeof r.firstVisitPercentage}. +Falling back to default value.`))),(r?.returnVisitPercentage||r.returnVisitPercentage===0)&&(typeof r.returnVisitPercentage=="number"?r.returnVisitPercentage>=0&&r.returnVisitPercentage<=1?e.returnVisitPercentage=r.returnVisitPercentage:(e.returnVisitPercentage=A,console.warn(`The returnVisitPercentage option must be a number between 0 and 1. You passed in ${r.returnVisitPercentage}. +Falling back to default value.`)):(e.returnVisitPercentage=A,console.warn(`The returnVisitPercentage option must be a number. You passed in a ${typeof r.returnVisitPercentage}. +Falling back to default value.`))),e}function M(r=""){return{"User-Agent":`co2js/0.15.0 ${r}`}}var{fifthPercentile:Ee,tenthPercentile:fe,twentiethPercentile:Re,thirtiethPercentile:ge,fortiethPercentile:Ce,fiftiethPercentile:me}=$,L=class{constructor(e){this.allowRatings=!0,this.options=e,this.version=3}energyPerByteByComponent(e){let i=e/I.GIGABYTE*x;return{consumerDeviceEnergy:i*p,networkEnergy:i*D,productionEnergy:i*w,dataCenterEnergy:i*b}}co2byComponent(e,t=u,i={}){let n=u,a=u,l=u,c=u;if(i?.gridIntensity){let{device:o,network:s,dataCenter:d}=i.gridIntensity;(o?.value||o?.value===0)&&(n=o.value),(s?.value||s?.value===0)&&(a=s.value),(d?.value||d?.value===0)&&(l=d.value)}t===!0&&(l=T);let E={};for(let[o,s]of Object.entries(e))o.startsWith("dataCenterEnergy")?E[o.replace("Energy","CO2")]=s*l:o.startsWith("consumerDeviceEnergy")?E[o.replace("Energy","CO2")]=s*n:o.startsWith("networkEnergy")?E[o.replace("Energy","CO2")]=s*a:E[o.replace("Energy","CO2")]=s*c;return E}perByte(e,t=!1,i=!1,n=!1,a={}){e<1&&(e=0);let l=this.energyPerByteByComponent(e,a);if(typeof t!="boolean")throw new Error(`perByte expects a boolean for the carbon intensity value. Received: ${t}`);let c=this.co2byComponent(l,t,a),o=Object.values(c).reduce((d,R)=>d+R),s=null;return n&&(s=this.ratingScale(o)),i?n?{...c,total:o,rating:s}:{...c,total:o}:n?{total:o,rating:s}:o}perVisit(e,t=!1,i=!1,n=!1,a={}){let l=this.energyPerVisitByComponent(e,a);if(typeof t!="boolean")throw new Error(`perVisit expects a boolean for the carbon intensity value. Received: ${t}`);let c=this.co2byComponent(l,t,a),o=Object.values(c).reduce((d,R)=>d+R),s=null;return n&&(s=this.ratingScale(o)),i?n?{...c,total:o,rating:s}:{...c,total:o}:n?{total:o,rating:s}:o}energyPerByte(e){let t=this.energyPerByteByComponent(e);return Object.values(t).reduce((n,a)=>n+a)}energyPerVisitByComponent(e,t={},i=_,n=A,a=y){(t.dataReloadRatio||t.dataReloadRatio===0)&&(a=t.dataReloadRatio),(t.firstVisitPercentage||t.firstVisitPercentage===0)&&(i=t.firstVisitPercentage),(t.returnVisitPercentage||t.returnVisitPercentage===0)&&(n=t.returnVisitPercentage);let l=this.energyPerByteByComponent(e),c={},E=Object.values(l);for(let[o,s]of Object.entries(l))c[`${o} - first`]=s*i,c[`${o} - subsequent`]=s*n*a;return c}energyPerVisit(e){let t=0,i=0,n=Object.entries(this.energyPerVisitByComponent(e));for(let[a,l]of n)a.indexOf("first")>0&&(t+=l);for(let[a,l]of n)a.indexOf("subsequent")>0&&(i+=l);return t+i}emissionsPerVisitInGrams(e,t=u){return N(e*t)}annualEnergyInKwh(e,t=1e3){return e*t*12}annualEmissionsInGrams(e,t=1e3){return e*t*12}annualSegmentEnergy(e){return{consumerDeviceEnergy:N(e*p),networkEnergy:N(e*D),dataCenterEnergy:N(e*b),productionEnergy:N(e*w)}}ratingScale(e){return f(e,Ee)?"A+":f(e,fe)?"A":f(e,Re)?"B":f(e,ge)?"C":f(e,Ce)?"D":f(e,me)?"E":"F"}};var h=L;var{fifthPercentile:Ie,tenthPercentile:Ne,twentiethPercentile:Oe,thirtiethPercentile:Te,fortiethPercentile:_e,fiftiethPercentile:Ae}=J,{OPERATIONAL_KWH_PER_GB_DATACENTER:ye,OPERATIONAL_KWH_PER_GB_NETWORK:Pe,OPERATIONAL_KWH_PER_GB_DEVICE:he,EMBODIED_KWH_PER_GB_DATACENTER:Ge,EMBODIED_KWH_PER_GB_NETWORK:Se,EMBODIED_KWH_PER_GB_DEVICE:ve,GLOBAL_GRID_INTENSITY:O}=Z,k=class{constructor(e){this.allowRatings=!0,this.options=e,this.version=4}operationalEnergyPerSegment(e){let t=e/I.GIGABYTE,i=t*ye,n=t*Pe,a=t*he;return{dataCenter:i,network:n,device:a}}operationalEmissions(e,t={}){let{dataCenter:i,network:n,device:a}=this.operationalEnergyPerSegment(e),l=O,c=O,E=O;if(t?.gridIntensity){let{device:R,network:m,dataCenter:C}=t.gridIntensity;(R?.value||R?.value===0)&&(E=R.value),(m?.value||m?.value===0)&&(c=m.value),(C?.value||C?.value===0)&&(l=C.value)}let o=i*l,s=n*c,d=a*E;return{dataCenter:o,network:s,device:d}}embodiedEnergyPerSegment(e){let t=e/I.GIGABYTE,i=t*Ge,n=t*Se,a=t*ve;return{dataCenter:i,network:n,device:a}}embodiedEmissions(e){let{dataCenter:t,network:i,device:n}=this.embodiedEnergyPerSegment(e),a=O,l=O,c=O,E=t*a,o=i*l,s=n*c;return{dataCenter:E,network:o,device:s}}perByte(e,t=!1,i=!1,n=!1,a={}){if(e<1)return 0;let l=this.operationalEmissions(e,a),c=this.embodiedEmissions(e),E=0;t?E=1:(a?.greenHostingFactor||a?.greenHostingFactor===0)&&(E=a.greenHostingFactor);let o={dataCenter:l.dataCenter*(1-E)+c.dataCenter,network:l.network+c.network,device:l.device+c.device},s=o.dataCenter+o.network+o.device,d=null;if(n&&(d=this.ratingScale(s)),i){let R={dataCenterOperationalCO2e:l.dataCenter,networkOperationalCO2e:l.network,consumerDeviceOperationalCO2e:l.device,dataCenterEmbodiedCO2e:c.dataCenter,networkEmbodiedCO2e:c.network,consumerDeviceEmbodiedCO2e:c.device,totalOperationalCO2e:l.dataCenter+l.network+l.device,totalEmbodiedCO2e:c.dataCenter+c.network+c.device,dataCenterCO2e:l.dataCenter+c.dataCenter,networkCO2e:l.network+c.network,consumerDeviceCO2e:l.device+c.device};return n?{...R,total:s,rating:d}:{...R,total:s}}return n?{total:s,rating:d}:s}perVisit(e,t=!1,i=!1,n=!1,a={}){let l=1,c=0,E=0,o=0,s=this.operationalEmissions(e,a),d=this.embodiedEmissions(e);if(e<1)return 0;t?o=1:(a?.greenHostingFactor||a?.greenHostingFactor===0)&&(o=a.greenHostingFactor),(a.firstVisitPercentage||a.firstVisitPercentage===0)&&(l=a.firstVisitPercentage),(a.returnVisitPercentage||a.returnVisitPercentage===0)&&(c=a.returnVisitPercentage),(a.dataReloadRatio||a.dataReloadRatio===0)&&(E=a.dataReloadRatio);let R=s.dataCenter*(1-o)+d.dataCenter+s.network+d.network+s.device+d.device,m=(s.dataCenter*(1-o)+d.dataCenter+s.network+d.network+s.device+d.device)*(1-E),C=R*l+m*c,G=null;if(n&&(G=this.ratingScale(C)),i){let Y={dataCenterOperationalCO2e:s.dataCenter,networkOperationalCO2e:s.network,consumerDeviceOperationalCO2e:s.device,dataCenterEmbodiedCO2e:d.dataCenter,networkEmbodiedCO2e:d.network,consumerDeviceEmbodiedCO2e:d.device,totalOperationalCO2e:s.dataCenter+s.network+s.device,totalEmbodiedCO2e:d.dataCenter+d.network+d.device,dataCenterCO2e:s.dataCenter+d.dataCenter,networkCO2e:s.network+d.network,consumerDeviceCO2e:s.device+d.device,firstVisitCO2e:R,returnVisitCO2e:m};return n?{...Y,total:C,rating:G}:{...Y,total:C}}return n?{total:C,rating:G}:C}ratingScale(e){return f(e,Ie)?"A+":f(e,Ne)?"A":f(e,Oe)?"B":f(e,Te)?"C":f(e,_e)?"D":f(e,Ae)?"E":"F"}};var W=k;var U=class{constructor(e){if(this.model=new h,e?.model==="1byte")this.model=new B;else if(e?.model==="swd")this.model=new h,e?.version===4&&(this.model=new W);else if(e?.model)throw new Error(`"${e.model}" is not a valid model. Please use "1byte" for the OneByte model, and "swd" for the Sustainable Web Design model. +See https://developers.thegreenwebfoundation.org/co2js/models/ to learn more about the models available in CO2.js.`);if(e?.rating&&typeof e.rating!="boolean")throw new Error(`The rating option must be a boolean. Please use true or false. +See https://developers.thegreenwebfoundation.org/co2js/options/ to learn more about the options available in CO2.js.`);let t=!!this.model.allowRatings;if(this._segment=e?.results==="segment",this._rating=e?.rating===!0,!t&&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. +See https://developers.thegreenwebfoundation.org/co2js/models/ to learn more about the models available in CO2.js.`)}perByte(e,t=!1){return this.model.perByte(e,t,this._segment,this._rating)}perVisit(e,t=!1){if(this.model?.perVisit)return this.model.perVisit(e,t,this._segment,this._rating);throw new Error(`The perVisit() method is not supported in the model you are using. Try using perByte() instead. +See https://developers.thegreenwebfoundation.org/co2js/methods/ to learn more about the methods available in CO2.js.`)}perByteTrace(e,t=!1,i={}){let n={};return i&&(n=V(i)),{co2:this.model.perByte(e,t,this._segment,this._rating,n),green:t,variables:{description:"Below are the variables used to calculate this CO2 estimate.",bytes:e,gridIntensity:{description:"The grid intensity (grams per kilowatt-hour) used to calculate this CO2 estimate.",network:n?.gridIntensity?.network?.value??u,dataCenter:t?T:n?.gridIntensity?.dataCenter?.value??u,production:u,device:n?.gridIntensity?.device?.value??u}}}}perVisitTrace(e,t=!1,i={}){if(this.model?.perVisit){let n={};return i&&(n=V(i)),{co2:this.model.perVisit(e,t,this._segment,this._rating,n),green:t,variables:{description:"Below are the variables used to calculate this CO2 estimate.",bytes:e,gridIntensity:{description:"The grid intensity (grams per kilowatt-hour) used to calculate this CO2 estimate.",network:n?.gridIntensity?.network?.value??u,dataCenter:t?T:n?.gridIntensity?.dataCenter?.value??u,production:u,device:n?.gridIntensity?.device?.value??u},dataReloadRatio:n?.dataReloadRatio??.02,firstVisitPercentage:n?.firstVisitPercentage??.75,returnVisitPercentage:n?.returnVisitPercentage??.25}}}else throw new Error(`The perVisitTrace() method is not supported in the model you are using. Try using perByte() instead. +See https://developers.thegreenwebfoundation.org/co2js/methods/ to learn more about the methods available in CO2.js.`)}SustainableWebDesignV3(){return new h}SustainableWebDesignV4(){return new W}OneByte(){return new B}};var K=U;var z=le(Q());function Me(r,e){let t=typeof e=="string"?{userAgentIdentifier:e}:e;if(t?.db&&t.verbose)throw new Error("verbose mode cannot be used with a local lookup database");return typeof r=="string"?Le(r,t):ke(r,t)}async function Le(r,e={}){let t=await fetch(`https://api.thegreenwebfoundation.org/greencheck/${r}`,{headers:M(e.userAgentIdentifier)});if(e?.db)return z.default.check(r,e.db);let i=await t.json();return e.verbose?i:i.green}async function ke(r,e={}){try{let t="https://api.thegreenwebfoundation.org/v2/greencheckmulti",i=JSON.stringify(r),a=await(await fetch(`${t}/${i}`,{headers:M(e.userAgentIdentifier)})).json();return e.verbose?a:We(a)}catch{return e.verbose?{}:[]}}function We(r){return Object.entries(r).filter(([i,n])=>n.green).map(([i,n])=>n.url)}var ee={check:Me};function Ue(r,e){return ee.check(r,e)}var F={check:Ue};var Ke={AFG:"414",ALB:"0",DZA:"528",ASM:"753",AND:"188",AGO:"1476",AIA:"753",ATG:"753",ARG:"478",ARM:"390",ABW:"753",AUS:"808",AUT:"242",AZE:"534","AZORES (PORTUGAL)":"753",BHS:"753",BHR:"726",BGD:"528",BRB:"749",BLR:"400",BEL:"252",BLZ:"403",BEN:"745",BMU:"753",BTN:"0",BOL:"604",BES:"753",BIH:"1197",BWA:"1486",BRA:"284",VGB:"753",BRN:"681",BGR:"911",BFA:"753",BDI:"414",KHM:"1046",CMR:"659",CAN:"372",CYM:"753",CPV:"753",CAF:"188",TCD:"753","CHANNEL ISLANDS (U.K)":"753",CHL:"657",CHN:"899",COL:"410",COM:"753",COD:"0",COG:"659",COK:"753",CRI:"108",CIV:"466",HRV:"294",CUB:"559",CUW:"876",CYP:"751",CZE:"902",DNK:"362",DJI:"753",DMA:"753",DOM:"601",ECU:"560",EGY:"554",SLV:"547",GNQ:"632",ERI:"915",EST:"1057",SWZ:"0",ETH:"0",FLK:"753",FRO:"753",FJI:"640",FIN:"267",FRA:"158",GUF:"423",PYF:"753",GAB:"946",GMB:"753",GEO:"289",DEU:"650",GHA:"495",GIB:"779",GRC:"507",GRL:"264",GRD:"753",GLP:"753",GUM:"753",GTM:"798",GIN:"753",GNB:"753",GUY:"847",HTI:"1048",HND:"662",HUN:"296",ISL:"0",IND:"951",IDN:"783",IRN:"592",IRQ:"1080",IRL:"380",IMN:"436",ISR:"394",ITA:"414",JAM:"711",JPN:"471",JOR:"529",KAZ:"797",KEN:"574",KIR:"753",PRK:"754",KOR:"555",XKX:"1145",KWT:"675",KGZ:"217",LAO:"1069",LVA:"240",LBN:"794",LSO:"0",LBR:"677",LBY:"668",LIE:"151",LTU:"211",LUX:"220",MDG:"876","MADEIRA (PORTUGAL)":"663",MWI:"489",MYS:"551",MDV:"753",MLI:"1076",MLT:"520",MHL:"753",MTQ:"753",MRT:"753",MUS:"700",MYT:"753",MEX:"531",FSM:"753",MDA:"541",MCO:"158",MNG:"1366",MNE:"899",MSR:"753",MAR:"729",MOZ:"234",MMR:"719",NAM:"355",NRU:"753",NPL:"0",NLD:"326",NCL:"779",NZL:"246",NIC:"675",NER:"772",NGA:"526",NIU:"753",MKD:"851",MNP:"753",NOR:"47",OMN:"479",PAK:"592",PLW:"753",PSE:"719",PAN:"477",PNG:"597",PRY:"0",PER:"473",PHL:"672",POL:"828",PRT:"389",PRI:"596",QAT:"503",REU:"772",ROU:"489",RUS:"476",RWA:"712",SHN:"753",KNA:"753",LCA:"753",MAF:"753",SPM:"753",VCT:"753",WSM:"753",SMR:"414",STP:"753",SAU:"592",SEN:"870",SRB:"1086",SYC:"753",SLE:"489",SGP:"379",SXM:"753",SVK:"332",SVN:"620",SLB:"753",SOM:"753",ZAF:"1070",SSD:"890",ESP:"402",LKA:"731",SDN:"736",SUR:"1029",SWE:"68",CHE:"48",SYR:"713",TWN:"484",TJK:"255",TZA:"531",THA:"450",TLS:"753",TGO:"859",TON:"753",TTO:"559",TUN:"468",TUR:"376",TKM:"927",TCA:"753",TUV:"753",UGA:"279",UKR:"768",ARE:"556",GBR:"380",USA:"416",URY:"174",UZB:"612",VUT:"753",VEN:"711",VNM:"560",VIR:"650",YEM:"807",ZMB:"416",ZWE:"1575","MEMO: EU 27":"409"},Fe="marginal",He="2021";var H={data:Ke,type:Fe,year:He};var Ye={co2:K,hosting:F,averageIntensity:g,marginalIntensity:H};return ce(je);})(); //# sourceMappingURL=index.js.map diff --git a/src/co2.js b/src/co2.js index ff48b44..e2e2549 100644 --- a/src/co2.js +++ b/src/co2.js @@ -60,7 +60,8 @@ */ import OneByte from "./1byte.js"; -import SustainableWebDesign from "./sustainable-web-design.js"; +import SustainableWebDesignV3 from "./sustainable-web-design-v3.js"; +import SustainableWebDesignV4 from "./sustainable-web-design-v4.js"; import { GLOBAL_GRID_INTENSITY, @@ -70,13 +71,16 @@ import { parseOptions } from "./helpers/index.js"; class CO2 { constructor(options) { - this.model = new SustainableWebDesign(); + this.model = new SustainableWebDesignV3(); // Using optional chaining allows an empty object to be passed // in without breaking the code. if (options?.model === "1byte") { this.model = new OneByte(); } else if (options?.model === "swd") { - this.model = new SustainableWebDesign(); + this.model = new SustainableWebDesignV3(); + if (options?.version === 4) { + this.model = new SustainableWebDesignV4(); + } } else if (options?.model) { throw new Error( `"${options.model}" is not a valid model. Please use "1byte" for the OneByte model, and "swd" for the Sustainable Web Design model.\nSee https://developers.thegreenwebfoundation.org/co2js/models/ to learn more about the models available in CO2.js.` @@ -236,7 +240,7 @@ class CO2 { }; } else { throw new Error( - `The perVisitDetailed() 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.` + `The perVisitTrace() 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.` ); } } diff --git a/src/co2.test.js b/src/co2.test.js index 6044812..b6ed814 100644 --- a/src/co2.test.js +++ b/src/co2.test.js @@ -1,6 +1,6 @@ "use strict"; -import { MILLION, SWD } from "./constants/test-constants.js"; +import { MILLION, SWDV3 } from "./constants/test-constants.js"; import CO2 from "./co2.js"; import { averageIntensity, marginalIntensity } from "./index.js"; @@ -15,7 +15,7 @@ describe("co2", () => { // we include more of the system in calculations for the // same levels of data transfer - const { MILLION_PERVISIT_GREY, MILLION_PERVISIT_GREEN } = SWD; + const { MILLION_PERVISIT_GREY, MILLION_PERVISIT_GREEN } = SWDV3; // We're not passing in a model parameter here to check that SWD is used by default beforeEach(() => { @@ -63,7 +63,7 @@ describe("co2", () => { MILLION_GREY_DATACENTERS, MILLION_GREY_PRODUCTION, MILLION_GREEN_DATACENTERS, - } = SWD; + } = SWDV3; describe("perVisit", () => { it("returns an object with devices, networks, data centers, and production emissions shown separately, as well as the total emissions", () => { co2 = new CO2({ results: "segment" }); @@ -297,7 +297,7 @@ describe("co2", () => { MILLION_PERBYTE_GREY_DEVICE_GRID_INTENSITY_CHANGE, MILLION_GREY, MILLION_PERVISIT_GREY, - } = SWD; + } = SWDV3; const co2 = new CO2(); it("expects an object or number", () => { expect( @@ -397,7 +397,7 @@ describe("co2", () => { MILLION_PERBYTE_GREY_DATACENTER_GRID_INTENSITY_CHANGE, MILLION_GREY, MILLION_PERVISIT_GREY, - } = SWD; + } = SWDV3; const co2 = new CO2(); it("expects an object or number", () => { expect( @@ -495,7 +495,7 @@ describe("co2", () => { MILLION_PERBYTE_GREY_NETWORK_GRID_INTENSITY_CHANGE, MILLION_GREY, MILLION_PERVISIT_GREY, - } = SWD; + } = SWDV3; const co2 = new CO2(); it("expects an object or number", () => { expect( @@ -589,7 +589,7 @@ describe("co2", () => { }); describe("Using custom caching values in SWD", () => { - const { MILLION_PERVISIT_GREY } = SWD; + const { MILLION_PERVISIT_GREY } = SWDV3; const co2 = new CO2(); it("uses the custom value", () => { expect( @@ -637,7 +637,7 @@ describe("co2", () => { }); describe("Using custom first and return visitor figures in SWD", () => { - const { MILLION_PERVISIT_GREY, MILLION_GREY } = SWD; + const { MILLION_PERVISIT_GREY, MILLION_GREY } = SWDV3; const co2 = new CO2(); it("uses the custom values", () => { @@ -867,4 +867,17 @@ describe("co2", () => { expect(co2RatingSegmented.perByte(MILLION)).toHaveProperty("networkCO2"); }); }); + + describe("Switch versions of the Sustainable Web Design model", () => { + const co2 = new CO2({ model: "swd" }); + const co2SWDV4 = new CO2({ model: "swd", version: 4 }); + + it("uses the SWD model version 3 by default", () => { + expect(co2.model.version).toBe(3); + }); + + it("uses the SWD model version 4 when specified", () => { + expect(co2SWDV4.model.version).toBe(4); + }); + }); }); diff --git a/src/constants/file-size.js b/src/constants/file-size.js index 94b4915..458e00a 100644 --- a/src/constants/file-size.js +++ b/src/constants/file-size.js @@ -1,4 +1,4 @@ -const GIGABYTE = 1000 * 1000 * 1000; +export const GIGABYTE = 1000 * 1000 * 1000; export default { GIGABYTE, diff --git a/src/constants/index.js b/src/constants/index.js index f53735d..ec42302 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -24,13 +24,32 @@ 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, +const SWDV4 = { + OPERATIONAL_KWH_PER_GB_DATACENTER: 0.055, + OPERATIONAL_KWH_PER_GB_NETWORK: 0.059, + OPERATIONAL_KWH_PER_GB_DEVICE: 0.08, + EMBODIED_KWH_PER_GB_DATACENTER: 0.012, + EMBODIED_KWH_PER_GB_NETWORK: 0.013, + EMBODIED_KWH_PER_GB_DEVICE: 0.081, + GLOBAL_GRID_INTENSITY: 494, +}; + +const SWDMV3_RATINGS = { + FIFTH_PERCENTILE: 0.095, + TENTH_PERCENTILE: 0.186, + TWENTIETH_PERCENTILE: 0.341, + THIRTIETH_PERCENTILE: 0.493, + FORTIETH_PERCENTILE: 0.656, + FIFTIETH_PERCENTILE: 0.846, +}; + +const SWDMV4_RATINGS = { + FIFTH_PERCENTILE: 0.04, + TENTH_PERCENTILE: 0.079, + TWENTIETH_PERCENTILE: 0.145, + THIRTIETH_PERCENTILE: 0.209, + FORTIETH_PERCENTILE: 0.278, + FIFTIETH_PERCENTILE: 0.359, }; export { @@ -45,5 +64,7 @@ export { FIRST_TIME_VIEWING_PERCENTAGE, RETURNING_VISITOR_PERCENTAGE, PERCENTAGE_OF_DATA_LOADED_ON_SUBSEQUENT_LOAD, - SWDMv3Ratings, + SWDV4, + SWDMV3_RATINGS, + SWDMV4_RATINGS, }; diff --git a/src/constants/test-constants.js b/src/constants/test-constants.js index 8a41971..9b73fcc 100644 --- a/src/constants/test-constants.js +++ b/src/constants/test-constants.js @@ -9,7 +9,7 @@ export const ONEBYTE = { MILLION_GREEN: 0.23196, }; -export const SWD = { +export const SWDV3 = { MILLION_GREY: 0.39365, MILLION_GREEN: 0.34068, MILLION_PERVISIT_GREY: 0.29721, @@ -44,3 +44,25 @@ export const SWD = { MILLION_PERVISIT_GREY_PRODUCTION_FIRST: 0.05701, MILLION_PERVISIT_GREY_PRODUCTION_SECOND: 0.00034, }; + +export const SWDV4 = { + PERBYTE_EMISSIONS_GB: 148.2, + PERBYTE_EMISSIONS_GB_GREEN: 121.03, + PERBYTE_EMISSIONS_GB_GREEN_PARTIAL: 134.615, + PERVISIT_EMISSIONS_GB: 140.79, + PERVISIT_EMISSIONS_GB_FIRSTVIEW: 148.2, + PERVISIT_EMISSIONS_GB_RETURNVIEW: 118.56, + PERVISIT_EMISSIONS_GB_GREEN: 114.9785, + PERVISIT_EMISSIONS_GB_GREEN_PARTIAL: 127.88425, + DC_OPERATIONAL_EMISSIONS_GB: 27.17, + NETWORK_OPERATIONAL_EMISSIONS_GB: 29.146, + DEVICE_OPERATIONAL_EMISSIONS_GB: 39.52, + TOTAL_OPERATIONAL_EMISSIONS_GB: 95.836, + DC_EMBODIED_EMISSIONS_GB: 5.928, + NETWORK_EMBODIED_EMISSIONS_GB: 6.422, + DEVICE_EMBODIED_EMISSIONS_GB: 40.014, + TOTAL_EMBODIED_EMISSIONS_GB: 52.364, + TOTAL_DC_EMISSIONS_GB: 33.098, + TOTAL_NETWORK_EMISSIONS_GB: 35.568, + TOTAL_DEVICE_EMISSIONS_GB: 79.534, +} diff --git a/src/helpers/index.js b/src/helpers/index.js index aac11b5..b009147 100644 --- a/src/helpers/index.js +++ b/src/helpers/index.js @@ -4,6 +4,8 @@ import { PERCENTAGE_OF_DATA_LOADED_ON_SUBSEQUENT_LOAD, FIRST_TIME_VIEWING_PERCENTAGE, RETURNING_VISITOR_PERCENTAGE, + SWDMV3_RATINGS, + SWDMV4_RATINGS, } from "../constants/index.js"; // Shared type definitions to be used across different files @@ -194,4 +196,53 @@ function getApiRequestHeaders(comment = "") { return { "User-Agent": `co2js/${process.env.CO2JS_VERSION} ${comment}` }; } -export { formatNumber, parseOptions, getApiRequestHeaders, lessThanEqualTo }; +/** + * Returns the SWDM rating for a given CO2e value and version of the SWDM. + * @param {number} co2e - The CO2e value to rate. + * @param {number} swdmVersion - The version of the SWDM to use. Defaults to version 3. + * @returns {string} The SWDM rating. + */ + +function outputRating(co2e, swdmVersion) { + let { + FIFTH_PERCENTILE, + TENTH_PERCENTILE, + TWENTIETH_PERCENTILE, + THIRTIETH_PERCENTILE, + FORTIETH_PERCENTILE, + FIFTIETH_PERCENTILE, + } = SWDMV3_RATINGS; + + if (swdmVersion === 4) { + FIFTH_PERCENTILE = SWDMV4_RATINGS.FIFTH_PERCENTILE; + TENTH_PERCENTILE = SWDMV4_RATINGS.TENTH_PERCENTILE; + TWENTIETH_PERCENTILE = SWDMV4_RATINGS.TWENTIETH_PERCENTILE; + THIRTIETH_PERCENTILE = SWDMV4_RATINGS.THIRTIETH_PERCENTILE; + FORTIETH_PERCENTILE = SWDMV4_RATINGS.FORTIETH_PERCENTILE; + FIFTIETH_PERCENTILE = SWDMV4_RATINGS.FIFTIETH_PERCENTILE; + } + + if (lessThanEqualTo(co2e, FIFTH_PERCENTILE)) { + return "A+"; + } else if (lessThanEqualTo(co2e, TENTH_PERCENTILE)) { + return "A"; + } else if (lessThanEqualTo(co2e, TWENTIETH_PERCENTILE)) { + return "B"; + } else if (lessThanEqualTo(co2e, THIRTIETH_PERCENTILE)) { + return "C"; + } else if (lessThanEqualTo(co2e, FORTIETH_PERCENTILE)) { + return "D"; + } else if (lessThanEqualTo(co2e, FIFTIETH_PERCENTILE)) { + return "E"; + } else { + return "F"; + } +} + +export { + formatNumber, + parseOptions, + getApiRequestHeaders, + lessThanEqualTo, + outputRating, +}; diff --git a/src/sustainable-web-design.js b/src/sustainable-web-design-v3.js similarity index 94% rename from src/sustainable-web-design.js rename to src/sustainable-web-design-v3.js index a7d5649..cd31093 100644 --- a/src/sustainable-web-design.js +++ b/src/sustainable-web-design-v3.js @@ -20,23 +20,14 @@ import { FIRST_TIME_VIEWING_PERCENTAGE, RETURNING_VISITOR_PERCENTAGE, PERCENTAGE_OF_DATA_LOADED_ON_SUBSEQUENT_LOAD, - SWDMv3Ratings, } from "./constants/index.js"; -import { formatNumber, lessThanEqualTo } from "./helpers/index.js"; - -const { - fifthPercentile, - tenthPercentile, - twentiethPercentile, - thirtiethPercentile, - fortiethPercentile, - fiftiethPercentile, -} = SWDMv3Ratings; +import { formatNumber, outputRating } from "./helpers/index.js"; class SustainableWebDesign { constructor(options) { this.allowRatings = true; this.options = options; + this.version = 3; } /** @@ -387,21 +378,7 @@ class SustainableWebDesign { * @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"; - } + return outputRating(co2e, this.version); } } diff --git a/src/sustainable-web-design.test.js b/src/sustainable-web-design-v3.test.js similarity index 75% rename from src/sustainable-web-design.test.js rename to src/sustainable-web-design-v3.test.js index 3ecc812..dadd3d1 100644 --- a/src/sustainable-web-design.test.js +++ b/src/sustainable-web-design-v3.test.js @@ -1,15 +1,15 @@ -import SustainableWebDesign from "./sustainable-web-design.js"; -import { MILLION, SWD } from "./constants/test-constants.js"; -import { SWDMv3Ratings } from "./constants/index.js"; +import SustainableWebDesign from "./sustainable-web-design-v3.js"; +import { MILLION, SWDV3 } from "./constants/test-constants.js"; +import { SWDMV3_RATINGS } from "./constants/index.js"; const { - fifthPercentile, - tenthPercentile, - twentiethPercentile, - thirtiethPercentile, - fortiethPercentile, - fiftiethPercentile, -} = SWDMv3Ratings; + FIFTH_PERCENTILE, + TENTH_PERCENTILE, + TWENTIETH_PERCENTILE, + THIRTIETH_PERCENTILE, + FORTIETH_PERCENTILE, + FIFTIETH_PERCENTILE, +} = SWDMV3_RATINGS; describe("sustainable web design model", () => { const swd = new SustainableWebDesign(); @@ -51,21 +51,30 @@ describe("sustainable web design model", () => { }); it("returns a result for grey energy", () => { - expect(swd.perByte(MILLION)).toBeCloseTo(SWD.MILLION_GREY, 3); + expect(swd.perByte(MILLION)).toBeCloseTo(SWDV3.MILLION_GREY, 3); }); it("returns a result for green energy", () => { - expect(swd.perByte(MILLION, true)).toBeCloseTo(SWD.MILLION_GREEN, 3); + expect(swd.perByte(MILLION, true)).toBeCloseTo(SWDV3.MILLION_GREEN, 3); }); it("can segment results", () => { const result = swd.perByte(MILLION, false, true); - expect(result.dataCenterCO2).toBeCloseTo(SWD.MILLION_GREY_DATACENTERS, 3); - expect(result.consumerDeviceCO2).toBeCloseTo(SWD.MILLION_GREY_DEVICES, 3); - expect(result.networkCO2).toBeCloseTo(SWD.MILLION_GREY_NETWORKS, 3); - expect(result.productionCO2).toBeCloseTo(SWD.MILLION_GREY_PRODUCTION, 3); - expect(result.total).toBeCloseTo(SWD.MILLION_GREY, 3); + expect(result.dataCenterCO2).toBeCloseTo( + SWDV3.MILLION_GREY_DATACENTERS, + 3 + ); + expect(result.consumerDeviceCO2).toBeCloseTo( + SWDV3.MILLION_GREY_DEVICES, + 3 + ); + expect(result.networkCO2).toBeCloseTo(SWDV3.MILLION_GREY_NETWORKS, 3); + expect(result.productionCO2).toBeCloseTo( + SWDV3.MILLION_GREY_PRODUCTION, + 3 + ); + expect(result.total).toBeCloseTo(SWDV3.MILLION_GREY, 3); }); }); @@ -135,12 +144,12 @@ describe("sustainable web design model", () => { }); 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(FIFTH_PERCENTILE)).toBe("A+"); + expect(swd.ratingScale(TENTH_PERCENTILE)).toBe("A"); + expect(swd.ratingScale(TWENTIETH_PERCENTILE)).toBe("B"); + expect(swd.ratingScale(THIRTIETH_PERCENTILE)).toBe("C"); + expect(swd.ratingScale(FORTIETH_PERCENTILE)).toBe("D"); + expect(swd.ratingScale(FIFTIETH_PERCENTILE)).toBe("E"); expect(swd.ratingScale(0.9)).toBe("F"); }); }); diff --git a/src/sustainable-web-design-v4.js b/src/sustainable-web-design-v4.js new file mode 100644 index 0000000..80ba577 --- /dev/null +++ b/src/sustainable-web-design-v4.js @@ -0,0 +1,347 @@ +"use strict"; + +/** + * Sustainable Web Design version 4 + * + * Updated calculations and figures from + * https://sustainablewebdesign.org/estimating-digital-emissions/ + * + */ + +import { fileSize, SWDV4 } from "./constants/index.js"; +import { outputRating } from "./helpers/index.js"; + +const { + OPERATIONAL_KWH_PER_GB_DATACENTER, + OPERATIONAL_KWH_PER_GB_NETWORK, + OPERATIONAL_KWH_PER_GB_DEVICE, + EMBODIED_KWH_PER_GB_DATACENTER, + EMBODIED_KWH_PER_GB_NETWORK, + EMBODIED_KWH_PER_GB_DEVICE, + GLOBAL_GRID_INTENSITY, +} = SWDV4; + +/** + * Output the CO2e emissions for each system segment + * @param {object} operationalEmissions + * @param {object} embodiedEmissions + * @returns {object} + */ +function outputSegments(operationalEmissions, embodiedEmissions) { + const totalOperationalCO2e = + operationalEmissions.dataCenter + + operationalEmissions.network + + operationalEmissions.device; + const totalEmbodiedCO2e = + embodiedEmissions.dataCenter + + embodiedEmissions.network + + embodiedEmissions.device; + + const dataCenterCO2e = + operationalEmissions.dataCenter + embodiedEmissions.dataCenter; + const networkCO2e = operationalEmissions.network + embodiedEmissions.network; + const consumerDeviceCO2e = + operationalEmissions.device + embodiedEmissions.device; + + return { + dataCenterOperationalCO2e: operationalEmissions.dataCenter, + networkOperationalCO2e: operationalEmissions.network, + consumerDeviceOperationalCO2e: operationalEmissions.device, + dataCenterEmbodiedCO2e: embodiedEmissions.dataCenter, + networkEmbodiedCO2e: embodiedEmissions.network, + consumerDeviceEmbodiedCO2e: embodiedEmissions.device, + totalEmbodiedCO2e, + totalOperationalCO2e, + dataCenterCO2e, + networkCO2e, + consumerDeviceCO2e, + }; +} + +class SustainableWebDesign { + constructor(options) { + this.allowRatings = true; + this.options = options; + this.version = 4; + } + + /** + * Calculate the operational energy of data transfer for each system segment + * + * @param {number} bytes + * @returns {object} + */ + operationalEnergyPerSegment(bytes) { + const transferedBytesToGb = bytes / fileSize.GIGABYTE; + const dataCenter = transferedBytesToGb * OPERATIONAL_KWH_PER_GB_DATACENTER; + const network = transferedBytesToGb * OPERATIONAL_KWH_PER_GB_NETWORK; + const device = transferedBytesToGb * OPERATIONAL_KWH_PER_GB_DEVICE; + + return { + dataCenter, + network, + device, + }; + } + + /** + * Calculate the operational emissions of data transfer for each system segment + * + * @param {number} bytes + * @param {object} options + * @returns {object} + */ + operationalEmissions(bytes, options = {}) { + const { dataCenter, network, device } = + this.operationalEnergyPerSegment(bytes); + + let dataCenterGridIntensity = GLOBAL_GRID_INTENSITY; + let networkGridIntensity = GLOBAL_GRID_INTENSITY; + let deviceGridIntensity = GLOBAL_GRID_INTENSITY; + + if (options?.gridIntensity) { + const { device, network, dataCenter } = options.gridIntensity; + + if (device?.value || device?.value === 0) { + deviceGridIntensity = device.value; + } + + if (network?.value || network?.value === 0) { + networkGridIntensity = network.value; + } + + if (dataCenter?.value || dataCenter?.value === 0) { + dataCenterGridIntensity = dataCenter.value; + } + } + + const dataCenterEmissions = dataCenter * dataCenterGridIntensity; + const networkEmissions = network * networkGridIntensity; + const deviceEmissions = device * deviceGridIntensity; + + return { + dataCenter: dataCenterEmissions, + network: networkEmissions, + device: deviceEmissions, + }; + } + + /** + * Calculate the embodied energy of data transfer for each system segment + * + * @param {number} bytes + * @returns {object} + */ + embodiedEnergyPerSegment(bytes) { + const transferedBytesToGb = bytes / fileSize.GIGABYTE; + const dataCenter = transferedBytesToGb * EMBODIED_KWH_PER_GB_DATACENTER; + const network = transferedBytesToGb * EMBODIED_KWH_PER_GB_NETWORK; + const device = transferedBytesToGb * EMBODIED_KWH_PER_GB_DEVICE; + + return { + dataCenter, + network, + device, + }; + } + + /** + * Calculate the embodied emissions of data transfer for each system segment + * + * @param {number} bytes + * @returns {object} + */ + embodiedEmissions(bytes) { + const { dataCenter, network, device } = + this.embodiedEnergyPerSegment(bytes); + + const dataCenterGridIntensity = GLOBAL_GRID_INTENSITY; + const networkGridIntensity = GLOBAL_GRID_INTENSITY; + const deviceGridIntensity = GLOBAL_GRID_INTENSITY; + + // NOTE: Per the guidance in the SWDM v4, the grid intensity values for embodied emissions are fixed to the global grid intensity. + + const dataCenterEmissions = dataCenter * dataCenterGridIntensity; + const networkEmissions = network * networkGridIntensity; + const deviceEmissions = device * deviceGridIntensity; + + return { + dataCenter: dataCenterEmissions, + network: networkEmissions, + device: deviceEmissions, + }; + } + + // NOTE: Setting green: true should result in a GREEN_HOSTING_FACTOR of 1.0 + perByte( + bytes, + green = false, + segmented = false, + ratingResults = false, + options = {} + ) { + if (bytes < 1) { + return 0; + } + + const operationalEmissions = this.operationalEmissions(bytes, options); + const embodiedEmissions = this.embodiedEmissions(bytes); + let GREEN_HOSTING_FACTOR = 0; + + if (green) { + GREEN_HOSTING_FACTOR = 1.0; + } else if ( + options?.greenHostingFactor || + options?.greenHostingFactor === 0 + ) { + GREEN_HOSTING_FACTOR = options.greenHostingFactor; + } + + const totalEmissions = { + dataCenter: + operationalEmissions.dataCenter * (1 - GREEN_HOSTING_FACTOR) + + embodiedEmissions.dataCenter, + network: operationalEmissions.network + embodiedEmissions.network, + device: operationalEmissions.device + embodiedEmissions.device, + }; + + const total = + totalEmissions.dataCenter + + totalEmissions.network + + totalEmissions.device; + + let rating = null; + if (ratingResults) { + rating = this.ratingScale(total); + } + + if (segmented) { + const segments = { + ...outputSegments(operationalEmissions, embodiedEmissions), + }; + + if (ratingResults) { + return { + ...segments, + total, + rating, + }; + } + return { ...segments, total }; + } + + if (ratingResults) { + return { total, rating }; + } + + return total; + } + + perVisit( + bytes, + green = false, + segmented = false, + ratingResults = false, + options = {} + ) { + let firstViewRatio = 1; + let returnViewRatio = 0; + let dataReloadRatio = 0; + let GREEN_HOSTING_FACTOR = 0; + const operationalEmissions = this.operationalEmissions(bytes, options); + const embodiedEmissions = this.embodiedEmissions(bytes); + + if (bytes < 1) { + return 0; + } + + if (green) { + GREEN_HOSTING_FACTOR = 1.0; + } else if ( + options?.greenHostingFactor || + options?.greenHostingFactor === 0 + ) { + GREEN_HOSTING_FACTOR = options.greenHostingFactor; + } + + if (options.firstVisitPercentage || options.firstVisitPercentage === 0) { + firstViewRatio = options.firstVisitPercentage; + } + + if (options.returnVisitPercentage || options.returnVisitPercentage === 0) { + returnViewRatio = options.returnVisitPercentage; + } + + if (options.dataReloadRatio || options.dataReloadRatio === 0) { + dataReloadRatio = options.dataReloadRatio; + } + + // NOTE: First visit emissions are calculated as the sum of all three segments without any caching. + + const firstVisitEmissions = + operationalEmissions.dataCenter * (1 - GREEN_HOSTING_FACTOR) + + embodiedEmissions.dataCenter + + operationalEmissions.network + + embodiedEmissions.network + + operationalEmissions.device + + embodiedEmissions.device; + + // NOTE: First visit emissions are calculated as the sum of all three segments with caching applied. + + const returnVisitEmissions = + (operationalEmissions.dataCenter * (1 - GREEN_HOSTING_FACTOR) + + embodiedEmissions.dataCenter + + operationalEmissions.network + + embodiedEmissions.network + + operationalEmissions.device + + embodiedEmissions.device) * + (1 - dataReloadRatio); + + // NOTE: The total emissions account for the percentage of first and return visits. + const total = + firstVisitEmissions * firstViewRatio + + returnVisitEmissions * returnViewRatio; + + let rating = null; + if (ratingResults) { + rating = this.ratingScale(total); + } + + if (segmented) { + const segments = { + ...outputSegments(operationalEmissions, embodiedEmissions), + firstVisitCO2e: firstVisitEmissions, + returnVisitCO2e: returnVisitEmissions, + }; + + if (ratingResults) { + return { + ...segments, + total, + rating, + }; + } + + return { ...segments, total }; + } + + if (ratingResults) { + return { total, rating }; + } + + return total; + } + + /** + * 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) { + return outputRating(co2e, this.version); + } +} + +export { SustainableWebDesign }; +export default SustainableWebDesign; diff --git a/src/sustainable-web-design-v4.test.js b/src/sustainable-web-design-v4.test.js new file mode 100644 index 0000000..03fea28 --- /dev/null +++ b/src/sustainable-web-design-v4.test.js @@ -0,0 +1,390 @@ +import SustainableWebDesign from "./sustainable-web-design-v4.js"; +import { MILLION, SWDV4 } from "./constants/test-constants.js"; +import { GIGABYTE } from "./constants/file-size.js"; +import { SWDMV4_RATINGS } from "./constants/index.js"; + +const { + FIFTH_PERCENTILE, + TENTH_PERCENTILE, + TWENTIETH_PERCENTILE, + THIRTIETH_PERCENTILE, + FORTIETH_PERCENTILE, + FIFTIETH_PERCENTILE, +} = SWDMV4_RATINGS; + +describe("sustainable web design model version 4", () => { + const swd = new SustainableWebDesign(); + + describe("operational emissions", () => { + it("returns the expected emissions for 1GB data transfer", () => { + const result = swd.operationalEmissions(GIGABYTE); + expect(result).toEqual( + expect.objectContaining({ + dataCenter: expect.any(Number), + network: expect.any(Number), + device: expect.any(Number), + }) + ); + expect(result.dataCenter).toBeCloseTo(27.17, 3); + expect(result.network).toBeCloseTo(29.146, 3); + expect(result.device).toBeCloseTo(39.52, 3); + }); + + it("returns the expected emissions for 0 bytes data transfer", () => { + const result = swd.operationalEmissions(0); + expect(result).toEqual( + expect.objectContaining({ + dataCenter: expect.any(Number), + network: expect.any(Number), + device: expect.any(Number), + }) + ); + expect(result.dataCenter).toBeCloseTo(0, 3); + expect(result.network).toBeCloseTo(0, 3); + expect(result.device).toBeCloseTo(0, 3); + }); + + it("returns the expected emissions for 1GB data transfer with custom grid intensities", () => { + const result = swd.operationalEmissions(GIGABYTE, { + gridIntensity: { + dataCenter: { value: 100 }, + network: { value: 200 }, + device: { value: 300 }, + }, + }); + + expect(result).toEqual( + expect.objectContaining({ + dataCenter: expect.any(Number), + network: expect.any(Number), + device: expect.any(Number), + }) + ); + expect(result.dataCenter).toBeCloseTo(5.5, 3); + expect(result.network).toBeCloseTo(11.8, 3); + expect(result.device).toBeCloseTo(24, 3); + }); + }); + + describe("embodied emissions", () => { + it("returns the expected emissions for 1GB data transfer", () => { + const result = swd.embodiedEmissions(GIGABYTE); + expect(result).toEqual( + expect.objectContaining({ + dataCenter: expect.any(Number), + network: expect.any(Number), + device: expect.any(Number), + }) + ); + expect(result.dataCenter).toBeCloseTo(5.928, 3); + expect(result.network).toBeCloseTo(6.422, 3); + expect(result.device).toBeCloseTo(40.014, 3); + }); + + it("returns the expected emissions for 0 bytes data transfer", () => { + const result = swd.embodiedEmissions(0); + expect(result).toEqual( + expect.objectContaining({ + dataCenter: expect.any(Number), + network: expect.any(Number), + device: expect.any(Number), + }) + ); + expect(result.dataCenter).toBeCloseTo(0, 3); + expect(result.network).toBeCloseTo(0, 3); + expect(result.device).toBeCloseTo(0, 3); + }); + }); + + describe("emissions per byte", () => { + it("returns the expected emissions for 1GB data transfer with no green energy factor", () => { + const result = swd.perByte(GIGABYTE); + expect(result).toBeCloseTo(SWDV4.PERBYTE_EMISSIONS_GB, 3); + }); + + it("returns the expected emissions for 1GB data transfer with green energy factor", () => { + const result = swd.perByte(GIGABYTE, true); + expect(result).toBeCloseTo(SWDV4.PERBYTE_EMISSIONS_GB_GREEN, 3); + }); + + it("returns the expected emissions for 1GB data transfer with green energy factor of 0.5", () => { + const result = swd.perByte(GIGABYTE, false, false, false, { + greenHostingFactor: 0.5, + }); + + expect(result).toBeCloseTo(SWDV4.PERBYTE_EMISSIONS_GB_GREEN_PARTIAL, 3); + }); + + it("returns the expected emissions for 0 bytes data transfer", () => { + const result = swd.perByte(0); + expect(result).toBe(0); + }); + + it("returns the expected emissions results for each segment for 1GB data transfer", () => { + const result = swd.perByte(GIGABYTE, false, true); + expect(result).toEqual( + expect.objectContaining({ + dataCenterOperationalCO2e: expect.any(Number), + networkOperationalCO2e: expect.any(Number), + consumerDeviceOperationalCO2e: expect.any(Number), + totalOperationalCO2e: expect.any(Number), + dataCenterEmbodiedCO2e: expect.any(Number), + networkEmbodiedCO2e: expect.any(Number), + consumerDeviceEmbodiedCO2e: expect.any(Number), + totalEmbodiedCO2e: expect.any(Number), + dataCenterCO2e: expect.any(Number), + networkCO2e: expect.any(Number), + consumerDeviceCO2e: expect.any(Number), + total: expect.any(Number), + }) + ); + + expect(result.dataCenterOperationalCO2e).toBeCloseTo( + SWDV4.DC_OPERATIONAL_EMISSIONS_GB, + 3 + ); + expect(result.networkOperationalCO2e).toBeCloseTo( + SWDV4.NETWORK_OPERATIONAL_EMISSIONS_GB, + 3 + ); + expect(result.consumerDeviceOperationalCO2e).toBeCloseTo( + SWDV4.DEVICE_OPERATIONAL_EMISSIONS_GB, + 3 + ); + expect(result.totalOperationalCO2e).toBeCloseTo( + SWDV4.TOTAL_OPERATIONAL_EMISSIONS_GB, + 3 + ); + expect(result.dataCenterEmbodiedCO2e).toBeCloseTo( + SWDV4.DC_EMBODIED_EMISSIONS_GB, + 3 + ); + expect(result.networkEmbodiedCO2e).toBeCloseTo( + SWDV4.NETWORK_EMBODIED_EMISSIONS_GB, + 3 + ); + expect(result.consumerDeviceEmbodiedCO2e).toBeCloseTo( + SWDV4.DEVICE_EMBODIED_EMISSIONS_GB, + 3 + ); + expect(result.totalEmbodiedCO2e).toBeCloseTo( + SWDV4.TOTAL_EMBODIED_EMISSIONS_GB, + 3 + ); + expect(result.dataCenterCO2e).toBeCloseTo(SWDV4.TOTAL_DC_EMISSIONS_GB, 3); + expect(result.networkCO2e).toBeCloseTo( + SWDV4.TOTAL_NETWORK_EMISSIONS_GB, + 3 + ); + expect(result.consumerDeviceCO2e).toBeCloseTo( + SWDV4.TOTAL_DEVICE_EMISSIONS_GB, + 3 + ); + expect(result.total).toBeCloseTo(SWDV4.PERBYTE_EMISSIONS_GB, 3); + }); + + it("returns a rating without segments", () => { + const result = swd.perByte(GIGABYTE, false, false, true); + + expect(result).toEqual( + expect.objectContaining({ + rating: expect.any(String), + total: expect.any(Number), + }) + ); + + expect(result.rating).toBe("F"); + }); + + it("returns a rating with segments", () => { + const result = swd.perByte(GIGABYTE, false, true, true); + + expect(result).toEqual( + expect.objectContaining({ + rating: expect.any(String), + dataCenterOperationalCO2e: expect.any(Number), + networkOperationalCO2e: expect.any(Number), + consumerDeviceOperationalCO2e: expect.any(Number), + totalOperationalCO2e: expect.any(Number), + dataCenterEmbodiedCO2e: expect.any(Number), + networkEmbodiedCO2e: expect.any(Number), + consumerDeviceEmbodiedCO2e: expect.any(Number), + totalEmbodiedCO2e: expect.any(Number), + dataCenterCO2e: expect.any(Number), + networkCO2e: expect.any(Number), + consumerDeviceCO2e: expect.any(Number), + total: expect.any(Number), + }) + ); + + expect(result.rating).toBe("F"); + }); + }); + + describe("emissions per visit", () => { + it("returns the expected emissions for 1GB data transfer with no green energy factor, 75% new visitors, 20% data reload ratio", () => { + const result = swd.perVisit(GIGABYTE, false, false, false, { + firstVisitPercentage: 0.75, + returnVisitPercentage: 0.25, + dataReloadRatio: 0.2, + }); + expect(result).toBeCloseTo(SWDV4.PERVISIT_EMISSIONS_GB, 3); + }); + + it("returns the expected emissions for 1GB data transfer with green energy factor", () => { + const result = swd.perVisit(GIGABYTE, true, false, false, { + firstVisitPercentage: 0.75, + returnVisitPercentage: 0.25, + dataReloadRatio: 0.2, + }); + expect(result).toBeCloseTo(SWDV4.PERVISIT_EMISSIONS_GB_GREEN, 3); + }); + + it("returns the expected emissions for 1GB data transfer with green energy factor of 0.5", () => { + const result = swd.perVisit(GIGABYTE, false, false, false, { + greenHostingFactor: 0.5, + firstVisitPercentage: 0.75, + returnVisitPercentage: 0.25, + dataReloadRatio: 0.2, + }); + + expect(result).toBeCloseTo(SWDV4.PERVISIT_EMISSIONS_GB_GREEN_PARTIAL, 3); + }); + + it("returns the expected emissions for 0 bytes data transfer", () => { + const result = swd.perVisit(0); + expect(result).toBe(0); + }); + + it("returns the expected emissions results for each segment for 1GB data transfer", () => { + const result = swd.perVisit(GIGABYTE, false, true, false, { + firstVisitPercentage: 0.75, + returnVisitPercentage: 0.25, + dataReloadRatio: 0.2, + }); + expect(result).toEqual( + expect.objectContaining({ + dataCenterOperationalCO2e: expect.any(Number), + networkOperationalCO2e: expect.any(Number), + consumerDeviceOperationalCO2e: expect.any(Number), + totalOperationalCO2e: expect.any(Number), + dataCenterEmbodiedCO2e: expect.any(Number), + networkEmbodiedCO2e: expect.any(Number), + consumerDeviceEmbodiedCO2e: expect.any(Number), + totalEmbodiedCO2e: expect.any(Number), + dataCenterCO2e: expect.any(Number), + networkCO2e: expect.any(Number), + consumerDeviceCO2e: expect.any(Number), + firstVisitCO2e: expect.any(Number), + returnVisitCO2e: expect.any(Number), + total: expect.any(Number), + }) + ); + + expect(result.dataCenterOperationalCO2e).toBeCloseTo( + SWDV4.DC_OPERATIONAL_EMISSIONS_GB, + 3 + ); + expect(result.networkOperationalCO2e).toBeCloseTo( + SWDV4.NETWORK_OPERATIONAL_EMISSIONS_GB, + 3 + ); + expect(result.consumerDeviceOperationalCO2e).toBeCloseTo( + SWDV4.DEVICE_OPERATIONAL_EMISSIONS_GB, + 3 + ); + expect(result.totalOperationalCO2e).toBeCloseTo( + SWDV4.TOTAL_OPERATIONAL_EMISSIONS_GB, + 3 + ); + expect(result.dataCenterEmbodiedCO2e).toBeCloseTo( + SWDV4.DC_EMBODIED_EMISSIONS_GB, + 3 + ); + expect(result.networkEmbodiedCO2e).toBeCloseTo( + SWDV4.NETWORK_EMBODIED_EMISSIONS_GB, + 3 + ); + expect(result.consumerDeviceEmbodiedCO2e).toBeCloseTo( + SWDV4.DEVICE_EMBODIED_EMISSIONS_GB, + 3 + ); + expect(result.totalEmbodiedCO2e).toBeCloseTo( + SWDV4.TOTAL_EMBODIED_EMISSIONS_GB, + 3 + ); + expect(result.dataCenterCO2e).toBeCloseTo(SWDV4.TOTAL_DC_EMISSIONS_GB, 3); + expect(result.networkCO2e).toBeCloseTo( + SWDV4.TOTAL_NETWORK_EMISSIONS_GB, + 3 + ); + expect(result.consumerDeviceCO2e).toBeCloseTo( + SWDV4.TOTAL_DEVICE_EMISSIONS_GB, + 3 + ); + expect(result.firstVisitCO2e).toBeCloseTo(148.2, 3); + expect(result.returnVisitCO2e).toBeCloseTo(118.56, 3); + expect(result.total).toBeCloseTo(SWDV4.PERVISIT_EMISSIONS_GB, 3); + }); + + it("returns a rating without segments", () => { + const result = swd.perVisit(GIGABYTE, false, false, true); + + expect(result).toEqual( + expect.objectContaining({ + rating: expect.any(String), + total: expect.any(Number), + }) + ); + + expect(result.rating).toBe("F"); + }); + + it("returns a rating with segments", () => { + const result = swd.perVisit(GIGABYTE, false, true, true); + + expect(result).toEqual( + expect.objectContaining({ + rating: expect.any(String), + dataCenterOperationalCO2e: expect.any(Number), + networkOperationalCO2e: expect.any(Number), + consumerDeviceOperationalCO2e: expect.any(Number), + totalOperationalCO2e: expect.any(Number), + dataCenterEmbodiedCO2e: expect.any(Number), + networkEmbodiedCO2e: expect.any(Number), + consumerDeviceEmbodiedCO2e: expect.any(Number), + totalEmbodiedCO2e: expect.any(Number), + dataCenterCO2e: expect.any(Number), + networkCO2e: expect.any(Number), + consumerDeviceCO2e: expect.any(Number), + firstVisitCO2e: expect.any(Number), + returnVisitCO2e: expect.any(Number), + total: expect.any(Number), + }) + ); + + expect(result.rating).toBe("F"); + }); + }); + + describe("SWD Rating Scale", () => { + it("should return a string", () => { + expect(typeof swd.ratingScale(GIGABYTE)).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(FIFTH_PERCENTILE)).toBe("A+"); + expect(swd.ratingScale(TENTH_PERCENTILE)).toBe("A"); + expect(swd.ratingScale(TWENTIETH_PERCENTILE)).toBe("B"); + expect(swd.ratingScale(THIRTIETH_PERCENTILE)).toBe("C"); + expect(swd.ratingScale(FORTIETH_PERCENTILE)).toBe("D"); + expect(swd.ratingScale(FIFTIETH_PERCENTILE)).toBe("E"); + expect(swd.ratingScale(0.9)).toBe("F"); + }); + }); +});