Billing parity
Billing parity is concorbit's pre-flip confidence surface. While a company's contracts are still at_mirrored (AT generates the customer's invoice), the parity report compares what AT actually billed against what concorbit's native billing engine would have billed for the same charges. By the time a company is eligible to cutover, the maths has already matched AT for weeks, so the flip confirms rather than gambles.
Parity is computed on demand, with no persisted ledger. There is no per-company toggle to switch on, no nightly staging job, and no shadow charge left behind, BillingParityService::forCompany recomputes the comparison fresh every time the parity tab loads or the gate re-checks eligibility, straight from the same at_billed charge rows and the same pricing engine (RecurringLineQuote) that real billing uses. The report can never disagree with what billing would actually do, because it calls the identical code.
The scope is deliberately narrow: parity covers recurring-service contract lines only (ContractService / ContractServiceBundle). One-off product lines, manual ticket charges, and adjustments are out of scope for the match itself, they are surfaced separately as a windowed "non-recurring billing present" blocker (see Cutover) rather than folded into drift.
How a period's outcome is decided
For a company over a window (default trailing six months, capped at twelve), BillingParityService::forCompany builds the AT side and the concorbit side per (contract line, billing period) and buckets every line into one of:
| Outcome | Meaning |
|---|---|
| Match | AT's billed amount for the period equals what concorbit's RecurringLineQuote::periodTotal would bill. |
| Amount drift | Same line and period on both sides, different amounts. |
| AT only | AT billed a line/period concorbit can't reproduce (the line no longer exists, or the period falls outside the line's active date range). |
| Concorbit only | concorbit would bill a line/period AT never billed, an over-bill risk if the company flips today. |
| Null line | An AT recurring charge that could not be resolved to a concorbit contract line at all, a data-quality finding. |
Tax is compared separately, per AT-billed period: AT's actual billed tax against the tenant's current default rate applied to the matched concorbit subtotal. A mismatch breaks that period's green verdict even when every line amount matched.
A recurring line with no billing cadence (or no known start date) can't be honestly enumerated for the over-bill check, it is reported as un-enumerable and blocks the period rather than silently assuming it's covered.
A period is green only when every row in it matched AND its tax matched.
The parity tab
Settings, Autotask, Billing parity shows one company at a time:
Pick a company from the dropdown, only companies with at least one active, AT-mirrored recurring-service contract are listed (the same candidate set the cutover gate assesses).
Adjust the window (
period_start/period_end), default six months back, capped at twelve.The report lists every period in the window with its green/red verdict, and the line-level rows underneath (match, drift, AT-only, concorbit-only, null-line) with amounts and diffs in pence.
Un-enumerable line ids are resolved to their descriptions so the blocker list is readable, not just a stream of uuids.
Run parity now re-runs the same check synchronously via
autotask:parity-check, so an operator can force a fresh read after fixing a mapping instead of waiting for the nightly schedule. It works even while the connection is wound down (--allow-wound-down), see Wind-down.
Every load of the tab is a live recompute against current data, there's no separate "run and wait" step and nothing to seed or backfill first.
The green streak
AutotaskCoverageService::assessParity turns the per-period verdicts into a trailing green streak, ordered by each period's end date, most recent first, counting consecutive green periods until the first non-green one breaks it. That streak (consecutiveZeroDiffPeriods) is compared against the tenant's configurable threshold (billing.cutover.min_green_periods, default 2) to decide cutover eligibility, see Cutover for the full eligibility list and the flip flow.
Because parity is recomputed fresh every time, there's nothing to acknowledge or replay here if a period's drift turns out to be expected (a manual AT adjustment, a back-dated correction), the fix is upstream: correct the mismatch (in AT, in concorbit, or in the mapping causing a null-line/un-enumerable finding), and the next parity read reflects it. A green streak only ever measures the current state of the data, never a stale snapshot.