···2233[executive dashboard](https://prefect-metrics.waow.tech/d/executive-overview/executive-overview?orgId=1&from=now-6h&to=now&timezone=browser) · [hub](https://hub.waow.tech)
4455+see [docs/hub.md](docs/hub.md) for how the data pipeline and hub frontend work.
66+57<details>
68<summary>deployment</summary>
79
+97
docs/hub.md
···11+# hub
22+33+action item dashboard at [hub.waow.tech](https://hub.waow.tech). aggregates issues and PRs from github and [tangled.org](https://tangled.org), scores them by importance, and generates a daily briefing with an LLM.
44+55+## data sources
66+77+two ingestion flows run hourly at :00, writing to separate DuckDB tables:
88+99+**gh-notifications** (`flows/gh_notifications.py`) — fetches github notifications (issues + PRs) and open items authored by `zzstoatzz` via the search API. each issue is cached by repo+number for 24h. persists to `raw_github_issues`.
1010+1111+**tangled-items** (`flows/tangled_items.py`) — fetches issues, PRs, and comments from the tangled.org PDS (`pds.zzstoatzz.io`) via AT Protocol's `com.atproto.repo.listRecords`. no auth needed — records are public. targets repos: zat, zlay, plyr.fm, at-me, pollz, typeahead. persists to `raw_tangled_items`.
1212+1313+## pipeline
1414+1515+```
1616+github API ──► gh-notifications ──► raw_github_issues ──┐
1717+ (hourly :00) │
1818+ ▼
1919+ ┌─── enrich (dbt) ───┐
2020+ │ (hourly :05) │
2121+ └────────────────────┘
2222+ │
2323+tangled PDS ──► tangled-items ───► raw_tangled_items ────┘
2424+ (hourly :00) │
2525+ ▼
2626+ hub_action_items
2727+ (mart, top 200)
2828+ │
2929+ ┌──────────────┼──────────────┐
3030+ ▼ ▼ ▼
3131+ curate /api/cards.json +page.svelte
3232+ (hourly :10) (SSR loader)
3333+ │
3434+ ▼
3535+ briefing.json ──► /api/briefing.json
3636+```
3737+3838+## flows
3939+4040+| deployment | schedule | what it does |
4141+|---|---|---|
4242+| `diagnostics` | `*/5 * * * *` | prints system info — canary for worker health |
4343+| `gh-notifications` | `0 * * * *` | github notifications + authored open issues/PRs → `raw_github_issues` |
4444+| `tangled-items` | `0 * * * *` | tangled.org issues/PRs/comments → `raw_tangled_items` |
4545+| `enrich` | `5 * * * *` | dbt build: staging → enrichment → mart. concurrency limit 1. runs under python 3.13 (dbt-core compat) |
4646+| `curate` | `10 * * * *` | loads top 200 scored items, sends to claude haiku 4.5 via pydantic-ai, writes `briefing.json` |
4747+| `cleanup` | `0 2 * * 0` | deletes old terminal flow runs (completed, failed, cancelled, crashed) older than 30 days |
4848+4949+all flows run in the `kubernetes-pool` work pool. code is pulled at runtime via `git clone` from tangled.sh (github fallback). deps install via `uv run --with 'my-prefect-server @ git+...'`. deployments are registered by CI on every push to main.
5050+5151+## dbt layer
5252+5353+project lives in `analytics/`. DuckDB database at `/var/lib/prefect-analytics/analytics.duckdb`.
5454+5555+| model | type | description |
5656+|---|---|---|
5757+| `stg_github_issues` | view | dedup `raw_github_issues` by (repo, number), keep most recent fetch |
5858+| `stg_tangled_items` | view | dedup `raw_tangled_items` by `at_uri`, exclude comments, keep most recent |
5959+| `int_github_issues_scored` | table | scoring: recency (30-day decay) x engagement (log scale) x label multiplier (bug=1.5) x contributor weight |
6060+| `int_tangled_items_scored` | table | scoring: recency (30-day decay) x 0.5 (no engagement data) x contributor weight |
6161+| `hub_action_items` | mart | union of both scored tables, ordered by `importance_score` desc, limit 200 |
6262+6363+contributor weights come from the `known_contributors` seed (zzstoatzz + zzstoatzz.io at 2.0x).
6464+6565+## curation
6666+6767+the `curate` flow runs at :10 each hour after `enrich` refreshes the mart. it:
6868+6969+1. snapshots DuckDB to `/tmp` (bypass exclusive flock)
7070+2. loads top 200 items from `hub_action_items`
7171+3. sends them to claude haiku 4.5 with a system prompt that groups by actionability
7272+4. writes a structured `Briefing` (headline, 2-5 themed sections with accent colors, icons, priority) to `briefing.json`
7373+7474+briefing model is defined in `packages/mps/src/mps/briefing.py`.
7575+7676+## frontend
7777+7878+SvelteKit app in `web/`. bun runtime, node adapter, port 3000.
7979+8080+### routes
8181+8282+| route | description |
8383+|---|---|
8484+| `/` | SSR page — loads stats, cards, and briefing in parallel |
8585+| `/api/cards.json` | JSON array of scored action items from `hub_action_items` |
8686+| `/api/briefing.json` | curated briefing object from `briefing.json` on disk |
8787+| `/api/stats.json` | aggregate counts from `raw_github_issues` (tracked, open, with_reactions, repos) |
8888+8989+the frontend reads DuckDB through a snapshot copy (`/tmp/hub_analytics_snapshot.duckdb`) that refreshes when the source file's mtime changes. all queries are read-only.
9090+9191+### deployment
9292+9393+```bash
9494+just web # build + push + deploy
9595+```
9696+9797+this runs: docker build (bun image) → push to `atcr.io/zzstoatzz.io/hub:latest` → apply k8s manifests → rolling restart. the hub pod mounts the analytics PVC at `/analytics` and reads `DUCKDB_PATH=/analytics/analytics.duckdb`.