Applying the FLSA Salary-Basis Test for Exempt Classification

A payroll engine that stops at “does this salary clear the federal floor” will misclassify an employee who is docked half a day’s pay for leaving early, suspended for two days as discipline, or paid on an hourly-equivalent schedule dressed up as a weekly figure — because the white-collar exemptions require a predetermined amount paid on a true salary basis, not merely a salary-sized number. This page codes the classification gate itself: the sequence of tests — salary level, salary basis, and duties — a payroll system must evaluate, in that order, before a record is trusted with exempt status, and it is part of the FLSA Threshold Mapping topic within the Core Architecture & Compliance Mapping framework.

Problem Framing

Most engines get the salary-level test right and stop there, because it is the arithmetic-looking half of the exemption: annualize the pay rate, compare it to a floor, done. That comparison — governed by 29 CFR § 541.600 — is necessary but not sufficient, and treating it as sufficient produces two distinct classes of misclassification that a level-only gate cannot see:

  • Salary-basis violations. 29 CFR § 541.602 requires the salary to be a predetermined amount “not subject to reduction because of variations in the quality or quantity of the work performed.” An employer that docks an exempt employee’s pay for a partial-day absence, or suspends them for two days without invoking a permissible category, has converted a salaried position into something paid by the hour in substance — and the regulation does not care that the annualized figure still clears the floor.
  • The “actual practice” trap. A single improper deduction does not automatically strip exemption from the whole workforce, but a pattern of improper deductions does, under the actual-practice doctrine embedded in § 541.603. An engine that flags every deduction identically — one isolated, reimbursed error the same as a repeated payroll-wide policy — either overreacts on a harmless correction or, worse, misses the pattern because no single instance looks disqualifying in isolation.

The duties test compounds this: 29 CFR § 541.700 requires an evaluation of actual job responsibilities that no payroll field can determine on its own. A classification gate that treats “salary clears the floor” as equivalent to “exempt” silently promotes untested duties determinations into a compliance record. The correct architecture resolves salary level and salary basis deterministically in code, and treats the duties result as an external input the gate consumes but never infers — flagging it for review rather than guessing.

The weekly-to-annual comparison that anchors the salary-level test is a fixed multiplier, applied before any threshold comparison, exactly as in the parent registry’s annualization rule:

Sannual=Sweekly×52S_{\text{annual}} = S_{\text{weekly}} \times 52

FLSA salary-level, salary-basis, and duties decision tree A compensation record enters a salary-level check against the weekly threshold under section 541.600; failing routes to non-exempt. Passing records enter a salary-basis check under section 541.602; an improper-deduction actual practice routes to non-exempt, while an isolated deduction cured under the section 541.603 safe harbor does not void exemption. Passing records enter a duties-test check under section 541.700, which the engine cannot resolve from payroll data alone, so an unresolved result is flagged for manual review instead of being auto-classified exempt. Only a record that clears salary level, salary basis, and a confirmed duties pass is marked exempt. Compensation record salary · deduction log · duties flag Salary ≥ weekly threshold? (29 CFR § 541.600) NO NON-EXEMPT fails salary-level test YES Paid on true salary basis? (29 CFR § 541.602) NO NON-EXEMPT improper deduction — actual practice (§ 541.602) unless cured Safe harbor (29 CFR § 541.603) isolated deduction + policy + reimbursement ⇒ intact YES Duties test satisfied? (29 CFR § 541.700 — flag only) NO / UNKNOWN PENDING REVIEW duties determination required YES EXEMPT level + basis + duties all satisfied
Salary level, salary basis, and duties are evaluated in sequence, and an improper-deduction actual practice or an unresolved duties flag both stop the record short of an exempt determination.

Prerequisites & Data Requirements

The gate consumes a normalized compensation record plus a deduction event log; it does not compute the salary basis from raw timesheets, and it does not infer duties from a job title.

