Davis Higgins logoDAVIS HIGGINSCurated Notesdavishiggins.com
ProjectsMay 2026·7 min read

Building Propify: Architecture, Failures, and What It Took


Propify started the way most of my projects start. I needed something that did not exist.

I was spending more time pulling stats from five different sites than actually thinking about the pick I was trying to evaluate. Box scores on one tab. Prop lines on another. Recent averages somewhere else. Defense matchup data from a fourth source. By the time I had everything in front of me, I had already lost the thread of what I was trying to figure out. I built Propify to fix that for myself.

The first version was a Streamlit app. It worked. It also looked rough, loaded slowly, and Render would put it to sleep after fifteen minutes of inactivity. Every time someone clicked the link, they were waiting thirty seconds for it to wake up before anything was usable. I moved it to Railway, which helped, but by then I already knew the architecture was wrong. Streamlit is a prototype tool. If I wanted Propify to be a real platform, it needed to be built like one.

So I migrated to FastAPI on the backend and Next.js on the frontend. That decision added about three weeks to the timeline and was completely worth it.

The ML Layer

The prediction engine is built on a Random Forest Regressor with Ridge regression as a fallback. The question I kept asking during the build was what this model actually needed to be useful. Not accurate in some abstract benchmark sense. Useful for a human making a real decision.

Player prop data is noisy. Small sample sizes, opponent variance, pace of play, injury context that does not show up in box scores. A complex model on that data does not give better predictions. It gives confidently wrong predictions. The Random Forest was chosen deliberately for its resistance to overfitting on small samples. Ridge regression sits underneath it as the fallback when the forest's confidence is low.

One thing I learned the hard way: you cannot use standard k-fold cross-validation on sequential sports data. K-fold shuffles the data randomly before splitting, which means the model trains on future games to predict past ones. That is data leakage. Time-series validation splits chronologically. Train on everything before a certain date, validate on what comes after. The model showed lower validation accuracy when I switched. But it was measuring the right thing for the first time.

The feature set runs to approximately thirty features per player: rolling averages at multiple windows, head-to-head historical performance, positional matchup data, home and away splits, rest days, and pace adjustments. The value is not in any single feature. It is in having all of them in one place with consistent preprocessing.

I want to be honest about what this model is not. It is not going to beat Vegas. Vegas has decades of data, entire teams of quantitative analysts, and real-time information flows I do not have access to. That was never the goal. The goal was to remove enough noise that I could think clearly about a pick and track the results honestly over time.

The Technical Pain

CORS nearly broke me. The issue was a specific conflict. When you send requests with credentials, the server cannot respond with a wildcard Access-Control-Allow-Origin. You have to specify the exact origin. I was doing both and the browser was rejecting every response silently. The error message was almost identical to a completely different CORS error. It took longer to diagnose than I would like to admit.

Stripe subscriptions were new territory. Implementing a payment wall as a twenty-year-old who had never touched billing infrastructure was a genuinely different experience. The Stripe docs are good but they do not tell you what to do when your webhook fires before your database write completes. I learned about idempotency keys the hard way.

Clerk auth with Cloudflare DNS took longer than the actual auth integration. The CNAME setup and certificate validation order matters in ways that are not obvious until something fails.

What I Would Do Differently

Design the database schema before writing any application code. I made assumptions about how player data would be structured that required two migrations to fix. Both were avoidable with thirty more minutes of upfront thinking.

Write tests earlier. Not because I believe in test-driven development as a religion, but because the manual testing burden for a stateful application with auth and subscriptions gets heavy fast.

What It Taught Me

Propify is the first thing I built where I had to think like a product manager, a data scientist, a backend engineer, and a designer at the same time, not in sequence. That forced context-switching is disorienting at first. After several months of it, it starts to feel like an advantage.

The thing I am most proud of is not the ML model. It is the pick tracker. Being honest about results over time, logging every pick, tracking net performance, acknowledging losing streaks, is harder than building the prediction engine. It is what makes Propify a tool for disciplined thinking rather than just a way to feel confident.

Related Project

View Propify