Back to Projects

GovChime Analytics Platform

Federal Contract Data Platform — ETL, CDC & CQRS

Overview

Role: Main developer in a team of four senior engineers — designed and built the core platform end to end: the SmartSync ETL and its move to Temporal, the CDC projection into the ClickHouse read model and the PostgreSQL mirror, the Express + Sequelize API, the Next.js frontend on Cloudflare Workers, the Python AI content service, and CI/CD.

Data platform. Federal contract data from SAM.gov runs through a Temporal-orchestrated ETL into PostgreSQL and, via CDC, into a ClickHouse CQRS read model. SmartSync holds ~7.06M contracts, ~1.48M vendors and ~94.4M award transactions from SAM.gov and USAspending.gov, used by a few hundred daily active users.

SmartSync on Temporal. In production since 2026-09-25: 32 schedules on 6 task queues run extract → transform → load → verify per window for opportunities, entities and contract awards, plus enrichment (descriptions, attachments, SBA certifications) and guard jobs. The legacy scheduler was deleted, and replay tests of production histories run in CI. A 13-key SAM.gov pool with a usage ledger keeps every pipeline inside the API quota.

Completeness, proven. A green run only proves execution, so each dataset is checked against a second copy of the source: 0 of 80,922 active notices and 0 of 733,627 FY2025–26 archive notices missing against SAM.gov's own files. The opportunities sync that once reported success while holding 17.5% of notices is gone with it.

CQRS read model and mirror. PostgreSQL is the write model; one Temporal coordinator per table projects new rows into ClickHouse by watermark (query-based CDC), and a daily reconciliation checks checksum parity — contracts, entities and 94.4M award transactions match. A logical-replication PostgreSQL → PostgreSQL mirror (pgoutput) runs as Temporal Workflows with hourly health and daily hash checks. A teammate introduced ClickHouse and wrote the first copy scripts; I rewrote them with checkpoints and a parity check, moved them onto Temporal, and built the mirror.

Backend and Next.js on Cloudflare. A headline query went from 13.6 s to ~1 s (a trigger-maintained indexed slug column instead of a per-row function) and ClickHouse queries from 2.2 s to 0.27 s. I added a single-flight guard to the Express API's in-memory response cache against query stampedes and aligned the React Query, ISR and API cache TTLs at 10 minutes, cutting worst-case feed lag from ~3–5 h to ~10 min by design. The Next.js 16 frontend runs on Cloudflare Workers via OpenNext with an R2 page cache; I added the D1 tag cache that makes on-demand revalidation work and edge-cached the sitemaps. The backend moved from Hetzner to OVHcloud in August 2026, when a change of stakeholders consolidated hosting there.

AI content. A smaller Python service drafts blog, social and SEO copy from contract data behind a human review queue and a guard against invented figures, on Cloudflare Workers AI.

Engineering: Data Platform, AI & Frontend

Main developer on GovChime: the data platform, the AI features, and the frontend performance work, one view at a time.

A Temporal-orchestrated ETL from SAM.gov into PostgreSQL, projected by CDC into a ClickHouse read model and mirrored to a second PostgreSQL — every hop is checked against a second copy of the data.

SAM.gov APIs + files · USAspending.gov
0

Orchestrate — Temporal

Temporal is the control plane: Schedules start Workflows, rows never pass through it. Production cut over on 2026-09-25 and the legacy scheduler was deleted.

32 Temporal Schedules6 task queues2 production workers (pipelines · mirror)replay tests of production histories in CI
1

Extract

Every SAM.gov call runs on one task queue with a queue-wide rate limit and leased API keys.

13 SAM.gov API keysKeyProvider + usage ledger~8,050 calls/day poolraw JSON into staging tables
2

Transform + Load

One canonical transform per source, then change-aware idempotent upserts into PostgreSQL — a failed window just reruns.

staging → transform → loadchange-aware upsert (IS DISTINCT FROM)natural-key idempotencyopportunities · entities · contract awards
3

Verify

A green run proves execution. Completeness is proven against a second copy of the source.

verify gates on every rundaily key reconciliationcensuses against SAM.gov files
verify gatesevery rundistinct rows = the API's declared total
reconciliationdailystaged keys of each closed day vs the table
censusdaily · weeklySAM.gov's own notice files, no API key
a gap starts a repair run of the pipeline for the missing days
Completeness over error-catching

