| 1 | import * as GCP from "alchemy/GCP"; |
| 2 | import * as Effect from "effect/Effect"; |
| 3 | |
| 4 | /** The warehouse the nightly job reads from. */ |
| 5 | export const Warehouse = GCP.BigQuery.Dataset("Warehouse", { |
| 6 | forceDestroy: true, |
| 7 | }); |
| 8 | |
| 9 | /** |
| 10 | * One row per order. Amounts are integer cents so the sums the job |
| 11 | * computes are exact. |
| 12 | */ |
| 13 | export const Orders = Effect.gen(function* () { |
| 14 | const dataset = yield* Warehouse; |
| 15 | return yield* GCP.BigQuery.Table("Orders", { |
| 16 | datasetId: dataset.datasetId, |
| 17 | tableId: "orders", |
| 18 | schema: [ |
| 19 | { name: "id", type: "STRING", mode: "REQUIRED" }, |
| 20 | { name: "region", type: "STRING", mode: "REQUIRED" }, |
| 21 | { name: "amountCents", type: "INTEGER", mode: "REQUIRED" }, |
| 22 | { name: "occurredAt", type: "TIMESTAMP", mode: "REQUIRED" }, |
| 23 | ], |
| 24 | }); |
| 25 | }); |
| 26 | |
| 27 | /** |
| 28 | * Where the daily summaries land, one JSON object per day under |
| 29 | * `daily/`. `forceDestroy` empties the bucket on `alchemy destroy`; drop |
| 30 | * it in production so a destroy cannot take the reports with it. |
| 31 | */ |
| 32 | export const Reports = GCP.Storage.Bucket("Reports", { |
| 33 | forceDestroy: true, |
| 34 | }); |
| 35 | |
| 36 | /** Summaries are written under this prefix. */ |
| 37 | export const SUMMARY_PREFIX = "daily/"; |
| 38 | |
| 39 | /** `daily/2026-09-27.json` for a window ending on that UTC day. */ |
| 40 | export const summaryKey = (windowEnd: Date) => |
| 41 | `${SUMMARY_PREFIX}${windowEnd.toISOString().slice(0, 10)}.json`; |
| 42 | |
| 43 | /** Per-region totals inside a {@link DailySummary}. */ |
| 44 | export interface RegionTotal { |
| 45 | region: string; |
| 46 | orders: number; |
| 47 | revenueCents: number; |
| 48 | } |
| 49 | |
| 50 | /** The JSON document the job writes for each run. */ |
| 51 | export interface DailySummary { |
| 52 | /** Start of the 24-hour window (inclusive, RFC3339). */ |
| 53 | from: string; |
| 54 | /** End of the window (exclusive, RFC3339) — the moment the run started. */ |
| 55 | to: string; |
| 56 | /** The Cloud Run execution that produced this summary, if known. */ |
| 57 | execution: string | null; |
| 58 | orders: number; |
| 59 | revenueCents: number; |
| 60 | regions: RegionTotal[]; |
| 61 | } |