Skip to content

Commit

Permalink
satellite/{console, emission, web}: calculate saved trees based on sa…
Browse files Browse the repository at this point in the history
…ved emission impact

Added calculation of saved trees based on saved emission impact.
Replaced Billing card with Savings card on prject dashboard.

Issue:
#6694

Change-Id: If55cb099881c3409ef55b93032058c9ea7c8514d
  • Loading branch information
VitaliiShpital committed Feb 16, 2024
1 parent 7d3c13c commit e85c47d
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 32 deletions.
10 changes: 1 addition & 9 deletions satellite/console/consoleweb/consoleapi/projects.go
Expand Up @@ -494,15 +494,7 @@ func (p *Projects) GetEmissionImpact(w http.ResponseWriter, r *http.Request) {
return
}

var response struct {
StorjImpact float64 `json:"storjImpact"`
HyperscalerImpact float64 `json:"hyperscalerImpact"`
}

response.StorjImpact = impact.EstimatedKgCO2eStorj
response.HyperscalerImpact = impact.EstimatedKgCO2eHyperscaler

err = json.NewEncoder(w).Encode(response)
err = json.NewEncoder(w).Encode(impact)
if err != nil {
p.serveJSONError(ctx, w, http.StatusInternalServerError, err)
}
Expand Down
25 changes: 22 additions & 3 deletions satellite/console/service.go
Expand Up @@ -1728,8 +1728,16 @@ func (s *Service) GetSalt(ctx context.Context, projectID uuid.UUID) (salt []byte
return s.store.Projects().GetSalt(ctx, isMember.project.ID)
}

// EmissionImpactResponse represents emission impact response to be returned to client.
type EmissionImpactResponse struct {
StorjImpact float64 `json:"storjImpact"`
HyperscalerImpact float64 `json:"hyperscalerImpact"`
SavedTrees int64 `json:"savedTrees"`
}