Field Type Precondition
weekly_salary Decimal Predetermined weekly amount; already isolated from bonuses per Data Boundary Definitions.
weekly_threshold Decimal Resolved by FLSA Threshold Mapping for the employee’s jurisdiction and effective date; never hardcoded here.
deduction_events List[DeductionEvent] Each event carries a reason_code, amount, and is_full_day flag; empty list means no deductions this period.
duties_test_result str enum One of PASS, FAIL, UNDETERMINED, supplied by HR/legal — never derived from payroll fields.
has_communicated_policy bool Whether a written anti-improper-deduction policy with a complaint mechanism is on file, required for the § 541.603 safe harbor.
deduction_reimbursed bool Whether an improper deduction was reimbursed before the safe-harbor evaluation ran.

Two preconditions sit outside the function signature:

  1. The threshold is resolved, not chosen here. This module calls into the same effective-dated registry built in FLSA Threshold Mapping; it never embeds a dollar figure, because the federal floor and any higher state or municipal floor both change on statutory schedules.
  2. Duties are an input, not an output. duties_test_result must arrive from a system of record for job-duties determinations. A gate that defaults UNDETERMINED to PASS converts a missing HR review into a fabricated exempt classification.

Step-by-Step Implementation

Step 1 — Model the record and the deduction reason taxonomy. Separate permissible deduction categories under § 541.602(b) from everything else, so the detector never has to special-case strings later.

from dataclasses import dataclass, field
from decimal import Decimal
from enum import Enum
from typing import List
import logging

logger = logging.getLogger("payroll.flsa.salary_basis")

WEEKS_PER_YEAR = Decimal("52")


class DeductionReason(str, Enum):
    FULL_DAY_PERSONAL = "full_day_personal"          # permissible: 541.602(b)(1)
    FULL_DAY_SICKNESS_PLAN = "full_day_sickness_plan"  # permissible: 541.602(b)(2)
    JURY_WITNESS_MILITARY_OFFSET = "jury_witness_offset"  # permissible: 541.602(b)(3)
    SAFETY_RULE_PENALTY = "safety_rule_penalty"        # permissible: 541.602(b)(4)
    CONDUCT_SUSPENSION_FULL_DAY = "conduct_suspension_full_day"  # permissible: 541.602(b)(5)
    FIRST_LAST_WEEK_PRORATION = "first_last_week_proration"      # permissible: 541.602(b)(6)
    FMLA_LEAVE = "fmla_leave"                          # permissible: 541.602(b)(7)
    PARTIAL_DAY_DISCIPLINE = "partial_day_discipline"  # improper
    LACK_OF_WORK = "lack_of_work"                      # improper
    QUALITY_OR_QUANTITY = "quality_or_quantity"        # improper


PERMISSIBLE_REASONS = frozenset({
    DeductionReason.FULL_DAY_PERSONAL,
    DeductionReason.FULL_DAY_SICKNESS_PLAN,
    DeductionReason.JURY_WITNESS_MILITARY_OFFSET,
    DeductionReason.SAFETY_RULE_PENALTY,
    DeductionReason.CONDUCT_SUSPENSION_FULL_DAY,
    DeductionReason.FIRST_LAST_WEEK_PRORATION,
    DeductionReason.FMLA_LEAVE,
})


@dataclass(frozen=True)
class DeductionEvent:
    reason: DeductionReason
    amount: Decimal
    is_full_day: bool


@dataclass(frozen=True)
class CompRecord:
    employee_id: str
    weekly_salary: Decimal
    weekly_threshold: Decimal
    deduction_events: List[DeductionEvent] = field(default_factory=list)
    duties_test_result: str = "UNDETERMINED"
    has_communicated_policy: bool = False
    deduction_reimbursed: bool = False

Step 2 — Evaluate the salary-level test. Compare in Decimal with an inclusive floor, since 29 CFR § 541.600 sets a minimum the salary must meet or exceed. Expected output: a weekly salary equal to the threshold passes.

