Type
In-house product
Coverage
Nine tax years, TY2019–TY2027
Test suite
79 assertions
Status
Live
WordPressPHP 8.3REST API

What it is

Filer includes a public income tax calculator covering nine Pakistani tax years, TY2019 through TY2027. A Pakistani tax year is named for the year it ends and runs 1 July to 30 June, so TY2027 is FY 2026-27 — the calculator rolls over to the new year automatically on 1 July rather than waiting for someone to remember.

It also exposes a read-only REST endpoint so other properties can reuse the same figures rather than keeping a second copy of the slab tables. Duplicated tax data is data that will eventually disagree with itself.

Where the numbers came from

The slab data was ported from the tax regime library inside our own payroll product, which had itself been researched against PwC Tax Summaries and the Finance Bills. By any normal standard that is a trustworthy source: it was already in production, already calculating real payroll.

We re-verified it from the legislation anyway, on the principle that inherited data is not the same thing as verified data. That re-check is the reason this case study exists — it found two defects.

The first defect: TY2019 used the wrong structure entirely

The inherited library applied the twelve-slab table to TY2019. It does not apply. The twelve-slab structure arrived with the Finance Act 2019, which means it takes effect from TY2020.

TY2019 ran under the Finance Act 2018, which unified the salaried and non-salaried rates, raised the taxable threshold to Rs 1,200,000, and charged flat sums of Rs 1,000 and Rs 2,000 on the two bands below it — seven slabs, not twelve.

At a Rs 3,000,000 salary, that error reported tax as Rs 282,500 when the correct figure is Rs 140,000 — an overstatement of Rs 142,500. The flat bands are now modelled as a fixed amount at a 0% rate, which the existing engine formula already handled. They create genuine small cliffs at Rs 400,000 and Rs 800,000, because that is how the law actually worked.

The second defect: a surcharge that was missing altogether

The section 4AB surcharge was absent from the inherited data. That had never mattered for payroll, but it matters a great deal for a public calculator, so it was verified separately and added:

  • TY2019 to TY2024 — no surcharge; section 4AB did not exist
  • TY2025 — 10%, introduced by the Finance Act 2024
  • TY2026 — 9%, reduced by the Finance Act 2025
  • TY2027 — abolished from 1 July 2026

The cliff we decided to warn about rather than hide

The surcharge is a cliff, not a taper. One rupee of income over Rs 10,000,000 levies it on the entire tax bill, not on the excess.

In TY2026 that costs roughly Rs 241,000 of take-home pay at the threshold, and you have to earn about Rs 10.39 million before you are better off than you were at exactly Rs 10 million. That is real law rather than a defect in the arithmetic — which left a design decision about what to do with it.

The calculator detects the band and warns the user, instead of silently reporting a worse outcome and leaving them to work out why. The recovery point is solved analytically, and the test suite checks that solution against a brute-force search so the two have to agree.

How it is tested

The suite runs 79 assertions with no PHPUnit and no WordPress bootstrap required — slab arithmetic against hand-worked figures, surcharge behaviour and its threshold boundary, the TY2019 flat-charge structure, monotonicity outside the cliff, slab contiguity, and the 1 July rollover.

One detail is worth calling out because it is the sort of thing that silently produces a wrong-looking bill. The per-band breakdown derives each band's tax as the difference in total tax across that band, not as amount multiplied by rate. The second form only holds for purely marginal schedules, and it reports TY2019's flat charges as zero. Because the bands are contiguous from zero, the differences telescope — so the breakdown reconciles to the headline figure by construction, whatever structure the year uses.

This is an OffSetup product, so there is no client to anonymise and the figures above are drawn from the project's own engineering documentation.