DEV Community

Olga Dimitrova
Olga Dimitrova

Posted on

Why a pay-gap cost calculator should make zero network calls

I maintain a small open-source calculator that prices what it costs to close an unexplained gender pay gap under the EU Pay Transparency Directive (2023/970). You give it a CSV of employees (salary, gender, grade, tenure, category), and it prices two options: minimum compliance and full equalisation. It ships as a static HTML page and, separately, as an Excel workbook with the same calculation in live formulas.

The page makes no network requests at all. It doesn't promise to skip logging or to encrypt your data on a server, because there is no server. Open the browser's Network tab while you use it and it stays empty. Most of the design follows from that.

The input is some of the most sensitive data a company holds

A pay-gap calculation needs individual salaries by gender, category, grade and tenure. Most HR and reward teams would rather not let that leave the company, even briefly, even to a vendor with a data processing agreement and a SOC 2 report. A web app that uploads the CSV to a server asks every user to trust a company they have probably never audited, for a calculation their laptop does in milliseconds.

So index.html is the whole application. HTML, CSS and JavaScript sit in one file. There is no build step to run it and no backend to operate or patch, and nobody's salary file ends up on a server.

What "no network calls" requires in practice

"Runs in your browser" is easy to say. Keeping it true means giving up a few conveniences:

  • No CDN for fonts or libraries. A dependency is either vendored into the file or not used. The chart is plain SVG, not a charting library.
  • No analytics, error reporting or telemetry, including the "just to see if people use it" kind. Each of those is a network call.
  • The CSV comes in through an <input type="file"> and is read into memory with the FileReader API. The page computes the result, and the data is gone on refresh. Nothing is written anywhere unless the user downloads a result.

The repo has two checks for this. check-render.js scans the built page and fails (exit 1) if it references any external script, stylesheet or image. check-meta.js opens the page in headless Chrome, records every network request made while it loads, and fails (exit 1) if there is even one. Without a test that can fail, "no network calls" only means "no network calls that we noticed."

Why the Excel workbook exists

If you can't read JavaScript, the web page is hard to verify. Without developer tools, how do you see what it does with your data? So the same calculation also exists as an Excel workbook, all in live formulas: medians by rank, and the OLS regression written out with explicit sums and Cramer's rule instead of an array formula or a macro. Click a cell, read the formula, change an input and watch every downstream number move. There is no VBA and nothing hidden behind a button.

I cross-checked the web version, the workbook and a third, independent Python port on the demo dataset, and they agree to 1e-9. The port is in the repo at pay-gap-calculator/build/excel/reference_calc.py. It follows pay-gap-calculator/build/calc.js step by step in a different language and prints every category, so you can rerun the comparison yourself instead of taking the README's word for it.

What this doesn't solve

Open source and zero network calls answer "can I trust this with my data." They don't answer "is this the right method." The calculator prices the unexplained residual after controlling for grade and tenure (OLS) and remediates only that residual. It doesn't decide whether a gap is lawful, and it doesn't group work of equal value into categories for you. Categories too small to be statistically reliable get flagged rather than hidden. Those are choices, and someone else building the same tool could reasonably make different ones. Whether your salary data should touch a server to find out is a separate question, and I think it has one answer.

Repo, workbook, sample CSV and the checks: https://github.com/OlgaDimitrovaGit/comp-budget-lab/tree/main/pay-gap-calculator

Top comments (1)

Collapse
 
danorie profile image
MinSoo Kim •

"Without a test that can fail, no network calls only means no network calls that we noticed." That line is the post for me.

I run a static calculator site with the same kind of check: a headless Chromium suite that records every request and asserts which third-party hosts get contacted, 321 assertions in all. The ad loader on it is the reason. It fetches a second script from the same host and then stops, and the suite asserts that no actual ad request ever follows. Reading the code wouldn't tell you that.

A question about check-meta.js. Does it record only the page load, or also what happens after a CSV is picked? I'd want both. FileReader itself stays local, but anything that lazy-loads on first interaction would slip past a load-only check.