Skip to main content

payday-super-checker

tests License: MIT Python 3.10+

Experimental review aid — not a compliance determination. Check Australian super contributions against the payday-super deadlines and produce an experimental SG-charge estimate for lines that the supplied facts establish as late.

Since 1 July 2026, super is generally due within 7 business days of each payday instead of quarterly. A missed deadline can create an SG shortfall, notional earnings and administrative uplift; the ATO makes the assessment. This tool reviews a CSV from payroll, clearing-house and fund records. It refuses or marks UNKNOWN where those records do not establish the statutory facts.

Built by Ryan Duguid, a provisional member of Chartered Accountants ANZ. Written independently, in his own time and on his own equipment.

Install

Python 3.10 or later. No runtime dependencies.

git clone https://github.com/ryanduguid/payday-super-checker.git
cd payday-super-checker && pip install .

Cloning first means you have the sample file the next command uses. To skip the clone, pip install git+https://github.com/ryanduguid/payday-super-checker.git installs the tool alone; point it at your own CSV.

Use

payday-super-check examples/sample_payrun.csv --as-at 2026-08-10 --confirm-transition-allocation
payday-super-checker: 10 contribution lines, as at 2026-08-10

  ON_TIME: 5  AT_RISK: 1  LATE: 2  UNPAID: 1  UNKNOWN: 0  SKIPPED: 1

Lines with exposure (experimental estimates, largest first):
  row 5  QE day 2026-07-09  due 2026-07-20  UNPAID, 21 days late to as-at date (nothing applied to this payday)
      shortfall $780.00  notional earnings $5.15  experimental SG charge estimate $785.15 - $1256.24
      note: the deadline passed on 2026-07-20 and no remittance or fund-receipt date is recorded...
  row 3  QE day 2026-07-09  due 2026-07-20  LATE, 15 days late to fund receipt
      super $540.00 (received, so the shortfall is nil)  notional earnings $2.54  experimental SG charge estimate $2.54 - $4.07

  Total across 3 line(s): shortfall $780.00, notional earnings $8.07,
  experimental estimated SG charge $788.07 - $1260.92.

The block above is abridged: the real run lists every exposed line and then a page of assumptions. It deliberately identifies an exposed record by its input row rather than its employee identifier, so redirected output does not place payroll identifiers in process logs. Use that row number to find the full record in report.csv.

Full detail goes to report.csv: due date, which deadline rule applied, days late, the final shortfall after any offset, notional earnings, best and worst case uplift, and every warning that applies to that line.

The sample contains contributions from the 1–28 July 2026 transition period, so its command includes --confirm-transition-allocation. Do not copy that flag mechanically. LCR 2026/1 applies those contributions first to any employee shortfall for the quarter ended 30 June 2026. Use the flag only after you have reconciled every affected employee; the confirmation is recorded in the report. Without it the checker stops before writing a verdict.

Verdicts are ON_TIME, AT_RISK (remitted in time but no fund receipt recorded), LATE, UNPAID (a supported deadline has passed and nothing is recorded against it), UNKNOWN (the verdict is not available) and SKIPPED (defined-benefit interests). LATE and UNPAID both carry experimental exposure figures.

Some UNKNOWN rows are quiet because there is nothing to assess yet: a supported deadline has not passed, the row carries no SG amount, or a stale pre-payment is recorded before a supported deadline. Other UNKNOWN rows need attention and drive exit code 2. This happens when a deadline extends past the holiday table's complete horizon, including an unfunded row whose deadline may not have passed, or when an earlier positive row could trigger item 4 but does not evidence an eligible contribution that was received, legally allocated to that earlier QE day and on time. These rows get their own console block and the CSV records both candidates in unassessable_between, such as LATE or ON_TIME, UNPAID or NOT_YET_DUE, or LATE or AT_RISK. Read that column when parsing the file: UNKNOWN alone also covers nil rows with nothing at risk.

To get a real verdict, enter the missing holidays in a --holidays-override file and add "verified_until": "YYYY-MM-DD" naming the last date you entered them for. The holidays alone are not enough, and that is deliberate: a file holding one 2029 holiday is not a file that has 2029 covered, and treating it as one would silence the warning across every gap it left. Only you know how far you went.

{
  "verified_until": "2029-12-31",
  "add": [
    {"date": "2029-03-30", "name": "Good Friday", "jurisdictions": ["ALL"]},
    {"date": "2029-04-02", "name": "Easter Monday", "jurisdictions": ["ALL"]}
  ],
  "remove": []
}

