Fallback Routing for Unclassified Deductions

A deduction code that passes structural validation but matches no canonical entry must resolve to a defensible statutory treatment or stop the record — never default to NULL, GENERAL, or a guessed pre-tax flag. This guide is the deduction-specific application of the Fallback Routing Strategies pattern within the broader Core Architecture & Compliance Mapping framework: it covers the failure surface unique to deductions — vendor suffixes, truncated codes, pre/post-tax ambiguity, and garnishment caps — layered on top of the general tier hierarchy.

Problem Framing

Naive deduction handling assumes an exact-match dictionary is sufficient: look up deduction_code, apply the mapped tax treatment, move on. That assumption breaks the moment a vendor renames 401K to RET_PRETAX_V2, a CSV export truncates an 8-character code to 6, or two vendors reuse the same string for different statutory buckets. The dictionary returns nothing, and an unguarded pipeline does the worst possible thing: it coerces the unknown to a catch-all bucket and keeps running. The deduction still hits the paycheck, but with the wrong tax treatment.

The cost is asymmetric. A misrouted deduction does not raise an exception — it produces a wrong-but-plausible paycheck that reconciles to itself and surfaces only at a Department of Labor audit or year-end W-2 reconciliation. A deduction defaulted to pre-tax when it should be post-tax understates taxable wages and corrupts the ACA Tracking Logic affordability denominator. A garnishment routed without a cap check can breach the federal Consumer Credit Protection Act ceiling under 15 U.S.C. § 1673 and illegally erode wages below the FLSA Threshold Mapping floor. Each of these is a per-employee, per-pay-period violation that compounds silently.

Fallback routing replaces the silent default with a deterministic decision: match by similarity if confident, otherwise apply a threshold-guarded statutory default, otherwise quarantine for human review. It never infers intent.

Deduction fallback routing: four tiers evaluated in strict order A schema-valid deduction with no canonical match enters the router and is evaluated in strict order. Tier 1 normalizes the code (stripping case, separators, and version suffix) and tries the canonical dictionary; an exact hit resolves to PRIMARY. On a miss, Tier 2 scores the normalized code against the versioned alias registry with difflib; a confidence at or above 0.85 resolves to SIMILARITY. Below 0.85, Tier 3 applies statutory threshold guards: the federal CCPA garnishment cap, the lesser of 25 percent of disposable earnings or disposable earnings minus the 217.50 dollar protected floor, and the FLSA floor requiring gross minus amount over hours worked to stay at or above 7.25 dollars; any breach routes to QUARANTINE flagged requires_review equals true. When both guards pass, Tier 4 applies the post-tax safe default, which never understates taxable wages. Every terminal outcome writes one immutable entry to an append-only audit ledger stamped with the registry version. Unclassified deduction schema-valid · raw_code · amount · D · work_state 1 Normalize + exact match strip case · separators · version suffix → canonical dict 2 Similarity match difflib ratio vs versioned alias registry 3 Statutory threshold guards garnishment cap = min(0.25·D, D − $217.50) FLSA floor: (gross − amount) / hours ≥ $7.25 4 Post-tax safe default clean unknown · never understates taxable wages no exact key below 0.85 both guards pass PRIMARY exact hit SIMILARITY confidence ≥ 0.85 QUARANTINE cap / floor breach · requires_review POST_TAX_DEFAULT applied after guards Append-only audit ledger one immutable entry · stamped with registry version

Prerequisites & Data Requirements

The router runs only after structural validation guarantees a record is the right shape. Boundary enforcement is owned upstream by Data Boundary Definitions; a malformed payload must be rejected there so the router never confuses noise with an unmapped-but-valid deduction. Every record reaching this stage must carry:

  • record_id — a stable, deduplicated identifier so a retried run resolves to the same decision rather than minting a new one.
  • raw_code — the vendor deduction code preserved verbatim. Normalization happens inside the router so the original survives in the audit trail.
  • work_state — a resolved jurisdiction key, not free-text. An unresolved state is itself a quarantine condition, never a silent default to federal.
  • amount — a Decimal, parsed with Decimal(str(value)). A native float amount must be rejected at the boundary; binary rounding must never enter monetary state.
  • disposable_earnings, hours_worked, gross_payDecimal values needed to evaluate the garnishment cap and FLSA floor.

You also need a versioned alias registry: a list of canonical code strings plus their known vendor aliases, with a registry version stamped into every routing decision so a re-run can be tied to the exact mapping that produced it.

Step-by-Step Implementation

The router evaluates four tiers in strict order and stops at the first that resolves. All monetary arithmetic uses decimal, logs are structured key=value, and state transitions are immutable.

