HomeProjectsAboutToolkitContact
Projects

Four projects. The caveats are included.

Each one below is public, and every number on this page comes from its repository. Where something doesn't work, I've said so, because that's usually the first thing a good interviewer asks about anyway.

01 / 04

Reasonable Doubt

A contract-clause classifier that tells you when it isn't sure

Live
0.8091
macro-F1 on 3,000 held-out clauses
$0.00088
per 1,000 clauses, on a CPU
496×
what Sonnet 5 zero-shot costs, for 0.6148

The question

LEDGAR has about 80,000 provisions from SEC filings, labelled into 100 clause types. Can a small model do this job as well as a frontier LLM, for a fraction of the price, and know when it's guessing?

What I built

I planned a three-tier cascade: a fine-tuned encoder, a QLoRA-tuned Qwen2.5-1.5B, and Claude for the hard rows. I measured each tier before trusting it. The small decoder lost to the encoder, and sending low-confidence rows to Sonnet 5 moved macro-F1 by +0.0008, with a confidence interval across zero. Both were dropped. What ships is one DeBERTa-v3 model in ONNX behind FastAPI on Cloud Run, with a needs_review flag on low-margin answers.

What surprised me

The INT8 build of the same weights scored 0.000166 macro-F1 on CPUs without AVX-512 VNNI. That's chance level, and it raised no error. Cloud Run doesn't let you pick the CPU, so the service now classifies 200 fixed rows at startup and refuses to answer until they pass.

Where it falls short

The flag doesn't catch text that isn't a contract at all. I sent 20 non-contract paragraphs through it and 10 came back confident. A data-access policy got labelled Records at 99.9%. That's written up in the report, not hidden.

DeBERTa-v3QLoRAONNX RuntimeFastAPIDockerCloud RunAnthropic APIpytest · CI
02 / 04

Honest Mistake

A credit model built without leakage, and an agent that tries to catch it cheating

Live
0.7296
ROC-AUC on a future year it never saw
41
post-outcome columns found and removed
12
recorded agent runs, replayable turn by turn

The question

Public models on the Lending Club data regularly report AUCs above 0.90. Most of them train on columns that only exist after a loan's outcome is known. What does an honest model score, and can an agent spot the leak without being told where to look?

What I built

Layer 1 is XGBoost on 1,061,042 loans from 2014–2017, trained on three years and tested once on the fourth. Finding all 41 leaking columns took three passes: name patterns, reading the data dictionary, and asking why one column was almost always empty. Layer 2 is a plain ReAct loop on the Anthropic API, no framework, with eight read-only tools and pgvector search over the data dictionary. Layer 3 tried runtime tool generation and ended with one generated tool, admitted with its limitations written down.

What surprised me

I planted a leaking column as a canary. The agent caught it in every configuration, even with its per-vintage view withheld. What changed between configurations was the number of false alarms, not whether the leak was found.

Where it falls short

The planted column's description gives it away. So the canary shows a floor, not a ceiling: nothing here proves the agent would catch a leak with no descriptive hint. The repo says that in as many words.

XGBoostSHAPOptunaReAct loopAnthropic APIpgvectorsentence-transformersPostgres
03 / 04

Predictive Engine

A stock-ranking strategy for Indian markets, and the record of everything tested against it

M.Tech thesis
8
NSE universes, Nifty 50 to Smallcap 250
17
price and volume features, 10-seed LightGBM
1,842
trading days per backtest, Jan 2019 to Jun 2026

The question

Can a cross-sectional ranking model pick Indian stocks well enough to survive real brokerage, slippage and tax, and how would I know if it only looked good by accident?

What I built

Every 20 trading days a 10-seed LightGBM ensemble ranks the universe, retrained monthly on an expanding window. The top eight are held, sized inverse to volatility, and exposure scales with market breadth. Decisions are made on the close and fill at the next open, with Zerodha's actual delivery charges and 15 bps of slippage. The execution path is ported to NautilusTrader and matches the research engine share for share. Universe, arm and rebalance cadence are each a single switch.

What surprised me

I specified a portfolio drawdown exit. It fired once, in March 2020, and never let the book back in: 84% of the window sat in cash. The re-entry rule could not be satisfied by construction. Resetting the peak at re-entry fixed that, but whether the rule actually helps is still unmeasured, and the record says so.

Where it falls short

Survivorship bias. Each universe is today's index membership taken back to 2019, and point-in-time membership only exists from March 2024. The fix is written and tested; the data to use it isn't available yet.

LightGBMpandas · NumPyNautilusTraderParquetjoblibPre-registration
04 / 04

RegretZero

Inventory ordering scored on money lost, not on forecast error

Live
£54,631
less lost over 12 held-out weeks
12.0%
saving over ordering the median forecast
91.7%
P90 coverage against a 90% target

The question

Running out of stock and overstocking don't cost a retailer the same. So why judge the forecast on RMSE, which treats both mistakes as equal?

What I built

Two years of transactions from a UK online retailer, 1,067,371 lines, cleaned down to 183,459 product-weeks across 3,218 products. LightGBM predicts five demand quantiles directly, with a strict date split and rolling features shifted so no week sees itself. Each product then orders the quantile at its critical ratio, from 0.333 for cheap items to 0.818 for premium ones. That's the newsvendor rule from operations research.

What the numbers say

Ordering this way cost £54,631 less than ordering the median, and all three price tiers came out ahead. Premium gained most, 14.9%, because a missed sale there costs the most.

Where it falls short

The dataset has prices but no costs, so margins are assumptions. I set them below typical retail margins on purpose; with full gross margins the saving would be 21.2%. The dashboard puts every assumption on a slider, and the advantage holds until holding cost reaches about 26% of price per week.

LightGBM quantileNewsvendor modelSciPyStreamlitPlotlypandas