A date on or before a deadline that runs past the calendar's coverage still gets a verdict. A missing holiday can only push the real deadline later, so paying early is provably on time whatever the calendar is missing.

The exit code is 0 when nothing is exposed and nothing is left undecided, 2 when either is true, and 1 on a data or file error, so you can run it from a scheduled job. Argparse also uses 2 for a bad command line, so a wrapper should check stderr before raising an alarm.

Options

Option What it does
-o, --output Where to write the report CSV (default report.csv)
--as-at DATE Measures notional earnings on still-unpaid contributions to this date (default: today)
--assessment-date DATE The day the ATO assessed the charge for these paydays. Only contributions received before it clear the shortfall. Omit if no assessment has issued
--map FIELD=COLUMN Point one field at your column name; repeatable
--mapping-file FILE Same thing as JSON, see examples/mapping.example.json
--holidays-override FILE Add or remove public holidays from the bundled calendar; its optional verified_until declares how far you have entered them
--confirm-transition-allocation Confirm you reconciled LCR 2026/1 for every contribution dated no later than 28 July 2026: pre-1 July amounts are unused excess and 1–28 July amounts remain after any June-quarter employee shortfall

Input columns

Required: employee_id, payment_date, sg_amount. Everything else is optional but sharpens the answer.

Field Column in the sample Meaning
employee_id employee_id Anything that identifies the employee consistently
qe_day payment_date The day you actually paid the wages, not the period end or payslip date
sg_amount sg_amount Super guarantee for that payment
remitted remitted_date Day you sent the money
received fund_received_date Day the fund received the eligible contribution associated with this QE day. Receipt is necessary for an on-time result; the row association also asserts the contribution was allocable and applied to this QE day under the statutory ordering
first_to_fund first_contribution_to_fund Yes for the first contribution to that fund (new starter, or a fund switch)
out_of_cycle out_of_cycle Yes only for an allowance, bonus, commission, loading, payment in advance or back payment made outside an established payment timing, pattern or schedule, where the statutory next-standard-payment conditions are met
next_standard_qe_day next_standard_payday The next schedule-consistent day on which the employer actually made a subsequent non-out-of-cycle QE payment. A planned payday or a date after employment ended is not enough
db_interest defined_benefit Yes for defined-benefit interests, which are skipped

Dates read as YYYY-MM-DD, day-first DD/MM/YYYY, or 9 Jul 2026, and a time component is ignored. Amounts accept $ and thousands separators. Anything unreadable stops the run and names up to twenty bad cells at once, and so does a truncated row, a duplicated column heading, or a mapping that points at a column your file does not have. This experimental review tool refuses facts it cannot safely infer.

The amount column must hold the operator-determined SG amount only. Apply the employee and payment boundaries in regulations 11 and 12, the qualifying-earnings rules, the maximum contribution base and any other relevant facts before supplying it. Salary sacrifice and additional contributions have a different base and deadline, so filter them out. The tool does not classify raw pay, apply regulations 11 or 12, or decide whether a termination payment is qualifying earnings. LCR 2026/D1 remains draft, so those decisions stay human.

Where the fund receipt date comes from. Payroll exports (Xero, MYOB, KeyPay, Employment Hero) give you the payday and the batch remittance date, which is what remitted is for and why those lines come back AT_RISK. A fund receipt date lives somewhere else: your clearing house's per-contribution settlement or status report, or the fund's own contribution history. Without it the tool tells you what you sent and when, not what the law tests.

Import from your payroll system

Two commands turn a payroll export and a super payments export into a checked report, with no column mapping to write by hand:

payday-super-check import --payroll "Payroll Activity Details.csv" --super "Superannuation Payments.csv" -o contributions.csv --confirm-statutory-allocation
payday-super-check contributions.csv --confirm-transition-allocation

The first command reads both exports and writes the canonical CSV the second command checks. Where an employee has more than one in-scope positive payday, it stops without output unless you pass --confirm-statutory-allocation. Do not copy that flag mechanically. LCR 2026/2 paragraphs 31–33 apply contributions in fund-receipt order to the earliest QE day with a base or final shortfall. Vendor exports provide employer payment dates and pay-period labels, not that receipt order or the assessment facts that can change it. Use the flag only after you have reconciled every relevant payday, contribution receipt and assessment and confirmed that the export's periods plus payment-date/row order reproduce the statutory allocation. The importer allocates a short shared payment to the earliest covered shortfall; it no longer treats the vendor period end as an instruction to pay a later QE day first. The confirmation is printed in the import record.