def salary_level_passes(record: CompRecord) -> bool:
    """29 CFR 541.600 — salary must meet or exceed the resolved weekly floor."""
    return record.weekly_salary >= record.weekly_threshold


# weekly_salary = Decimal("1128.00"), weekly_threshold = Decimal("1128.00")
# salary_level_passes(record) -> True

Step 3 — Detect improper deductions and apply the actual-practice test. A single improper, full-day-equivalent deduction with a communicated policy and a reimbursement is cured by the § 541.603 safe harbor; anything else with an improper deduction fails the basis test. Expected output: one improper partial-day deduction, uncured, returns False.

def salary_basis_passes(record: CompRecord) -> bool:
    """29 CFR 541.602 with the 541.603 safe harbor for isolated, cured deductions."""
    improper = [
        e for e in record.deduction_events
        if e.reason not in PERMISSIBLE_REASONS
    ]
    if not improper:
        logger.info(
            "sbt_basis_check emp=%s improper_count=0 result=pass",
            record.employee_id,
        )
        return True

    isolated = len(improper) == 1
    cured = (
        isolated
        and record.has_communicated_policy
        and record.deduction_reimbursed
    )
    logger.info(
        "sbt_basis_check emp=%s improper_count=%s isolated=%s cured=%s",
        record.employee_id, len(improper), isolated, cured,
    )
    return cured


# One partial-day discipline deduction, no policy, no reimbursement:
# salary_basis_passes(record) -> False

Step 4 — Combine the tests into the classification gate. Salary level and salary basis are decided in code; duties is consumed as a flag, never inferred. Expected output for a record passing level and basis but with duties_test_result="UNDETERMINED": status PENDING_REVIEW.

class ClassificationStatus(str, Enum):
    EXEMPT = "exempt"
    NON_EXEMPT = "non_exempt"
    PENDING_REVIEW = "pending_review"


def classify_exempt_status(record: CompRecord) -> ClassificationStatus:
    if not salary_level_passes(record):
        status = ClassificationStatus.NON_EXEMPT
        logger.info(
            "flsa_sbt_classify emp=%s status=%s reason=salary_level",
            record.employee_id, status.value,
        )
        return status

    if not salary_basis_passes(record):
        status = ClassificationStatus.NON_EXEMPT
        logger.info(
            "flsa_sbt_classify emp=%s status=%s reason=salary_basis",
            record.employee_id, status.value,
        )
        return status

    if record.duties_test_result == "FAIL":
        status = ClassificationStatus.NON_EXEMPT
    elif record.duties_test_result == "PASS":
        status = ClassificationStatus.EXEMPT
    else:
        status = ClassificationStatus.PENDING_REVIEW

    logger.info(
        "flsa_sbt_classify emp=%s status=%s reason=duties_%s",
        record.employee_id, status.value, record.duties_test_result.lower(),
    )
    return status

Step 5 — Run the gate end to end. Feed a record that clears the floor, carries one cured improper deduction, and has a confirmed duties pass.

record = CompRecord(
    employee_id="E-70213",
    weekly_salary=Decimal("1150.00"),
    weekly_threshold=Decimal("1128.00"),
    deduction_events=[
        DeductionEvent(
            reason=DeductionReason.PARTIAL_DAY_DISCIPLINE,
            amount=Decimal("45.00"),
            is_full_day=False,
        )
    ],
    duties_test_result="PASS",
    has_communicated_policy=True,
    deduction_reimbursed=True,
)
result = classify_exempt_status(record)
# result is ClassificationStatus.EXEMPT — the isolated, reimbursed deduction
# is cured by the 541.603 safe harbor, so it does not reach salary-basis failure.

Verification

