Calibrating a tax simulator against URSSAF's own engine
I built a French freelance-vs-CDI net-income simulator. The hard part wasn't the math — it was proving the math was right. Here's how I used the URSSAF's own engine as an oracle.
I shipped freelance-ou-cdi.fr, a calculator that answers a question every French developer asks at least once: at what daily rate does going freelance actually beat my CDI? It compares net income — after social contributions and income tax — across micro-entreprise, EI, EURL, SASU, portage salarial and the salaried baseline, under 2026 rates.
Writing the arithmetic was the easy half. The hard half was proving it wasn't wrong. A tax simulator that is plausibly, quietly off by ten percent is worse than no simulator at all: it hands people a number they'll trust to make a five-figure decision. So the interesting engineering here isn't the model — it's how I kept the model honest.
Don't trust your own tax code
French social contributions are not a flat percentage. The TNS regime layers base pension, complementary pension, sickness, family allowances and CSG-CRDS, several of them progressive, several capped at multiples of the PASS. A SASU president is taxed as an employee but can route income through dividends at a flat 31.4%. The 2026 reform of the self-employed contribution base changed the assiette. Transcribe all of that from PDFs and you will get a handful of lines subtly wrong — and you'll never notice, because your wrong number still looks reasonable.
The naive shortcut makes this concrete. Plenty of "brut to net" converters apply a fixed −25% for an executive salary. Run a real progressive calculation and that flat factor is off by several points at most salary levels, in both directions depending on where you land against the ceilings. "Looks about right" is exactly the failure mode you can't catch by eyeballing.
The URSSAF ships an oracle — use it
The French administration open-sources modele-social, a publicodes ruleset that powers mon-entreprise.urssaf.fr, the URSSAF's own simulator. It is the closest thing to ground truth that exists for these calculations. The trick is to treat it not as a dependency I ship, but as a test oracle: a reference my own engine has to agree with.
So there's a comparison harness — npm run compare — that instantiates the publicodes engine, asks it for the official figure on a grid of scenarios (micro BNC at several revenue levels, EI/TNS contributions, the income-tax schedule, SASU cost-to-net, CDI net and employer cost for cadre and non-cadre, even the civil-servant case), and prints my number beside it with the delta. Anything drifting past a few percent gets flagged. The goal isn't to match to the cent — the official engine models edge cases I deliberately don't — it's to stay inside a couple of percent across the realistic range, and to know immediately when a rate change or a refactor pushes me out of it.
Why not just ship publicodes?
Fair question — if the reference engine is right there, why reimplement? Three reasons, all about control:
- Inversion. The product's best feature is "at what daily rate do I break even with my CDI?" That's a root-find: I run the forward model inside a binary search over the rate. Driving a general rules engine through dozens of inversions per interaction is the wrong tool; a direct function is trivial.
- Transparency. I wanted every result to expand into a line-by-line breakdown — this contribution, that tax, this deduction — with each parameter editable in an "advanced" panel. A purpose-built model exposes those internals naturally.
- Weight and reach. The whole thing runs client-side with nothing sent to a server. A compact, audited engine keeps the page light; the heavy reference engine stays in the build, as the thing that grades my homework.
The payoff is also a GEO trick
Calibration buys correctness. But generating from the engine buys something else: numbers that can't contradict each other. The FAQ ("a freelancer beats a 55k CDI from about 410€/day in SASU") and the break-even table are both computed by the same functions that drive the live simulator — not typed into the copy by hand. Change a 2026 rate in one place and the prose, the table and the tool all move together.
That self-consistency turns out to matter for getting cited by generative engines. An LLM summarizing "freelance vs CDI in France" will quote a page far more readily when its stated figures, its structured data, and its interactive tool all agree — because nothing on the page undercuts the claim. Internal contradiction is the fastest way to look untrustworthy to both a careful reader and a model. The cheapest way to never contradict yourself is to never write the number twice.
What it still can't do
A calibrated model is not an accountant. CFE, the family-quotient cap, first-year ACRE relief, mandatory mutuelle, regional quirks — some of these are modeled, some deliberately aren't, and the gap is exactly why the harness flags differences instead of asserting equality. The honest framing on the site is "indicative, validated against the official engine within a couple of percent," not "tax advice." Knowing precisely where you diverge from ground truth is itself a feature; it's the difference between a confident wrong answer and a useful estimate with stated limits.
The whole thing is open-source — engine, harness and all — at github.com/aelmufti/is-freelance-worth-it-for-you. If you spot a status where I drift from the URSSAF's engine, that's the bug report I most want to receive.