- Web
VentureScope
Financial planning and scenario simulation for startups, from assumptions to board pack: forecasts, scenarios, risk checks and board-ready reports, with a record of every change that nobody can quietly rewrite.
- Personal project, full-stack
- 52 pages, 24 database tables, 16 migrations
- 282 unit and 32 browser test files, with accessibility checks
- Next.js, React, TypeScript, Tailwind CSS, Prisma, PostgreSQL, Auth.js, Recharts, Vitest, Playwright

The problem
Early-stage companies often plan in spreadsheets. A spreadsheet is quick, but it is easy to change an old number without noticing, there is no clear record of who changed what, and different tabs can disagree. I wanted to see how far a web app can go in the other direction: one calculation engine, one place for the assumptions, and a record that nobody can quietly rewrite.
The approach
A founder enters assumptions about customers, prices, costs and funding, and one deterministic engine turns them into monthly and yearly forecasts, a profit and loss statement, cash flow, runway and valuation figures. Base, Bull, Bear and custom scenarios run through the same engine, so the differences come only from the assumptions that were changed.
Once the company is running, the team loads real monthly results, compares them with the plan, explains the variances, sets KPI targets and closes finished months. Decisions, approvals, board reviews and board packs sit on top of the same numbers, and models live in workspaces with owner, editor and viewer roles, invitation links and saved versions.
A service layer sits between the pages and the database: pages call services for imports, approvals and closing a month, and the demo data script calls the same services, so the audit trail it leaves is real. Everything runs on a free stack with no paid APIs.
Who uses it
- Founders
- Enter the assumptions and see how long the cash lasts and what happens if the plan is wrong.
- Finance teams
- Load monthly results, explain the variances, close finished months and ask for approvals.
- The board
- Receives a board pack built from the same numbers, printed or saved as a PDF.
What it does
- Assumptions for revenue, expenses and financing, and a planning timeline
- Monthly and yearly forecasts, profit and loss, cash flow, runway and customer analytics from one engine
- Base, Bull, Bear and custom scenarios, a scenario comparison and a sensitivity lab
- Actuals against the forecast, forecast accuracy, variance review and a KPI scorecard
- A data import center for actuals, KPI targets and variance comments, with a preview before anything is saved
- A financial close that locks finished months, with a record of every close and reopen
- A liquidity risk center, capital planning, a decision center and planning templates
- An approval center with a separate requester and approver, and an activity center that nobody can edit
- Saved versions, a model diff, a board room and a printable board pack
- A portfolio dashboard, consolidation across models and executive reports as PDF
- Workspaces with owner, editor and viewer roles, and invitation links
- A read-only live demo, light and dark themes, a command palette and phone layouts
Where the engineering is
History that the database refuses to rewrite
Two rules live in PostgreSQL as triggers: the activity ledger is append-only, and the numbers of a closed month cannot be changed. Closing a month blocks every insert, update and delete of its actuals at the database boundary, so a bug or a missing check in the app cannot break the rule.
Reopening a month is a deliberate action, with a reason and an approval, and only a workspace owner can do it. Every close and every reopen stays on the record.
, Open the full-size image (opens in a new tab)Writing nine months of believable history
The demo company needs nine months of history, but the ledger cannot be edited and a closed month cannot be changed. The obvious plan, create everything and then move the dates, is refused by the database itself.
So the demo data script sets a story clock before every step, and each row and each event is written with its own date from the start. A check on the first account stops the run early if the clock does not work, before anything permanent is written.
, Open the full-size image (opens in a new tab)Imports that match how people really save files
Finance teams do not send perfect CSV files. They save from Excel in other languages, write months in many ways, start the table below a title and name the columns as they like. The data import center reads those files and shows a preview with the problems first.
Nothing is written until an explicit apply. The import respects closed months, writes everything in one step and keeps a history of what was loaded.
, Open the full-size image (opens in a new tab)One engine, so two pages never disagree
Forecast, profit and loss, cash flow, scenarios, reports and the board pack all read the same calculated numbers. The engine is deterministic: the same assumptions always give the same result, so any change can be traced to the input that caused it.
Money is calculated with decimal.js, not with floating-point numbers. The scenario comparison picks any scenario as the reference and shows which drivers differ and how far each one is from it.
, Open the full-size image (opens in a new tab)Sign-in without email, and decisions that need a second person
The app sends no emails, so a forgotten password is reset with a recovery key calculated from the secret, the account and the current password hash. Passwords are hashed, sessions can be ended from the account page, sensitive routes are rate limited, every request gets an id, cross-origin requests that change data are rejected, and security headers are set on every page.
Important decisions go through the approval center. Editors and owners can submit a request, only owners can approve or reject it, and nobody can approve their own request. Each decision is final and recorded in the audit trail with its reason.
, Open the full-size image (opens in a new tab)Screens
, Open the full-size image (opens in a new tab)The public home page.
, Open the full-size image (opens in a new tab)Forecasts from one deterministic engine.
, Open the full-size image (opens in a new tab)Real monthly results against the plan.
, Open the full-size image (opens in a new tab)Cash guardrails that turn numbers into warnings.
, Open the full-size image (opens in a new tab)Revenue downside and cost inflation, changed together.
, Open the full-size image (opens in a new tab)Exactly what changed between two versions.
, Open the full-size image (opens in a new tab)A board pack to print or save as a PDF.
, Open the full-size image (opens in a new tab)The board room in the dark theme.
How it's built
| Layer | Technology |
|---|---|
| Framework | Next.js 16 App Router with server components and server actions, so the data code stays on the server, with React 19 and TypeScript |
| Database | PostgreSQL with Prisma 7: 24 tables, 4 enums and 16 migrations, plus triggers for the rules that protect history |
| Sign-in | Auth.js with email and password, hashed passwords and a recovery key instead of reset emails |
| Calculation | decimal.js for exact money maths, Zod schemas shared by the client and the server, and pdf-lib for PDF reports |
| Interface | Tailwind CSS 4 and shadcn/ui design tokens for light and dark themes, and Recharts charts that screen readers can read |
| Operations | A Docker image, backup and restore with a verify step, deploy and rollback scripts, and health, readiness and diagnostics endpoints |
Testing
282 unit test files cover the calculation engine, the services and the components with Vitest, and 32 browser test files run the main flows in a real browser with Playwright, with automated accessibility checks.
A release check runs lint, the type check, the unit tests and the browser tests together, and a smoke test checks a running copy.
Results
- 52 pages: 8 public, 7 in the app shell and 37 model pages.
- 24 database tables and 4 enums over 16 migrations, and 10 API routes.
- 282 unit test files and 32 browser test files.
- Runs on a free stack with no paid APIs.
What I would build next
- Put a public, read-only copy online with the demo data.
- Move the rate limiter from memory to a shared store before running more than one copy of the app.
- Cache the heavy analysis pages, since the engine is deterministic.
- Move PDF creation to a background job.
What I learned
- Put the rules that must never break into the database, then test them there.
- A shared service layer pays off: the pages, the demo data and the tests all use the same code paths.
- Accessibility and phone layouts are cheaper when every page is built for them from the start.
- A believable demo needs a story with a timeline, not random numbers.
Built with
- Next.js
- React
- TypeScript
- Tailwind CSS
- Prisma
- PostgreSQL
- Auth.js
- Recharts
- Vitest
- Playwright