SPB Git forge

spb/market-atlas

Public
12commits 1branches 0releases
1.1 MBsize
maindefault branch
10 days agolast push
TypeScript 96.7% SQL 1.6% CSS 0.8% JavaScript 0.5%
5.7 KB · 124 lines typescript
Raw Blame History
1/**2 * Market Atlas CLI:3 *   ma migrate                      apply SQL migrations4 *   ma seed                         reference data (countries, exchanges, holidays)5 *   ma connectors                   list connectors with rights/realtime status6 *   ma run <connector> [--file p]   run one poll (or normalize a fixture file offline) and print the batch7 *   ma replay <day> [connector]     re-run normalization on archived raw payloads of a day (no network)8 *   ma status                       quick DB/quotes/events status9 *   ma archive                      archive observation partitions older than the retention window10 *   ma backup                       pg_dump metadata to data/backups11 */12import { readFileSync, readdirSync, existsSync } from "node:fs";13import { join } from "node:path";14import { gunzipSync } from "node:zlib";15import { CONNECTORS } from "@market-atlas/connectors";16import { makeTestContext, normalizeFixture } from "@market-atlas/connector-sdk";17import { config } from "./config.js";18import { migrate } from "./db/migrate.js";19import { pool } from "./db/pool.js";20import { seed } from "./seed/index.js";21import { archiveOldPartitions, backupMetadata } from "./core/jobs.js";2223const [cmd, ...args] = process.argv.slice(2);2425async function main() {26  switch (cmd) {27    case "migrate": {28      const applied = await migrate();29      console.log(applied.length ? `applied: ${applied.join(", ")}` : "up to date");30      break;31    }32    case "seed":33      await migrate();34      await seed();35      console.log("seed ok");36      break;37    case "connectors":38      for (const c of CONNECTORS) {39        const m = c.metadata;40        console.log(`${m.id.padEnd(30)} ${m.sourceType.padEnd(12)} ${m.rightsStatus.padEnd(34)} ${m.realtimeStatus.padEnd(12)} ${m.assetClasses.join(",")}`);41      }42      break;43    case "run": {44      const id = args[0];45      const def = CONNECTORS.find((c) => c.metadata.id === id);46      if (!def) throw new Error(`unknown connector ${id}`);47      const fileIdx = args.indexOf("--file");48      if (fileIdx >= 0) {49        const path = args[fileIdx + 1]!;50        const text = readFileSync(path, "utf8");51        const payload = path.endsWith(".json") ? JSON.parse(text) : text;52        const kind = args[args.indexOf("--kind") + 1] ?? "poll";53        const batch = normalizeFixture(def, args.includes("--kind") ? kind : "poll", payload);54        console.log(JSON.stringify(batch, null, 2));55        break;56      }57      if (!def.poll) throw new Error("streaming connector: use the running service or --file");58      const ctx = makeTestContext(def, { fetchImpl: fetch, secrets: process.env as Record<string, string> });59      const raws = await def.poll(ctx);60      let total = 0;61      for (const r of raws) {62        const b = def.normalize(r);63        total += b.observations.length;64        console.log(`kind=${r.kind} observations=${b.observations.length} events=${b.events?.length ?? 0} instruments=${b.instruments?.length ?? 0} filings=${b.filings?.length ?? 0}`);65        for (const o of b.observations.slice(0, 8)) console.log(`  ${o.symbol.padEnd(12)} ${o.field.padEnd(15)} ${String(o.value).padEnd(14)} ${o.sourceTimestamp ? new Date(o.sourceTimestamp).toISOString() : "-"} ${o.realtimeStatus}`);66      }67      console.log(`total observations: ${total}`);68      break;69    }70    case "replay": {71      const day = args[0];72      if (!day) throw new Error("usage: ma replay YYYY-MM-DD [connector]");73      const dir = join(config.dataDir, "raw", day);74      if (!existsSync(dir)) throw new Error(`no raw archive for ${day}`);75      const connectors = args[1] ? [args[1]] : readdirSync(dir);76      for (const cid of connectors) {77        const def = CONNECTORS.find((c) => c.metadata.id === cid);78        if (!def) continue;79        let n = 0,80          obs = 0,81          failed = 0;82        for (const f of readdirSync(join(dir, cid))) {83          const rec = JSON.parse(gunzipSync(readFileSync(join(dir, cid, f))).toString("utf8"));84          try {85            const b = def.normalize({ connectorId: cid, sourceId: def.metadata.sourceId, kind: rec.kind, payload: rec.payload, receivedAt: Date.parse(rec.received_at) });86            obs += b.observations.length;87          } catch {88            failed++;89          }90          n++;91        }92        console.log(`${cid}: ${n} payloads → ${obs} observations, ${failed} normalization failures`);93      }94      break;95    }96    case "status": {97      const [i, q, e, o, c] = await Promise.all([98        pool.query<{ n: number }>("select count(*)::int as n from instruments"),99        pool.query<{ n: number }>("select count(*)::int as n from canonical_quotes where price is not null"),100        pool.query<{ n: number }>("select count(*)::int as n from market_events where ts > now() - interval '24 hours'"),101        pool.query<{ n: number }>("select coalesce(sum(observations),0)::bigint as n from daily_stats"),102        pool.query("select id, status, messages_total, reliability_score, last_message_at from connectors order by id"),103      ]);104      console.log(`instruments: ${i.rows[0]?.n}  quotes: ${q.rows[0]?.n}  events 24h: ${e.rows[0]?.n}  observations total: ${o.rows[0]?.n}`);105      for (const r of c.rows) console.log(`  ${String(r.id).padEnd(30)} ${String(r.status).padEnd(12)} msgs=${r.messages_total} score=${r.reliability_score ?? "-"} last=${r.last_message_at ?? "-"}`);106      break;107    }108    case "archive":109      console.log(await archiveOldPartitions());110      break;111    case "backup":112      console.log(await backupMetadata());113      break;114    default:115      console.log("usage: ma migrate|seed|connectors|run <id> [--file f --kind k]|replay <day> [connector]|status|archive|backup");116  }117  await pool.end();118}119120main().catch((err) => {121  console.error(err instanceof Error ? err.message : err);122  process.exit(1);123});124