Sign inCreate account
Guided Projects / Worth Backing? โ€” Lanternfield: The Spring Round
๐Ÿงญ PROJECT BRIEF

Worth Backing? โ€” Lanternfield: The Spring Round

Beginner Finance ๐Ÿ•’ ~26 hours ๐Ÿ“ฆ 0+ published takes

April 2026. Lanternfield's investment committee meets to decide its spring round: 80 applications are pending, and the round has 2,500,000 dollars and room for at most ten cheques. Four years of records are there to learn from: 920 decided applications, 91 funded startups and their monthly metrics. The chair asks the analyst five questions. What did four years of decisions get us? Are we backing the right signals? Which markets are worth being in? Are we fair to every founder? Which ten of the eighty should we back?

Work through the seventeen tasks with the book Worth Backing? beside you: each task follows one chapter, and each chapter ends with a Try It box that describes the same task. Learn Python and SQL from their first lines, query the six tables with DuckDB, find what is wrong with them and clean them in SQL with a log of every decision, analyze the clean data with pandas, chart it with matplotlib, and build the scorecard, the ten picks and the memo.

The project is one notebook for the whole book. Add each chapter's blocks below the previous chapter's, in order, and assign them to that chapter's task. The book's code runs as printed in Python blocks: con = duckdb.connect() makes a DuckDB database inside Python, and con.sql runs the book's SQL on it. The tables are loaded under their file names: Python reads data/business_plans.csv as in the book, lp.table("business_plans") is a shortcut, and SQL blocks can query business_plans directly. Later tasks use names made by earlier ones (con from Task 2, the six views from Task 3, segment_map and region_map from Task 6, the clean views and cleaning_log from Task 8, apps and months from Task 9, startups from Task 12, scorecard and picks from Task 15), so keep the blocks that make a name above the blocks that use it. Each task asks you to make a variable of your own, and its automatic check reads it. Python names live in the browser session: after reopening the project, press Run all before running anything else, so that every earlier block rebuilds what later blocks use.

You need no SQL or Python to start: the book teaches both from the first line. Your notebook is the deliverable: assign every block to its task, write the notes each task asks for, run the automatic checks, press Run all until it passes from top to bottom, and submit.

๐Ÿงช Runs in your browser
Dataset โ†’ Profile โ†’ Clean โ†’ SQL โ†’ Chart โ†’ Quality โ†’ Python โ†’ Output

Real SQL (DuckDB) and real Python (pandas) run entirely in your browser โ€” nothing to install, and your data never leaves it. โ–ถโ–ถ Run all replays the whole notebook top-to-bottom; a clean pass marks the published repo as reproducible โ›“.

โ˜‘๏ธ The tasks