Profiles ship for Xero Payroll, MYOB AccountRight, MYOB Business and Employment Hero / KeyPay, one profile each for the payroll-activity report and the super-payments report. The importer picks a profile per file by scoring column headings against what it knows. Force one with --vendor xero, --vendor myob-ar, --vendor myob-business or --vendor employment-hero when detection cannot pick, or to skip detection outright.

Every shipped profile is unverified against a real export. Each one's column names come from vendor help documentation, and Xero, MYOB and Employment Hero do not publish an actual column list for these reports, so the first real export you try may match no profile at all. When that happens the importer prints the column headings it found in your file next to the headings each candidate profile wanted. Send both lists back and fixing the profile is a one-line edit to its JSON file, not a rewrite.

No payroll system or clearing house exports a fund receipt date. Xero's report gives the date a payment was sent to the fund. MYOB gives a Paid Date. Employment Hero gives a Beam status (Sent to fund, Reconciled, and so on). None of these is the date the fund received the money. The legal deadline tests receipt by the fund, and time in transit through a clearing house is the employer's risk, not the fund's, so a vendor date is a remittance date and fund_received_date is left blank on every row. Fill that column in from your fund or clearing house before treating any verdict from the checker as final. remitted_date carries the vendor date only where every super row behind that payday's match has one; where any of them does not, it is blank, for the reason in the next paragraph.

Two kinds of payday are written the same as a completely unpaid one. The canonical CSV has one amount column and one remittance-date column per payday, with no room for "999.99 of 1000.00 arrived" and none for "1000.00 arrived, 600.00 of it on a date anyone recorded".

A part payment is written with remitted_date blank, the same as a payday nothing was paid against, so the checker reports the whole 1000.00 as a shortfall. So is a payday matched in full where any of the super rows behind the match carries no vendor date: writing the date that covers 600.00 of the 1000.00 would tell the checker the whole payday settled that day, which nothing on record supports.

Both figures survive in the importer's own warning lines, written as row N: partial: 999.99 of 1000.00 matched and row N: 400.00 of 1000.00 matched has no payment date on record; latest known payment date 2026-07-15. Neither line is ever truncated by the warning cap. Apply them by hand until the canonical format has columns for them.

A full financial-year export needs trimming first. The check refuses any file holding a payday before 1 July 2026, because the old quarterly law governs those and this tool does not model it. An export that starts at 1 July 2025 therefore imports fine and then fails the check outright. The import names those rows in a warning and writes them anyway; delete them from the canonical file, or re-export from 1 July 2026, before running the second command.

A bare filename of import does not work. payday-super-check import, run against a file that is genuinely named import with no extension, is read as the import subcommand and fails on the missing --payroll/--super arguments instead of checking the file. payday-super-check import.csv and payday-super-check ./import both check the file as expected; only the exact bare string import is swallowed.

Local file boundary

This is a single-user command-line tool. Its positional input, importer input, mapping, calendar override and output arguments designate files the invoking operating-system account has chosen to read or write; they are not a sandbox. Do not expose the command as a web endpoint, multi-user service, or automation that accepts path values from a less-trusted caller without adding an appropriate safe-root boundary.

Generated outputs must have an explicit .csv filename. They are staged in the selected output directory and atomically replace the selected output name. This means an existing output symlink is replaced rather than followed, and a failed write does not leave a partial report at that name. Both commands still refuse an output path that resolves to any file they read: for the check, the contribution CSV, a --mapping-file and a --holidays-override; for the import, both exports. The .csv rule does not cover this on its own, because a mapping or override file is free to be named .csv too.

The rules it applies

The enacted framework is the Treasury Laws Amendment (Payday Superannuation) Act 2025 (No. 57 of 2025, assent 6 November 2025), the Superannuation Guarantee Charge Amendment Act 2025 (No. 58 of 2025), and the regulations registered as F2026L00133, which amend the Superannuation Guarantee (Administration) Regulations 2018; the current 1 July 2026 compilation is F2026C00535. It applies to paydays from 1 July 2026. A primary-source implementation review was completed on 15 August 2026. The review records the exact legislation, final ruling status, holiday authorities, runtime comparison and residual limits. It does not turn this experimental tool into an ATO compliance determination or authorise a release. The original 2 August research notes remain as a historical audit trail.

ATO LCR 2026/1, LCR 2026/2 and LCR 2026/3 were issued on 5 August 2026. LCR 2026/D1 remains draft pending the appeal from Department of Education v Commissioner of Taxation [2026] FCA 898. The tool therefore accepts an operator-provided SG amount and does not decide qualifying-earnings or termination classifications.

