Live demo · SQLite standing in for a warehouse, real data

One schema for signal, brief, and draft — queried with plain SQL

The first three demos in this portfolio each produce real output on their own: a signal score, a pre-call brief, a drafted message. This one gives all three outputs a single relational shape — the same modeling problem a real GTM data warehouse solves — and answers it with SQL, not another API call.

gtm-warehouse-demo— SQLite, loaded from real captured API responses
Scale note

8 companies is a demo-scale dataset, not a statistically meaningful sample. The queries below demonstrate schema design and SQL fluency — not a production warehouse or a claim about the broader dev-tool market.

Why this, not Snowflake

I don't have production Snowflake/Databricks experience, and I'm not going to pretend a weekend demo gives me it. What I can show honestly is the actual skill underneath that line on a job description: designing a normalized schema for GTM data (accounts → signals → activities) and writing SQL that answers real questions against it. SQLite is the honest stand-in — same relational shape, zero infrastructure, nothing to fake.

The schema

Full file: schema.sql — 4 tables, one row per real event captured by the other three demos in this portfolio.

companies · signals · briefs · drafts

tablerowssourced from
companies8github.com/<org>/<repo> metadata, signal-engine-demo
signals8commit-velocity scores, signal-engine-demo / signal_score.py
briefs1generated brief, account-brief-demo / brief_generator.py
drafts1drafted outreach message, signal-engine-demo / draft_outreach.py

Six queries against the real data

Full file: queries.sql — run locally with python3 load_data.py && sqlite3 warehouse.db < queries.sql. Every result below is the actual output of that command against the real dataset.

query 1 — rank by signal score

SELECT c.repo, s.score, s.velocity_change_pct FROM companies c JOIN signals s ON s.company_id = c.company_id ORDER BY s.score DESC;

reposcorevelocity_change_pct
ComposioHQ/composio183.3183.3
mem0ai/mem061.761.7
block/goose42.242.2
browser-use/browser-use0.0-91.7
e2b-dev/E2B0.0-15.8
langflow-ai/langflow0.0-77.7
crewAIInc/crewAI0.0-21.2
letta-ai/letta0.0null
query 3 — full pipeline trace: how far did each company get

JOIN signals → LEFT JOIN briefs → LEFT JOIN drafts, ordered by score

reposcorehas_briefhas_draft
ComposioHQ/composio183.3noyes
mem0ai/mem061.7yesno
block/goose42.2nono
browser-use/browser-use0.0nono
e2b-dev/E2B0.0nono
langflow-ai/langflow0.0nono
crewAIInc/crewAI0.0nono
letta-ai/letta0.0nono

This is the honest state of the pipeline: only 2 of 8 companies were ever taken past the scoring step, because the brief and draft demos were each run once, by hand, against a top-scoring company — exactly what "no fabrication" looks like when the dataset is this small.

query 6 — stars vs. score: does bigger mean faster-accelerating?

SELECT c.repo, c.stars, s.score FROM companies c JOIN signals s ... ORDER BY c.stars DESC;

repostarsscore
langflow-ai/langflow151,1800.0
browser-use/browser-use102,8170.0
mem0ai/mem060,12661.7
crewAIInc/crewAI54,9400.0
block/goose50,66942.2
ComposioHQ/composio29,096183.3
letta-ai/letta23,6580.0
e2b-dev/E2B12,8440.0

The two most-starred repos in the set are both decelerating right now — star count is a lagging popularity signal, not a proxy for current momentum. That's the actual argument for signal-based targeting over static list size, made by the join, not by me asserting it.

2 more in the repo

Queries 2, 4, and 5 (acceleration-bucket counts, the full signal→draft trace for Composio, and raw commit-volume totals) are in queries.sql with the same real-output guarantee — trimmed here for length, not cherry-picked for the result.

Extension: closed-loop attribution

Full files: schema_attribution.sql, generate_attribution_data.py, attribution_queries.sql.

Synthetic data

Unlike the tables above, channels/touches/opportunities are synthetic — these 8 open-source repos never ran through a real sales funnel. Rather than fabricate a fake case study, generate_attribution_data.py documents every generation rule in its docstring: 6 channels reused from the real ones run in a prior GTM role, a deterministic seeded touch sequence per company, and a won/lost/open status derived directly from each company's already-real signal score. Rerun it and the output is identical — nothing here is hand-picked per row.

This models the piece of GTM engineering the rest of this warehouse doesn't touch: the plumbing that tells you whether a channel is actually moving revenue. Four attribution models — first-touch, last-touch, linear, and U-shaped (40/40/20) — computed with plain SQL over the same 3 won deals.

model comparison — same 3 won deals, 4 different answers

first-touch vs. last-touch vs. linear vs. U-shaped, per channel

channelfirst-touchlast-touchlinearU-shaped
linkedin_outbound$10,078$7,666$6,083$7,582
warm_list_reactivation$7,666$0$3,661$4,113
cold_email$0$5,234$3,661$2,860
affiliate_program$0$4,844$2,422$2,422
agency_partner$0$0$1,917$767

linkedin_outbound wins under every model — but the #2 channel flips depending which model you run: warm_list_reactivation by first-touch, cold_email by last-touch. And agency_partner gets zero credit under either single-touch model despite being a real touch in the funnel — it only shows up once linear or U-shaped credit is run. That's the actual argument for running more than one attribution model before cutting a channel's budget, made by the query, not by me asserting it.

Why I built this

The other three demos in this portfolio (signal-engine-demo, account-brief-demo, the mem0 audit) each produce a real result in isolation. GTM engineering also means giving separate outputs one queryable home — this is that step, done honestly at the scale the underlying data actually supports.

Let's talk