The Problem
Every payroll department faces the same question from finance: "What will payroll cost next quarter?" The naive answer — take this month's payrun and multiply by three — fails the moment anything changes. And in payroll, something always changes.
A salary increase takes effect July 1st. A new employee starts in August with a benefits package that doesn't exist yet. The government publishes new tax brackets for January that haven't been enacted. A collective agreement revision is under negotiation with three possible outcomes. The CFO wants to see all of these scenarios — individually and combined — as fully calculated payslips, not back-of-envelope estimates.
Key insight: A payroll forecast is not a simplified projection — it must run the exact same calculation engine as the legal payrun, with the same tax algorithms, contribution formulas, and ceiling checks. The difference is not in the math. It's in the data isolation: forecast results must never contaminate the legal record, and multiple forecasts must never contaminate each other.
The architectural challenge is harder than it appears. Consider a December forecast that projects January payslips. January may use new tax parameters that haven't been officially published yet. The forecast must either use estimated parameters (marked as provisional) or fall back to current-year parameters with an explicit assumption flag. Neither approach is trivial — and both must be clearly distinguished from legal calculations.
Then layer on retroactive corrections. An employee has a pending retro correction from October that hasn't been processed yet. The legal payrun for December will include that retro delta. Should the January forecast include it too? Or assume it's already processed? The answer depends on timing — and the system must handle both cases without manual intervention.
Finally, consider parallelism. The HR team is running a "what if we promote 12 people" scenario. Finance is running a "what if the new CBA adds EUR 200/month across the board" scenario. Both are active simultaneously, both modify the same employees, and neither should see the other's changes. This is a hard isolation requirement that most payroll systems solve by simply prohibiting parallel forecasts.
How It Works
To understand payroll forecasting, you must first understand the three job types that a payroll engine can execute. Each serves a fundamentally different purpose, has different persistence guarantees, and operates under different isolation constraints.
The three job types
| Aspect | Preview Job | Forecast Job | Legal Job |
|---|---|---|---|
| Persistence | None (synchronous response only) | Own result store (isolated) | Primary result store |
| Retro support | HTTP 422 (rejected) | Isolated retro (own store) | Full retro with delta flow |
| Parallel execution | N/A (stateless) | Multiple allowed simultaneously | One active job per period |
| YTD access | Reads legal store (read-only) | Reads legal + own forecast store | Reads and writes primary store |
| Approval workflow | None (immediate) | None (advisory only) | Full approval cycle |
| Use case | CI/CD validation, quick checks | Budget what-if, planning | Production payslip |
Key forecast scenarios
Understanding the job types enables a taxonomy of forecast scenarios, each with distinct isolation and data requirements:
Single-period preview: The simplest case. Run next month's payslip using current data to verify that the configuration is correct. No persistence, no retro, instant response. This is the CI/CD use case — automated tests run preview jobs after every regulation change to verify that no calculation has regressed.
Multi-period forecast: Project payslips for the next 3, 6, or 12 months. Each projected period builds on the previous one — July's YTD feeds into August's calculation. The forecast accumulates its own YTD separately from the legal payrun, so a forecast running July–December doesn't interfere with the actual legal runs happening in real time.
Annual budget projection: Run 12 months of forecast for all employees to produce the annual labor cost budget. This is computationally intensive (hundreds or thousands of employees × 12 periods × full tax/SV calculation per period) and typically runs as a batch overnight. The result store must be addressable — finance needs to query "total employer cost by department by month" against the forecast results.
What-if comparison: Run two or more parallel forecasts with different assumptions (Scenario A: 3% raise across the board; Scenario B: 5% raise for top performers only) and compare the results. Each scenario has its own isolated result store. The comparison is performed after both complete — the system never merges results between scenarios.
Forecast with pending retro: An employee has a retroactive salary correction from October that hasn't been processed in the legal payrun yet. The forecast for December must decide: include the retro delta (simulating that it will be processed before December) or exclude it (assuming it's still pending). The correct answer depends on the payroll calendar — if the November legal run hasn't happened yet, the retro will likely be included there, so the December forecast should assume it's already in YTD.
New-year forecast (December → January): The hardest single-period forecast. In December, projecting January requires next year's tax parameters — which may not be published yet. The system must either use provisional parameters (if available as draft data regulations), carry forward current-year parameters with a "provisional" flag, or reject the forecast with a clear error indicating which parameters are missing.
The isolation guarantee
The central architectural requirement for forecasting is complete result isolation. This means:
- A forecast job's results are stored in a namespace that no legal job can read
- A forecast job's YTD accumulations are calculated from its own results plus legal results up to the forecast start point — but never written back to the legal store
- Two parallel forecast jobs cannot see each other's results, even for the same employee and period
- Deleting a forecast removes all of its results without affecting legal data or other forecasts
Without this guarantee, a forecast becomes dangerous — a "what if" scenario that accidentally contaminates the legal record is a compliance violation in every jurisdiction.
Where It Gets Tricky
New-year parameter gap
The most common forecast failure: projecting January when December isn't finished yet. In Germany, the BMF (Bundesministerium der Finanzen) publishes the PAP (Programmablaufplan) for the following year typically in November or December. But companies start budget planning in September or October. A forecast for Q1 of the following year requires tax parameters that don't officially exist yet.
The same pattern applies everywhere. France's PAS (prélèvement à la source) rates are published in the Loi de Finances, which is typically enacted in late December. The UK's PAYE thresholds are announced in the Autumn Statement (November) but can change in the Spring Budget. Belgian social security rates are confirmed only after the indexation pivot is definitively calculated.
A forecast engine must handle this gracefully. The options are:
- Provisional parameters: Load draft/estimated values for the next year and mark all forecast results as "provisional — subject to parameter confirmation"
- Carry-forward: Use current-year parameters with an explicit assumption log ("Forecast uses 2025 tax tables; 2026 tables not yet available")
- Validation error: Reject the forecast with a clear diagnostic ("Cannot project 2026-01: Data.LSt.2026 not found")
Each approach has trade-offs. Provisional parameters give the most accurate estimate but require someone to load draft values — and those draft values may change. Carry-forward is conservative but can significantly misestimate when rates change substantially (as with the German PAP 2026, where the Vorsorgepauschale calculation changed materially). Validation errors are the safest but least helpful for budget planning.
Forecast and retro interaction
Consider this timeline:
- October: Employee's salary was EUR 4,000 (legal payrun completed)
- November: HR discovers October salary should have been EUR 4,500 (retro correction pending)
- November: Finance requests forecast for December and January
The December forecast must answer: "Will the October retro correction be processed in November's legal run (before December), or will it still be pending when December is actually processed?"
If the retro is processed in November, then December's legal YTD will include the October delta. The forecast should incorporate this. If the retro is still pending, December's legal run will include it — but the forecast might not know this. The result: the forecast either double-counts or under-counts the retro delta depending on timing assumptions.
The correct solution is to let the forecast explicitly declare its retro assumption: "Forecast assumes all pending retro corrections are processed before the forecast start period." This makes the forecast result deterministic — if the assumption turns out to be wrong, the user knows why the forecast diverges from reality.
Parallel forecast contamination
Two forecast jobs running simultaneously for the same employee must produce results as if the other doesn't exist. This sounds obvious until you consider shared state:
- Case values: If Forecast A modifies an employee's salary (what-if scenario), Forecast B must not see that modification
- YTD accumulators: If Forecast A projects January with a bonus payment, Forecast B's January must not include that bonus in its YTD
- Ceiling checks: If Forecast A pushes an employee over the social security ceiling in March, Forecast B must independently determine whether that ceiling is reached based on its own data
The isolation must be total. Even at the parameter level — if Forecast A uses provisional 2027 tax rates and Forecast B uses carry-forward 2026 rates, each must resolve parameters independently without side effects.
Time-segmented changes in future periods
A planned salary increase from EUR 4,000 to EUR 5,000 effective July 15th (mid-month) requires time-segmented calculation in the forecast: 14 days at EUR 4,000 (prorated: EUR 1,806.45) + 17 days at EUR 5,000 (prorated: EUR 2,741.94) = EUR 4,548.39 gross for July. The tax calculation must also be segmented — the annualized income projection changes mid-month, affecting the marginal rate.
Now project this forward: August through December use the full EUR 5,000. But what about the annual bonus in November? It's calculated on average annual salary — which is partly EUR 4,000 (January–July 14) and partly EUR 5,000 (July 15–December). The forecast must carry the time-segmented history through all subsequent periods correctly.
This becomes significantly harder with multiple planned changes. A benefit enrollment starting September 1st adds a pre-tax deduction. A promotion on October 1st changes the job classification (and potentially the applicable collective agreement). Each change creates a new segment, and the forecast must chain them without losing precision.
How PE Solves It
Payroll Engine's forecast architecture is built on four structural guarantees that address the isolation, parameter, and segmentation challenges described above.
1. Three job types with isolation guarantees
The distinction between Preview, Forecast, and Legal is not just semantic — it's enforced at the storage layer. Each job type writes to a different result partition:
- Legal jobs write to the primary result store. YTD queries against the primary store see only legal results. This is the source of truth for compliance, reporting, and payslip generation.
- Forecast jobs write to a named forecast store (identified by forecast job ID). YTD queries within a forecast job see: primary store results (up to the forecast start period) + own forecast store results (for forecast periods). They never see another forecast's results.
- Preview jobs write to nothing. They compute results synchronously and return them in the HTTP response. No state is created, no cleanup is needed, and no isolation concerns exist because nothing persists.
This three-tier model means forecasts can run at any time — before, during, or after legal runs — without coordination. A finance team running annual budget forecasts in October doesn't block or interact with the payroll team processing October's legal run simultaneously.
2. Own result store per forecast
Each forecast job receives its own isolated result store. This is not a logical partition within a shared database table — it's a structurally separate storage scope that is addressable, queryable, and deletable as a unit.
The practical implications:
- Parallel what-if: Five forecasts for the same employee/period can exist simultaneously. Each has its own YTD chain, its own ceiling tracking, and its own retro assumptions.
- Clean deletion: Deleting a forecast removes exactly its results. No cascade effects, no orphaned data, no need to "undo" calculations.
- Queryable results: Finance can run aggregate queries against a forecast store ("total employer cost by cost center") using the same query API used for legal results. The store is a first-class data object, not a temporary buffer.
- Comparison API: Two forecast stores can be compared programmatically — "Show me all employees where Scenario A produces higher employer cost than Scenario B" is a standard query against two forecast stores.
3. ValidFrom versioning for parameter lookup
PE's data regulation model uses validFrom dates to version all parameters — tax tables, contribution rates, ceilings, minimum wages. When a forecast queries parameters for a future period, the resolution is deterministic:
- Look up the data regulation with the highest
validFromthat is ≤ the target period start - If found: use those parameters (this handles the case where next year's rates are pre-loaded with a future validFrom)
- If not found: return null — the calling wage type can then decide whether to fall back to current parameters or abort with a diagnostic
This design supports the provisional parameter pattern naturally. When the BMF publishes draft PAP 2027 parameters in October 2026, they can be loaded as a data regulation with validFrom: 2027-01-01. Any forecast projecting into 2027 will automatically pick them up. When the final parameters are published (potentially with corrections), the data regulation is updated in place — and any new forecast will use the corrected values without code changes.
The carry-forward pattern is equally natural: if no 2027 data regulation exists, the 2026 version resolves (highest validFrom ≤ target). The forecast runs with 2026 parameters and the result metadata records which data regulation version was used — providing a clear audit trail of assumptions.
4. Preview jobs as CI/CD compliance gates
Preview jobs serve a distinct purpose beyond "quick forecast": they are stateless compliance validators. In a CI/CD pipeline, every change to a regulation definition triggers a preview job against a reference employee population. If any result deviates from the expected values (tracked in integration test assertions), the pipeline fails.
This turns forecasting inside out. Instead of asking "what will payroll cost?", the preview asks "is the regulation still correct?" after every code change. The stateless nature (no persistence, no cleanup) makes this practical at scale — hundreds of preview jobs can run per commit without accumulating state or requiring cleanup.
The HTTP 422 response on retro attempts is intentional: a preview is meant to validate the current-period calculation in isolation. If retro data exists, it indicates that the regulation under test has period dependencies that require a full forecast or legal run to validate correctly. The 422 signals "this test scenario is more complex than a preview can handle" rather than being an error.
Combining the guarantees
These four mechanisms compose to handle the complex scenarios described earlier:
- Annual budget: A single forecast job runs 12 periods. Each period builds on the previous one within the forecast's own YTD store. Legal results for completed months anchor the YTD starting point. The result is a fully isolated, queryable 12-month projection.
- What-if comparison: Two forecast jobs with different input assumptions (salary changes, headcount changes) run in parallel. Each produces its own result store. A comparison query identifies differences.
- New-year gap: If 2027 data regulations exist (even as provisional), the forecast resolves them automatically. If they don't exist, the wage type receives null for the parameter lookup and can either carry forward or report an error — the behavior is defined in the wage type logic, not hardcoded in the engine.
- Retro + forecast: The forecast declares its start point (e.g., "forecast starting December"). It reads legal YTD up to November's completed run. If November hasn't run yet, it reads up to October. Pending retro corrections that haven't been processed in a legal run are not visible to the forecast — this is the "assume all prior periods are closed" semantic. If the user wants to include pending retro, they run the retro in a separate forecast first.
Test Case References
Payroll forecasting is validated through architectural tests that verify the isolation guarantees and parameter resolution behavior across all country regulations:
| Test area | Validation scope | Key assertion |
|---|---|---|
| Preview job isolation | All countries | Preview produces identical results to legal run for same period/employee; no state persisted after completion |
| Forecast YTD isolation | All countries | Forecast YTD accumulates only own results + legal baseline; parallel forecasts produce independent YTDs |
| Retro rejection in preview | All countries | Preview job returns HTTP 422 when retro data exists for prior periods |
| ValidFrom resolution | All countries | Forecast for 2027-01 resolves Data.*.2027 when available; falls back to Data.*.2026 when not |
| Parallel forecast independence | All countries | Two forecasts with different salary assumptions produce different results; deletion of one doesn't affect the other |
| Multi-period chaining | All countries | 12-month forecast correctly chains YTD across periods within forecast store; ceiling checks respect accumulated totals |
Forecast validation is primarily architectural — verifying isolation, parameter resolution, and YTD behavior — rather than country-specific. The same isolation guarantees apply regardless of whether the underlying calculation is German Lohnsteuer, French PAS, or US Federal Income Tax. Country-specific tests validate the calculation; forecast tests validate the execution model.
Preview jobs serve as the primary CI/CD mechanism across all country regulations. Every integration test in the PE test suite runs as a preview job — confirming that the stateless, non-persistent job type produces results identical to what a legal run would produce for the same inputs. This makes the test suite itself a continuous validation of the preview/forecast architecture.
See how PE handles this
Explore the full forecast architecture — preview/forecast/legal job types, isolated result stores, and multi-period budget projections with automatic parameter versioning.
Request a Demo →