Skip to content

Commit

Permalink
Bsodenkamp allocation identities refactor (#3115)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
bensodenkamp committed Aug 22, 2021
1 parent c124d7b commit 4d468ac
Show file tree
Hide file tree
Showing 13 changed files with 378 additions and 458 deletions.
53 changes: 53 additions & 0 deletions packages/sourcecred/src/core/credGrainView.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
197 changes: 197 additions & 0 deletions packages/sourcecred/src/core/credGrainView.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});
});
});
41 changes: 4 additions & 37 deletions packages/sourcecred/src/core/ledger/computeDistribution.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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<AllocationPolicy>,
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(),
Expand All @@ -49,19 +32,3 @@ export function computeDistribution(
};
return distribution;
}

export function _allocationIdentities(
credGrainView: CredGrainView,
effectiveTimestamp: TimestampMs
): $ReadOnlyArray<AllocationIdentity> {
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,
}));
}

0 comments on commit 4d468ac

Please sign in to comment.