Update output format to include credOverTime (#1791)
As requested by @s-ben, we map now include cred over time for all contributions, not just contributors. Based on discussion with @Beanow, we made it an optional field so that we can optionally filter to save space instead. I was initially concerned that we wouldn't be able to compute credOverTime for non-user nodes in CredRank, which is why I left it out. However, informed by discussions with @mZargham, I'm less concerned because PageRank (and thus CredRank) is a linear operator on the seed vector. So, if we want to compute the "cred over time" for individual contributions in CredRank, we can do so by constructing time-specific seed vectors (which flow only to activity minting cred in the specified interval), and the sum of contributions time-scoped cred will be equal to the non-time-scoped cred. It's good that we'll still have the epoch nodes for users, as that will allow us to model sponsorship cred flow dynamics. cc @wchargin for CredRank considerations. Test plan: Unit tests updated, `yarn test` passes.
This commit is contained in:
parent
ec56ce79c8
commit
91ca897d99
File diff suppressed because it is too large
Load Diff
|
@ -18,7 +18,7 @@ export type CredFlow = {|+forwards: number, +backwards: number|};
|
||||||
export type Output = OutputV1;
|
export type Output = OutputV1;
|
||||||
export const COMPAT_INFO = {
|
export const COMPAT_INFO = {
|
||||||
type: "sourcecred/analysis/output",
|
type: "sourcecred/analysis/output",
|
||||||
version: "0.1.0",
|
version: "0.2.0",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,6 +33,10 @@ export const COMPAT_INFO = {
|
||||||
export type OutputNode = {|
|
export type OutputNode = {|
|
||||||
+address: $ReadOnlyArray<string>,
|
+address: $ReadOnlyArray<string>,
|
||||||
+cred: number,
|
+cred: number,
|
||||||
|
// Full cred over time (aligned with output interval boundaries).
|
||||||
|
// It's optional because it inflates the output size a lot -- we'll
|
||||||
|
// want to filter out low-cred nodes for large projects
|
||||||
|
+credOverTime: ?$ReadOnlyArray<number>,
|
||||||
+minted: number,
|
+minted: number,
|
||||||
+timestamp: TimestampMs | null,
|
+timestamp: TimestampMs | null,
|
||||||
// Description comes from the underlying Graph node, so it's determined by the
|
// Description comes from the underlying Graph node, so it's determined by the
|
||||||
|
@ -50,6 +54,8 @@ export type OutputV1 = {|
|
||||||
// Ordered by address
|
// Ordered by address
|
||||||
+orderedNodes: $ReadOnlyArray<OutputNode>,
|
+orderedNodes: $ReadOnlyArray<OutputNode>,
|
||||||
+plugins: $ReadOnlyArray<PluginDeclaration>,
|
+plugins: $ReadOnlyArray<PluginDeclaration>,
|
||||||
|
// Interval endpoints, aligned with credOverTime
|
||||||
|
+intervalEndpoints: $ReadOnlyArray<TimestampMs>,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
export function fromTimelineCredAndPlugins(
|
export function fromTimelineCredAndPlugins(
|
||||||
|
@ -58,23 +64,26 @@ export function fromTimelineCredAndPlugins(
|
||||||
): Output {
|
): Output {
|
||||||
const {graph, weights} = tc.weightedGraph();
|
const {graph, weights} = tc.weightedGraph();
|
||||||
const nodeEvaluator = nodeWeightEvaluator(weights);
|
const nodeEvaluator = nodeWeightEvaluator(weights);
|
||||||
|
const intervalEndpoints = tc.intervals().map((x) => x.endTimeMs);
|
||||||
const orderedNodes = Array.from(graph.nodes()).map(
|
const orderedNodes = Array.from(graph.nodes()).map(
|
||||||
({description, address, timestampMs}) => {
|
({description, address, timestampMs}) => {
|
||||||
const cred = NullUtil.get(tc.credNode(address)).total;
|
const {cred, total} = NullUtil.get(tc.credNode(address));
|
||||||
// In TimelineCred, a node with a null timestamp will never mint cred, because we don't
|
// In TimelineCred, a node with a null timestamp will never mint cred, because we don't
|
||||||
// know what period to mint it in.
|
// know what period to mint it in.
|
||||||
// When we transition to CredRank, we should remove this check.
|
// When we transition to CredRank, we should remove this check.
|
||||||
const minted = timestampMs == null ? 0 : nodeEvaluator(address);
|
const minted = timestampMs == null ? 0 : nodeEvaluator(address);
|
||||||
return {
|
return {
|
||||||
address: NodeAddress.toParts(address),
|
address: NodeAddress.toParts(address),
|
||||||
cred,
|
cred: total,
|
||||||
|
// todo: add optional filtering to reduce the data size
|
||||||
|
credOverTime: cred,
|
||||||
minted,
|
minted,
|
||||||
description,
|
description,
|
||||||
timestamp: timestampMs,
|
timestamp: timestampMs,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return {orderedNodes, plugins};
|
return {orderedNodes, plugins, intervalEndpoints};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -115,7 +124,7 @@ export type OutputV2 = {|
|
||||||
+orderedNodes: $ReadOnlyArray<OutputNode>,
|
+orderedNodes: $ReadOnlyArray<OutputNode>,
|
||||||
+plugins: $ReadOnlyArray<PluginDeclaration>,
|
+plugins: $ReadOnlyArray<PluginDeclaration>,
|
||||||
+contributors: $ReadOnlyArray<Contributor>,
|
+contributors: $ReadOnlyArray<Contributor>,
|
||||||
+intervalEnd: $ReadOnlyArray<TimestampMs>,
|
+intervalEndpoints: $ReadOnlyArray<TimestampMs>,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -122,5 +122,15 @@ describe("src/analysis/output", () => {
|
||||||
expect(cred).toEqual(credNode.total);
|
expect(cred).toEqual(credNode.total);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
it("by default, all nodes have cred over time", () => {
|
||||||
|
const {output, timelineCred} = example();
|
||||||
|
for (const {address, credOverTime} of output.orderedNodes) {
|
||||||
|
const credNode = timelineCred.credNode(NodeAddress.fromParts(address));
|
||||||
|
if (credNode == null) {
|
||||||
|
throw new Error("Can't find node");
|
||||||
|
}
|
||||||
|
expect(credOverTime).toEqual(credNode.cred);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue