Automating Rebate True-Up Adjustments

A true-up is the closing move of reconciliation: once a manufacturer has invoiced and paid, the estimated rebate receivable that was accrued from adjudicated utilization has to be adjusted to the amount actually settled, and that adjustment has to post exactly once no matter how many times the period close runs. The exact decision this page settles is how to post that difference — as a reversing entry that unwinds the whole accrual and re-books the settled figure, as a signed delta that nudges the ledger by only the gap, or as a full restatement of a closed period — and how to guarantee the same true-up is never counted twice when a batch retries or a contract is amended retroactively. This is the terminal step of Rebate Accrual Reconciliation, consuming the variances classified by Reconciling Accrued vs Invoiced Rebates.

True-Up Posting Strategy Decision Matrix

The three posting strategies are not interchangeable; the right one depends on whether the affected period is still open, whether the GL permits back-dated entries, and how auditors expect the trail to read.

Strategy Mechanism When to use Double-count guard Audit trail shape
Reversing entry Reverse the full accrual, re-book the settled amount Period still open; clean restate preferred Idempotency key on (grain, close_run) Two entries: reversal + rebook
Delta adjustment Post only settled − already_posted Default; period open or soft-closed Track cumulative posted; delta = target − posted One signed entry per run
Full restatement Reopen a closed period, restate the balance Retroactive contract amendment materially changes a closed period New restatement id; original left immutable Restatement entry referencing the closed period

The default is the delta adjustment: it posts only the difference between the settled target and what has already been booked, so re-running the close over unchanged inputs yields a delta of zero and posts nothing. The reversing entry is cleaner to read but writes two lines per true-up and is best reserved for open periods where a full unwind is acceptable. Full restatement is the heavyweight option — it reopens a closed period and must leave the original entries immutable, appending a restatement rather than editing history, because a closed GL period is an audit artifact you never mutate in place.

Rebate true-up posting timeline A horizontal timeline runs across three general-ledger periods. In period one an accrual of the estimated receivable is posted. In period two the manufacturer invoice arrives and a delta adjustment trues the accrual toward the invoiced amount. In period three cash is collected and a final delta closes the receivable, while a separate restatement branch shows a retroactive contract amendment posting a new restatement entry against the earlier closed period without mutating the original. Period 1 Period 2 Period 3 Accrue estimate book receivable Invoice → delta true toward invoiced Collect → close final delta, retire Retroactive amendment new restatement id → closed period append, never mutate

Figure: True-up posting timeline — each period trues the receivable toward the latest settled figure with a signed delta, while a retroactive amendment appends a restatement to the earlier closed period rather than editing it.

Step-by-Step Implementation

The poster below computes the signed decimal.Decimal delta between the settled target and what has already been booked for a reconciliation grain, posts only that delta, and records an audit event. It is period-safe and idempotent: a re-run over unchanged inputs computes a zero delta and posts nothing.

1. Establish the target and the already-posted total. The target is the settled amount (collected if present, else invoiced). The already-posted total is the running sum of prior true-up postings for this grain, read from the ledger — never assumed to be just the original accrual.

2. Compute the signed delta. delta = target − already_posted, quantized to cents. This is the only number that posts. A zero delta is a legitimate, common outcome and must post nothing.

3. Guard the period. If the target period is closed and the delta is non-zero, do not back-date into it; emit a restatement referencing the closed period instead, leaving original entries immutable.

4. Post idempotently and audit. Derive a deterministic posting key from the grain, the close run, and the delta so a retry dedupes on it. Emit a PHI-safe audit event carrying only tokenized and financial identifiers.

python
import json
import logging
import hashlib
from decimal import Decimal, ROUND_HALF_UP
from datetime import datetime, timezone
from typing import Optional

logging.basicConfig(format="%(message)s", level=logging.INFO)
logger = logging.getLogger("rebate_trueup")  # PHI-safe: no 302-C2 / 310-CA / raw bytes
CENT = Decimal("0.01")


def _posting_key(contract_id: str, gpi_family: str, gl_period: str,
                 close_run: str, delta: Decimal) -> str:
    basis = f"{contract_id}|{gpi_family}|{gl_period}|{close_run}|{delta}"
    return hashlib.sha256(basis.encode("utf-8")).hexdigest()[:24]


def post_true_up(
    *,
    contract_id: str,
    gpi_family: str,
    gl_period: str,
    close_run: str,                 # id of this period-close execution
    settled_amount: Decimal,        # collected if present, else invoiced
    already_posted: Decimal,        # cumulative prior true-ups for this grain
    period_closed: bool,
) -> Optional[dict]:
    target = settled_amount.quantize(CENT, ROUND_HALF_UP)
    posted = already_posted.quantize(CENT, ROUND_HALF_UP)
    delta = (target - posted).quantize(CENT, ROUND_HALF_UP)

    if delta == 0:
        # Nothing to true up -- idempotent no-op on re-run.
        return None

    if period_closed:
        entry_type = "restatement"     # append to closed period, never mutate
    else:
        entry_type = "delta"

    key = _posting_key(contract_id, gpi_family, gl_period, close_run, delta)
    entry = {
        "posting_key": key,
        "entry_type": entry_type,
        "contract_id": contract_id,
        "gpi_family": gpi_family,
        "gl_period": gl_period,
        "delta": str(delta),
        "new_posted_total": str((posted + delta).quantize(CENT, ROUND_HALF_UP)),
        "posted_at": datetime.now(timezone.utc).isoformat(),
    }
    logger.info(json.dumps({"event": "rebate_true_up", **entry}))
    return entry

Because the delta is computed against the cumulative already-posted total rather than the original accrual, a second true-up in a later period stacks correctly: the running total moves toward the newest settled figure, and no prior posting is disturbed. The posting_key folds the delta into the hash, so a genuine second adjustment (a different delta) gets its own key while an accidental replay of the same delta collides and is deduped by the ledger writer.

Verification and Testing Pattern

The property that must hold above all others: re-running the true-up over unchanged inputs posts nothing. The tests also pin correct stacking and the closed-period restatement branch.

python
import pytest


def test_no_double_count_on_rerun():
    kw = dict(contract_id="C1", gpi_family="27100010", gl_period="2026-06",
              close_run="run-1", settled_amount=Decimal("800.00"),
              already_posted=Decimal("1000.00"), period_closed=False)
    first = post_true_up(**kw)
    assert first["delta"] == "-200.00"     # accrued 1000 -> settled 800
    # Re-run once the delta is reflected in already_posted: no new posting.
    kw2 = {**kw, "already_posted": Decimal("800.00")}
    assert post_true_up(**kw2) is None      # zero delta -> idempotent no-op


def test_second_period_stacks_toward_new_target():
    entry = post_true_up(
        contract_id="C1", gpi_family="27100010", gl_period="2026-06",
        close_run="run-2", settled_amount=Decimal("760.00"),
        already_posted=Decimal("800.00"), period_closed=False)
    assert entry["delta"] == "-40.00"
    assert entry["new_posted_total"] == "760.00"


def test_closed_period_posts_restatement():
    entry = post_true_up(
        contract_id="C1", gpi_family="27100010", gl_period="2026-03",
        close_run="run-9", settled_amount=Decimal("910.00"),
        already_posted=Decimal("1000.00"), period_closed=True)
    assert entry["entry_type"] == "restatement"
    assert entry["delta"] == "-90.00"

The first test is load-bearing: it proves that once the delta is reflected in the posted total, the next run computes zero and returns None, so a retried or re-triggered period close cannot post the same adjustment twice.

Gotchas and PHI Guardrails

  • Retroactive contract amendments. An amendment to Manufacturer Rebate Contract Modeling that changes a closed period must post as a restatement against a new snapshot version, not a silent re-price of the original accrual. Reconcile on the contract version stamped at accrual time and append the change.
  • Closed-period posting. Never back-date a delta into a closed GL period. The period_closed guard routes those to a restatement that references — but does not mutate — the original entries, preserving the audit trail.
  • Rounding residue. Repeated deltas can leave sub-cent residue if each is rounded independently. Quantize the target and posted totals with ROUND_HALF_UP and take their difference, rather than accumulating rounded deltas, per the Python decimal documentation.
  • Delta against the wrong base. Computing the delta from the original accrual instead of the cumulative posted total double-counts every prior true-up. Always read the running posted total from the ledger.
  • PHI in the audit event. A true-up entry carries only the contract id, GPI family, period, and amounts — never 302-C2 Cardholder ID, 310-CA Patient Name, or the tokenized 407-D7-level claim payload. The reconciliation grain is already aggregated above the claim.

Trued-up receivables feed the settled figures that reconcile the ledger; for standard field definitions consult the official NCPDP Standards, and route batch true-up runs through the idempotency posture of Asynchronous Batch Adjudication Workflows.

← Back to Rebate Accrual Reconciliation