Run these cases before the gate feeds any overtime or accrual routine:

  1. Salary-level boundary. Set weekly_salary == weekly_threshold and assert salary_level_passes returns True; set it one cent below and assert False. The test is inclusive, matching the >= in FLSA Threshold Mapping.
  2. Uncured improper deduction flips the result. Feed a record with one PARTIAL_DAY_DISCIPLINE event, has_communicated_policy=False, and assert classify_exempt_status returns NON_EXEMPT even though weekly_salary clears the floor by a wide margin.
  3. Safe harbor cures an isolated deduction. Feed the same improper event with has_communicated_policy=True and deduction_reimbursed=True, and assert salary_basis_passes returns True. Then add a second improper event in the same period and assert it returns False — the safe harbor only reaches truly isolated deductions.
  4. Permissible deductions never trip the detector. Feed a FULL_DAY_SICKNESS_PLAN event and assert salary_basis_passes returns True with zero improper deductions logged.
  5. Duties is a flag, not an inference. Feed a record passing level and basis with duties_test_result="UNDETERMINED" and assert the gate returns PENDING_REVIEW, never EXEMPT by default.
  6. Decimal determinism. Run case 1 three times and assert byte-identical booleans; any drift means a float entered the comparison path.

Failure Modes

  • Auto-exempting on salary level alone. Root cause: the gate short-circuits after salary_level_passes returns True and skips the basis and duties checks entirely, so a docked, hourly-equivalent employee is reported exempt. Remediation: enforce the three-stage sequence as a single function with no early success return before all applicable checks run, and add a regression test that asserts EXEMPT is unreachable without an explicit duties_test_result="PASS".
  • Treating every improper deduction as fatal. Root cause: the detector flags the exemption as lost on the first improper deduction it sees, ignoring the § 541.603 safe harbor, which turns a single corrected payroll error into a mass reclassification event and a retroactive overtime liability that was never actually owed. Remediation: implement the isolated-plus-cured branch explicitly, and log improper_count, isolated, and cured separately so the actual-practice pattern is auditable across pay periods, not just within one.
  • Missing communicated-policy evidence at safe-harbor time. Root cause: has_communicated_policy defaults to True when the field is absent from the HRIS feed, silently granting a safe harbor the employer never actually established. Remediation: require the field explicitly, quarantine records where it is null via Fallback Routing Strategies, and never treat an unset flag as satisfied.

Frequently Asked Questions

Does one improper deduction always destroy an employee's exempt status?

Not by itself. Under the actual-practice doctrine in 29 CFR § 541.603, an isolated or inadvertent improper deduction is cured if the employer has a clearly communicated policy prohibiting improper deductions — including a complaint mechanism — and reimburses the employee. What destroys the exemption is a pattern: repeated improper deductions across pay periods or employees, evaluated by factors like the number of improper deductions relative to infractions and the time period involved.

What deductions are permissible under the salary-basis test?

29 CFR § 541.602(b) enumerates a closed list: full-day absences for personal reasons or bona fide sickness plans, offsets for jury or witness fees and military pay, penalties for major safety-rule violations, full-day disciplinary suspensions for workplace conduct violations under a written policy, proportional pay in the first or last week of employment, and unpaid FMLA leave. Any deduction outside that list — most notably docking pay for a partial-day absence — is improper regardless of how small the amount is.

If a salary clears the federal threshold, is the employee automatically exempt?

No. Clearing the salary-level test under 29 CFR § 541.600 is one of three required conditions. The employee must also be paid on a true salary basis under § 541.602 and satisfy the applicable duties test under § 541.700. A classification gate that returns exempt on salary level alone is reporting an unverified determination, which is why this implementation routes an unresolved duties result to a pending-review state instead of a default pass.

How is the salary-basis test different from the salary-level test?

The salary-level test is a single arithmetic comparison: does the annualized or weekly salary meet or exceed a fixed floor. The salary-basis test is behavioral: does the employer actually pay that salary as a predetermined amount without reducing it for variations in the quality or quantity of work. An employee can clear the level test every pay period and still fail the basis test the moment an improper, uncured deduction hits their pay.