A green run proves execution, not completeness — every dataset is checked against a second copy of the source.

0 of 80,922 active notices and 0 of 733,627 FY2025–26 archive notices missing against SAM.gov's own files; PostgreSQL and ClickHouse checksums match on 94.4M award transactions.

4

Enrich

Follow-up pipelines fill what the source APIs leave out, started by each source run and backed by their own Schedules.

opportunity descriptionsattachmentsSBA certificationsentity stub fill
Write model · PostgreSQL 16

~7.06M contracts · ~1.48M entities · every pipeline writes here

Read model · ClickHouse

~94.4M award transactions · fed by CDC · analytics reads here

5

CQRS read model — ClickHouse

PostgreSQL stays the write model; one Temporal coordinator per table projects new rows into ClickHouse, and a daily reconciliation proves parity.

query-based CDC (watermark)checksum parity: 94.4M award transactions7.06M contracts · 1.48M entities14 ClickHouse MVs, health-checked hourly
6

PostgreSQL mirror

A logical-replication mirror of production into a second PostgreSQL, run as Temporal Workflows on its own worker.

snapshot + streaming + guardsMIRROR_HEALTH (hourly)MIRROR_RECONCILE (daily count + hash)pgoutput · 4 mirrored tables
7

Serve

Express + Sequelize API reading the ClickHouse read model, with a ClickHouse → Postgres fallback chain that was later simplified.

headline query 13.6 s → ~1 sClickHouse 2.2 s → 0.27 ssingle-flight guard in the API behind the HTTP cache32 Postgres MVs dropped · ClickHouse MVs 31 → 1478 filters · ~242 endpoints
8

Present

Next.js 16 frontend on Cloudflare Workers via OpenNext, with cache freshness matched to the sync cadence.

R2 page cache + D1 tag cacheon-demand revalidationReact Query / ISR / API TTLs aligned at 10 minedge-cached sitemaps (Cache API)
A few hundred daily active users
Cross-cutting concerns

9. Guards & alerts

cross-cutting · spans every layer

Scheduled guards watch the pipelines themselves; alerts are stored and shown in the admin UI.

Temporal health (every 10 min)freshness · tombstones · SLO burn rateAPI key-pool healthalert log in the SLED admin

10. Delivery

cross-cutting · spans every layer

Tests gate every deploy; each push to main deploys SmartSync, then the app.

unit + integration tests gate the deploycontainer health asserted after deployGitHub Actions (self-hosted runner)

11. Infrastructure

cross-cutting · spans every layer

Self-hosted compute on OVHcloud, fronted by Cloudflare.

OVHcloud (Docker, Komodo)self-hosted Temporal Serviceself-managed PostgreSQL 16Cloudflare Workers

Key Features

  • SmartSync ETL on Temporal since 2026-09-25: 32 schedules on 6 task queues, extract → transform → load → verify per window; legacy scheduler deleted
  • Completeness checked against SAM.gov's own files: 0 of 80,922 active notices and 0 of 733,627 FY2025–26 archive notices missing
  • CQRS: PostgreSQL write model → ClickHouse read model via watermark-based CDC, daily checksum parity over ~94.4M award transactions
  • PostgreSQL → PostgreSQL logical-replication mirror as Temporal Workflows, with hourly health and daily hash reconciliation
  • Headline query 13.6 s → ~1 s, ClickHouse queries 2.2 s → 0.27 s; single-flight stampede guard on the API response cache
  • Next.js 16 on Cloudflare Workers (OpenNext) with R2 page cache and D1 tag cache; cache TTLs aligned at 10 min; Python AI content service

Tech Stack

Data Platform / ETL

TemporalSAM.gov APIUSAspending.govCDC (watermark)CQRSLogical ReplicationIdempotent UpsertsChecksum ParityCount Audits

Database & OLAP

PostgreSQL 16ClickHouseMaterialized ViewsOLAP

Backend

Express 4SequelizeNode.jsTypeScriptZodREST APInode-cacheiron-session + JWTStripePostHog

Frontend & Edge

Next.js 16React 19Cloudflare WorkersOpenNextR2D1TanStack Query 5TanStack Table 8Radix UIRechartsTailwind CSS

Infrastructure

DockerKomodoOVHcloudGitHub ActionsSelf-Hosted Runner

