{"id":50666682,"url":"https://github.com/decocms/weekstart-automation-worker","last_synced_at":"2026-06-08T07:04:08.277Z","repository":{"id":341885952,"uuid":"1171795090","full_name":"decocms/weekstart-automation-worker","owner":"decocms","description":"Cloudflare Worker responsible for managing scheduled and system events (cron jobs, automations, and internal workflows).","archived":false,"fork":false,"pushed_at":"2026-03-12T18:36:09.000Z","size":161,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-13T00:38:52.808Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/decocms.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-03T16:06:46.000Z","updated_at":"2026-03-12T18:36:12.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/decocms/weekstart-automation-worker","commit_stats":null,"previous_names":["decocms/weekstart-automation-worker"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/decocms/weekstart-automation-worker","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/decocms%2Fweekstart-automation-worker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/decocms%2Fweekstart-automation-worker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/decocms%2Fweekstart-automation-worker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/decocms%2Fweekstart-automation-worker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/decocms","download_url":"https://codeload.github.com/decocms/weekstart-automation-worker/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/decocms%2Fweekstart-automation-worker/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34051773,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-08T02:00:07.615Z","response_time":111,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-08T07:03:52.297Z","updated_at":"2026-06-08T07:04:08.269Z","avatar_url":"https://github.com/decocms.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# WeekStart Automation Worker\n\nCloudflare Worker that runs on a weekly schedule and publishes a business scorecard to Linear.\n\n## What this is\n\nA Cloudflare Worker triggered by a Cron every week. It collects raw data from source systems, computes business metrics, assembles a canonical Scorecard, validates it, and publishes it to Linear as a Document inside the configured project. Each failed run sends a structured Discord alert.\n\n## Pipeline\n\n```\ncollect → calculate → consolidate → test → publish\n```\n\nEach stage has a single responsibility and a stable output contract.\n\n| Stage | Responsibility | Output |\n|---|---|---|\n| **collect** | Fetch and normalize raw data from source APIs | `CollectStageOutput` |\n| **calculate** | Run all business blocks over collected data | `CalculateStageOutput` |\n| **consolidate** | Merge block outputs with run metadata | `Scorecard` |\n| **test** | Validate the Scorecard contract before publishing | throws on failure |\n| **publish** | Create a Linear Document in the configured project | `PublishStageResult` |\n\nOn every successful run a Discord embed is sent with the scorecard metrics and a link to the Linear document.\n\n## Architecture — Block-Based Design\n\nBusiness logic lives in `src/pipeline/blocks/`. Each block is a self-contained module with its own types and pure functions. The calculate stage is a thin orchestrator that calls each block and merges their outputs.\n\nAdding a new block (e.g. Block 4 — Costs):\n1. Create `src/pipeline/blocks/costs.ts` with types + `runCostsBlock`.\n2. Import and call it in `src/pipeline/stages/calculate.ts`.\n3. Add a `costs` field to `CalculateStageOutput`.\n4. Uncomment the `costs` line in `src/pipeline/stages/consolidate.ts`.\n\n## File Structure\n\n```\nsrc/\n├── index.ts                        # Worker entry point, HTTP/cron handlers, Discord embeds\n├── core/\n│   ├── types.ts                    # Collect-layer domain types (CollectRecord, CollectStageOutput, …)\n│   └── date.ts                     # Shared date utilities (toYearMonth, previousMonth, getYearMonth)\n├── pipeline/\n│   ├── blocks/\n│   │   ├── revenue.ts              # Block 3 — Revenue (types + logic, pure functions)\n│   │   └── costs.ts                # Block 4 — Infrastructure Costs (GCP via ClickHouse)\n│   └── stages/\n│       ├── collect.ts              # Stage 1 — Airtable fetch + normalization\n│       ├── calculate.ts            # Stage 2 — Thin orchestrator, calls each block\n│       ├── consolidate.ts          # Stage 3 — Assembles Scorecard (Scorecard type lives here)\n│       ├── test.ts                 # Stage 4 — Contract validation (throws on failure)\n│       └── publish.ts              # Stage 5 — Creates Linear Document + Scorecard Card\n└── scorecard/\n    ├── index.ts                    # Exports all scorecard modules\n    ├── nodes.ts                    # AST node types (metric, chart, section, text, metric_group)\n    ├── builders/\n    │   ├── revenue.ts              # Converts RevenueBlock → ScorecardNode[]\n    │   ├── costs.ts                # Converts CostsBlock → ScorecardNode[]\n    │   └── card.ts                 # Builds complete card HTML from Scorecard (unused)\n    └── renderers/\n        ├── markdown.ts             # Renders nodes to Linear-compatible markdown\n        ├── discord.ts              # Renders nodes to Discord embed fields\n        ├── card.ts                 # Renders nodes to HTML (unused, kept for reference)\n        └── svg.ts                  # Renders scorecard to SVG image (used by publish)\n\ntest/\n├── collect.spec.ts\n├── calculate.spec.ts               # Revenue block tests (unit + integration)\n├── revenue.spec.ts                 # Revenue-specific tests\n└── index.spec.ts\n```\n\n## Blocks\n\n### Block 3 — Revenue (`src/pipeline/blocks/revenue.ts`)\n\nComputes accounts-receivable metrics from normalized `CollectRecord[]`.\n\nAll logic is pure (no I/O, no side effects). Exclusion rules applied before every metric:\n- status `unknown` → excluded (data quality issue)\n- status `canceled` → excluded (guarded for safety)\n\n**Metrics (reference month + previous month for comparison):**\n\n| Field | Description |\n|---|---|\n| `billedAmount` | Sum of Valor for records whose NF was emitted (`invoiceCreatedAt`) this month |\n| `receivedAmount` | Sum of Valor for records whose payment was confirmed (`paidDate`) this month |\n| `expectedInflow` | All `registered`/`overdue` records with `dueDate` (effective due date) on or before the last day of this month |\n| `totalOpen` | Snapshot of all `registered`/`overdue` records across all months |\n\n**Note on `expectedInflow`:** uses the `Vencimento` column (effective due date after any renegotiation), not `Vencimento original`. `originalDueDate` is kept on `CollectRecord` for audit purposes only.\n\n### Block 4 — Infrastructure Costs (`src/pipeline/blocks/costs.ts`)\n\nTracks GCP infrastructure costs with same-period month-over-month comparisons. Data is fetched from ClickHouse (Stats Lake).\n\n**Metrics:**\n\n| Field | Description |\n|---|---|\n| `current.totalCost` | MTD accumulated cost (first N days of current month) |\n| `previous.totalCost` | Same period of previous month (first N days) |\n| `previousMonthTotal` | Full previous month total (for context) |\n| `projectedEOM` | Weighted projection to end of month (7-day rolling average) |\n| `samePeriodDiffPct` | % change vs same period last month |\n| `topServices` | Top 10 services by cost with MoM comparison |\n\n### Block 5 — Margin and Result _(planned)_\n### Block 6 — AI Block _(planned)_\n### Block 7 — Automatic Executive Summary _(planned)_\n\n## Scorecard AST (`src/scorecard/`)\n\nThe scorecard uses an AST (Abstract Syntax Tree) architecture for flexible rendering to multiple formats.\n\n### Node Types\n\n| Node | Description |\n|---|---|\n| `metric` | Labeled value with optional comparison (e.g., \"Cash In: R$ 339k vs R$ 528k\") |\n| `chart` | Image URL with alt text |\n| `section` | Groups nodes under a heading |\n| `text` | Plain text content |\n| `metric_group` | Table of related metrics (e.g., service breakdown) |\n\n### Builders\n\nConvert block outputs to nodes:\n- `revenueToNodes(revenue, config)` → revenue metrics + charts\n- `costsToNodes(costs, config)` → infrastructure metrics\n- `buildScorecardCard(scorecard, config)` → complete card HTML\n\n### Renderers\n\nConvert nodes/data to output formats:\n- `renderToMarkdown(nodes)` → Linear-compatible markdown\n- `renderToDiscordFields(nodes)` → Discord embed fields\n- `renderScorecardSvg(scorecard, config)` → SVG image string (used by publish stage)\n\n## Scorecard Card\n\nThe publish stage generates a visual scorecard card as an **SVG image** (no external dependencies). The card features:\n\n- Dark theme with gradient background (#1a1a2e → #16213e)\n- Metrics with delta indicators (green/red/neutral)\n- Sections for Revenue, A/R, and Infrastructure\n- BRL formatting with proper thousand separators\n\n**Flow:**\n```\nScorecard → renderScorecardSvg() → SVG string → Linear Upload\n```\n\nThe card image is the only content in the Finance section — no additional text/markdown is rendered.\n\n## Publish Stage (`src/pipeline/stages/publish.ts`)\n\nCreates a Linear Document in the configured project on every run. The document uses the \"Week end\" template structure with the finance scorecard card (SVG) injected in the \"We are profitable\" section.\n\n- Title format: `Week-end | DD/MM/YYYY` (run date in configured timezone)\n- Finance section shows only the visual SVG card (no text metrics)\n- Skipped gracefully if `LINEAR_API_KEY` or `LINEAR_PROJECT_ID` are not set\n\n## Requirements\n\n- Node.js (LTS recommended)\n- npm\n- Cloudflare account access (decocms)\n- Wrangler (installed as a dev dependency)\n\n## Install\n\n```bash\nnpm install\n```\n\n## Local development\n\n```bash\nnpm run dev\n```\n\nTest health:\n\n```bash\ncurl http://localhost:8787/health\n```\n\n## Manual run (local)\n\n```bash\ncurl -X POST http://localhost:8787/run \\\n  -H \"Authorization: Bearer your-key-here\"\n```\n\nForce an error alert (for testing Discord):\n\n```bash\ncurl -X POST \"http://localhost:8787/run?forceError=true\" \\\n  -H \"Authorization: Bearer your-key-here\"\n```\n\n## Tests\n\n```bash\nnpm test\n```\n\nTests run inside the Cloudflare Workers runtime via `@cloudflare/vitest-pool-workers`.\n\n## Scheduled runs (cron)\n\nConfigured in `wrangler.jsonc`. Uses Cloudflare Cron Triggers (UTC-based).\n\n## Secrets / Environment Variables\n\nRequired:\n- `RUN_KEY` — protects the manual `/run` endpoint.\n- `AIRTABLE_TOKEN` — Airtable Personal Access Token used by the collect stage.\n\nOptional:\n- `DISCORD_WEBHOOK_URL` — receives failure alerts and scorecard embeds. Worker runs normally without it.\n- `TIMEZONE` — IANA timezone for date calculations. Defaults to `America/Sao_Paulo`.\n- `WORKER_PUBLIC_URL` — base URL used to build links in Discord alerts.\n- `AIRTABLE_BASE_ID` — defaults to `applTenaA2A7ElyNl`.\n- `AIRTABLE_TABLE_ID` — defaults to `tblnpSGZ1jqQhJNnm` (Accounts Receivable).\n- `AIRTABLE_VIEW_ID` — defaults to `viwC8eUcFU9tp01dw` (Pelinsari-CeremonyWorker).\n- `LINEAR_API_KEY` — Linear Personal API Key. Publish stage is skipped if not set.\n- `LINEAR_PROJECT_ID` — UUID of the Linear project to publish documents into.\n- `STATS_LAKE_URL` — ClickHouse HTTP endpoint for GCP costs data.\n- `STATS_LAKE_USER` — ClickHouse username.\n- `STATS_LAKE_PASSWORD` — ClickHouse password.\n\n## Deploy\n\n```bash\nnpm run deploy\n```\n\nDeploy is managed via Cloudflare Workers UI (Git integration) or directly via Wrangler.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdecocms%2Fweekstart-automation-worker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdecocms%2Fweekstart-automation-worker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdecocms%2Fweekstart-automation-worker/lists"}