Skip to content

ADR-016-F: Award Interpretation — V2 Port (Phase 1) + Native Engine via FWC MAPD (Phase 2)

Status: Accepted Date: 2026-04-18 Decision Makers: Gautham Chellappa Supersedes: ADR-009-F Depends on: ADR-006-F (Hexagonal ports), ADR-010-F (Strangler Fig migration from v2), ADR-007-F (IRAP architecture)

Context

ADR-009-F committed to KeyPay (Employment Hero Payroll) as the Phase 1 award-interpretation engine on the assumption that Finnest needed Day-1 coverage of all 120+ Modern Awards. The costed benefit was "launch with competitor-parity award coverage without a $500K native build".

Two material facts have surfaced since ADR-009-F:

  1. Actual Phase 1 award scope is 10–20 awards, not 120+. ASG Central v2 (the system Finnest is the Strangler Fig migration target for — see ADR-010-F) has only 10–20 Modern Awards actively configured and in use by real placements. The 120+ number was a theoretical surface area that assumed we needed full-catalogue coverage at launch. We don't — we need to match v2's actual coverage at launch.

  2. Fair Work Commission publishes the Modern Awards Pay Database (MAPD) API as public government data. Reference: https://www.fwc.gov.au/work-conditions/awards/modern-awards-pay-database/modern-awards-pay-database-api — no commercial contract, no per-employee licensing, no API-key acquisition negotiation. MAPD exposes raw award structure (classifications, base rates, allowances, penalty schedules), not computed rates — the interpretation logic is ours to build.

With Phase 1 scope compressed 6–12× (from 120+ to 10–20 awards) and a free public data source for Phase 2, the cost/benefit ratio underpinning ADR-009-F has collapsed:

  • The "avoid $500K native build" argument no longer holds — native build for 10–20 awards is tractable within Phase 2 capacity.
  • The "per-employee KeyPay fee" becomes a recurring cost with no offsetting avoided-build-cost once Phase 2 exists anyway.
  • The IRAP argument (KeyPay is AU-hosted) is irrelevant if no commercial SaaS is in the path at all.

Decision

Phase 1 (launch): Port the 10–20 Modern Awards currently configured in ASG Central v2 into Finnest's own tables via the Strangler Fig migration (ADR-010-F). Awards become Finnest-owned data from Day 1; no third-party rate calculator is called.

Phase 2 (post-launch, timeline TBD): Build a native Finnest.Payroll.Adapters.Native engine in Elixir that consumes raw award data from FWC MAPD. The native engine handles classifications, base rates, allowances, and penalty schedules. All interpretation logic lives in-tree and is auditable. FWC MAPD serves as:

  • the upstream data source for awards not yet in Finnest's tables,
  • the update feed for Fair Work's quarterly award amendments,
  • the cross-reference for the integrity of ported v2 award data.

Phase 3 (conditional, re-evaluate): Only if Finnest needs to expand beyond the 10–20 ported awards before Phase 2 native engine is production-ready does a commercial stopgap become viable. Not necessarily KeyPay — evaluate current market at that point.

Hexagonal port Finnest.Payroll.AwardInterpreter (ADR-006-F) is unchanged. Adapter implementations:

Adapter Phase Source
Finnest.Payroll.Adapters.V2Ported Phase 1 Finnest tables populated from the v2 migration
Finnest.Payroll.Adapters.Native Phase 2 Native interpretation over FWC MAPD data
Finnest.Payroll.Adapters.Mock Test Deterministic fixtures

The port contract (calculate_rate/5 returning {:ok, %RateCalculation{}} / {:error, reason}) survives unchanged. Consumers do not need to know which adapter is active.

Alternatives Considered

Option Rejected because
Retain KeyPay (ADR-009-F original decision) Scope compression from 120+ to 10–20 awards removes the build-cost justification. Per-employee recurring fees + commercial dependency + external API risk no longer offset by an avoided native build — Phase 2 native build is tractable at this scope regardless.
Build native award engine from Day 1 Phase 0/Phase 1 already carries a heavy foundation workload (umbrella, auth, tenancy, agent orchestrator, event store, deploy pipeline). Native engine for 10–20 awards is legitimate Phase 2 work, not Phase 1. Port-first keeps launch tight.
FWC MAPD-only (skip the v2 port; build native on MAPD for Phase 1) v2 data contains ops-encoded knowledge (classification choices, allowance defaults, penalty edge cases Finnest clients actually use). Porting it forward carries that institutional knowledge into Phase 1. Rebuilding from MAPD Day 1 means re-discovering those configuration choices.
Hybrid: KeyPay for Day-1 + v2 port in parallel Adds integration complexity and cost for a bridge period with no durable return — Phase 2 replaces it anyway.

Consequences

