BACK TO THE BLOG

I Actually Made Money (Testing)

V-Star#go#actuarial#profit-testing#api#architecture
TL;DR

Profit testing with full metrics (IRR, profit margin, payback year), a server that won't fall over, and a native desktop app via Wails. The Python bridge died so HTTP could live. v1.0.0 is close.

v0.7.0 introduced interfaces. PathGenerator, ContingencyCalculator — the engine got composable. That was the foundation.

v0.9.5 is where it pays off.

I added profit testing. I redesigned the server so it doesn't fall over under load. I killed the Python bridge. I froze the API. And somewhere along the way, a desktop app appeared.


Quick context if you're not an actuary: Insurance companies collect money upfront (premiums) but don't know when they'll have to pay it out (claims). Actuaries figure out if you're charging enough. They build models projecting cashflows 30, 40, 50 years into the future — mortality rates, investment returns, expenses. Historically, this is done in Excel with VBA macros. v-star is me replacing that whole mess with proper software.

The Big One: Profit Testing

When an insurance company designs a new product, they need to know: will it make money? Not "probably yes" — actual numbers. How much profit each year? When do you break even? What's your return?

That's what profit testing does. Takes a policy, a set of assumptions, and spits out cashflows year by year for the entire life of the policy. The stuff people spend weeks building in Excel.

Here's what it looks like in Go:

mort, _ := mortality.LoadCSV("utilsoc-qx.csv")
assumptions := profit.Assumptions{
    Mortality:       mort,
    EarnedRate:      0.05,
    DiscountRate:    0.08,
    Expenses:        500,
    RenewalExpense:  50,
    CommissionRate:  0.05,
}
policy := profit.Policy{
    Age: 30, Term: 20, SumAssured: 100000, Premium: 5000,
}
results := profit.Run(policy, assumptions)
fmt.Printf("Profit margin: %.2f%%\n", results.ProfitMargin*100)

Define your assumptions, define the policy, call Run(). You get back profit signature, profit vector, PV of profits, profit margin, payback year, and IRR. Everything an actuary needs to know if a product is viable.

The math is straightforward: every year of a policy, money flows in and out. Premiums come in at the start. Expenses and commission get paid. The reserve earns interest. Death claims get paid at the end. Profit = what's left. Discount it back at the risk rate, and you know if your product makes sense.

457 lines of code. 453 lines of tests. 93.1% coverage. Because if I'm projecting money for actual insurance products, the code better be tested.

The IRR Fix (And Why I Almost Shipped Broken Code)

IRR is the interest rate that makes net profit zero when discounted back. Higher IRR = better product. I found two bugs in my implementation, and fixing them taught me something.

Bug 1: The 200% ceiling. The old code had a hardcoded ceiling at 200%. If your policy was very profitable, it just returned 2.0 — a sentinel, a lie. Fixed: now it dynamically searches up to 100,000,000% (effectively infinite). Because if you're making 200% IRR, you're not selling insurance, you're printing money.

Bug 2: Linear interpolation doesn't work. I told an AI coding agent to implement IRR using linear interpolation — the simple method from math class. It looked at me and said "No. Use the Illinois algorithm."

The Illinois algorithm (1971, University of Illinois) fixes a specific problem with interpolation: when the same endpoint keeps getting selected, its value gets halved, breaking the stall. Converges faster, more precise, still no calculus.

I almost shipped janky linear interpolation. The AI saved me from myself. Then I shipped the Illinois algorithm with the stagnation flags inverted — halving the wrong endpoint — and had to fix that too. Long-term policies failed within 100 iterations because the algorithm pulled in the wrong direction. One line fix once I tracked it down.

The Server Grew Up

The v0.7.0 server was functional. Under load it fell over like a spreadsheet with circular references.

v0.9.5 fixes that. Six routes: /value, /simulate, /annuity, /reserve, /profit, /health. That's it. Everything else got killed or merged. Stateless — deploy it, kill it, scale it horizontally.

Concurrency limiter: Bounded wait queue — up to 5 seconds, then 503. Under 50 concurrent /simulate requests, P95 is 65ms and every single one succeeds. Before this change, 46 out of 50 got instant 503s.

Result cache: /annuity and /reserve responses are cached. 1000-entry FIFO. X-Cache: hit/miss headers. P50 dropped from 2.19ms to 0.75ms.

Middleware stack: Composable and independently testable. 266 lines of tests. Because middleware is one of those things everyone writes and nobody tests, and that's exactly why it breaks in production.

I Killed the Python Bridge

I was maintaining a Python package — pyproject.toml, uv.lock, .venv, CI for building wheels, demo notebooks. Over-engineered. Worse, it sent the wrong message: "You need Python to use v-star." No. You need HTTP.

Replaced with a directory of example scripts — one Python, one JavaScript, one curl. No dependencies beyond the stdlib. Same endpoints, any language. The way it should be.

There's a Desktop App Now

v-desktop is v-star in a window. Built with Wails (Go backend, web frontend, native window — no Electron bloat). Nine tabs, mortality table browser with built-in CSO 2017 tables, Chart.js graphs, keyboard shortcuts, dark theme. Runs on Windows, macOS, Linux. One binary. Pre-built releases for all platforms.

API Freeze

I audited every single export across all 12 packages. Wrote an ADR. Eight packages locked, three already stable, one minor fix. The public API is stable. Not going to break your code with the next release.

The v0.9.5 Stack

  • Core: Go 1.24+, zero dependencies, standard library only
  • Profit Testing: Go package + /profit endpoint, full metrics
  • CensusSource: Interface with 3 implementations, 100% coverage
  • Server: 6 routes, stateless, middleware, cache, concurrency limiter
  • Desktop: v-desktop, native GUI, 9 tools, cross-platform
  • API Clients: Python, JavaScript, cURL. Just HTTP
  • Performance: VaR/CTE 1.8× faster, zero allocations
  • Coverage: Annuities 94%, Server 82%, Profit 93.1%

v1.0.0 is close. Push coverage past 90%, maybe a Dockerfile, actual documentation that doesn't read like a stream of consciousness.

Repo at github.com/lubasinkal/v-star. MIT. Zero dependencies. Actually usable.

Now I have some business stuff to break.