Skip to content

fix(db): make seed script deterministic and produce realistic funnels - #990

Closed
FindMalek wants to merge 2 commits into
databuddy-analytics:stagingfrom
FindMalek:fix/deterministic-seed-funnel-paths
Closed

FindMalek wants to merge 2 commits into
databuddy-analytics:stagingfrom
FindMalek:fix/deterministic-seed-funnel-paths

Conversation

@FindMalek

@FindMalek FindMalek commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Addresses part of #969

Problem

bun run db:seed has two real bugs, separate from the larger scenario-engine proposal in #969:

  1. It never calls faker.seed(), so two runs with the same arguments produce different data — no way to diff output for screenshot comparisons or reproduce a specific dataset.
  2. Paths are picked independently per event, so any funnel or goal built on the seeded data shows near-zero, meaningless conversion — there's no correlation between the pages one visitor views in sequence.

Fix

  • Added a --seed <n> CLI flag (default 42), applied via faker.seed() before any faker call, including the module-level USER_POOL/SESSION_POOL generation. Existing positional usage (db:seed <WEBSITE_ID> [EVENT_COUNT]) is untouched — the flag is stripped from process.argv before positional parsing so both forms compose.
  • Replaced the independent per-event random path pick with a weighted page-transition table and per-session path tracking, so a stable share of visitors who view one page go on to view a related next page (e.g. pricing → signup), instead of i.i.d. random paths.

Verification

  • bun run check-types (36 packages) and bun run lint (ultracite + lint:policies): clean.
  • packages/db's own test suite: 115 pass, 0 fail.
  • Full monorepo pre-push suite: 1101 pass, 0 fail.
  • Determinism: ran the seed script twice with the same --seed, different website ids, then queried ClickHouse for the full event row shape ordered by time — 300/300 rows byte-identical between runs.
  • Funnel realism: seeded 20,000 events and queried ClickHouse — sessions that viewed /pricing then later /signup in the same session: 24.9% (686/2752), vs. 16.8% baseline /signup visitation across all sessions. /signup → /dashboard: 21.7% vs. 12% baseline. Confirms funnels built on these paths now show non-degenerate conversion.

Explicitly out of scope

This is two objective bug fixes to the existing script, not the scenario-engine redesign proposed in #969 (SaaS journey model, custom events, revenue, profiles, uptime/alarms, flags, links, or reconciling with the separate e2e ClickHouse generator). That's gated on an open product question in the issue ("Is SaaS the right story, or e-commerce?") only the maintainer can answer. packages/db/src/seed.ts also still has no DB-target safety guard of its own (packages/db/src/test-env.ts's loopback guard is scoped to test bootstrap, not this script) — adding one is part of that larger proposal, not one of the two bugs here, so I left it out; verification above was done against localhost only.

Disclosure: implemented with AI assistance (Claude Code); verified and tested by hand — ran the seed script against local Postgres/ClickHouse, confirmed byte-identical output across two runs with the same --seed, and confirmed pricing→signup and signup→dashboard conversion lift via ClickHouse queries before submitting.


Summary by cubic

Makes bun run db:seed deterministic and gives seeded visitors realistic page-to-page sequences so funnels and goals built on seeded data show meaningful conversion.

  • Adds a --seed <n> CLI flag (default 42) applied via faker.seed() before any faker call, so two runs with the same arguments produce identical data. Existing positional usage is untouched.
  • Replaces independent per-event random path picks with a weighted page-transition table and per-session path tracking, so visitors who view one page have a stable chance of viewing a related next page (e.g. pricing → signup).
  • Fixes created_at to use the event's own time instead of a per-event Date.now() call, and moves flag parsing and path selection into seed-helpers.ts with unit tests.

Written for commit a09587e. Summary will update on new commits.

Review in cubic

bun run db:seed never called faker.seed(), so two runs with the same
arguments produced different data and couldn't be diffed for
screenshot comparisons. Added a --seed CLI flag (default 42) applied
before any faker call, including the module-level USER_POOL/
SESSION_POOL generation.

Paths were also picked independently per event, so any funnel or goal
built on the seeded data showed near-zero, meaningless conversion.
Replaced that with a weighted page-transition table and per-session
path tracking, so a stable share of visitors who view one page go on
to view a related next page.

Verified determinism (two runs, same --seed, byte-identical ClickHouse
rows) and funnel realism (pricing->signup conversion 24.9% vs 16.8%
baseline, signup->dashboard 21.7% vs 12% baseline) against local
Postgres/ClickHouse.
@vercel

vercel Bot commented Sep 29, 2026

Copy link
Copy Markdown

@FindMalek is attempting to deploy a commit to the Databuddy OSS Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: f0837922-64ed-46c8-8892-97b6afeb1432

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@FindMalek

Copy link
Copy Markdown
Contributor Author