Step 1 — Normalize the code

Strip everything that vendors mutate but that carries no meaning: case, whitespace, separators, and version suffixes.

import re

def normalize(raw_code: str) -> str:
    return re.sub(r"[^A-Z0-9]", "", raw_code.upper())

assert normalize(" ret_pretax_v2 ") == "RETPRETAXV2"
assert normalize("401(k)") == "401K"

Expected output: the assertions pass. Normalization is idempotent — normalize(normalize(x)) == normalize(x) — so it is safe to re-apply on retries.

Step 2 — Exact then similarity match

Try the canonical map first. On a miss, score the normalized code against the alias registry with difflib.SequenceMatcher and accept only a confidence at or above the threshold.

from decimal import Decimal
from difflib import SequenceMatcher

CONFIDENCE_THRESHOLD = Decimal("0.85")

def best_confidence(normalized: str, registry: list[str]) -> Decimal:
    best = 0.0
    for alias in registry:
        best = max(best, SequenceMatcher(None, normalized, alias).ratio())
    return Decimal(str(best))

assert best_confidence("401KMATCH", ["401KMATCH"]) == Decimal("1.0")
assert best_confidence("XYZ", ["401KMATCH"]) < CONFIDENCE_THRESHOLD

Expected output: an exact alias scores 1.0; an unrelated code scores below 0.85 and falls through to the next tier. The 0.85 floor is deliberately conservative — a false match applies a wrong tax treatment, so ambiguity must escalate, not resolve.

Step 3 — Apply threshold guards before any default

Below the confidence floor, a record may still be safely defaulted, but only after it clears the statutory guards. The federal CCPA garnishment cap is the lesser of 25% of disposable earnings or the amount by which disposable earnings exceed 30× the federal minimum wage:

cap=min(0.25D,max(D30wmin,0))\text{cap} = \min\!\left(0.25 \cdot D,\; \max(D - 30 \cdot w_{\min},\, 0)\right)

where DD is disposable earnings and wminw_{\min} is the federal hourly minimum (7.257.25, so the protected floor is $217.50\$217.50).

from decimal import Decimal

FED_MIN_WAGE = Decimal("7.25")
FED_PROTECTED = Decimal("30") * FED_MIN_WAGE  # $217.50

def garnishment_cap(disposable: Decimal) -> Decimal:
    by_pct = disposable * Decimal("0.25")
    by_excess = max(disposable - FED_PROTECTED, Decimal("0"))
    return min(by_pct, by_excess)

assert garnishment_cap(Decimal("1000")) == Decimal("250.00")
assert garnishment_cap(Decimal("200")) == Decimal("0")  # fully protected

Expected output: at $1000 disposable the cap is $250.00; at $200 the worker is fully protected and the cap is $0. State overrides (for example California child-support allowances) are applied from a jurisdictional table and may only lower the worker’s exposure, never raise it above the federal cap.

Step 4 — Route through the tier hierarchy

Fold the steps into one deterministic route() call. Unknown-but-clean deductions default to post-tax — the safe direction, since post-tax never understates taxable wages — but only after the garnishment and FLSA guards pass; otherwise the record is quarantined.

import logging
from dataclasses import dataclass
from decimal import Decimal
from enum import Enum

logger = logging.getLogger("payroll.deduction_router")

class Tier(Enum):
    PRIMARY = "primary_match"
    SIMILARITY = "similarity_match"
    POST_TAX_DEFAULT = "post_tax_default"
    QUARANTINE = "pending_classification"

@dataclass(frozen=True)
class Deduction:
    record_id: str
    employee_id: str
    work_state: str
    raw_code: str
    amount: Decimal
    disposable_earnings: Decimal
    hours_worked: Decimal
    gross_pay: Decimal

    def __post_init__(self) -> None:
        if not isinstance(self.amount, Decimal):
            raise TypeError(f"amount must be Decimal, got {type(self.amount).__name__}")

def violates_flsa_floor(d: Deduction) -> bool:
    if d.hours_worked <= 0:
        return False
    effective_rate = (d.gross_pay - d.amount) / d.hours_worked
    return effective_rate < FED_MIN_WAGE

def route(d: Deduction, canonical: dict[str, str], registry: list[str], version: str) -> Tier:
    code = normalize(d.raw_code)
    if code in canonical:
        tier = Tier.PRIMARY
    elif best_confidence(code, registry) >= CONFIDENCE_THRESHOLD:
        tier = Tier.SIMILARITY
    elif d.amount > garnishment_cap(d.disposable_earnings):
        tier = Tier.QUARANTINE
    elif violates_flsa_floor(d):
        tier = Tier.QUARANTINE
    else:
        tier = Tier.POST_TAX_DEFAULT
    logger.info(
        "event=deduction_route record=%s emp=%s state=%s tier=%s registry=%s review=%s",
        d.record_id, d.employee_id, d.work_state, tier.value, version,
        tier in (Tier.POST_TAX_DEFAULT, Tier.QUARANTINE),
    )
    return tier

Expected output: a canonical code returns PRIMARY; a close alias returns SIMILARITY; an over-cap garnishment or floor-breaching deduction returns QUARANTINE; a clean unknown returns POST_TAX_DEFAULT. Every call emits one structured log line tying the decision to the registry version.

Verification

Confirm correctness with boundary cases specific to unclassified deductions, run in CI and against a daily reconciliation job:

  1. Confidence boundary. Assert a code scoring exactly 0.85 is accepted as SIMILARITY and 0.849... falls through. The threshold comparison must be >=, evaluated in Decimal, never float.
  2. Garnishment cap boundary. With disposable earnings of $1000, assert amount == Decimal("250.00") routes to a default (at the cap) while Decimal("250.01") quarantines. Test the protected-wage edge: any disposable at or below $217.50 yields a $0 cap, so any positive garnishment quarantines.
  3. FLSA floor boundary. Construct a record where (gross_pay - amount) / hours_worked lands exactly on $7.25 (passes) and one cent below (quarantines).
  4. Safe-default direction. Assert an unknown clean code resolves to POST_TAX_DEFAULT, never pre-tax — a default must never understate taxable wages.
  5. Decimal enforcement. Assert constructing a Deduction with a float amount raises TypeError, and reconcile a synthetic batch to the cent.
  6. Normalization idempotency. Property-test that normalize is stable under re-application across suffix, case, and separator mutations.

Failure Modes

  • Suffix drift accepted as a new bucket. A vendor ships MED_V3 and the canonical map silently treats it as unmapped, defaulting a pre-tax medical premium to post-tax. Root cause: normalization that strips digits but not version tokens, or an alias registry that was never updated. Fix: strip non-alphanumerics so MED_V3 normalizes toward MEDV3, keep MED aliases in the versioned registry, and alert when similarity-match volume for a vendor spikes — a spike means the registry, not the queue, needs the update.
  • Truncated code creating a false high-confidence match. A CSV export cuts 401KCATCHUP to 401KCAT, which scores above 0.85 against 401KCATCHUP and is mapped — but it could equally be a different catch-up plan. Root cause: similarity matching on truncated input without a length-overlap guard. Fix: require a minimum normalized-length overlap before accepting a similarity match, and quarantine codes shorter than the registry’s minimum canonical length.
  • Garnishment evaluated on gross instead of disposable earnings. The cap is computed against gross_pay, inflating the allowable amount and breaching the CCPA ceiling. Root cause: conflating gross with disposable earnings (gross minus legally required withholdings). Fix: the cap function takes disposable_earnings as an explicit argument; never pass gross_pay into it, and assert the two fields are distinct at the boundary.

Frequently Asked Questions

Why default unknown deductions to post-tax instead of pre-tax?

Post-tax is the conservative direction. A deduction wrongly defaulted to pre-tax reduces reported taxable wages and corrupts the ACA affordability denominator, both of which are statutory understatements that surface at audit. A deduction wrongly defaulted to post-tax merely over-taxes the employee temporarily and is trivially correctable once classification is confirmed. When you must guess the direction of an error, guess toward the one that does not under-report to the IRS.

What confidence threshold should similarity matching use?

Start at 0.85 and treat it as a floor, not a target. The cost of a false positive — applying a wrong tax treatment that reconciles to itself — is far higher than the cost of an extra quarantine review, so the threshold should be conservative. Pair it with a minimum length-overlap guard so truncated codes cannot score artificially high, and review the threshold whenever a vendor’s alias set changes.

How is the garnishment cap affected by state overrides?

The federal CCPA cap under 15 U.S.C. § 1673 is the ceiling on the worker’s exposure. State rules may only reduce it — for example a state that limits general garnishments to 10% of gross — never raise it. Apply the federal cap first, then take the more protective of the federal and state results. Child-support and tax-levy garnishments follow separate, higher statutory ceilings and must be classified before the general cap is applied.

When does an unclassified deduction belong in quarantine rather than a default?

Quarantine whenever resolving the record would require a guess that an auditor could challenge: the amount breaches the garnishment cap, the deduction would push the effective rate below the FLSA floor, the jurisdiction is unresolved, or the code is too short to match safely. A clean unknown that clears every guard can take the post-tax default; anything that touches a statutory limit escalates to human review.