01
How many funded startups closed
Chapter 1. Run the chapter's first look at the data in Python blocks. Then make closed_share, the percentage of the funded startups whose Status was Closed at 31 March 2026, rounded to one decimal place, from data/funding_outcomes.csv, in SQL (duckdb.sql with fetchone()[0] returns a query's single value) or in pandas. The check reads closed_share. In a note, turn it into a finding by comparing it with something: a number on its own is not an insight. Assign the blocks to Task 1.
02
Count the rows of every file
Chapter 2. Run the chapter's blocks in order: values, names, lists, dictionaries, loops, functions, the errors, then DuckDB inside Python, which creates con. Then build rows_per_file, a dictionary from each of the six file names (business_plans, market_analysis, pitch_decks, funding_outcomes, revenue_models, monthly_metrics) to its number of rows, with a for loop and a count query run with con.sql and fetchone()[0]; an f-string can put each name into the path. The check adds up the six counts. In a note, name the file whose row count is not the number of things its rows describe, and why.
03
Find the text in the number columns
Chapter 3. Run the chapter's code below Chapter 2's. It creates the six views under the names the rest of the book uses (business_plans and the others); later tasks rely on them. Then make ch3_text_cells, the total number of cells in three columns that cannot be read as the type the column should have: Market Potential (USD) as BIGINT, Pitch Date as DATE and Growth Rate as DOUBLE, with one TRY_CAST query per column and a sum in Python. The check reads ch3_text_cells. In a note, say what one of those cells holds and what Chapter 8 should do with it.
04
Change the screen
Chapter 4. Run the chapter's code below Chapter 3's. Then answer the partner's question from the chapter: what changes if the revenue bar drops to 750,000 dollars and the round cap tightens to 1,500,000? Make ch4_variant, a DataFrame of the pending applications that pass both, one row per application, from revenue_models with IN subqueries as in the shortlist query, then df(). The check counts its rows. In a note, say which placeholder values could still slip through this screen.
05
Count each market once
Chapter 5. Run the chapter's code below Chapter 4's. Then make market_2025, a DataFrame with one row per segment (a column segment) and the segment's 2025 market size in billions of dollars, added over regions with each market counted once (a column size_bn, rounded to two decimals). The check reads Mobile Games; adding the market size over competitor rows gives a number five times too large and fails. In a note, say how you would spot the same trap in a pivot table.
06
Place every application in its market
Chapter 6. Run the chapter's code below Chapter 5's; it creates segment_map and region_map, which later tasks use. Then make app_markets, a DataFrame with one row per application (1,000 rows) and a column market_size_usd: the market size of the application's segment and region in the year before its pitch, with each market counted once, names matched after trim and lower, and each pitch counted once. Build it yourself from the raw tables and the two mapping tables. The check counts the applications that found a market. In a note, list the reasons the others did not.
07
How concentrated is each market
Chapter 7. Run the chapter's code below Chapter 6's. Then make concentration_2025, a DataFrame with one row per market (segment and region) in 2025 and the columns segment, region and top_two_pct: the combined share of the market's two largest competitors, in percent, rounded to one decimal, computed from market_analysis with a window function and leaving out rows with no Product Name. The check reads the mean of top_two_pct. In a note, name the most concentrated market the applicants sell into and say what that means for a startup entering it.
08
Reconcile the cleaning log
Chapter 8. Run the chapter's code below Chapter 7's; it builds the clean views (plans_clean and the others, applications) and cleaning_log, which later tasks use. Then reconcile the log with the data: make reconcile, a DataFrame with one row per raw table (six rows) and the columns raw_rows (the rows of the raw view), clean_rows (the rows of its clean view) and dropped_in_log (the rows_affected of that table's log rows whose action is row dropped or one kept). Every row should have raw_rows minus clean_rows equal to dropped_in_log. The check adds up raw_rows minus clean_rows. In a note, say what a table that does not reconcile would tell you.
09
Funding rate by region
Chapter 9. Run the chapter's code below Chapter 8's; it makes the DataFrames apps and months, which later tasks use. Then make rate_by_region, a DataFrame with one row per region of the decided applications in apps, indexed by region, with a column decided (the number of decided applications) and a column rate (the percentage of them funded, rounded to one decimal), with groupby and named aggregations. The check reads Europe. Then add dropna=False and, in a note, write one sentence about the row it adds.
10
The typical revenue per customer
Chapter 10. Run the chapter's code below Chapter 9's. Then make typical_rpc, a Series of the median revenue per customer for each combination of industry and revenue model in apps, indexed by the two. The check reads Software with Subscription, rounded to two decimals. Then find the pending application with the highest revenue per customer relative to its typical value and, in a note, say whether it looks like an error or like news, and why.
11
Expected funding rates by country
Chapter 11. Run the chapter's code below Chapter 10's. Then make expected_by_country, a DataFrame indexed by country with the columns observed (the funding rate of the decided applications, in percent) and expected (the rate the country's industry mix predicts, in percent, using each industry's funding rate over all decided applications). The check reads India's expected rate; using the overall rate for every application, or an unweighted mean of the industry rates, gives another number and fails. In a note, answer the chair's fourth question for India in two sentences.
12
Find the follow-on rounds in the cash
Chapter 12. Run the chapter's code below Chapter 11's; it makes startups, which later tasks use. Then make follow_on, a DataFrame with one row for each month in which a startup's cash rose by more than its burn explains, with the columns business, month and round_usd (the unexplained increase, in dollars), computed from months. The check adds up round_usd in millions; a version without groupby mixes startups at their boundaries and fails. In a note, compare the number of rounds you find with the startups whose status says Raised Follow-on.
13
Closed by industry, as a chart
Chapter 13. Run the chapter's code below Chapter 12's; the charts appear under their blocks. Then make closed_by_industry, the percentage of each industry's funded startups (in startups) whose status is Closed, sorted ascending. Draw it as horizontal bars with the values on the bars, Gaming in violet and the rest in gray, and a title stating the finding; save it as output/closed_by_industry.png at dpi=200. The check reads Gaming. In a note, say why the chart starts at zero.
14
Pending applications in fast, open markets
Chapter 14. Run the chapter's code below Chapter 13's. Then make pending_fast_open, the number of pending applications whose market (segment and region) grew faster than the median market from 2018 to 2025, in compound annual terms, and whose two largest competitors held less than half of it in 2025, from ch14_market_map or your own query, with fetchone()[0]. The check reads pending_fast_open. Add it to the dashboard as a fifth tile, with its comparison: the 80 pending applications in all.
15
The ten picks under equal weights
Chapter 15. Run the chapter's code below Chapter 14's; it makes scorecard and picks. Then rerun the decision under equal weights: make picks_equal, a DataFrame of the scorecard rows chosen by the same budget loop (at most ten cheques, 2,500,000 dollars) when the applications are ranked by the equal scores in alt_scores instead of score. The check adds up their cheques. In a note, say which applications enter, which leave, and whether the memo's sentence about the robust picks still holds.
16
Check the pitch-format finding
Chapter 16. Run the chapter's code below Chapter 15's. Then run checks 1, 5 and 9 on the pitch-format finding: make rate_by_format, a DataFrame with one row per pitch format and the columns decided (the number of decided applications), funded (the number funded) and rate (funded as a percentage of decided), from apps. Add Wilson's 90 percent interval for each rate. The check reads the gap between the highest and the lowest rate. In a note, write the sentence a memo should use about pitch format.
17
An honest industry chart, and submit
Chapter 17. Run the chapter's code below Chapter 16's. Then fix way 10 for industries: make industry_table, a DataFrame indexed by industry with the columns decided, funded and rate (funded as a percentage of decided), from apps. Draw the honest chart with the denominators printed on the bars, and in a note write a one-sentence caption that says which industry the committee favors and how many applications the claim rests on. The check reads Gaming. Then press Run all until the notebook passes from top to bottom, and submit.