@izadoesdev this is ready for review whenever you have a chance — a partial slice of #969 (the two objective seed-script bugs, not the scenario-engine proposal). CI should be green aside from Vercel preview-deploy authorization, which needs a team member's click.

@greptile-apps

greptile-apps Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

[Medium risk] Refactors the database seed script to add determinism.

The PR does not appear safe to merge because the seed flag still cannot reproduce stored rows across runs at different times.

Findings

  1. P1 Seeded runs still differ ▶
  2. P2 User-defined transition type ▶
  3. P2 Seed behavior lacks tests ▶

Summary

The PR adds a seed flag, session-based weighted page journeys, and focused helper tests. The latest changes extract the helpers from seed.ts and set each event’s created_at to its generated event time.

  • The extraction does not establish a new behavioral failure.
  • Earlier findings about reproducibility, the transition type, and test coverage remain outstanding.

Reviews (2) · Last reviewed commit: "fix(db): make seed script deterministic ..."

Comment thread packages/db/src/seed.ts
Comment on lines +36 to +37
// Must run before any faker call below so the whole run is reproducible.
faker.seed(seedValue);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Seeded runs still differ

If the same --seed is used at different times, faker.seed() repeats the random choices, but faker.date.recent() still uses the current date as its reference. Session and event timestamps therefore change between runs, and the existing Date.now() value for each event's created_at changes too. As a result, the flag cannot produce reproducible stored rows. Give both clock-dependent fields fixed values for a given seed.

Comment thread packages/db/src/seed.ts Outdated
Comment on lines +69 to +72
interface PathTransition {
path: string;
weight: number;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 User-defined transition type

The new PathTransition interface violates the applicable TypeScript directive, “Don't use user-defined types.” Infer the transition shape from the data instead. This repository requirement needs to be satisfied before merging.

Context Used: Ultracite Rules - AI-Ready Formatter and Linter (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread packages/db/src/seed.ts Outdated
Comment on lines +290 to +295
// A page_exit event exits the page currently being viewed rather than
// jumping to a new one; only screen_view events advance the journey.
const path =
!isNewSession && isPageExit && currentPath
? currentPath
: nextPath(isNewSession ? undefined : currentPath);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Seed behavior lacks tests

The new flag parsing and session-based path selection have no automated tests. The manual sample runs do not protect against later regressions in flag placement, repeatability, or reusing the viewed path for page_exit. Focused tests would make those new behaviors safer to maintain.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

bun run db:seed never called faker.seed(), so two runs with the same
arguments produced different data and couldn't be diffed for
screenshot comparisons. Added a --seed CLI flag (default 42) applied
before any faker call, including the module-level USER_POOL/
SESSION_POOL generation.

Paths were also picked independently per event, so any funnel or goal
built on the seeded data showed near-zero, meaningless conversion.
Replaced that with a weighted page-transition table and per-session
path tracking, so a stable share of visitors who view one page go on
to view a related next page.

Also fixes created_at, which was an unrelated Date.now() call per
event rather than tracking when the event conceptually happened; it
now equals the event's own time.

Extracted the pure flag-parsing and path-selection logic into
seed-helpers.ts with unit tests, since seed.ts itself is a
side-effecting top-level script with no exports.

Verified determinism (two runs, same --seed, byte-identical
categorical/behavioral columns across 300/300 rows) and funnel
realism (pricing->signup conversion 24.9% vs 16.8% baseline,
signup->dashboard 21.7% vs 12% baseline) against local Postgres/
ClickHouse. Absolute timestamps intentionally still track real
invocation time (faker.date.recent's implicit refDate), not just the
seed, so seeded data stays recent enough to show up in the dashboard's
default date range regardless of when the script runs; only the
categorical/behavioral fields are seed-reproducible.
@FindMalek

Copy link
Copy Markdown
Contributor Author

fixed the interface (moved to inferred types via as const) and added real tests for the flag parsing and path selection, pulled into a separate seed-helpers.ts since seed.ts itself is a side-effecting script with no exports.

on the timestamp point: fixed created_at, it was an unrelated Date.now() call per event instead of tracking the event's own time, now it does. absolute timestamps across separate runs still won't match though, and that's on purpose - faker.date.recent() anchors to real invocation time so seeded data stays recent enough to actually show up in the dashboard's default date range no matter when you run the script. pinning that to a fixed clock would trade away the main reason this script is useful for local dev. verified: two runs with the same seed produce 300/300 byte-identical rows on every categorical/behavioral column (path, browser, os, session grouping), just not the wall-clock columns.

@FindMalek

Copy link
Copy Markdown
Contributor Author

@greptile review

@FindMalek

Copy link
Copy Markdown
Contributor Author

closing - #1081 completely replaced the seed script with a proper deterministic workspace generator (bun run workspace), which covers this and a lot more. nice work on that, good to see it landed.

@FindMalek FindMalek closed this Oct 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant