Skip to content

Daily ledger

Trigger: BullMQ cron at 23:30 America/Mexico_City (30 23 * * *). Worker: workers/daily-snapshots.ts. Manual trigger: npx tsx workers/daily-snapshots.ts --run-once.

Replaces Tonder's dailyLedgerCronHandler + XLSX dispatch. Vecnet doesn't render an XLSX every night — snapshot rows are the durable record and the admin UI reads them live. XLSX exports are on-demand (per settlement, not per day).

What runs

lib/snapshots/build.ts:buildSnapshotsForDay({ date }):

  1. List every Account where active = true.
  2. For each account, two groupBy queries:
    • Lifetime sums: direction × amountCents over every entry with createdAt < dayEnd. Produces balanceCents per the account type (asset/expense = DR−CR; liability/revenue/equity = CR−DR).
    • Day's sums: same shape but bounded gte dayStart, lt dayEnd. Produces inflowsCents + outflowsCents per type.
  3. BalanceSnapshot.upsert on (accountId, date) — idempotent on re-runs.

Day boundaries are computed in America/Mexico_City. The cron fires at local 23:30 so the snapshot reflects the day that's about to close (the last 30 minutes are negligible operationally).

Why 23:30 local (not 10:00 UTC like Tonder)

Tonder's worker writes for the day that just ended. Vecnet writes for the day that's about to end, then reuses the row as the current day's "as of" reading in the morning. The latency difference is irrelevant for treasury reporting; the local time lets finops sanity-check just before shift-change without a "wait for the snapshot to drop at 4 AM" gap.

Surfaces that read from BalanceSnapshot

  • /finances/snapshots — pivot or single-account view, date-range filtered. The pivot caps at 14 columns for readability.
  • /finances/accounts/[id] — the last 7 rows render as a text-bar sparkline next to the live current balance.

Redis-optional pattern

ts
if (process.argv.includes("--run-once")) {
  await runOnce();
  return;
}
if (!process.env.REDIS_URL) {
  logger.warn("REDIS_URL is not set — daily-snapshots worker is a no-op.");
  return;
}
// otherwise wire up BullMQ scheduler + worker

Same pattern in workers/settlement-scheduler.ts and workers/rolling-release.ts. Without Redis the workers exit cleanly so a fresh clone works in local dev without infrastructure overhead.

Vecnet — Build Spec v0.2 · Obsidian Terminal