A participant walks into the call center with a folder of payslips and says the pension statement understates their contributions by roughly 14,000 TL accumulated between 2014 and 2019. The agent escalates. Operations escalates. Eventually it lands on the data team, because the source of truth is no longer obvious — and that is the polite version of the story.
What looks like a customer complaint is actually a bitemporal archaeology problem. You are being asked to reconstruct what the system believed about this participant at a specific historical moment, while also knowing what was actually true at that moment. Those are two different timelines, and if your pipeline conflates them, you cannot defend the answer in front of a regulator or a court.
Why this is the hardest query you will write
Most analytical queries answer: what is the state now? A few answer: what was the state on date X? Almost none answer the question that matters here:
What did our system report as the contribution balance on 31 March 2017, given only the information available to us on that date, and how does that compare to what we now believe was true on 31 March 2017?
That second clause is what kills naive implementations. Between 2014 and today, the following things almost certainly happened to this participant's records:
- Employer submitted a contribution file late and it was backdated
- A payroll correction in 2018 retroactively adjusted a 2016 contribution
- The participant switched funds twice; unit prices were restated once due to a NAV correction
- Your core system was migrated in 2017 and the old contribution table was archived with a lossy transformation
- Regulatory reporting rules changed in 2019 and the calculation of "vested contribution" was redefined
- A reconciliation job in 2020 silently corrected ~3,000 records flagged as orphaned
Each of these is a legitimate business event. Together, they mean the contribution history is not a log — it is a palimpsest.
Two timelines, not one
The minimum viable model is bitemporal:
- Valid time: when the contribution actually applied to the participant's account in the real world
- Transaction time: when your system became aware of it
A contribution for March 2016 payroll (valid time) might have been recorded on 5 April 2016 (transaction time), corrected on 12 November 2018 (new transaction time, same valid time), and then re-corrected during the 2020 reconciliation (third transaction time, still same valid time).
To answer "what did we tell the participant in their 2017 annual statement," you need transaction time ≤ 2017-01-31. To answer "what should we have told them," you need the latest transaction-time view of valid time ≤ 2016-12-31. The dispute is almost always about the gap between those two answers.
If your contribution table only has a single updated_at column and overwrites in place, you have already lost. The reconstruction will depend on database backups, CDC logs, or the regulatory reports you filed at the time — and you will be reading those reports as your source of truth, not your own database.
What a defensible reconstruction actually requires
In practice, when I have had to assemble this for an insurance or pension context, the inputs are never just one table. You need:
- The contribution ledger with full history — append-only, with both valid_from/valid_to and recorded_from/recorded_to. If you do not have this, your fallback is the next item.
- Archived regulatory submissions — monthly files sent to the regulator are time-stamped, immutable, and legally authoritative. They are often the only clean point-in-time snapshot you have.
- Annual participant statements as PDFs — what the participant actually received. This is what they will bring to court. Your reconstruction must explain every number on that document.
- Fund switch and unit price history — separately bitemporal, because NAV restatements are their own category of retroactive change.
- Schema migration mapping documents — if the 2017 migration collapsed three contribution types into two, you need the mapping to walk backward.
- The calculation engine version — the formula for vested balance in 2016 is not the formula in 2024. You need to run the historical formula on the historical data.
The last point is the one most teams miss. Even if you perfectly reconstruct the inputs, applying today's calculation logic to 2016 data gives you a number that is internally consistent but legally wrong. You need versioned business logic, not just versioned data.
The query, roughly
A real reconstruction query for one participant, one date, looks something like this in shape:
- Pull all contribution rows where
valid_time <= target_dateANDtransaction_time <= as_of_date - For each (participant, valid_period), take the row with the maximum transaction_time still ≤ as_of_date
- Join against the fund allocation table using the same bitemporal logic
- Join against the unit price table using the same logic, including NAV restatement flags
- Apply the calculation engine version active at as_of_date
- Reconcile the output against the archived regulatory submission for the nearest month-end
- Flag any row where the reconciliation delta exceeds tolerance
That last step is non-negotiable. If your reconstruction does not tie out to the regulatory file you submitted in that period, your reconstruction is wrong — or the file was wrong, which is a much larger problem.
Operational consequences
If you take this seriously, a few things follow:
- Append-only contribution ledgers are not optional. Updating in place to "fix" a record is destroying evidence. Corrections are new rows.
- Schema migrations must preserve the pre-migration table, not just the data. The mapping logic is part of the audit trail.
- Business rule changes need version tags and effective dates, stored alongside the data they operate on.
- Regulatory submissions are your real backup. Treat them as a first-class data asset, not as output artifacts to be archived and forgotten.
- Disputes should trigger a standard reconstruction workflow, not a bespoke investigation each time. The first dispute is archaeology; the hundredth should be a parameterized query.
Most Turkish pension and insurance operations I have seen are somewhere between step 1 and step 3 on that list. The cost of getting there is real, but it is much smaller than the cost of one losing court case where you cannot explain your own numbers.
The uncomfortable part
When you finally produce the reconstruction, there is a non-trivial chance the participant is right. The 14,000 TL gap is real, and it traces back to a 2018 payroll correction that was applied to the current balance but never propagated through the historical statement logic. That is not a data problem you discovered during the dispute — it is a data problem that has been quietly affecting an unknown number of other participants for six years.
This is why contribution history disputes are the most expensive tickets in the building. They do not just cost you the settlement with one participant. They cost you the reconciliation project that follows, once you realize the same defect touched 40,000 accounts.
The query is hard. The cleanup is harder. The only thing more expensive than building bitemporal infrastructure is not building it and finding out, ten years in, that you cannot prove what you told your customers.