Files
Jyotisha/frontend/src/lib/rectification-v4/evidence-ledger.ts
T
2026-07-29 18:17:46 +08:00

46 lines
2.0 KiB
TypeScript

import { randomUUID } from "node:crypto";
import type { LifeEventRevision } from "./contracts.ts";
import { assertScorerSupports, scoreabilityFor } from "./domain-scorers.ts";
export type NewEventRevision = Omit<LifeEventRevision, "id" | "revision" | "supersedesRevisionId" | "createdAt" | "scoreability"> & {
readonly scoreability?: LifeEventRevision["scoreability"];
};
export function latestEventRevisions(revisions: readonly LifeEventRevision[]): readonly LifeEventRevision[] {
const latest = new Map<string, LifeEventRevision>();
for (const revision of revisions) {
const current = latest.get(revision.eventId);
if (!current || revision.revision > current.revision) latest.set(revision.eventId, revision);
}
return [...latest.values()].sort((left, right) => left.eventId.localeCompare(right.eventId));
}
export function chronologicalEvents(events: readonly LifeEventRevision[]): readonly LifeEventRevision[] {
return [...events].sort((left, right) => left.createdAt.localeCompare(right.createdAt)
|| left.eventId.localeCompare(right.eventId)
|| left.revision - right.revision);
}
export function appendEventRevision(
revisions: readonly LifeEventRevision[],
input: NewEventRevision,
options: { readonly now?: Date; readonly id?: string } = {},
): LifeEventRevision {
const prior = revisions.filter((value) => value.eventId === input.eventId)
.sort((left, right) => right.revision - left.revision)[0] ?? null;
const revision: LifeEventRevision = {
...input,
id: options.id ?? randomUUID(),
revision: (prior?.revision ?? 0) + 1,
scoreability: input.scoreability ?? scoreabilityFor(input.domain),
supersedesRevisionId: prior?.id ?? null,
createdAt: (options.now ?? new Date()).toISOString(),
};
assertScorerSupports(revision);
return revision;
}
export function scoreableEvents(revisions: readonly LifeEventRevision[]): readonly LifeEventRevision[] {
return latestEventRevisions(revisions).filter((event) => event.scoreability === "scoreable");
}