refactor: split brokerage operations repository and cursor codec (WealthLedger/backend#167) (#190)
This commit was merged in pull request #190.
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
import { accountHistory } from '../../investment-sync-plan';
|
||||
import { confirmedOperationRanges, type OperationCoverage,
|
||||
type OperationInterval,
|
||||
operationIntervalsOverlap,
|
||||
operationsSyncPriority,
|
||||
planOperationRead,
|
||||
uncoveredOperationIntervals } from './operations-sync-plan';
|
||||
|
||||
const UNKNOWN_AS_OF_MS = 0;
|
||||
|
||||
type WorkState = {
|
||||
errorCode: null | string;
|
||||
id: string;
|
||||
intervals: OperationInterval[];
|
||||
priority: number;
|
||||
state: string;
|
||||
};
|
||||
|
||||
type CoverageAccount = { accountId: string; openedOn: null | string };
|
||||
|
||||
// Pure derivation of the ledger read view: what was requested, what is
|
||||
// confirmed, what is missing, and which sync work is currently relevant.
|
||||
export function deriveReadCoverage(input: {
|
||||
accounts: CoverageAccount[];
|
||||
connectionMembershipAt: Date | null;
|
||||
coverage: OperationCoverage[];
|
||||
from: Date;
|
||||
instrumentUid?: string;
|
||||
movingEnd: boolean;
|
||||
now: Date;
|
||||
to: Date;
|
||||
work: WorkState[];
|
||||
}) {
|
||||
const targets = input.accounts.flatMap((account) =>
|
||||
input.from < input.to
|
||||
? [
|
||||
{
|
||||
accountId: account.accountId,
|
||||
from: input.from,
|
||||
to: input.to,
|
||||
instrumentUid: input.instrumentUid,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
);
|
||||
const plan = planOperationRead(targets, input.coverage, input.now, {
|
||||
movingEnd: input.movingEnd,
|
||||
});
|
||||
const confirmed = confirmedOperationRanges(plan.intervals, input.coverage);
|
||||
const missing = uncoveredOperationIntervals(
|
||||
plan.intervals,
|
||||
confirmed.map((range) => ({
|
||||
...range,
|
||||
instrumentUid: input.instrumentUid,
|
||||
})),
|
||||
);
|
||||
const available = Boolean(input.connectionMembershipAt);
|
||||
const complete = available && !missing.length;
|
||||
const relevant = input.work.filter((work) =>
|
||||
work.intervals.some((interval) =>
|
||||
targets.some((target) => operationIntervalsOverlap(interval, target)),
|
||||
),
|
||||
);
|
||||
const work =
|
||||
relevant.find((work) => work.state === 'running') ??
|
||||
relevant.find((work) => work.state === 'failed') ??
|
||||
null;
|
||||
const sources = input.coverage.filter((row) =>
|
||||
targets.some(
|
||||
(target) =>
|
||||
operationIntervalsOverlap(row, target) &&
|
||||
(!row.instrumentUid || row.instrumentUid === target.instrumentUid),
|
||||
),
|
||||
);
|
||||
const lastSuccessfulAt =
|
||||
sources
|
||||
.map((row) => row.fetchedAt)
|
||||
.sort((a, b) => a.valueOf() - b.valueOf())[0] ??
|
||||
(complete ? input.connectionMembershipAt : null) ??
|
||||
null;
|
||||
const history = planOperationRead(
|
||||
accountHistory(input.accounts, input.now),
|
||||
input.coverage,
|
||||
input.now,
|
||||
{ movingEnd: true },
|
||||
);
|
||||
const knownBoundary = input.accounts.every(
|
||||
(account) => account.openedOn !== null,
|
||||
);
|
||||
return {
|
||||
targets,
|
||||
plan,
|
||||
confirmed,
|
||||
missing,
|
||||
available,
|
||||
complete,
|
||||
work,
|
||||
lastSuccessfulAt,
|
||||
history,
|
||||
knownBoundary,
|
||||
};
|
||||
}
|
||||
|
||||
export function coverageAsOf(
|
||||
plan: ReturnType<typeof planOperationRead>,
|
||||
accountIds: string[],
|
||||
): null | string {
|
||||
if (
|
||||
accountIds.length &&
|
||||
accountIds.every((accountId) =>
|
||||
plan.intervals.some(
|
||||
(interval) =>
|
||||
interval.accountId === accountId && interval.asOf,
|
||||
),
|
||||
)
|
||||
) {
|
||||
return new Date(
|
||||
Math.min(
|
||||
...accountIds.map((accountId) =>
|
||||
Math.max(
|
||||
...plan.intervals
|
||||
.filter((interval) => interval.accountId === accountId)
|
||||
.map((interval) => interval.asOf?.valueOf() ?? UNKNOWN_AS_OF_MS),
|
||||
),
|
||||
),
|
||||
),
|
||||
).toISOString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function historyState(
|
||||
knownBoundary: boolean,
|
||||
history: ReturnType<typeof planOperationRead>,
|
||||
work: WorkState[],
|
||||
coverageCount: number,
|
||||
): 'complete' | 'failed' | 'loading' | 'missing' | 'partial' {
|
||||
if (
|
||||
work.some(
|
||||
(work) => work.state === 'running' && work.priority > operationsSyncPriority.screen,
|
||||
)
|
||||
) {
|
||||
return 'loading';
|
||||
}
|
||||
if (
|
||||
work.some(
|
||||
(work) => work.state === 'failed' && work.priority > operationsSyncPriority.screen,
|
||||
)
|
||||
) {
|
||||
return 'failed';
|
||||
}
|
||||
if (
|
||||
knownBoundary &&
|
||||
history.intervals.every(
|
||||
(interval) => interval.availability === 'complete',
|
||||
)
|
||||
) {
|
||||
return 'complete';
|
||||
}
|
||||
return coverageCount ? 'partial' : 'missing';
|
||||
}
|
||||
|
||||
export function serializeRange(range: { accountId: string; from: Date; to: Date }) {
|
||||
return { accountId: range.accountId, from: range.from.toISOString(), to: range.to.toISOString() };
|
||||
}
|
||||
Reference in New Issue
Block a user