AI & Dev Tools

PythonCloudflare Workers AIAnthropic APIClaude CodeMCPPlaywright

Challenges & Solutions

An ETL That Must Prove It Is Complete

Problem

The SAM.gov pipelines ran on a hand-rolled scheduler, and a green run only proved that code executed. The opportunities sync once reported success while holding only 17.5% of notices — API-key quota starvation, plus a completeness check that passed when nothing was proven.

Solution

Rebuilt SmartSync on Temporal: 32 schedules on 6 task queues, per-window extract → transform → load → verify, a 13-key SAM.gov pool with a usage ledger, and replay tests of production histories in CI. Completeness is checked against SAM.gov's own files — 0 of 80,922 active notices and 0 of 733,627 FY2025–26 archive notices missing. Production cut over on 2026-09-25 and the legacy scheduler was deleted.

Keeping ClickHouse and a Mirror in Exact Sync

Problem

Analytics read from ClickHouse, and a second PostgreSQL needs a live copy of production. Both drift silently if nothing proves they match.

Solution

Rewrote the teammate's first PostgreSQL → ClickHouse copy scripts with checkpoints and a parity check and moved them onto Temporal. PostgreSQL is the CQRS write model; a coordinator per table projects new rows into ClickHouse by watermark (query-based CDC), and a daily reconciliation compares counts, distinct ids and checksums — contracts, entities and 94.4M award transactions match. The PostgreSQL mirror uses logical replication (pgoutput; snapshot, then streaming) as Temporal Workflows, with hourly slot-lag checks and a daily count-and-hash reconciliation.

Slow Queries and Stampedes on ~94M Rows

Problem

A headline Postgres query took 13.6 s because it applied a slug function to every row of an 89M-row table, which forces a full scan. Separately, concurrent requests for the same heavy ClickHouse count could pile up into several multi-GiB scans at once.

Solution

Filtered on a trigger-maintained indexed slug column with a 2-year window instead (13.6 s → ~1 s) and on an agency-code lookup in ClickHouse (2.2 s → 0.27 s). Added a single-flight guard to the API's in-memory response cache, so concurrent callers share one query and a failed refresh serves the last good value. Built the ClickHouse → Postgres fallback chain on the team's ClickHouse read model, then simplified it — 32 Postgres MVs dropped, ClickHouse MVs 31 → 14.

Next.js Caching on Cloudflare Workers

Problem

The Next.js 16 frontend runs on Cloudflare Workers via OpenNext with an R2 page cache. With a long-lived regional cache and no tag cache, a published page kept serving the stale copy and on-demand revalidation did nothing; in the worst case feed pages lagged the database by ~3–5 hours.

Solution

Switched the regional cache to short-lived mode and added a D1 tag cache, so revalidatePath and revalidateTag take effect after a publish; aligned the React Query, ISR and API cache TTLs at 10 minutes (worst-case feed lag ~10 min by design); and edge-cached the sitemaps with the Cloudflare Cache API.

Silent Data Loss in Ingestion

Problem

For 10 days the SAM.gov sync reported success while silently dropping ~95–99% of records: SAM.gov changed `totalRecords` from a number to a string, the pipeline read it as null, `null ?? 0` coerced it to 0, and the completeness check `0 >= floor(0 × 0.95)` always passed — no exception thrown, no alert.

Solution

A `parseTotalRecords()` helper that accepts both string and number shapes and treats any NaN / 0 / missing value as an API error (triggering key rotation) rather than coercing to 0. The deeper fix was completeness verification comparing expected vs actual counts — `no exception thrown` does not mean the data is correct.

AI Content Without Invented Figures

Problem

AI-generated blog, social and SEO copy about federal contracts must never state numbers the data doesn't support.

Solution

A Python AI content service generates the copy from contract data, a guard strips any AI-stated figure not present in the source data, and every piece goes through a human review queue. It moved from the Anthropic API to Cloudflare Workers AI (Llama 4 Scout) in September 2026.

Key Achievements

ETL on Temporal
32 schedules, 6 task queues, in production since 2026-09-25
0 of 80,922
Active notices missing, checked against SAM.gov's own file
94.4M Rows in Parity
PostgreSQL = ClickHouse checksums on award transactions
13.6 s → ~1 s
Headline query; ClickHouse queries 2.2 s → 0.27 s