The deadline. A contribution is on time only if the fund receives it, with enough information to allocate it, by the end of the seventh business day after the payday (SGAA 1992 s 6(1) "usual period", s 18C(1)(c)). Paying a clearing house by the deadline does not count, and the ATO's small business clearing house closed on 30 June 2026, so transit time is now the employer's risk. That is why a line with a remittance date but no fund receipt date comes back AT_RISK rather than ON_TIME.

Business days. SGAA s 6(1) defines a business day as any day that is not a Saturday, a Sunday, or a public holiday for the whole of any State, the ACT or the NT. One national calendar applies to every employer: WA Day stops the clock for a Sydney employer. Regional and locally substitutable holidays do not. The Brisbane Ekka, WA's default King's Birthday date and Melbourne Cup Day are therefore business days for this definition. The bundled calendar is complete from July 2026 through 31 August 2027. Business Victoria still lists the exact 2027 grand-final holiday as subject to the AFL schedule, so later deadlines fail closed until that date is officially published or supplied through a reviewed override. Unconfirmed dates do not extend a deadline.

20 business days instead of 7 for the first contribution to a particular fund, whether that is a new starter or an existing employee switching funds (s 18C(2) item 1). Under item 4, a later QE day's deadline can align to an earlier contribution's later end only if that earlier eligible contribution was made and applied under s 18C(1). The checker applies the extension only where the earlier canonical row evidences an on-time fund receipt and thereby asserts the statutory allocation. A positive amount or remittance alone is not enough. If the missing fact could change the verdict, the later row is attention-driving UNKNOWN, not treated as extended. Include each employee's earlier paydays and reconcile their LCR 2026/2 allocation; a single pay run cannot establish the rule.

Out-of-cycle payments can ride a subsequent standard QE payment's window rather than their own (SGAA s 18C(2) item 2 and s 18C(3); final determination F2026L00784). The earlier review called this “LI 2026/20”, but that shorthand is not shown on the Federal Register's as-made page; this project uses the controlling registered identifier and text instead. The final determination covers six kinds of qualifying earnings: allowances, bonuses, commissions, loadings, payments in advance and back payments. The employer must have an established timing, pattern or schedule for qualifying-earnings payments, and the payment must fall outside it. It also requires the employer to actually make a subsequent non-out-of-cycle qualifying-earnings payment on the next day consistent with that schedule. A planned next payday, or a termination/final payment with no later payment, does not establish item 2. out_of_cycle=yes without the date of that actual subsequent payment is a hard error. Where both this rule and the new-fund rule apply, the later deadline governs.

When it is late, notional earnings compound daily at the general interest charge rate from the day after the deadline until the fund receives the money (s 19A), and an administrative uplift of up to 60% applies on top. A late contribution that reaches the fund before the ATO assesses the charge clears the shortfall itself (s 18D), which is why a paid-but-late line shows a small experimental estimate rather than the whole contribution. Pass --assessment-date if an assessment has already issued, and the shortfall stays in the figure.

The uplift starts at 60% and falls 20 points where the clean-history test is met, and another 40, 35, 30 or 15 points according to voluntary-disclosure timing. For QE days through 30 June 2028, the transitional clean-history lookback begins on 1 July 2026 rather than reaching back a full 24 months. The report shows both ends of the range because the ATO, not this tool, decides which reductions apply. Read the low figure carefully: it assumes both a clean history and a voluntary disclosure lodged within 30 days of the payday, so for an old payday with no disclosure already lodged the real floor is higher.

What it does not do

  • Choice loading (25% of contributions paid in breach of choice of fund, capped at $1,200) is not estimated. You cannot see a choice breach in a pay run.
  • The maximum contributions base ($270,830 a year per employer for 2026-27) is not applied, because it needs each employee's cumulative earnings for the year. Contributions for people earning above it may show a larger shortfall than the law requires.
  • The late payment penalty and interest on an unpaid assessment sit after assessment and are out of scope.
  • Exceptional circumstances determinations under s 18C(2) item 3 are not detected. If one covers you, your deadline is later than what you see here.
  • Paydays before 1 July 2026 stop the run. Those are governed by the old quarterly law and this tool does not model it.
  • Fund deeds, enterprise agreements and awards can require payment earlier than the SG rules. This tool only tests the SG charge.
  • Qualifying-earnings and termination classifications are not decided. LCR 2026/D1 remains draft and the input contains an operator-provided SG amount, not raw pay components.
  • Assessment rounding is not reproduced. The tool displays report components to cents with ROUND_HALF_UP; TAA 1953 s 16B applies a five-cent down-round only to the Commissioner's final assessed SG charge. Exposure figures are experimental estimates.

