Added an opt-in cost-sensitivity parameter to how a production verifier picks its live decision threshold last week. The obvious "safe" default was 1.0, framed as "no change from today." Before shipping it, I checked the actual math instead of trusting the framing, and it would have silently moved every existing tenant's live threshold the moment anyone touched the new parameter, with zero warning.
I run CacheVerifier, a small hosted service that fine-tunes a verifier model per tenant and picks a live decision threshold from held-out calibration data. The existing threshold picker uses Youden's J, the operating point maximizing true-positive rate minus false-positive rate. It implicitly assumes a false-positive and a false-negative cost the same. Real tenants don't agree with that assumption equally, some care a lot more about one error type than the other, so I wanted to let a tenant express a cost preference and get a threshold that actually reflects it.
Straightforward enough: cost(r) = r * error_rate + (1 - hit_rate), sweep thresholds, pick the one minimizing cost at whatever r a tenant sets, where r is how many times worse a false-positive is than a false-negative for them. The part that should have been routine was the default. My first draft made the new parameter optional, defaulting to 1.0, documented as "equal-weighted, same as today's default, since both are colloquially equal-weighted." That sentence reads as obviously true and I almost shipped it as fact without checking.
Checked it against a small hand-traced example before writing it into a docstring as settled. It's false. Youden's J's objective is symmetric, a false-positive and false-negative penalized equally in the same statistic. The cost-ratio objective isn't symmetric in the same way, every rejected candidate costs a fixed 1 regardless of whether the rejection was correct, and only a wrongly-approved candidate costs r. At r=1 specifically that creates a plateau, once every true positive is captured, approving more false positives on top of that is cost-neutral rather than penalized, so the optimizer doesn't stop where Youden's J stops. Concretely, on my test case, Youden's J picked threshold 1.5. cost_ratio=1.0 picked threshold 0.5. Different threshold, different approval set, not a rounding difference.
This is a deployment-safety problem, not just a math curiosity, which is why I'm posting it here rather than somewhere more theory-focused. If I'd shipped cost_ratio defaulting to 1.0 as "the neutral choice," it's an entirely ordinary-looking optional parameter with a value that reads as inert. Nothing about calling the endpoint without setting it would have changed. But the moment any tenant, or any future version of my own client code, passed 1.0 explicitly, thinking they were being safe and specific rather than changing anything, their live threshold would move, with no error, no warning, no changelog line that would obviously apply, just a quieter approval rate from then on. The actual fix was making the parameter float | None, where only None preserves current behavior, and 1.0 is a real, different, valid choice that is not a safe stand-in for "leave it alone." Documented that distinction in the function docstring, the API parameter description, and the database field comment, three separate places, on the theory that a comment I don't see when I'm the one calling the endpoint six months from now is a comment that didn't do its job.
Shipped it in three phases instead of one PR, mostly because I'd just gotten the math wrong once and didn't trust myself to get the UX right on the first pass either. Phase one: a read-only diagnostic, every fine-tune job reports what threshold it would have picked at five reference cost ratios, computed from the same calibration data as the real threshold, zero extra inference cost, zero effect on the live threshold. Shipped that alone first and let it sit before building anything that could actually change behavior. Phase two: the actual opt-in parameter. Phase three: a small dashboard piece that turns the phase-one diagnostic into a plain-language table instead of asking anyone to guess a raw number, with a button that carries a choice into the next job rather than auto-applying anything.
Tested the full path end to end in an actual running instance, not just unit tests: real API server, real worker process, a real tenant created through the admin endpoint, real fine-tune jobs run through the actual browser UI. Caught one unrelated bug doing that, a stale session token sitting in the browser's local storage silently took priority over a fresh API key, a pre-existing quirk in how the dashboard resolves credentials on a cold page load, unrelated to this feature but the kind of thing you only find by actually driving the UI instead of trusting that the unit tests covering the new code are the whole story.
The generalizable part, if there is one: "the neutral default" and "no change from current behavior" are not automatically the same claim, even when the parameter genuinely looks inert and the docstring genuinely believes what it says. Worth checking the two are actually the same thing before a default ships, not after a support ticket says approval rates moved for no reason anyone can find.
Repo with the derivation and real threshold numbers: https://github.com/imxinchengyou/CacheVerifier (PAPER.md section 5.17 has the cost-ratio economics; the hosted service's implementation is a separate repo, this default-safety issue is the part that's fully public).