# Clocks and SLAs

Clock definitions, business time, the SLA register, compliance math and the minimum routing contract. Platform facts behind the timers are in [platform-mechanics.md](platform-mechanics.md).

## Clock events

Store each as its own timestamp on the request record or ledger row:

- received_at: when the source system got the request (form tool, chat, scheduler, product event). Not CRM creation. A hidden browser timestamp is editable, so prefer a server receipt.
- crm_seen_at: when the CRM first held the request. The gap to received_at is integration delay.
- assigned_at: first assignment to an accountable person, or to a staffed queue that has its own pickup SLA in the register. In Salesforce an OwnerId that starts 00G is a queue and one that starts 005 is a user (confirm on one known queue and user); a Lead parked in an unstaffed queue is not assigned.
- last_assigned_at: the current assignment. HubSpot's lead Owner Assigned Date holds only this one.
- first_response_at: occurrence time of the first relevant human attempt, such as an email that answers the request or a call to the right number.
- disposition_at: when the request was accepted, rejected, recycled or converted.
- first_connected_at: the first two-way exchange (a reply from the buyer, a connected call, a held meeting). Reported beside the response clock, never its stop.
- meeting_booked_at: when the buyer booked a meeting through a scheduler, at submission or later.
- acknowledged_at (optional): the automatic receipt message. Measured separately; it never stops the response clock.

These never count as a first relevant response: an auto-reply, the scheduler's confirmation, creating a task, changing a status, an internal alert, a failed delivery, an automatic sequence step or workflow email, or the time an activity was logged rather than when it happened. A template or sequence email counts only when a person sent it individually after the request arrived (a manual send or a manual email task).

Self-booked meetings:

- A request whose buyer books a meeting at submission goes in its own class, booked_at_submit, reported apart from hand_raise.
- Its clock stops at the first relevant human attempt before the meeting (a personal confirmation or a preparation question) or at the meeting held, whichever comes first.
- A booking never counts as first_response_at for hand_raise. Without this class, teams either count the booking as a zero-minute response, which inflates compliance, or as unanswered, which raises false S03 and S07 findings.

## Two clocks

- Buyer clock: received_at to the stop event. It never resets. This is the SLA.
- Owner clock: last_assigned_at to the stop event. It resets on reassignment. It is a coaching metric and must be labelled as one.
- HubSpot's Lead response time report is an owner clock: assignment to first interaction, reset on reassignment, and marking a task in progress counts.
- The HubSpot lead property Time to First Touch runs from lead creation to the first outreach activity. It misses ingest delay and counts any outreach.
- Salesforce has no buyer clock unless the team records received_at. A queued Web-to-Lead request gets a CreatedDate later than the submission.

Split the buyer clock into three parts, each with an owner:

- received to CRM (integration owner), in wall-clock minutes
- CRM to assigned (routing owner), in wall-clock minutes
- assigned to first response (team lead), in business minutes

## Coverage calendar

One calendar per team. A request class uses the calendar of the team that owns it. The audit config mirrors this: named blocks under `calendars` and `classes.<class>.calendar` ([reading-the-crm.md](reading-the-crm.md)), so one sla run measures every team on its own hours.

```yaml
calendars:
  sdr_us:
    owner: sales_ops
    timezone: America/New_York     # IANA name, so daylight saving is handled
    hours:
      mon: ["09:00", "17:00"]
      tue: ["09:00", "17:00"]
      wed: ["09:00", "17:00"]
      thu: ["09:00", "17:00"]
      fri: ["09:00", "17:00"]
    holidays: ["2026-09-07", "2026-11-26", "2026-12-25"]
    away_status_source: HubSpot user availability, set by each rep
```

- A request outside hours starts counting at the next opening.
- A day takes one span or a list of spans, for a break or an overnight shift: `tue: [["00:00", "06:00"], ["22:00", "24:00"]]`, where `"24:00"` closes at midnight. The audit rejects overlapping spans.
- Each class uses one calendar. Split a follow-the-sun queue into one class per owning team (hand_raise_us, hand_raise_emea), each on its own calendar.
- Report business minutes and wall-clock minutes side by side. The buyer still feels the weekend.

Worked numbers (America/New_York, Monday to Friday 09:00 to 17:00, holiday 2026-09-07):

- Received Friday 16:55, answered Monday 09:20: 5 + 20 = 25 business minutes, 3,865 wall-clock minutes.
- Received on the Monday holiday at 11:00, answered Tuesday 09:05: 5 business minutes, 1,325 wall-clock minutes.
- Received Friday 2026-10-30 at 16:50, answered Monday 2026-11-02 at 09:10, across the end of daylight saving: 20 business minutes. The script's self-test checks this case.

## SLA register

One entry per handoff in [../assets/sla-register.yaml](../assets/sla-register.yaml): trigger, unit, request class, start and stop fields, calendar, target, compliance target, breach actions with named roles, sender and receiver owners, report, the baseline with its n and overdue count, scope where the entry covers only some contracts, and whether the target is a proposal.

Target rules:

- Baseline: the median business minutes over every due request in the class across at least the last 8 full weeks, counting unanswered requests at their business age at the as-of time and leaving not-yet-due requests out. A median over answered requests only is not a baseline: it improves while the queue is ignored. The sla run prints it as the S04 baseline line.
- With fewer than 30 due requests, keep the labelled proposal below (or the buyer promise) and say the baseline is too small.
- First target: the baseline, or the promise already made to buyers if that is tighter.
- A median target starts near 50% compliance, so the first S02 FAIL is expected and is not a defect of the target. If the team wants a first target it already meets, use the baseline p90 and tighten toward the median. While more than 10% of due requests stay unanswered, no target reaches 90%: fix S03 first.
- Tighten only after two consecutive months at 90% compliance or better.
- Never set a target from a published benchmark.

Labelled proposals for a team with no history (mark each `proposal: true`):

- Hand-raise: first relevant response within 15 business minutes; escalate at 60.
- Booked at submission: a personal confirmation within 1 business day (480 business minutes), and always before the meeting starts; list meetings that start sooner than the target separately, because the script measures the business-minute target only.
- MQL: accept or reject within 1 business day (480 business minutes on an 8-hour day).
- SDR to AE: AE accepts or rejects within 1 business day after the meeting is held.
- Closed-won to customer success: accepted within 2 business days; kickoff scheduled within 5.
- Renewal: cycle owned with a next action 120 days before the decision date, or 30 days before the notice deadline, whichever is earlier.

Escalation ladder proposal:

- Unassigned after 5 business minutes: alert the fallback owner.
- No response at the target: notify the owner and the backup.
- At twice the target: the backup takes the request.
- At four times the target: notify the team lead.
- Re-check the request's state before each step. Stop on a response, a disqualification, a withdrawal, or the class's own stop event (for booked_at_submit, the personal confirmation or the held meeting). A self-booked request never enters the hand-raise ladder.
- Escalate against the original deadline from received_at. A workflow that starts late computes the time left; it never adds a fresh 15 minutes after a 40-minute integration delay.

## Compliance math

Put each eligible request in exactly one category:

- excluded: has an excluded_reason (spam, test, duplicate delivery); reported with counts by reason
- on time: stopped within the target
- late: stopped after the target
- overdue: no stop and its business age at the as-of time is past the target
- not yet due: no stop and still inside the target

Compliance = on time / (eligible - not yet due). Requests missing the start timestamp leave the denominator and are reported in S01 instead.

Acme hand-raise example (from the worked example):

- 12 requests, 1 excluded as spam, so 11 eligible.
- 4 on time, 4 late, 2 overdue, 1 not yet due.
- Compliance = 4 / (11 - 1) = 40.0%.
- The answered-only rate, 4 of 8 = 50.0%, is not compliance. A median over answered requests can improve while the queue is ignored.
- Baseline: 32.5 business minutes over the 10 due requests, with r-06 and r-09 counted at their age. The answered-only median, 19.5, understates the wait. Only 10 are due, under 30, so the target stays the buyer promise of 15.
- Never give an unanswered request a response time of zero. Store its age and status until it stops.

## Implementing timers

- HubSpot: a deadline datetime property (computed from received_at and the calendar outside HubSpot, or in a custom code action where the account has that feature), an "Until a date property" delay on it, then a branch that re-checks state. Delays capture the time zone at entry. The business days only delay counts whole days and cannot express minutes.
- Salesforce: scheduled paths or the routing tool's timers. Verify how business hours apply in the live docs before promising minute-level SLAs.
- On every reassignment keep received_at, set last_assigned_at and increment owner_changes.

## Owner clock design worth copying

A pattern from a public company handbook (listed in `sources.yaml`, paraphrased here): a timer starts when an MQL is assigned, stops on any status change out of MQL, a new timer starts when the owner changes, a transfer count goes up, and both total and business-hours durations are stored. It answers "how fast did each owner act". Pair it with the buyer clock, which answers "how long did the buyer wait".

## Minimum routing contract

Use this until the lead-routing-and-speed-to-lead skill is installed. That skill decides who gets the request and how distribution and escalation work. This contract fixes the order, the records and the clock.

Precedence, first match wins:

1. Recognise a repeat delivery (same source submission ID) and keep the earlier outcome.
2. Route support and billing intent to their own workflows.
3. Keep the open opportunity owner.
4. Keep the managed account owner. A covering rep handles the request while the owner is away; account ownership does not change.
   - Salesforce: standard Lead records have no AccountId before conversion, so native assignment rules cannot see the open opportunity or account owner. A lead-to-account match (by email domain, or a matching tool) must write the matched account and its owner onto the Lead before assignment rules run, or steps 3 and 4 never fire.
5. Check eligibility and the fields routing needs. Missing data goes to a clarification path with a timeout, never silently to the smallest segment.
6. Choose the segment pool.
7. Distribute among available reps.
8. Send everything else to a monitored fallback with a named duty owner and an unowned view checked hourly during coverage.

Every routing outcome writes: request ID and received_at, matched account and the matching basis, rule ID and policy version, pool and owner, assigned_at, retry count and exception reason.

Enrichment gets a timeout and a fallback. A demo request never waits for an enrichment response.

## Provenance of the famous response-time numbers

- A 2011 Harvard Business Review article reported that firms attempting contact within an hour were far more likely to qualify the lead than those waiting longer, and that firms responding within 30 days took 42 hours on average.
- A 2007 InsideSales.com and Oldroyd study of six companies on one vendor's platform reported large differences in contact and qualification odds between 5 and 30 minutes, and excluded close rates.
- Both are historical evidence that delay matters. They are not targets, and their multipliers do not belong in a business case. First targets come from the team's own baseline.