PCG 2026/1 sets out how the ATO will allocate compliance resources for paydays up to 30 June 2027. Fixing a late contribution quickly lowers the chance of review. It does not remove the liability: the Commissioner has no discretion to waive the charge once a shortfall is known (PCG 2026/1 paragraph 11).

Keeping it current

Everything that goes stale lives in paydaysuper/data/.

  • gic_rates.json : the general interest charge rate, which the ATO resets every quarter. Each entry records where the figure came from and when that was checked. Update it each quarter or the notional earnings estimate drifts; the tool warns when a calculation runs past the last quarter it knows.
  • rates.json : SG rate, concessional cap, maximum contributions base, per financial year, with the same source and checked-date fields.
  • business_days.json : national non-business days plus the eight official jurisdiction URLs and their check date. Regenerate with python tools/generate_calendar.py > paydaysuper/data/business_days.json, which uses holidays==0.102 as a development dependency, then check every line against those official pages before shipping. Raw generator output deliberately sets verified_until to 1 July 2026 and official_sources.checked to null; a human reviewer must record the check date and raise the horizon only as far as every jurisdiction's official material supports. Regional, part-day and locally substitutable dates are excluded. Unconfirmed dates are reference-only and do not extend a deadline until an official override confirms them.

Part-day holidays, such as Christmas Eve evening in South Australia, Queensland and the Northern Territory, are treated as business days because applying them as a full day would extend the statutory deadline without authority. Melbourne Cup Day and WA's default King's Birthday date are also business days for this definition because the official pages permit regional substitution, so neither applies throughout its State. The override exists for newly proclaimed whole-of-jurisdiction dates and for an operator who has completed a later official calendar.

Tests

pip install -e ".[dev]"
pytest

The suite pins the ATO's own worked examples: a first payday of 9 July 2026 falling due 7 August 2026, and notional earnings on a payday whose usual period ends 18 June 2027 starting to accrue on 19 June 2027. It also pins the traps that make this hard, including the Ekka staying a business day, deadlines that must not change when you reorder rows in the CSV, and the leap-year divisor in the interest calculation. Test data is synthetic. Never commit client payroll data to this repository; .gitignore blocks the usual formats, case-insensitively.

Disclaimer

This is an educational tool, not tax, legal or financial advice, and using it creates no professional relationship. The ATO assesses the SG charge; figures here are experimental estimates that exclude components listed above. LCR 2026/1–3 are final, while LCR 2026/D1 remains draft and may change after the pending appeal. Check anything material against current ATO guidance and calculators, and get advice for your circumstances.

MIT licensed.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

payday_super_checker-0.1.1.tar.gz (244.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

payday_super_checker-0.1.1-py3-none-any.whl (85.9 kB view details)

Uploaded Python 3

File details

Details for the file payday_super_checker-0.1.1.tar.gz.

File metadata

  • Download URL: payday_super_checker-0.1.1.tar.gz
  • Upload date:
  • Size: 244.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for payday_super_checker-0.1.1.tar.gz
Algorithm Hash digest
SHA256 df0dd9ddf7984a2727a405e2b594e3741aa9d8bbd8b17ef36d72fb3a3b938722
MD5 bfa5374c9a99295cd24d901f3f505999
BLAKE2b-256 66f0d4ee4768aed9339b280ba3ca602851cd4be398511c11d6c51bd4a01288c7

See more details on using hashes here.

Provenance

The following attestation bundles were made for payday_super_checker-0.1.1.tar.gz:

Publisher: publish-pypi.yml on ryanduguid/payday-super-checker

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file payday_super_checker-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for payday_super_checker-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 ea3be1173e41d3eeaa604a65a4b89af4c6957d75f27a199f10f28cfbd614042c
MD5 ef3d9f8fc477686cfca861bce4d48c7c
BLAKE2b-256 22df4468a56d210e67a4b7cd0ca074b1f44a2bb6a3f7f019793070ff61076549

See more details on using hashes here.

Provenance

The following attestation bundles were made for payday_super_checker-0.1.1-py3-none-any.whl:

Publisher: publish-pypi.yml on ryanduguid/payday-super-checker

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.1.2

2 files

This release

0.1.1 This release

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page