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:
-
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.
-
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 auditable — IRAP-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.V2Portedtables 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
AwardInterpreterport 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{}viaex_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_KEYsecret is removed from thefinnestBitwarden project inventory.CLAUDE.mdsecret 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.Nativeskeleton + 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 Enginebrainstorm-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.