// GetEmissionImpact is a method for querying project emission impact by id.
func (s *Service) GetEmissionImpact(ctx context.Context, projectID uuid.UUID) (impact *emission.Impact, err error) {
func (s *Service) GetEmissionImpact(ctx context.Context, projectID uuid.UUID) (*EmissionImpactResponse, error) {
var err error
defer mon.Task()(&ctx)(&err)
user, err := s.getUserAndAuditLog(ctx, "get project emission impact", zap.String("projectID", projectID.String()))
if err != nil {
Expand All @@ -1750,7 +1758,7 @@ func (s *Service) GetEmissionImpact(ctx context.Context, projectID uuid.UUID) (i
period := now.Sub(isMember.project.CreatedAt)
dataInTB := memory.Size(storageUsed).TB()

impact, err = s.emission.CalculateImpact(&emission.CalculationInput{
impact, err := s.emission.CalculateImpact(&emission.CalculationInput{
AmountOfDataInTB: dataInTB,
Duration: period,
IsTBDuration: false,
Expand All @@ -1759,7 +1767,18 @@ func (s *Service) GetEmissionImpact(ctx context.Context, projectID uuid.UUID) (i
return nil, Error.Wrap(err)
}

return impact, nil
savedValue := impact.EstimatedKgCO2eHyperscaler - impact.EstimatedKgCO2eStorj
if savedValue < 0 {
savedValue = 0
}

savedTrees := s.emission.CalculateSavedTrees(savedValue)

return &EmissionImpactResponse{
StorjImpact: impact.EstimatedKgCO2eStorj,
HyperscalerImpact: impact.EstimatedKgCO2eHyperscaler,
SavedTrees: savedTrees,
}, nil
}

// GetUsersProjects is a method for querying all projects.
Expand Down
13 changes: 5 additions & 8 deletions satellite/console/service_test.go
Expand Up @@ -37,7 +37,6 @@ import (
"storj.io/storj/satellite/buckets"
"storj.io/storj/satellite/console"
"storj.io/storj/satellite/console/consoleweb/consoleapi"
"storj.io/storj/satellite/emission"
"storj.io/storj/satellite/nodeselection"
"storj.io/storj/satellite/payments"
"storj.io/storj/satellite/payments/billing"
Expand Down Expand Up @@ -863,7 +862,7 @@ func TestService(t *testing.T) {
impact, err := service.GetEmissionImpact(userCtx1, pr.ID)
require.NoError(t, err)
require.NotNil(t, impact)
require.EqualValues(t, emission.Impact{}, *impact)
require.EqualValues(t, console.EmissionImpactResponse{}, *impact)

// Getting project salt as a non-member should not work
impact, err = service.GetEmissionImpact(userCtx2, pr.ID)
Expand All @@ -875,19 +874,17 @@ func TestService(t *testing.T) {

now := time.Now().UTC()
service.TestSetNow(func() time.Time {
return now.Add(24 * time.Hour)
return now.Add(365.25 * 24 * time.Hour)
})

zeroValue := float64(0)

impact, err = service.GetEmissionImpact(userCtx1, pr.ID)
require.NoError(t, err)
require.NotNil(t, impact)
require.Greater(t, impact.EstimatedKgCO2eStorj, zeroValue)
require.Greater(t, impact.EstimatedKgCO2eHyperscaler, zeroValue)
require.Greater(t, impact.EstimatedKgCO2eCorporateDC, zeroValue)
require.Greater(t, impact.EstimatedFractionSavingsAgainstCorporateDC, zeroValue)
require.Greater(t, impact.EstimatedFractionSavingsAgainstHyperscaler, zeroValue)
require.Greater(t, impact.StorjImpact, zeroValue)
require.Greater(t, impact.HyperscalerImpact, zeroValue)
require.Greater(t, impact.SavedTrees, int64(0))
})
})
}
Expand Down
1 change: 1 addition & 0 deletions satellite/emission/config.go
Expand Up @@ -29,4 +29,5 @@ type Config struct {
HyperscalerUtilizationFraction float64 `help:"utilization fraction of hyperscaler networks, in fraction" default:"0.75"`
CorporateDCUtilizationFraction float64 `help:"utilization fraction of corporate data center networks, in fraction" default:"0.40"`
StorjUtilizationFraction float64 `help:"utilization fraction of storj network, in fraction" default:"0.85"`
AverageCO2SequesteredByTree float64 `help:"weighted average CO2 sequestered by a medium growth coniferous or deciduous tree, in kgCO2e/tree" default:"60"`
}
6 changes: 6 additions & 0 deletions satellite/emission/service.go
Expand Up @@ -4,6 +4,7 @@
package emission

import (
"math"
"time"

"github.com/zeebo/errs"
Expand Down Expand Up @@ -164,6 +165,11 @@ func (sv *Service) CalculateImpact(input *CalculationInput) (*Impact, error) {
return rv, nil
}

// CalculateSavedTrees calculates saved trees count based on emission impact.
func (sv *Service) CalculateSavedTrees(impact float64) int64 {
return int64(math.Round(impact / sv.config.AverageCO2SequesteredByTree))
}

func (sv *Service) prepareExpansionFactorRow() *Row {
storjExpansionFactor := unitless.Value(sv.config.StorjExpansionFactor)

Expand Down
3 changes: 3 additions & 0 deletions satellite/satellite-config.yaml.lock
Expand Up @@ -553,6 +553,9 @@ contact.external-address: ""
# amount of time before sending second reminder to users who need to verify their email
# email-reminders.second-verification-reminder: 120h0m0s

# weighted average CO2 sequestered by a medium growth coniferous or deciduous tree, in kgCO2e/tree
# emission.average-co2sequestered-by-tree: 60

# carbon from power per year of operations, in kg/TB-year
# emission.carbon-from-drive-powering: 15.9

Expand Down
2 changes: 1 addition & 1 deletion web/satellite/src/api/projects.ts
Expand Up @@ -277,7 +277,7 @@ export class ProjectsHttpApi implements ProjectsApi {
}

const json = await response.json();
return json ? new Emission(json.storjImpact, json.hyperscalerImpact) : new Emission();
return json ? new Emission(json.storjImpact, json.hyperscalerImpact, json.savedTrees) : new Emission();
}

/**
Expand Down
1 change: 1 addition & 0 deletions web/satellite/src/types/projects.ts
Expand Up @@ -268,6 +268,7 @@ export class Emission {
public constructor(
public storjImpact: number = 0,
public hyperscalerImpact: number = 0,
public savedTrees: number = 0,
) {}
}

Expand Down
62 changes: 51 additions & 11 deletions web/satellite/src/views/Dashboard.vue
Expand Up @@ -50,17 +50,47 @@
<v-col cols="6" md="4" lg="2">
<CardStatsComponent icon="team" title="Team" subtitle="Project members" :data="teamSize.toLocaleString()" :to="ROUTES.Team.path" />
</v-col>
<v-col v-if="emissionImpactViewEnabled" cols="12" sm="6" md="4" lg="2">
<v-tooltip
activator="parent"
location="bottom"
offset="-20"
>
Estimated if using traditional cloud storage = {{ emission.hyperscalerImpact.toLocaleString(undefined, { maximumFractionDigits: 3 }) }} kg CO2e
</v-tooltip>
<CardStatsComponent icon="globe" title="CO2 Saved" subtitle="By using Storj" :data="`${emission.storjImpact.toLocaleString(undefined, { maximumFractionDigits: 3 })} kg CO2e`" />
</v-col>
<v-col v-if="billingEnabled" cols="6" md="4" lg="2">
<template v-if="emissionImpactViewEnabled">
<v-col cols="12" sm="6" md="4" lg="2">
<v-tooltip
activator="parent"
location="bottom"
offset="-20"
>
{{ emission.storjImpact.toLocaleString(undefined, { maximumFractionDigits: 3 }) }} kg CO2e estimated if using Storj
<br>
{{ emission.hyperscalerImpact.toLocaleString(undefined, { maximumFractionDigits: 3 }) }} kg CO2e estimated if using traditional cloud storage
<br>
<a
href="https://www.storj.io/documents/storj-sustainability-whitepaper.pdf"
target="_blank"
rel="noopener noreferrer"
>
More info about our methodology
</a>
</v-tooltip>
<CardStatsComponent icon="globe" title="CO2 Saved" subtitle="By using Storj" :data="co2Savings" />
</v-col>
<v-col cols="12" sm="6" md="4" lg="2">
<v-tooltip
activator="parent"
location="bottom"
offset="-20"
>
Number of urban tree seedlings grown for 10 years.
<br>
<a
href="https://www.epa.gov/energy/greenhouse-gases-equivalencies-calculator-calculations-and-references#seedlings"
target="_blank"
rel="noopener noreferrer"
>
Learn more about Greenhouse Gases Equivalencies
</a>
</v-tooltip>
<CardStatsComponent icon="globe" title="Savings" subtitle="Equivalent" :data="`${emission.savedTrees.toLocaleString()} trees`" />
</v-col>
</template>
<v-col v-if="billingEnabled && !emissionImpactViewEnabled" cols="6" md="4" lg="2">
<CardStatsComponent icon="card" title="Billing" :subtitle="`${paidTierString} account`" :data="paidTierString" :to="ROUTES.Account.with(ROUTES.Billing).path" />
</v-col>
</v-row>
Expand Down Expand Up @@ -321,6 +351,16 @@ const isCreateBucketDialogOpen = ref<boolean>(false);
const isDatePicker = ref<boolean>(false);
const datePickerModel = ref<Date[]>([]);
/**
* Returns calculated and formatted CO2 savings info.
*/
const co2Savings = computed<string>(() => {
let saved = emission.value.hyperscalerImpact - emission.value.storjImpact;
if (saved < 0) saved = 0;
return `${saved.toLocaleString(undefined, { maximumFractionDigits: 3 })} kg CO2e`;
});
/**
* Returns formatted date range string.
*/
Expand Down

0 comments on commit e85c47d

Please sign in to comment.