44. Weekly-settled outsourced pay (fix bi-monthly boundary underpayment)
Date: 2026-06-21
Status
Proposed
Amends 43. Full-time pay basis, approved overage pay, and in-house holiday proration (outsourced pricing only; the in-house model and the overage/holiday rules of 0043 stand).
Context
ADR-0043 priced outsourced contractors per Sunday–Saturday week clipped to the bi-monthly period:
the rate is allocated across the period's clipped weeks by day-count, and each clip pays
allocation × min(1, workedInClip / (40 × clipDays/7)), capped at 1 unless that week's overage is
approved. It asserted the golden invariant — "full-time attendance every week pays exactly the
bi-monthly rate" — held, and that boundary proration only "shifts slightly".
That assertion is false for real schedules. A year-long simulation running the actual engine
(calculateContractorPay) across all 24 bi-monthly periods × 8 realistic weekday patterns, plus a
five-agent adversarial verification (all clear, high confidence), shows the current model
systematically underpays a fully-compliant 40h/week contractor by ~3–6.6% per year — for
essentially every weekday pattern:
| Model | Compliant-worker annual pay | Periods short / 24 |
|---|---|---|
| Current (Sunday clip + per-clip cap) | 93.4–97.0% of salary | 16–20 |
| Monday-anchored clip (the "shift") | 90.4–96.8% (worse for Mon–Fri & 4×10) | 17–20 |
| Whole-week settle by Sunday | 100.00%, every schedule | 0 |
| Period-level FTE proration | 96.6–98.0% | 7–13 |
Root cause is mechanical (src/lib/pay/index.ts:186-188): the per-clip cap discards surplus from
over-FTE clips, while the short period-boundary clips are docked, with no cross-clip carry. It
is strictly worse than even period-level proration — it underpays in periods where the worker
rendered more than the period's full FTE-hours, so it cannot be defended as "pay for hours actually
rendered."
Why this went unseen: the golden test's fullTimeEntries helper (tests/lib/pay/pay.test.ts:24-25)
logs a full 40h at each clip's start — including the 2-day boundary clip — so every clip caps to 1
and pays full. No test distributes hours by real weekday across a period boundary. The "every
alignment" coverage is illusory.
Real instances (June 1–15 2026, live data): Kristine (MDS, ₱37,500) paid ₱35,000; Genesis (MDS, ₱25,000) ₱24,583; Genesis (RN, ₱25,000) ₱21,583; Jay-Ar (in-house) ₱17,500 (in-house is period-level and unaffected).
Week-anchor inconsistency. Pre-0044 pay keyed weeks on Sunday-start (sundayWeekStart, Sun–Sat),
but the live approval_requests row is Mon–Sun ISO (week_start = Mon 2026-06-08, a pre-0043
leftover). For a Sunday-working contractor the anchor is not cosmetic: the same hours form
different 7-day windows, so a worker compliant in Mon–Sun weeks can be docked under Sun–Sat (verified —
Genesis RN's June 1–15 pay was ₱19,687 under Sun–Sat vs ₱23,125 under Mon–Sun, the latter matching
her 38h/36h weekly compliance). Pay and overage must use the same window, so unifying on ISO
Mon–Sun is required, not optional.
Owner decision (2026-06-21): pay whole Mon–Sun weeks (the "M2" model). Do not adopt a partial shift — and (after a verified misstep) not Sun–Sat, which underpays Sunday-workers.
As-built note (2026-06-22): Shipped as the Mon–Sun plan in §1–4 below, with one refinement: no RPC migration. Whole-week settlement means a boundary week's days can sit in the prior period, but rather than widen the close/lock + discard RPCs to the settling-week range (which conflicts with the period-aligned in-house model), the lock/TOCTOU stay period-aligned — every calendar day is locked by exactly one period — and the close-time snapshot counts only the in-period subset while pay settles whole weeks. The anchor flip touched pay, weekly-hours, overage detection/approval, manual entry, the contractor calendar, and performance scoring; facility invoicing keeps its own Sunday weeks (a separate billing domain). An interim Sun–Sat implementation was built and reverted after the Genesis-RN underpayment above surfaced in real-data validation.
Decision
1. Canonical pay week — ISO Mon–Sun, settled by its Sunday
Define one week everywhere — pay settlement, the weekly-hours matrix, overage detection, and approval keys: the ISO Mon–Sun week, which settles in the bi-monthly period containing its Sunday (the week's last day). This reverses 0043's Sunday-start choice; the existing approval data is already Mon–Sun, so it becomes consistent rather than conflicting. (0043 chose Sunday-start to align pay with overage; we preserve that alignment goal by moving overage to the same ISO week, and pick ISO because settling by the Sunday-end attributes a week's pay to the period its work mostly falls in — e.g. June P1 pays the weeks ending Jun 7 and Jun 14, i.e. work Jun 1–14 — which Sunday-start does not.)
2. Outsourced engine (calculateOutsourcedPay, pure)
Replace clip-by-day-allocation + per-clip cap with whole-week settlement:
weeks = ISO Mon–Sun weeks whose Sunday ∈ [period.start, period.end]
ratio(w) = approvedOverage(w) ? worked(w)/40 : min(1, worked(w)/40) // worked = the week's FULL Mon–Sun hours
pay = rate × ( Σ ratio(w) / weeks.length ) // rate split equally across settling weeks
Properties (proven by simulation): a worker who renders 40h in every week earns exactly the
bi-monthly rate, for every period length and weekday alignment; hours over 40/week never inflate base
unless approved (cap preserved per-week, not per-clip); a part-timer still earns the worked fraction of
full-time. The in-house admin path (calculateInHousePay, period-level, holiday/PTO) is unchanged.
3. Data fetch (the real ripple — computePayRun, estimateContractorPay, computeCatchupCentavos)
These currently fetch time_entries with entry_date BETWEEN periodStart AND periodEnd. Whole-week
settlement needs each settling week's full hours, and a settling week's Mon–Sat days can fall in
the prior period. Therefore:
- Widen the entry query to cover the settling weeks' span (≈
periodStart − 6 days … periodEnd), then group by ISO week and keep only weeks whose Sunday is in the period. - No double-pay: each week has exactly one Sunday → one period (the bi-monthly halves partition the calendar). A week's Mon–Sat days that sit in an adjacent period are paid by this period (the one holding its Sunday), never twice.
- Keep the close-time TOCTOU check (
pricedEntryCount/pricedHours) consistent with the widened set between preview and close.
4. Overage coupling
Detect overage and key approval_requests on the same ISO Mon–Sun week; the engine's
approvedOverageWeeks lookup must use the identical key. Audit/repair existing rows (the lone live row
is already Mon–Sun). This closes the latent "approved overage never matches" gap.
5. Offboarding / termination — IMPLEMENTED (owner chose day-proration, 2026-06-22)
Without this, a mid-week offboard could lose the contractor's last days: their final week settles by
its Sunday, which may fall in the next period — but the next run excludes the ended assignment
(end_date < periodStart), so the week is paid by neither period.
Decision: when an assignment's end_date falls inside a period, that final period switches from
whole-week settlement to day-proration:
active window = [max(period.start, effective_date), end_date]
pay = rate × (activeDays / periodDays) × min(1, worked / (40 × activeDays/7))
The time-fraction prorates by how much of the period the contractor was active; the capped fulfillment
is their worked share of full-time over that window. A contractor active the whole period still earns
the full rate; no day is dropped; no week-boundary over/under-pay. Ongoing assignments (no in-period
end_date) are untouched (whole-week settlement). Implemented in calculateContractorPay via
calculateTerminatingPay; end_date/effective_date are threaded from the assignment through
computePayRun / estimateContractorPay / the catch-up service. Rejected alternative: re-homing the
final week into the equal split (dilutes full weeks or over-pays by ~a week-share at the boundary).
6. Tests
Add a permanent test that distributes hours per real weekday (a genuine 40h/week schedule) across a
period boundary and asserts full pay — the gap the current golden test cannot see. Fix/augment
fullTimeEntries so the suite exercises realistic daily distributions, not 40h-at-clip-start.
7. Migration of already-paid periods
Historical pay has not been imported yet (the timesheet/payroll reconstruction is still pending), so there are effectively no M0-priced closed periods in the system to true up. M2 therefore applies cleanly to all open and future runs, and the historical true-up question is deferred:
- Open periods (e.g. June 1–15 2026, not yet closed) close under M2 directly.
- When historical pay is imported/reconstructed, reconstruct under M2 from the start so those periods are correct rather than re-broken.
- Only if some periods were already closed under the 0043 clip+cap before this change ships: true-up
the per-assignment delta (M2 − paid) via the existing catch-up →
pending_pay_items→payment_adjustmentsmachinery over an owner-chosen look-back, after sign-off on the reconciliation report (the simulation's Query-4-style diff). Owner to decide the look-back at that time.
Consequences
- A fully-compliant contractor is paid exactly their salary regardless of weekday pattern; the documented golden invariant becomes true for real schedules, not just the test fixture.
- Per-period lumpiness for sub-compliant workers: a period settles 1–3 whole weeks and pay is the average of their fulfillment, so a light week weighs more in a 2-week period than a 3-week one. This is inherent to whole-week settlement and is annually neutral for compliant workers.
- Boundary attribution changes (a week's pay follows its Sunday), and the entry-fetch now reaches one partial week before the period — a behavioural change to verify against the close TOCTOU.
- The week anchor is unified on ISO Mon–Sun across pay, weekly-hours, overage, and approvals, fixing the approved-overage key mismatch.
- Supersedes ADR-0043's outsourced pricing only; 0043's full-time basis, approved-overage payment, catch-up flow, and in-house holiday/PTO proration are retained.
- Back-pay exposure if historical true-up is chosen (bounded by the look-back window); the per-period amounts are small (≈₱14.6k–₱31.5k/yr per ₱480k-salary contractor) but real.
Evidence / reproduction
Year-long, multi-model simulation against the real engine + a 5-agent adversarial verification
(engine-faithfulness, M2-correctness, methodology-bias, golden-test-masking, and a steelman that the
current engine is correct-by-design — all failed to refute). Throwaway harnesses (tests/tmp-*.test.ts)
were used and removed; the permanent regression test in §6 replaces them.