aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-06-04 00:42:15 -0700
committerAdam Soltys <[email protected]>2026-06-04 00:42:15 -0700
commit3db24424438322c3b341623456bd235ae64f98a1 (patch)
tree2f3064847d018aa559a32d8f1bad79578e4ab044
parent183a3cb03e9ca04a0b1433fa2850a47747de00d6 (diff)
downloadcoinos-server-3db24424438322c3b341623456bd235ae64f98a1.tar.gz
coinos-server-3db24424438322c3b341623456bd235ae64f98a1.zip
analytics: --csv flag for month-over-month trend tracking
Appends one metrics row per run to a CSV (header written if file is new): date, users, MAU/WAU, payments, volume, custody, fee revenue, bolt12 recipients, and per-type volume/txn columns. Notice goes to stderr so it doesn't pollute the saved stdout report or --json output. Drives the new monthly snapshot cron (~/scripts/analytics-cron.sh). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
-rw-r--r--scripts/analytics.ts51
1 files changed, 50 insertions, 1 deletions
diff --git a/scripts/analytics.ts b/scripts/analytics.ts
index d4c8ea54..7cefa3d4 100644
--- a/scripts/analytics.ts
+++ b/scripts/analytics.ts
@@ -3,16 +3,21 @@
// aggregates payment + balance + user data into an insight report.
//
// Usage:
-// bun scripts/analytics.ts [--days N] [--top N] [--json]
+// bun scripts/analytics.ts [--days N] [--top N] [--json] [--csv PATH]
//
// Defaults: --days 30, --top 20. Reads only the live db (redis://127.0.0.1:6379);
// archived payments (older, in `arc`) are out of scope for the trailing window.
+// --csv appends one metrics row to PATH (writing a header if the file is new),
+// for month-over-month trend tracking; the human report still prints to stdout.
import { createClient } from "redis";
+import { appendFileSync, existsSync } from "fs";
const argDays = Number(process.argv[process.argv.indexOf("--days") + 1]) || 30;
const TOP = Number(process.argv[process.argv.indexOf("--top") + 1]) || 20;
const asJson = process.argv.includes("--json");
+const csvIdx = process.argv.indexOf("--csv");
+const csvPath = csvIdx >= 0 ? process.argv[csvIdx + 1] : null;
const now = Date.now();
const cutoff = now - argDays * 24 * 60 * 60 * 1000;
@@ -169,6 +174,50 @@ regularBolt12.sort((a, b) => b[1] - a[1]);
const totalVolume = Object.values(byType).reduce((s, a) => s + a.volume, 0);
+// ---- CSV append (trend tracking) -------------------------------------------
+// One row per run. Stable column set so months line up; per-type volume/count
+// columns let us chart the lightning/liquid/bitcoin/internal/bolt12 mix over
+// time. Written before stdout output so it happens in both text and --json modes.
+if (csvPath) {
+ const typeCols = ["lightning", "liquid", "bitcoin", "internal", "bolt12", "fund"];
+ const header = [
+ "date",
+ "window_days",
+ "users_total",
+ "new_users",
+ "mau",
+ "wau",
+ "payments",
+ "total_volume_sats",
+ "custodied_sats",
+ "holders",
+ "fee_revenue_sats",
+ "bolt12_recipients",
+ "regular_bolt12",
+ ...typeCols.flatMap((t) => [`${t}_vol`, `${t}_txns`]),
+ ];
+ const row = [
+ new Date(now).toISOString().slice(0, 10),
+ argDays,
+ userRecords,
+ newUsers,
+ activeUids.size,
+ weekActive.size,
+ paymentsInWindow,
+ totalVolume,
+ totalCustodied,
+ holders.length,
+ feeRevenue,
+ bolt12Recipients.size,
+ regularBolt12.length,
+ ...typeCols.flatMap((t) => [byType[t]?.volume || 0, byType[t]?.count || 0]),
+ ];
+ const fresh = !existsSync(csvPath);
+ appendFileSync(csvPath, (fresh ? header.join(",") + "\n" : "") + row.join(",") + "\n");
+ // To stderr so it doesn't pollute the saved stdout report / --json output.
+ console.error(`[csv] appended row to ${csvPath}`);
+}
+
// ---- output ----------------------------------------------------------------
if (asJson) {