From 4d468acde2c04e6675978f90659539692b7e4eac Mon Sep 17 00:00:00 2001 From: Benjamin Sodenkamp <54522068+bensodenkamp@users.noreply.github.com> Date: Sat, 21 Aug 2021 18:11:04 -0600 Subject: [PATCH] Bsodenkamp allocation identities refactor (#3115) * converted recent policy to credGrainView * converted recent policy to credGrainView * Converted special policy to credGrainView * Removed AllocationIdentities and ProcessIdentities * Removed debug output * Removed debug output * Removed stray comment * Removed extra participants * updated and debuged credGrainView validation * typo * deduplicated validation loops * 'map' to 'foreach' when not building datastructure * deduplicated nested validation loop --- packages/sourcecred/src/core/credGrainView.js | 53 +++++ .../sourcecred/src/core/credGrainView.test.js | 197 +++++++++++++++ .../src/core/ledger/computeDistribution.js | 41 +--- .../core/ledger/computeDistribution.test.js | 155 ------------ packages/sourcecred/src/core/ledger/grain.js | 1 + .../src/core/ledger/grainAllocation.js | 33 +-- .../src/core/ledger/grainAllocation.test.js | 225 +++++++----------- .../src/core/ledger/policies/balanced.js | 1 - .../src/core/ledger/policies/balanced.test.js | 17 +- .../src/core/ledger/policies/recent.js | 37 ++- .../src/core/ledger/policies/special.js | 5 +- .../src/core/ledger/processedIdentities.js | 67 ------ .../src/ui/components/SpecialDistribution.js | 4 +- 13 files changed, 378 insertions(+), 458 deletions(-) delete mode 100644 packages/sourcecred/src/core/ledger/computeDistribution.test.js delete mode 100644 packages/sourcecred/src/core/ledger/processedIdentities.js diff --git a/packages/sourcecred/src/core/credGrainView.js b/packages/sourcecred/src/core/credGrainView.js index a113d2653..26c08f02c 100644 --- a/packages/sourcecred/src/core/credGrainView.js +++ b/packages/sourcecred/src/core/credGrainView.js @@ -7,6 +7,7 @@ * It is useful for cases where you want to view a participant's Cred and Grain * data simultaneously, for example for creating summary dashboards. */ +import {sum} from "d3-array"; import deepFreeze from "deep-freeze"; import { CredGraph, @@ -129,6 +130,58 @@ export class CredGrainView { }); } + validateForGrainAllocation() { + if (this.activeParticipants().length === 0) { + throw new Error(`must have at least one identity to allocate grain to`); + } + + this.activeParticipants().forEach((p) => { + p.credPerInterval.forEach((c) => { + if (typeof c !== "number") { + throw new Error(`Non numeric cred value found`); + } + if (c < 0) { + throw new Error(`negative cred in interval data`); + } + }); + + if (typeof p.cred !== "number") { + throw new Error(`Non numeric cred value found`); + } + + if (p.credPerInterval.length !== this.intervals().length) { + throw new Error(`participant cred per interval length mismatch`); + } + + if (p.grainEarnedPerInterval.length !== this.intervals().length) { + throw new Error(`participant grain per interval length mismatch`); + } + + if (sum(p.credPerInterval) !== p.cred) { + throw new Error( + `participant cred per interval mismatched with participant cred total` + ); + } + + if (!G.eq(G.sum(p.grainEarnedPerInterval), p.grainEarned)) { + throw new Error( + `participant grain per interval mismatched with participant grain total:` + ); + } + + p.grainEarnedPerInterval.forEach((g) => { + if (g < G.ZERO) { + throw new Error(`negative grain paid in interval data`); + } + }); + }); + + if (sum(this.totalCredPerInterval()) < 1) + throw new Error( + "cred is zero. Make sure your plugins are configured correctly and remember to run 'yarn go' to calculate the cred scores." + ); + } + withTimeScope( startTimeMs: TimestampMs, endTimeMs: TimestampMs diff --git a/packages/sourcecred/src/core/credGrainView.test.js b/packages/sourcecred/src/core/credGrainView.test.js index 5008fb190..4ab682581 100644 --- a/packages/sourcecred/src/core/credGrainView.test.js +++ b/packages/sourcecred/src/core/credGrainView.test.js @@ -499,4 +499,201 @@ describe("core/credGrainView", () => { ).toThrow("The graph is missing account"); }); }); + + describe("validation tests", () => { + const credGrainViewNoCred = CredGrainView.fromJSON( + JSON.parse( + '{"participants":[{"active":true,"identity":{"id":"YVZhbGlkVXVpZEF0TGFzdA", "subtype":"USER",\ + "address":"N\\u0000sourcecred\\u0000core\\u0000IDENTITY\\u0000YVZhbGlkVXVpZEF0TGFzdA\\u0000",\ + "name":"steven","aliases":[]},"cred":0,"credPerInterval":[0,0],"grainEarned":"23",\ + "grainEarnedPerInterval":["13","10"]},{"active":true,"identity":{"id":"URgLrCxgvjHxtGJ9PgmckQ",\ + "subtype":"ORGANIZATION","address":"N\\u0000sourcecred\\u0000core\\u0000IDENTITY\\u0000URgLrCxgvjHxtGJ9PgmckQ\\u0000",\ + "name":"crystal-gems","aliases":[]},"cred":0,"credPerInterval":[0,0],"grainEarned":"27",\ + "grainEarnedPerInterval":["17","10"]}],"intervals":[{"startTimeMs":0,"endTimeMs":2},{"startTimeMs":2,"endTimeMs":4}]}' + ) + ); + + it("no cred", () => { + expect(() => credGrainViewNoCred.validateForGrainAllocation()).toThrow( + "cred is zero. Make sure your plugins are configured correctly and remember to run 'yarn go' to calculate the cred scores." + ); + }); + + const credGrainViewCredTotalMismatchedIntervals = CredGrainView.fromJSON( + JSON.parse( + '{"participants":[{"active":true,"identity":{"id":"YVZhbGlkVXVpZEF0TGFzdA","subtype":"USER",\ + "address":"N\\u0000sourcecred\\u0000core\\u0000IDENTITY\\u0000YVZhbGlkVXVpZEF0TGFzdA\\u0000",\ + "name":"steven","aliases":[]},"cred":2.999999337965189,"credPerInterval":[0.9479471486739683,\ + 2.0520521567464285],"grainEarned":"23","grainEarnedPerInterval":["13","10"]},{"active":true,\ + "identity":{"id":"URgLrCxgvjHxtGJ9PgmckQ","subtype":"ORGANIZATION",\ + "address":"N\\u0000sourcecred\\u0000core\\u0000IDENTITY\\u0000URgLrCxgvjHxtGJ9PgmckQ\\u0000",\ + "name":"crystal-gems","aliases":[]},"cred":1.286615549244117e-19,"credPerInterval":\ + [5.146462196976468e-20,7.719693295464702e-20],"grainEarned":"27","grainEarnedPerInterval":["17","10"]}],\ + "intervals":[{"startTimeMs":0,"endTimeMs":2},{"startTimeMs":2,"endTimeMs":4}]}' + ) + ); + + it("cred total mismatch", () => { + expect(() => + credGrainViewCredTotalMismatchedIntervals.validateForGrainAllocation() + ).toThrow( + "participant cred per interval mismatched with participant cred total" + ); + }); + + const credGrainViewNonNumericCred = CredGrainView.fromJSON( + JSON.parse( + '{"participants":[{"active":true,"identity":{"id":"YVZhbGlkVXVpZEF0TGFzdA","subtype":"USER",\ + "address":"N\\u0000sourcecred\\u0000core\\u0000IDENTITY\\u0000YVZhbGlkVXVpZEF0TGFzdA\\u0000",\ + "name":"steven","aliases":[]},"cred":2.999999337965189,"credPerInterval":[0.9479471812187605,\ + 2.0520521567464285],"grainEarned":"23","grainEarnedPerInterval":["13","10"]},{"active":true,\ + "identity":{"id":"URgLrCxgvjHxtGJ9PgmckQ","subtype":"ORGANIZATION","address":\ + "N\\u0000sourcecred\\u0000core\\u0000IDENTITY\\u0000URgLrCxgvjHxtGJ9PgmckQ\\u0000","name":"crystal-gems",\ + "aliases":[]},"cred":"foo","credPerInterval":[5.146462196976468e-20,7.719693295464702e-20],\ + "grainEarned":"27","grainEarnedPerInterval":["17","10"]}],"intervals":[{"startTimeMs":0,"endTimeMs":2},\ + {"startTimeMs":2,"endTimeMs":4}]}' + ) + ); + + it("non numeric cred", () => { + expect(() => + credGrainViewNonNumericCred.validateForGrainAllocation() + ).toThrow("Non numeric cred value found"); + }); + + const credGrainViewNonNumericCredInterval = CredGrainView.fromJSON( + JSON.parse( + '{"participants":[{"active":true,"identity":{"id":"YVZhbGlkVXVpZEF0TGFzdA","subtype":"USER",\ + "address":"N\\u0000sourcecred\\u0000core\\u0000IDENTITY\\u0000YVZhbGlkVXVpZEF0TGFzdA\\u0000",\ + "name":"steven","aliases":[]},"cred":2.999999337965189,"credPerInterval":[0.9479471812187605,\ + 2.0520521567464285],"grainEarned":"23","grainEarnedPerInterval":["13","10"]},{"active":true,\ + "identity":{"id":"URgLrCxgvjHxtGJ9PgmckQ","subtype":"ORGANIZATION","address":\ + "N\\u0000sourcecred\\u0000core\\u0000IDENTITY\\u0000URgLrCxgvjHxtGJ9PgmckQ\\u0000","name":"crystal-gems",\ + "aliases":[]},"cred":"20","credPerInterval":["20","bar"],\ + "grainEarned":"27","grainEarnedPerInterval":["17","10"]}],"intervals":[{"startTimeMs":0,"endTimeMs":2},\ + {"startTimeMs":2,"endTimeMs":4}]}' + ) + ); + + it("non numeric cred interval", () => { + expect(() => + credGrainViewNonNumericCredInterval.validateForGrainAllocation() + ).toThrow("Non numeric cred value found"); + }); + + const credGrainViewCredIntervalsLengthMismatch = CredGrainView.fromJSON( + JSON.parse( + '{"participants":[{"active":true,"identity":{"id":"YVZhbGlkVXVpZEF0TGFzdA","subtype":"USER",\ + "address":"N\\u0000sourcecred\\u0000core\\u0000IDENTITY\\u0000YVZhbGlkVXVpZEF0TGFzdA\\u0000",\ + "name":"steven","aliases":[]},"cred":2.999999337965189,"credPerInterval":[0.9479471812187605],\ + "grainEarned":"23","grainEarnedPerInterval":["13","10"]},{"active":true,"identity":\ + {"id":"URgLrCxgvjHxtGJ9PgmckQ","subtype":"ORGANIZATION","address":\ + "N\\u0000sourcecred\\u0000core\\u0000IDENTITY\\u0000URgLrCxgvjHxtGJ9PgmckQ\\u0000","name":\ + "crystal-gems","aliases":[]},"cred":1.286615549244117e-19,"credPerInterval":\ + [5.146462196976468e-20,7.719693295464702e-20],"grainEarned":"27","grainEarnedPerInterval":["17","10"]}],\ + "intervals":[{"startTimeMs":0,"endTimeMs":2},{"startTimeMs":2,"endTimeMs":4}]}' + ) + ); + + it("cred interval length mismatch", () => { + expect(() => + credGrainViewCredIntervalsLengthMismatch.validateForGrainAllocation() + ).toThrow("participant cred per interval length mismatch"); + }); + + const credGrainViewGrainIntervalsLengthMismatch = CredGrainView.fromJSON( + JSON.parse( + '{"participants":[{"active":true,"identity":{"id":"YVZhbGlkVXVpZEF0TGFzdA","subtype":"USER","address"\ + :"N\\u0000sourcecred\\u0000core\\u0000IDENTITY\\u0000YVZhbGlkVXVpZEF0TGFzdA\\u0000","name":"steven","aliases":[]}\ + ,"cred":2.999999337965189,"credPerInterval":[0.9479471812187605,2.0520521567464285],"grainEarned":"23",\ + "grainEarnedPerInterval":["13","10"]},{"active":true,"identity":{"id":"URgLrCxgvjHxtGJ9PgmckQ",\ + "subtype":"ORGANIZATION","address":"N\\u0000sourcecred\\u0000core\\u0000IDENTITY\\u0000URgLrCxgvjHxtGJ9PgmckQ\\u0000"\ + ,"name":"crystal-gems","aliases":[]},"cred":1.286615549244117e-19,"credPerInterval":\ + [5.146462196976468e-20,7.719693295464702e-20],"grainEarned":"27","grainEarnedPerInterval":["10"]}],\ + "intervals":[{"startTimeMs":0,"endTimeMs":2},{"startTimeMs":2,"endTimeMs":4}]}' + ) + ); + + it("grain interval length mismatch", () => { + expect(() => + credGrainViewGrainIntervalsLengthMismatch.validateForGrainAllocation() + ).toThrow("participant grain per interval length mismatch"); + }); + + const credGrainViewGrainTotalMismatchWithIntervals = CredGrainView.fromJSON( + JSON.parse( + '{"participants":[{"active":true,"identity":{"id":"YVZhbGlkVXVpZEF0TGFzdA","subtype":"USER","address"\ + :"N\\u0000sourcecred\\u0000core\\u0000IDENTITY\\u0000YVZhbGlkVXVpZEF0TGFzdA\\u0000","name":"steven","aliases":[]},\ + "cred":2.999999337965189,"credPerInterval":[0.9479471812187605,2.0520521567464285],"grainEarned":"23",\ + "grainEarnedPerInterval":["12","10"]},{"active":true,"identity":{"id":"URgLrCxgvjHxtGJ9PgmckQ",\ + "subtype":"ORGANIZATION","address":"N\\u0000sourcecred\\u0000core\\u0000IDENTITY\\u0000URgLrCxgvjHxtGJ9PgmckQ\\u0000",\ + "name":"crystal-gems","aliases":[]},"cred":1.286615549244117e-19,"credPerInterval":\ + [5.146462196976468e-20,7.719693295464702e-20],"grainEarned":"27","grainEarnedPerInterval":["17","10"]}],\ + "intervals":[{"startTimeMs":0,"endTimeMs":2},{"startTimeMs":2,"endTimeMs":4}]}' + ) + ); + + it("grain total mismatch with grain intervals", () => { + expect(() => + credGrainViewGrainTotalMismatchWithIntervals.validateForGrainAllocation() + ).toThrow( + "participant grain per interval mismatched with participant grain total" + ); + }); + + it("grain non numeric", () => { + expect(() => + CredGrainView.fromJSON( + JSON.parse( + '{"participants":[{"active":true,"identity":{"id":"YVZhbGlkVXVpZEF0TGFzdA","subtype":"USER",\ + "address":"N\\u0000sourcecred\\u0000core\\u0000IDENTITY\\u0000YVZhbGlkVXVpZEF0TGFzdA\\u0000",\ + "name":"steven","aliases":[]},"cred":2.999999337965189,"credPerInterval":[0.9479471812187605,2.0520521567464285],\ + "grainEarned":"23","grainEarnedPerInterval":["13","10"]},{"active":true,"identity":{"id":"URgLrCxgvjHxtGJ9PgmckQ",\ + "subtype":"ORGANIZATION","address":"N\\u0000sourcecred\\u0000core\\u0000IDENTITY\\u0000URgLrCxgvjHxtGJ9PgmckQ\\u0000",\ + "name":"crystal-gems","aliases":[]},"cred":1.286615549244117e-19,"credPerInterval":\ + [5.146462196976468e-20,7.719693295464702e-20],"grainEarned":"27","grainEarnedPerInterval":["17","bar"]}],\ + "intervals":[{"startTimeMs":0,"endTimeMs":2},{"startTimeMs":2,"endTimeMs":4}]}' + ) + ) + ).toThrow("Invalid integer: bar"); + }); + + const credGrainViewNegativeGrain = CredGrainView.fromJSON( + JSON.parse( + '{"participants":[{"active":true,"identity":{"id":"YVZhbGlkVXVpZEF0TGFzdA","subtype":"USER",\ + "address":"N\\u0000sourcecred\\u0000core\\u0000IDENTITY\\u0000YVZhbGlkVXVpZEF0TGFzdA\\u0000",\ + "name":"steven","aliases":[]},"cred":2.999999337965189,"credPerInterval":[0.9479471812187605,2.0520521567464285],\ + "grainEarned":"23","grainEarnedPerInterval":["13","10"]},{"active":true,"identity":{"id":"URgLrCxgvjHxtGJ9PgmckQ",\ + "subtype":"ORGANIZATION","address":"N\\u0000sourcecred\\u0000core\\u0000IDENTITY\\u0000URgLrCxgvjHxtGJ9PgmckQ\\u0000",\ + "name":"crystal-gems","aliases":[]},"cred":1.286615549244117e-19,"credPerInterval":\ + [5.146462196976468e-20,7.719693295464702e-20],"grainEarned":"15","grainEarnedPerInterval":["17","-2"]}],\ + "intervals":[{"startTimeMs":0,"endTimeMs":2},{"startTimeMs":2,"endTimeMs":4}]}' + ) + ); + + it("negative grain", () => { + expect(() => + credGrainViewNegativeGrain.validateForGrainAllocation() + ).toThrow("negative grain paid in interval data"); + }); + + const credGrainViewNegativeCred = CredGrainView.fromJSON( + JSON.parse( + '{"participants":[{"active":true,"identity":{"id":"YVZhbGlkVXVpZEF0TGFzdA","subtype":"USER",\ + "address":"N\\u0000sourcecred\\u0000core\\u0000IDENTITY\\u0000YVZhbGlkVXVpZEF0TGFzdA\\u0000",\ + "name":"steven","aliases":[]},"cred":1.104104975527668,"credPerInterval":[-0.9479471812187605,2.0520521567464285],\ + "grainEarned":"23","grainEarnedPerInterval":["13","10"]},{"active":true,"identity":{"id":"URgLrCxgvjHxtGJ9PgmckQ",\ + "subtype":"ORGANIZATION","address":"N\\u0000sourcecred\\u0000core\\u0000IDENTITY\\u0000URgLrCxgvjHxtGJ9PgmckQ\\u0000",\ + "name":"crystal-gems","aliases":[]},"cred":1.286615549244117e-19,"credPerInterval":\ + [5.146462196976468e-20,7.719693295464702e-20],"grainEarned":"27","grainEarnedPerInterval":["17","10"]}],\ + "intervals":[{"startTimeMs":0,"endTimeMs":2},{"startTimeMs":2,"endTimeMs":4}]}' + ) + ); + + it("negative cred", () => { + expect(() => + credGrainViewNegativeCred.validateForGrainAllocation() + ).toThrow("negative cred in interval data"); + }); + }); }); diff --git a/packages/sourcecred/src/core/ledger/computeDistribution.js b/packages/sourcecred/src/core/ledger/computeDistribution.js index 5d8b1045c..3a4ee5738 100644 --- a/packages/sourcecred/src/core/ledger/computeDistribution.js +++ b/packages/sourcecred/src/core/ledger/computeDistribution.js @@ -2,7 +2,7 @@ import * as uuid from "../../util/uuid"; import {type TimestampMs} from "../../util/timestamp"; -import {type AllocationIdentity, computeAllocation} from "./grainAllocation"; +import {computeAllocation} from "./grainAllocation"; import {type AllocationPolicy} from "./policies"; import {type CredGrainView} from "../credGrainView"; import {type Distribution} from "./distribution"; @@ -15,32 +15,15 @@ import {type Distribution} from "./distribution"; * effectiveTimestamp. * * Note: This method is untested as it is just a bit of plubming; flow gives me - * confidence that the semantics are correct. The helper method - * _allocationIdentities is tested, as it handles some naunces e.g. slicing - * down the cred interval data. - */ + * confidence that the semantics are correct. + **/ export function computeDistribution( policies: $ReadOnlyArray, credGrainView: CredGrainView, effectiveTimestamp: TimestampMs ): Distribution { - const allocationIdentities = _allocationIdentities( - credGrainView, - effectiveTimestamp - ); - - // As of now the balanced policy uses credGrainView and effective Timestamp - // but other policies are still using allocationIdentities. - // when all policies are converted to credGrainView, we won't need - // allocationIdentities, but for the time being, we need both. - const allocations = policies.map((p) => - computeAllocation( - p, - allocationIdentities, - credGrainView, - effectiveTimestamp - ) + computeAllocation(p, credGrainView, effectiveTimestamp) ); const distribution = { id: uuid.random(), @@ -49,19 +32,3 @@ export function computeDistribution( }; return distribution; } - -export function _allocationIdentities( - credGrainView: CredGrainView, - effectiveTimestamp: TimestampMs -): $ReadOnlyArray { - const timeSlicedActiveParticipants = credGrainView - .withTimeScope(0, effectiveTimestamp) - .participants() - .filter((participant) => participant.active); - - return timeSlicedActiveParticipants.map((x) => ({ - id: x.identity.id, - paid: x.grainEarned, - cred: x.credPerInterval, - })); -} diff --git a/packages/sourcecred/src/core/ledger/computeDistribution.test.js b/packages/sourcecred/src/core/ledger/computeDistribution.test.js deleted file mode 100644 index 1caab4a17..000000000 --- a/packages/sourcecred/src/core/ledger/computeDistribution.test.js +++ /dev/null @@ -1,155 +0,0 @@ -// @flow - -import {_allocationIdentities} from "./computeDistribution"; -import * as GraphUtil from "../credrank/testUtils"; -import {createTestLedgerFixture} from "../ledger/testUtils"; -import {CredGrainView} from "../credGrainView"; -import {g, nng} from "../ledger/testUtils"; -import * as uuid from "../../util/uuid"; - -describe("core/ledger/computeDistribution", () => { - describe("_allocationIdentities", () => { - let credGraph; - beforeEach(async (done) => { - credGraph = await GraphUtil.credGraph(); - done(); - }); - - it("does not include unassigned aliases", () => { - const {ledgerWithSingleIdentity} = createTestLedgerFixture(); - const idActive = GraphUtil.participant1.id; - - /* The GraphUtil will create 2 participants, but we're only going to include - 1 in the ledger. If things are working properly, we should not see data - for the second participant in the CredGrainView. */ - - const allocationId1 = uuid.random(); - const allocation1 = { - policy: { - policyType: "IMMEDIATE", - budget: nng("3"), - numIntervalsLookback: 1, - }, - id: allocationId1, - receipts: [{amount: g("3"), id: GraphUtil.participant1.id}], - }; - const distribution1 = { - credTimestamp: 1, - allocations: [allocation1], - id: uuid.random(), - }; - - const ledger = ledgerWithSingleIdentity(idActive); - ledger.activate(idActive); - ledger.distributeGrain(distribution1); - - const credGrainView = CredGrainView.fromCredGraphAndLedger( - credGraph, - ledger - ); - - const expectedAllocationIdentites = [ - { - id: idActive, - cred: GraphUtil.expectedParticipant1.credPerInterval, - paid: "3", - }, - ]; - - expect(_allocationIdentities(credGrainView, 999)).toEqual( - expectedAllocationIdentites - ); - }); - it("does not include inactive accounts", () => { - const {ledgerWithIdentities} = createTestLedgerFixture(); - const idActive = GraphUtil.participant1.id; - const idInactive = GraphUtil.participant2.id; - - /* The GraphUtil will create 2 participants, but we're only going to activate - 1 in the ledger. If things are working properly, we should not see data - for the second participant in the allocationIdentities. */ - - const allocationId1 = uuid.random(); - const allocation1 = { - policy: { - policyType: "IMMEDIATE", - budget: nng("5"), - numIntervalsLookback: 1, - }, - id: allocationId1, - receipts: [{amount: g("2"), id: GraphUtil.participant1.id}], - }; - const distribution1 = { - credTimestamp: 1, - allocations: [allocation1], - id: uuid.random(), - }; - - const ledger = ledgerWithIdentities(idActive, idInactive); - ledger.activate(idActive); - ledger.distributeGrain(distribution1); - - const credGrainView = CredGrainView.fromCredGraphAndLedger( - credGraph, - ledger - ); - - const expectedAllocationIdentites = [ - { - id: idActive, - cred: GraphUtil.expectedParticipant1.credPerInterval, - paid: "2", - }, - ]; - expect(_allocationIdentities(credGrainView, 999)).toEqual( - expectedAllocationIdentites - ); - }); - it("time slices the cred as expected", () => { - const {ledgerWithIdentities} = createTestLedgerFixture(); - const idActive = GraphUtil.participant1.id; - const idInactive = GraphUtil.participant2.id; - - /* The GraphUtil will create 2 participants, but we're only going to activate - 1 in the ledger. If things are working properly, we should not see data - for the second participant in the allocationIdentities. */ - - const allocationId1 = uuid.random(); - const allocation1 = { - policy: { - policyType: "IMMEDIATE", - budget: nng("9"), - numIntervalsLookback: 1, - }, - id: allocationId1, - receipts: [{amount: g("4"), id: GraphUtil.participant1.id}], - }; - const distribution1 = { - credTimestamp: 1, - allocations: [allocation1], - id: uuid.random(), - }; - - const ledger = ledgerWithIdentities(idActive, idInactive); - ledger.activate(idActive); - ledger.distributeGrain(distribution1); - - const credGrainView = CredGrainView.fromCredGraphAndLedger( - credGraph, - ledger - ); - - //Should only have one value in the cred array - const expectedAllocationIdentites = [ - { - id: idActive, - cred: [GraphUtil.expectedParticipant1.credPerInterval[0]], - paid: "4", - }, - ]; - expect( - _allocationIdentities(credGrainView, GraphUtil.intervals[0].endTimeMs) - ).toEqual(expectedAllocationIdentites); - }); - }); -}); diff --git a/packages/sourcecred/src/core/ledger/grain.js b/packages/sourcecred/src/core/ledger/grain.js index 0852e917b..76ffdc194 100644 --- a/packages/sourcecred/src/core/ledger/grain.js +++ b/packages/sourcecred/src/core/ledger/grain.js @@ -307,6 +307,7 @@ export function splitBudget( if (lt(budget, ZERO)) { throw new Error("negative budget"); } + const totalScore = scores.reduce((a, b) => a + b, 0); if (!isFinite(totalScore)) { throw new Error(`scores must all be finite, got: ${totalScore}`); diff --git a/packages/sourcecred/src/core/ledger/grainAllocation.js b/packages/sourcecred/src/core/ledger/grainAllocation.js index f38e96e1c..1235a2e48 100644 --- a/packages/sourcecred/src/core/ledger/grainAllocation.js +++ b/packages/sourcecred/src/core/ledger/grainAllocation.js @@ -15,7 +15,7 @@ import { random as randomUuid, parser as uuidParser, } from "../../util/uuid"; -import {type IdentityId} from "../identity"; +import {type IdentityId, type Identity} from "../identity"; import { type AllocationPolicy, allocationPolicyParser, @@ -24,10 +24,6 @@ import { recentReceipts, specialReceipts, } from "./policies"; -import { - type ProcessedIdentities, - processIdentities, -} from "./processedIdentities"; export type AllocationId = Uuid; @@ -42,28 +38,16 @@ export type Allocation = {| +receipts: $ReadOnlyArray, |}; -export type AllocationIdentity = {| - +cred: $ReadOnlyArray, - +paid: G.Grain, - +id: IdentityId, -|}; - export function computeAllocation( policy: AllocationPolicy, - identities: $ReadOnlyArray, credGrainView: CredGrainView, effectiveTimestamp: TimestampMs ): Allocation { const validatedPolicy = _validatePolicy(policy); - const processedIdentities = processIdentities(identities); + credGrainView.validateForGrainAllocation(); return _validateAllocationBudget({ policy, - receipts: receipts( - validatedPolicy, - processedIdentities, - credGrainView, - effectiveTimestamp - ), + receipts: receipts(validatedPolicy, credGrainView, effectiveTimestamp), id: randomUuid(), }); } @@ -71,14 +55,13 @@ export function computeAllocation( /* This is a simplified case that should not require a credGrainView */ export function computeAllocationSpecial( policy: AllocationPolicy, - identities: $ReadOnlyArray + identities: $ReadOnlyArray ): Allocation { const validatedPolicy = _validatePolicy(policy); - const processedIdentities = processIdentities(identities); if (validatedPolicy.policyType === "SPECIAL") { return _validateAllocationBudget({ policy, - receipts: specialReceipts(validatedPolicy, processedIdentities), + receipts: specialReceipts(validatedPolicy, identities), id: randomUuid(), }); } else { @@ -109,7 +92,6 @@ export function _validateAllocationBudget(a: Allocation): Allocation { function receipts( policy: AllocationPolicy, - identities: ProcessedIdentities, credGrainView: CredGrainView, effectiveTimestamp: TimestampMs ): $ReadOnlyArray { @@ -117,10 +99,13 @@ function receipts( case "IMMEDIATE": return immediateReceipts(policy, credGrainView, effectiveTimestamp); case "RECENT": - return recentReceipts(policy.budget, identities, policy.discount); + return recentReceipts(policy, credGrainView, effectiveTimestamp); case "BALANCED": return balancedReceipts(policy, credGrainView, effectiveTimestamp); case "SPECIAL": + const identities = credGrainView + .activeParticipants() + .map((participant) => participant.identity); return specialReceipts(policy, identities); // istanbul ignore next: unreachable per Flow default: diff --git a/packages/sourcecred/src/core/ledger/grainAllocation.test.js b/packages/sourcecred/src/core/ledger/grainAllocation.test.js index 7fe394ef8..470de8fe3 100644 --- a/packages/sourcecred/src/core/ledger/grainAllocation.test.js +++ b/packages/sourcecred/src/core/ledger/grainAllocation.test.js @@ -4,7 +4,6 @@ import {random as randomUuid, parser as uuidParser} from "../../util/uuid"; import { computeAllocation, computeAllocationSpecial, - type AllocationIdentity, _validateAllocationBudget, } from "./grainAllocation"; import {fromString as nngFromString} from "./nonnegativeGrain"; @@ -21,13 +20,6 @@ describe("core/ledger/grainAllocation", () => { // concise helper for grain from a number const nng = (x: number) => nngFromString(x.toString()); // concise helper for an allocation identity - function aid( - paid: number, - cred: $ReadOnlyArray, - id = randomUuid() - ): AllocationIdentity { - return {id: id, paid: nng(paid), cred}; - } const immediate = (n: number) => ({ policyType: "IMMEDIATE", budget: nng(n), @@ -48,45 +40,33 @@ describe("core/ledger/grainAllocation", () => { const {ledgerWithActiveIdentities} = createTestLedgerFixture(); let credGraph; let credGrainView; + let credGraph2; + let credGrainView2; const id1 = GraphUtil.participant1.id; const id2 = GraphUtil.participant2.id; const id3 = GraphUtil.participant3.id; const id4 = GraphUtil.participant4.id; const emptyLedger = ledgerWithActiveIdentities(id1, id2); + const emptyLedger2 = ledgerWithActiveIdentities(id3, id4); describe("validation", () => { beforeEach(async (done) => { credGraph = await GraphUtil.credGraph(); + credGraph2 = await GraphUtil.credGraph2(); + credGrainView = CredGrainView.fromCredGraphAndLedger( credGraph, emptyLedger ); + + credGrainView2 = CredGrainView.fromCredGraphAndLedger( + credGraph2, + emptyLedger2 + ); done(); }); - it("errors if there are no identities", () => { - const thunk = () => - computeAllocation(immediate(5), [], credGrainView, 0); - expect(thunk).toThrowError("must have at least one identity"); - }); - it("errors if the total cred is zero", () => { - const thunk = () => - computeAllocation(immediate(5), [aid(0, [0])], credGrainView, 0); - expect(thunk).toThrowError("cred is zero"); - }); - it("errors if there's NaN or Infinity in Cred", () => { - const thunk = () => - computeAllocation(immediate(5), [aid(0, [NaN])], credGrainView, 0); - expect(thunk).toThrowError("invalid cred"); - }); - it("errors if there's inconsistent Cred lengths", () => { - const i1 = aid(0, [1]); - const i2 = aid(0, [1, 2]); - const thunk = () => - computeAllocation(immediate(5), [i1, i2], credGrainView, 0); - expect(thunk).toThrowError("inconsistent cred length"); - }); it("errors if the receipts don't match the budget", () => { const badAllocation = { policy: immediate(5), @@ -114,11 +94,8 @@ describe("core/ledger/grainAllocation", () => { it("splits based on just most recent cred", () => { const policy = immediate(1000); - const i1 = aid(100, [10, 2]); - const i2 = aid(0, [0, 3]); const allocation = computeAllocation( policy, - [i1, i2], credGrainViewUnbalanced, 4 ); @@ -141,11 +118,8 @@ describe("core/ledger/grainAllocation", () => { it("handles 0 budget correctly", () => { const policy = immediate(0); - const i1 = aid(3, [1, 1]); - const i2 = aid(0, [3, 0]); const allocation = computeAllocation( policy, - [i1, i2], credGrainViewUnbalanced, 4 ); @@ -172,21 +146,33 @@ describe("core/ledger/grainAllocation", () => { done(); }); + let credGraph2; + let credGrainViewUnbalanced; + let credGrainViewUnbalancedUnpaid; + beforeEach(async (done) => { + credGraph2 = await GraphUtil.credGraph2(); + const unbalancedLedger = ledgerWithActiveIdentities(id3, id4); + credGrainViewUnbalanced = CredGrainView.fromCredGraphAndLedger( + credGraph2, + unbalancedLedger + ); + credGrainViewUnbalancedUnpaid = CredGrainView.fromCredGraphAndLedger( + credGraph2, + ledgerWithActiveIdentities(id3, id4) + ); + done(); + }); + it("splits based on discounted cred", () => { const policy = recent(100, 0.1); - const i1 = aid(0, [0, 0, 100]); - const i2 = aid(100, [100, 0, 0]); - const i3 = aid(0, [100, 0, 0]); const allocation = computeAllocation( policy, - [i1, i2, i3], - credGrainView, - 0 + credGrainViewUnbalancedUnpaid, + 4 ); const expectedReceipts = [ - {id: i1.id, amount: nng(38)}, - {id: i2.id, amount: nng(31)}, - {id: i3.id, amount: nng(31)}, + {id: id3, amount: nng(27)}, + {id: id4, amount: nng(73)}, ]; const expectedAllocation = { receipts: expectedReceipts, @@ -198,17 +184,14 @@ describe("core/ledger/grainAllocation", () => { it("is not influenced by grain paid", () => { const policy = recent(100, 0.1); - const i1 = aid(0, [100, 100, 100]); - const i2 = aid(100, [100, 100, 100]); const allocation = computeAllocation( policy, - [i1, i2], - credGrainView, - 0 + credGrainViewUnbalanced, + 4 ); const expectedReceipts = [ - {id: i1.id, amount: nng(50)}, - {id: i2.id, amount: nng(50)}, + {id: id3, amount: nng(27)}, + {id: id4, amount: nng(73)}, ]; const expectedAllocation = { receipts: expectedReceipts, @@ -220,17 +203,14 @@ describe("core/ledger/grainAllocation", () => { it("handles full discount correctly", () => { const policy = recent(100, 1); - const i1 = aid(50, [0, 50, 0]); - const i2 = aid(0, [0, 10, 100]); const allocation = computeAllocation( policy, - [i1, i2], - credGrainView, - 0 + credGrainViewUnbalanced, + 4 ); const expectedReceipts = [ - {id: i1.id, amount: nng(0)}, - {id: i2.id, amount: nng(100)}, + {id: id3, amount: nng(6)}, + {id: id4, amount: nng(94)}, ]; const expectedAllocation = { receipts: expectedReceipts, @@ -242,17 +222,14 @@ describe("core/ledger/grainAllocation", () => { it("handles 0 budget correctly", () => { const policy = recent(0, 0.1); - const i1 = aid(50, [100, 50, 10]); - const i2 = aid(0, [0, 10, 100]); const allocation = computeAllocation( policy, - [i1, i2], - credGrainView, - 0 + credGrainViewUnbalanced, + 4 ); const expectedReceipts = [ - {id: i1.id, amount: nng(0)}, - {id: i2.id, amount: nng(0)}, + {id: id3, amount: nng(0)}, + {id: id4, amount: nng(0)}, ]; const expectedAllocation = { receipts: expectedReceipts, @@ -290,15 +267,14 @@ describe("core/ledger/grainAllocation", () => { }; unbalancedLedger.distributeGrain(distribution1); - const emptyLedger2 = ledgerWithActiveIdentities(id3, id4); - let credGrainViewEmpty; let credGrainViewUnbalanced; + beforeEach(async (done) => { credGraph = await GraphUtil.credGraph2(); credGrainViewEmpty = CredGrainView.fromCredGraphAndLedger( credGraph, - emptyLedger2 + ledgerWithActiveIdentities(id3, id4) ); credGrainViewUnbalanced = CredGrainView.fromCredGraphAndLedger( @@ -311,26 +287,10 @@ describe("core/ledger/grainAllocation", () => { it("splits based on past Cred when there's no paid amounts", () => { const policy = balanced(100); - - const aid1 = aid( - 0, - GraphUtil.expectedParticipant3.credPerInterval, - id3 - ); - const aid2 = aid( - 0, - GraphUtil.expectedParticipant4.credPerInterval, - id4 - ); - const allocation = computeAllocation( - policy, - [aid1, aid2], - credGrainViewEmpty, - 4 - ); + const allocation = computeAllocation(policy, credGrainViewEmpty, 4); const expectedReceipts = [ - {id: aid1.id, amount: nng(28)}, - {id: aid2.id, amount: nng(72)}, + {id: id3, amount: nng(28)}, + {id: id4, amount: nng(72)}, ]; const expectedAllocation = { receipts: expectedReceipts, @@ -342,25 +302,15 @@ describe("core/ledger/grainAllocation", () => { it("takes past payment into account", () => { const policy = balanced(3000); - const aid1 = aid( - 0, - GraphUtil.expectedParticipant3.credPerInterval, - id3 - ); - const aid2 = aid( - 0, - GraphUtil.expectedParticipant4.credPerInterval, - id4 - ); + const allocation = computeAllocation( policy, - [aid1, aid2], credGrainViewUnbalanced, 4 ); const expectedReceipts = [ - {id: aid1.id, amount: nng(802)}, - {id: aid2.id, amount: nng(2198)}, + {id: id3, amount: nng(802)}, + {id: id4, amount: nng(2198)}, ]; const expectedAllocation = { receipts: expectedReceipts, @@ -372,25 +322,14 @@ describe("core/ledger/grainAllocation", () => { it("honors the lookback period", () => { const policy = balanced(2000, 1); - const aid1 = aid( - 0, - GraphUtil.expectedParticipant3.credPerInterval, - id3 - ); - const aid2 = aid( - 0, - GraphUtil.expectedParticipant4.credPerInterval, - id4 - ); const allocation = computeAllocation( policy, - [aid1, aid2], credGrainViewUnbalanced, 4 ); const expectedReceipts = [ - {id: aid1.id, amount: nng(34)}, - {id: aid2.id, amount: nng(1966)}, + {id: id3, amount: nng(34)}, + {id: id4, amount: nng(1966)}, ]; const expectedAllocation = { receipts: expectedReceipts, @@ -402,25 +341,14 @@ describe("core/ledger/grainAllocation", () => { it("handles 0 budget correctly", () => { const policy = balanced(0); - const aid1 = aid( - 0, - GraphUtil.expectedParticipant3.credPerInterval, - id3 - ); - const aid2 = aid( - 0, - GraphUtil.expectedParticipant4.credPerInterval, - id4 - ); const allocation = computeAllocation( policy, - [aid1, aid2], credGrainViewUnbalanced, 4 ); const expectedReceipts = [ - {id: aid1.id, amount: nng(0)}, - {id: aid2.id, amount: nng(0)}, + {id: id3, amount: nng(0)}, + {id: id4, amount: nng(0)}, ]; const expectedAllocation = { receipts: expectedReceipts, @@ -434,23 +362,29 @@ describe("core/ledger/grainAllocation", () => { describe("special policy", () => { beforeEach(async (done) => { credGraph = await GraphUtil.credGraph(); + credGraph2 = await GraphUtil.credGraph2(); + credGrainView = CredGrainView.fromCredGraphAndLedger( credGraph, emptyLedger ); + + credGrainView2 = CredGrainView.fromCredGraphAndLedger( + credGraph2, + emptyLedger2 + ); done(); }); it("distributes the budget to the stated recipient", () => { - const i1 = aid(0, [1]); const policy = { policyType: "SPECIAL", budget: nng(100), memo: "something", - recipient: i1.id, + recipient: id2, }; - const allocation = computeAllocation(policy, [i1], credGrainView, 0); - const expectedReceipts = [{id: i1.id, amount: nng(100)}]; + const allocation = computeAllocation(policy, credGrainView, 0); + const expectedReceipts = [{id: id2, amount: nng(100)}]; const expectedAllocation = { receipts: expectedReceipts, id: uuidParser.parseOrThrow(allocation.id), @@ -459,31 +393,31 @@ describe("core/ledger/grainAllocation", () => { expect(allocation).toEqual(expectedAllocation); }); it("errors if the recipient is not available", () => { - const {id} = aid(0, [1]); - const other = aid(0, [1]); const policy = { policyType: "SPECIAL", budget: nng(100), memo: "something", - recipient: id, + recipient: id3, }; - const thunk = () => - computeAllocation(policy, [other], credGrainView, 0); + const thunk = () => computeAllocation(policy, credGrainView, 0); expect(thunk).toThrowError("no active grain account for identity"); }); //Test ComputeAllocationSpecial function it("distributes the budget to the stated recipient", () => { - const i1 = aid(0, [1]); const policy = { policyType: "SPECIAL", budget: nng(100), memo: "something", - recipient: i1.id, + recipient: id1, }; - const allocation = computeAllocationSpecial(policy, [i1]); - const expectedReceipts = [{id: i1.id, amount: nng(100)}]; + const identities = credGrainView + .activeParticipants() + .map((participant) => participant.identity); + + const allocation = computeAllocationSpecial(policy, identities); + const expectedReceipts = [{id: id1, amount: nng(100)}]; const expectedAllocation = { receipts: expectedReceipts, id: uuidParser.parseOrThrow(allocation.id), @@ -492,15 +426,18 @@ describe("core/ledger/grainAllocation", () => { expect(allocation).toEqual(expectedAllocation); }); it("errors if the recipient is not available", () => { - const {id} = aid(0, [1]); - const other = aid(0, [1]); const policy = { policyType: "SPECIAL", budget: nng(100), memo: "something", - recipient: id, + recipient: id2, }; - const thunk = () => computeAllocationSpecial(policy, [other]); + + const identities = credGrainView2 + .activeParticipants() + .map((participant) => participant.identity); + + const thunk = () => computeAllocationSpecial(policy, identities); expect(thunk).toThrowError("no active grain account for identity"); }); }); diff --git a/packages/sourcecred/src/core/ledger/policies/balanced.js b/packages/sourcecred/src/core/ledger/policies/balanced.js index af5afb510..a1eb2ed15 100644 --- a/packages/sourcecred/src/core/ledger/policies/balanced.js +++ b/packages/sourcecred/src/core/ledger/policies/balanced.js @@ -1,5 +1,4 @@ // @flow - import {sum} from "d3-array"; import * as G from "../grain"; import * as P from "../../../util/combo"; diff --git a/packages/sourcecred/src/core/ledger/policies/balanced.test.js b/packages/sourcecred/src/core/ledger/policies/balanced.test.js index 8aad4f61e..e59b19d96 100644 --- a/packages/sourcecred/src/core/ledger/policies/balanced.test.js +++ b/packages/sourcecred/src/core/ledger/policies/balanced.test.js @@ -1,24 +1,14 @@ // @flow -import {random as randomUuid} from "../../../util/uuid"; import {balancedReceipts} from "./balanced"; import * as GraphUtil from "../../credrank/testUtils"; import {createTestLedgerFixture} from "../../ledger/testUtils"; import {CredGrainView} from "../../credGrainView"; -import {type AllocationIdentity} from "../grainAllocation"; import {fromString as nngFromString} from "../nonnegativeGrain"; const nng = (x: number) => nngFromString(x.toString()); import {g} from "../../ledger/testUtils"; import * as uuid from "../../../util/uuid"; -function aid( - paid: number, - cred: $ReadOnlyArray, - id = randomUuid() -): AllocationIdentity { - return {id: id, paid: nng(paid), cred}; -} - describe("core/ledger/policies/balanced", () => { const id1 = GraphUtil.participant1.id; const id2 = GraphUtil.participant2.id; @@ -73,9 +63,6 @@ describe("core/ledger/policies/balanced", () => { }); describe("balancedReceipts", () => { - const aid3 = aid(0, GraphUtil.expectedParticipant3.credPerInterval, id3); - const aid4 = aid(0, GraphUtil.expectedParticipant4.credPerInterval, id4); - it("errors on invalid range", () => { const policy = { policyType: "BALANCED", @@ -137,8 +124,8 @@ describe("core/ledger/policies/balanced", () => { numIntervalsLookback: 1, // 0 means forever, but there are only 2 intervals in the test data }; const expectedReceipts = [ - {id: aid3.id, amount: nng(34)}, - {id: aid4.id, amount: nng(1966)}, + {id: id3, amount: nng(34)}, + {id: id4, amount: nng(1966)}, ]; const actualReceipts = balancedReceipts( policy1, diff --git a/packages/sourcecred/src/core/ledger/policies/recent.js b/packages/sourcecred/src/core/ledger/policies/recent.js index b523ba41c..df19a5479 100644 --- a/packages/sourcecred/src/core/ledger/policies/recent.js +++ b/packages/sourcecred/src/core/ledger/policies/recent.js @@ -2,8 +2,9 @@ import * as G from "../grain"; import * as P from "../../../util/combo"; +import {type CredGrainView} from "../../credGrainView"; +import {type TimestampMs} from "../../../util/timestamp"; import {type GrainReceipt} from "../grainAllocation"; -import {type ProcessedIdentities} from "../processedIdentities"; import { type NonnegativeGrain, grainParser, @@ -41,16 +42,34 @@ export type RecentPolicy = {| * cred. */ export function recentReceipts( - budget: G.Grain, - identities: ProcessedIdentities, - discount: Discount + policy: RecentPolicy, + credGrainView: CredGrainView, + effectiveTimestamp: TimestampMs ): $ReadOnlyArray { - const computeDecayedCred = (i) => - i.cred.reduce((acc, cred) => acc * (1 - discount) + cred, 0); - const decayedCredPerIdentity = identities.map(computeDecayedCred); - const amounts = G.splitBudget(budget, decayedCredPerIdentity); + const lookback = 0; - return identities.map(({id}, i) => ({id, amount: amounts[i]})); + const timeLimitedCredGrainView = credGrainView.withTimeScopeFromLookback( + effectiveTimestamp, + lookback + ); + const timeLimitedParticipants = timeLimitedCredGrainView.activeParticipants(); + + const computeDecayedCred = (i) => { + return i.credPerInterval.reduce( + (acc, cred) => acc * (1 - policy.discount) + cred, + 0 + ); + }; + const decayedCredPerIdentity = timeLimitedParticipants.map( + computeDecayedCred + ); + + const amounts = G.splitBudget(policy.budget, decayedCredPerIdentity); + + return timeLimitedParticipants.map(({identity}, i) => ({ + id: identity.id, + amount: amounts[i], + })); } export const recentConfigParser: P.Parser = P.object({ diff --git a/packages/sourcecred/src/core/ledger/policies/special.js b/packages/sourcecred/src/core/ledger/policies/special.js index 905143ddf..14308400a 100644 --- a/packages/sourcecred/src/core/ledger/policies/special.js +++ b/packages/sourcecred/src/core/ledger/policies/special.js @@ -2,10 +2,9 @@ import {parser as uuidParser} from "../../../util/uuid"; import * as P from "../../../util/combo"; -import {type IdentityId} from "../../identity"; +import {type IdentityId, type Identity} from "../../identity"; import * as G from "../grain"; import {type GrainReceipt} from "../grainAllocation"; -import {type ProcessedIdentities} from "../processedIdentities"; import { type NonnegativeGrain, grainParser, @@ -32,7 +31,7 @@ export type SpecialPolicy = {| export function specialReceipts( policy: SpecialPolicy, - identities: ProcessedIdentities + identities: $ReadOnlyArray ): $ReadOnlyArray { for (const {id} of identities) { if (id === policy.recipient) { diff --git a/packages/sourcecred/src/core/ledger/processedIdentities.js b/packages/sourcecred/src/core/ledger/processedIdentities.js deleted file mode 100644 index 72c9fcd99..000000000 --- a/packages/sourcecred/src/core/ledger/processedIdentities.js +++ /dev/null @@ -1,67 +0,0 @@ -// @flow - -import {type IdentityId} from "../identity"; -import * as G from "./grain"; -import {type AllocationIdentity} from "./grainAllocation"; - -// ProcessedIdentities type has the following guarantees: -// - no Cred is negative -// - no Paid is negative -// - total Cred is positive -// - all cred arrays have same length -export opaque type ProcessedIdentities: $ReadOnlyArray<{| - +paid: G.Grain, - +id: IdentityId, - +cred: $ReadOnlyArray, - +lifetimeCred: number, - +mostRecentCred: number, -|}> = $ReadOnlyArray<{| - +paid: G.Grain, - +id: IdentityId, - +cred: $ReadOnlyArray, - +lifetimeCred: number, - +mostRecentCred: number, -|}>; -export function processIdentities( - items: $ReadOnlyArray -): ProcessedIdentities { - if (items.length === 0) { - throw new Error(`must have at least one identity to allocate grain to`); - } - let hasPositiveCred = false; - const credLength = items[0].cred.length; - const results = items.map((i) => { - const {cred, id, paid} = i; - if (G.lt(paid, G.ZERO)) { - throw new Error(`negative paid: ${paid}`); - } - if (credLength !== cred.length) { - throw new Error( - `inconsistent cred length: ${credLength} vs ${cred.length}` - ); - } - let lifetimeCred = 0; - for (const c of cred) { - if (c < 0 || !isFinite(c)) { - throw new Error(`invalid cred: ${c}`); - } - if (c > 0) { - hasPositiveCred = true; - } - lifetimeCred += c; - } - return { - id, - paid, - cred, - lifetimeCred, - mostRecentCred: cred[cred.length - 1], - }; - }); - if (!hasPositiveCred) { - throw new Error( - "cred is zero. Make sure your plugins are configured correctly and remember to run 'yarn go' to calculate the cred scores." - ); - } - return results; -} diff --git a/packages/sourcecred/src/ui/components/SpecialDistribution.js b/packages/sourcecred/src/ui/components/SpecialDistribution.js index 7c9e3d304..764c76cd8 100644 --- a/packages/sourcecred/src/ui/components/SpecialDistribution.js +++ b/packages/sourcecred/src/ui/components/SpecialDistribution.js @@ -72,9 +72,7 @@ export const SpecialDistribution = ({ memo, recipient: recipient.identity.id, }; - const allocation = computeAllocationSpecial(policy, [ - {cred: [1], paid: ZERO, id: recipient.identity.id}, - ]); + const allocation = computeAllocationSpecial(policy, [recipient.identity]); const distribution = { id: uuid.random(), credTimestamp,