Notes from Building the Efficient Frontier App
I started this project thinking the hard part would be the math.
I was building a small app on top of J.P. Morgan's Long-Term Capital Market Assumptions — the dense annual report institutional investors use to think about expected returns and risk. The idea: take that dataset, surface it through an interactive chart, and let someone change a target or a portfolio mix and immediately see what their assumptions imply. The live version sits at frontier.madsen7.com if you want to poke at it.
What it actually looks like. The chart vocabulary the post gets into in a minute — sampled cloud, max Sharpe, CML, estimated frontier — all lives in there.
When a new LTCMA came out, I figured the update would be a quick maintenance pass. Drop in the new numbers, redeploy, move on. That assumption did not survive contact with the codebase.
The Hard Part Was Never the Hard Part
Every "small" task in the session had a real issue sitting one layer underneath:
- "Update the report" turned into a question about how the app picks which return series matters and what it should call "current."
- Adding short labels to the chart legend — sampled cloud, max Sharpe, CML, finder result — turned into a question about who the app is actually for. A chart can be mathematically correct and emotionally inaccessible.
- A Cloud Run deployment that failed turned out not to be an app bug at all. It was missing deployment plumbing the repo had never had. After I added it, the deploy still looked broken — because Cloud Build was happily pointing at a stale revision.
- A custom domain that looked configured (DNS green) still wouldn't load over HTTPS, because the certificate layer wasn't ready. The site looked broken; the site was actually fine.
After the third or fourth time, I stopped treating these as isolated bugs and started treating them as signals about the system around the code — the data model, the deployment path, the user's mental model. The bug was rarely the thing I was looking at.
"Works" Is Not One State
When you're building solo, it's easy to feel like there's one bar to clear. There isn't. There are roughly seven:
- works in code
- works locally
- works in CI
- works in deployment
- works with DNS
- works with HTTPS
- works for users
The most frustrating moments in the session weren't the broken builds — they were the moments where I'd fixed something real and the system surfaced a different failure right behind it. It felt like the last fix hadn't worked. It had. The environment had just moved to the next bottleneck.
"A different failure now appears" is sometimes the only signal you get that you made progress.
The Code Review That Mattered
The most useful feedback I got was on a branch I called the "single-answer UX." I'd introduced new logic to pick a primary portfolio recommendation. The logic was clean. I was proud of it.
The reviewer pointed out the obvious thing I'd missed: the actual interface was still wired to the old ranked-list flow. The new logic existed; the user would never experience it.
Shipping a feature isn't the same as writing the function for it. A feature is real when the user-facing flow, the words on the page, and the interaction model all reflect the underlying logic. Until then, you've written code, not delivered behavior.
The Through-Line
The work was less about implementation and more about translation. Institutional market assumptions into app-ready data. Chart vocabulary into language a non-specialist could trust. "Build passes locally" into "system actually serves users." A code-complete branch into a feature someone could actually feel.
Every time I got stuck, the unblock wasn't writing more code. It was asking a different question. Not "how do I implement this?" — "what is being assumed right now that I haven't made explicit yet?"
That question turned out to be the most valuable tool in the project. Probably a useful one to keep in the back pocket whether you're shipping a side project or running a 24-engineer program.