Positive:

  • Zero commercial dependency for award interpretation across all three envs (integration / staging / production-finnest + IRAP). Removes a negotiation and a recurring cost.
  • All interpretation logic in-tree and auditableIRAP-friendly; no third-party rate-calc service in the audit boundary.
  • No per-employee / per-month KeyPay fees — Phase 1 has zero marginal cost per placement for award interpretation. Phase 2 adds engineering effort but no external spend.
  • Scope-realistic Phase 1 — 10–20 awards from v2 is tractable migration work; 120+ was never the actual launch need.
  • FWC MAPD is public — no API-key acquisition blocker for Phase 2 native engine start.
  • Strangler Fig alignment — porting awards is on-pattern with ADR-010-F; no architectural divergence.

Negative:

  • Finnest team owns award maintenance as Fair Work Commission publishes quarterly Modern Award updates. Phase 2 engine must subscribe to MAPD update feed (or poll) and reconcile changes. Commercial vendors (KeyPay et al.) previously absorbed that burden.
  • Phase 1 award surface is locked to v2's coverage. Any new award a Phase 1 client needs (outside the 10–20 ported) is blocked until Phase 2 native engine ships. Commercial clients with exotic award exposure may need a stopgap per Phase 3 above.
  • FWC MAPD API stability is a Phase 2 risk. Government APIs can carry less strict backward-compatibility guarantees than commercial SaaS. Contract drift → Phase 2 engine work.
  • No rate-calculation fallback if the Phase 2 native engine has a bug and Phase 1 ported data is stale for an affected award. Mitigation is a well-tested engine + canary + rollback plan, not a commercial fallback.

Mitigations:

  • Phase 2 engine caches MAPD responses locally; outage of FWC MAPD does not stop rate calculation for already-known awards.
  • Oban scheduled job (daily or weekly) polls MAPD for quarterly update diffs; diff review + apply is operator-gated.
  • Architecture test asserts port contract compliance so adapters cannot regress silently.
  • Canary rollout of Phase 2 native engine per-award (feature flag per award code) — fall back to Phase 1 ported rate on any mismatch beyond tolerance.
  • Finnest.Payroll.Adapters.V2Ported tables remain the source of truth for ported awards even after Phase 2 native engine lands — the native engine becomes an alternative calculation that cross-checks.

Tipping points for re-evaluation:

  • Client demand for an award outside v2's 10–20 before Phase 2 engine ships → evaluate commercial stopgap (current market, not necessarily KeyPay).
  • FWC MAPD API contract breaks or is deprecated → snapshot + fork the schema; consider commercial fallback for MAPD-derived data.
  • Phase 2 native engine discovers a class of award mechanics that are genuinely intractable natively (rare but possible — some EBA/Enterprise Agreements have highly custom rules) → hybrid: native engine for Modern Awards, commercial or manual for exotic EBAs.

B12 gap analysis (updated from ADR-009-F):

  • C5 Award-aware billing — reads Phase 1 ported award data, then Phase 2 native engine output. No KeyPay dependency. Client rate card multiplier applies in Finnest billing as before.
  • C6 Timesheet-to-invoice automation — same pipeline; rate calculation is internal rather than an external API call. Latency profile improves (no round trip).
  • STP Phase 2 — direct ATO API becomes the path. KeyPay's STP submission option is no longer in scope.

Relationship to Guardrails

Enforces / is enforced by:

  • AR-15 (hexagonal ports for external integrations) — the AwardInterpreter port is unchanged; FWC MAPD call (Phase 2) sits behind the Native adapter, not called directly from domain code.
  • D17 (financial precision) — all rate calculations remain in %Money{} via ex_money + DECIMAL(12,4) storage. Currency handling is unaffected by the source change.
  • SE-07 (TLS for external APIs) — applies to FWC MAPD calls when Phase 2 lands.
  • SE-15 (TLS for internal DB connections) — applies to the v2 MySQL connections backing the Phase 1 port (see F-009).

Implementation Impact

  • STORY-F-010 (KeyPay API validation spike + adapter skeleton) is dropped from Sprint 2. Sprint 2 reclaims 2 points of capacity.
  • KEYPAY_API_KEY secret is removed from the finnest Bitwarden project inventory. CLAUDE.md secret table updated to match.
  • Phase 1 migration stories (post-Phase-0, inside Scout+Verify go-live window or earlier) will include a "port v2 award tables into Finnest" story — the Phase 1 data-loading counterpart to this ADR. That story is not in Phase 0; it lands when the Strangler Fig migration writes begin.
  • Phase 2 stories (post-launch) will include:
  • Finnest.Payroll.Adapters.Native skeleton + MAPD client
  • FWC MAPD schema exploration + local snapshot
  • Native interpretation engine (classifications → base rate → penalties → allowances → final %RateCalculation{})
  • Oban scheduled job for quarterly MAPD update reconciliation
  • Downstream documentation drift — the following reference KeyPay and will read stale until edited:
  • brainstorm-05-multi-industry-design.md §Award Engine
  • brainstorm-08-integration-strategy.md §Award Interpretation
  • ADR-009-F itself (status flipped to Superseded below; content otherwise frozen per ADR immutability convention)

Supersession of ADR-009-F

ADR-009-F's status is updated to Superseded by ADR-016-F. Its content is preserved unchanged for audit trail — ADRs are immutable once accepted; supersession is expressed via status, not rewrite. Future readers encountering ADR-009-F should follow the supersession pointer to this ADR.