diff --git a/src/core/algorithm/distributionToCred.js b/src/core/algorithm/distributionToCred.js index 88eb40a..dc384d1 100644 --- a/src/core/algorithm/distributionToCred.js +++ b/src/core/algorithm/distributionToCred.js @@ -5,6 +5,7 @@ */ import {sum} from "d3-array"; +import {toCompat, fromCompat, type Compatible} from "../../util/compat"; import {type Interval} from "../interval"; import {type TimelineDistributions} from "./timelinePagerank"; import {NodeAddress, type NodeAddressT} from "../../core/graph"; @@ -81,3 +82,28 @@ export function distributionToCred( }); return {intervalCredScores, intervals}; } + +const COMPAT_INFO = {type: "sourcecred/timelineCredScores", version: "0.1.0"}; + +export type TimelineCredScoresJSON = Compatible<{| + +intervals: $ReadOnlyArray, + // TODO: Serializing floats as strings is space-inefficient. We can likely + // get space savings if we base64 encode a byte representation of the + // floats. + +intervalCredScores: $ReadOnlyArray<$ReadOnlyArray>, +|}>; + +export function toJSON(s: TimelineCredScores): TimelineCredScoresJSON { + return toCompat(COMPAT_INFO, { + intervals: s.intervals, + intervalCredScores: s.intervalCredScores.map((x) => Array.from(x)), + }); +} + +export function fromJSON(j: TimelineCredScoresJSON): TimelineCredScores { + const {intervals, intervalCredScores} = fromCompat(COMPAT_INFO, j); + return { + intervals, + intervalCredScores: intervalCredScores.map((x) => new Float64Array(x)), + }; +} diff --git a/src/core/algorithm/distributionToCred.test.js b/src/core/algorithm/distributionToCred.test.js index 8e2375b..566362c 100644 --- a/src/core/algorithm/distributionToCred.test.js +++ b/src/core/algorithm/distributionToCred.test.js @@ -1,7 +1,7 @@ // @flow import {NodeAddress} from "../graph"; -import {distributionToCred} from "./distributionToCred"; +import {distributionToCred, toJSON, fromJSON} from "./distributionToCred"; describe("src/core/algorithm/distributionToCred", () => { const na = (...parts) => NodeAddress.fromParts(parts); @@ -128,4 +128,59 @@ describe("src/core/algorithm/distributionToCred", () => { }); }); }); + describe("to/from JSON", () => { + const exampleCred = () => { + const ds = [ + { + interval: {startTimeMs: 0, endTimeMs: 10}, + intervalWeight: 2, + distribution: new Float64Array([0.5, 0.5]), + }, + { + interval: {startTimeMs: 10, endTimeMs: 20}, + intervalWeight: 10, + distribution: new Float64Array([0.9, 0.1]), + }, + ]; + const nodeOrder = [na("foo"), na("bar")]; + return distributionToCred(ds, nodeOrder, [na("bar")]); + }; + it("satisfies round-trip equality", () => { + const json = toJSON(exampleCred()); + const result = fromJSON(json); + expect(result).toEqual(exampleCred()); + }); + it("snapshots as expected", () => { + expect(toJSON(exampleCred())).toMatchInlineSnapshot(` + Array [ + Object { + "type": "sourcecred/timelineCredScores", + "version": "0.1.0", + }, + Object { + "intervalCredScores": Array [ + Array [ + 2, + 2, + ], + Array [ + 90, + 10, + ], + ], + "intervals": Array [ + Object { + "endTimeMs": 10, + "startTimeMs": 0, + }, + Object { + "endTimeMs": 20, + "startTimeMs": 10, + }, + ], + }, + ] + `); + }); + }); });