# ADP → FCT labor wire — schema contract v1.2.1 (ChatGPT labels approved)

**Project:** FCT Command Center  
**Job:** `e8d3a53f` FCT v2.2.0 [2/3]  
**Status:** Path + columns + join confirmed; blended allocation + UI labels approved by ChatGPT (2026-09-05); v1.2.1 ownership + acceptance-case addendum  
**Date:** 2026-09-05  
**Evidence:** `source-inspected` (ADP Employee Daily Totals XLS from Workforce Now + FCT `QBO_SEED.drivers`)

---

## 1) Drop path (confirmed)

| Location | Role |
| --- | --- |
| **Canonical (FCT app):** `dispatch/inputs/adp/` | What FCT calc should read (replace `.placeholder`) |
| **Box mirror:** `/workspace/dispatch/inputs/adp/` | Grok daily drop while OneDrive PC write is gated |
| **Hub-readable sample:** [`/docs/adp-sample-2026-09-03`](https://ai-hub.jamesgrunsky.workers.dev/docs/adp-sample-2026-09-03) | Claude/ChatGPT can fetch without OneDrive |
| **By-driver rollup:** [`/docs/adp-sample-2026-09-03-by-driver`](https://ai-hub.jamesgrunsky.workers.dev/docs/adp-sample-2026-09-03-by-driver) | Convenience aggregate |

**Filename pattern:** `Employee-Daily-Totals-YYYY-MM-DD.csv` (ISO date = report day in PT).

Raw ADP export remains `.xls` under `/workspace/adp/` (not for calc parse — use the normalized CSV).

---

## 2) Source report

- ADP Workforce Now → **Employee Daily Totals**
- Company code: `ZCT`
- Layout: employee header row (`Last Name`, `First Name`, `Position ID`) then pay lines (`Pay Date`, `Pay Code`, `Hours`, `Dollars`)
- Observed pay codes (2026-09-03): `REGULAR-Regular`, `OVERTIME-Overtime`, `DBLTME-Doubletime`, `PAID MEAL-Paid Meal`, `California Meal Plan-`

---

## 3) Column contract (line-level CSV)

Header row **exact**:

```text
pay_date,position_id,last_name,first_name,fct_driver_name,match_status,pay_code,hours,dollars,qbo_rate,source_file
```

| Column | Type | Notes |
| --- | --- | --- |
| `pay_date` | `YYYY-MM-DD` | Work date from ADP |
| `position_id` | string | e.g. `ZCT000053` |
| `last_name` / `first_name` | string | From ADP (punctuation stripped) |
| `fct_driver_name` | string | **Exact** `state.drivers[].name` or empty if unmatched |
| `match_status` | `matched` \| `unmatched` | Never silent-drop unmatched |
| `pay_code` | string | ADP pay code as exported |
| `hours` | number | May be 0 |
| `dollars` | number | ADP paid dollars for that code |
| `qbo_rate` | number or empty | Roster rate when matched (informational) |
| `source_file` | string | Origin XLS basename |

**Do not** parse the raw XLS in FCT calc — only this CSV.

---

## 4) Join option (confirmed)

**Option A (chosen):** Grok emits pre-normalized `fct_driver_name` = exact FCT roster string.  
Calc joins `load.driverName` → CSV on `fct_driver_name` equality only. **No fuzzy match at parse time.**

Unmatched employees (`match_status=unmatched`, empty `fct_driver_name`): **warn visibly** in UI; **do not** allocate their dollars onto other drivers’ loads.

2026-09-03 unmatched (office / non-roster — expected):

- `ZCT000058` Rene Casias  
- `ZCT000085` Kelly Kearns  
- `ZCT000095` Vincent Maimone  

---

## 5) OT attribution — **blended approved** (ChatGPT 2026-09-05)

**Decision:** Use **blended** allocation for the first wire: for each matched driver/workday, allocate imported payroll dollars in proportion to each load's recorded hours ÷ that driver's recorded load-hours that day.

This is an **allocation of recorded payroll**, not a measurement of each load's incremental/marginal cost. Do not present it as the cost of accepting a new load.

**Pay-code rule:** Meal-premium dollars stay in the payroll numerator; meal-premium nominal hours must **not** inflate the load-hour denominator.

**Zero load-hours:** Produce unallocated payroll for that driver-day — never divide by zero or silently zero labor.

**Unmatched payroll:** Never redistribute to other drivers; keep visible in reconciliation.

---

## 6) Acceptance proof for this drop

- File: `dispatch/inputs/adp/Employee-Daily-Totals-2026-09-03.csv` (also on hub sample URL above)  
- Rows: **85** pay lines  
- Matched drivers: **23**  
- Totals: **284.98** hours, **$7,106.22** dollars  
- Header matches section 3  

**Owner (current two-agent scope):** Grok under James — Claude is offline at the office PC; do not wait on Claude to build. Hub sample CSV is the read source; mirror into OneDrive `FCT-deploy/dispatch/inputs/adp/` when PC sync is available. Do not invent columns.

---

## 7) Next

1. **Grok (owner):** finish UI label verify + browser round-trip evidence on 2026-09-03; keep weekday ADP → CSV drops; map new Position IDs when roster changes.  
2. **ChatGPT (reviewer):** independent review of Grok's revision + browser evidence when posted; secondary bandwidth on job `92b7964f-e8ad-421d-855a-fb27635ee05e` SMS/TCPA once `fct_v220_sms_spec` is attached.  
3. **Claude:** offline for now — no build ask until Mac/office reconnect. Prior wire credit retained.

---
## 8) UI labels (ChatGPT-approved 2026-09-05) — contract v1.2

Use these exact label intents in the FCT UI:

| Label | Meaning |
| --- | --- |
| **Allocated payroll labor — ADP, [work date]** | Per-load allocated dollar amount from imported ADP |
| **Allocation rate per recorded load-hour** | Driver-day payroll $ ÷ recorded load-hours |
| **Default base wage estimate ($/hr)** | The $22.28 fallback, with dated source shown — **not** “loaded” or “ADP actual” |
| **Estimated employer burden** | Separate amount/rate when applied; never imply raw ADP $ already includes every employer cost without evidence |
| **Incomplete load coverage — full driver-day payroll is allocated across the recorded loads. Per-load costs may be overstated.** | Thin-load / partial coverage warning |

**Reconciliation identity (to the cent):**  
`allocated matched payroll + matched payroll without an allocation + unmatched payroll = total imported payroll`

**Out of scope for this wire (track in same job thread, no silent switch):** 1.31 vs 1.50 burden-factor path correction.

**Acceptance still requires (Grok):** current app revision + browser round-trip showing a real imported day, method/date labels, coverage warning, visible fallback when no ADP applies, zero-load + unmatched handling, and no double-count on reimport. Preserve prior 17/17 builder tests as evidence; ChatGPT review did not re-run them.

**Explicit cases retained from original ChatGPT review (must remain true):**
- Partial-day and multi-day `pay_date` allocation: dollars stay on the ADP work date; do not blend across dates.
- Never charge ADP-allocated labor and the default `$22.28` fallback on the same load/driver-day at once.
- Reimport of the same CSV must not double-count (idempotent replace).

Canonical review comment: job `e8d3a53f` / comment `98c679a8-5495-45bc-9027-687807bae34b`.
