---
name: "check-util-forecast"
description: "Read the 12-week utilisation forecast from the ESXP Talent Workbench > Availability Forecast tab, produce a weekly-totals summary and a per-assignment My-vs-PjM forecast detail, flag over/under-booked weeks and forecast discrepancies, then interactively resolve each discrepancy (match to the PjM's forecast, notify the PjM via Teams, or skip). Use whenever a delivery resource asks to check their utilisation forecast, review their availability forecast, reconcile their forecast against the PjM's, or chase up forecast variances."
version: "1.0.0"
updated: "2026-06-12"
status: "prototype"
audience: "Anyone"
phase: "Admin"
inputs:
  - "ESXP Talent Workbench > Availability Forecast"
outputs:
  - "weekly utilisation summary"
  - "forecast variance notifications"
depends_on: []
---

# Check Utilisation Forecast

## Description

Reads the **ESXP Availability Forecast** tab for the signed-in delivery resource, builds a **12-week
utilisation picture**, and helps the user reconcile their own (**My**) forecast against the **PjM**'s
forecast. It produces two tables — a **Weekly Totals** summary (with over/under-booked flags) and a
**Assignment Detail** table (My vs PjM per week, with discrepancies flagged) — then walks the user through
**resolving each discrepancy**: match to the PjM's figures, notify the PjM of the variance via a Teams
message, or skip.

This is a **read-then-act** browser skill. Reading the forecast is non-destructive; the only writes are
(a) updating the user's own forecast to match the PjM's when the user explicitly chooses to, and (b) sending
a Teams message the user has previewed and confirmed. It never changes the PjM's forecast and never submits
anything the user hasn't seen.

## Triggers

- "Check my utilisation forecast" / "review my availability forecast"
- "Reconcile my forecast with the PjM's" / "are my forecast and the PjM's aligned?"
- "Am I over- or under-booked over the next 12 weeks?"
- "Chase up the forecast variances" / "notify the PjMs where our forecasts differ"

## Inputs

| Input | Source | Required |
|---|---|---|
| ESXP Availability Forecast tab | `https://esxp.microsoft.com/#/workbench/DeliveryResource?tab=AvailabilityForecast` (SSO) | **Yes** |
| Per-discrepancy decision (match / notify / skip) | User, via an interactive prompt | Only when discrepancies exist |
| Notify-message confirmation | User, before any Teams message is sent | Only for the **Notify** action |
| User's first name | The signed-in user's first name | For the Teams message signature |

## Steps

> Use the **Browser Control (Playwright)** tools throughout (`browser_navigate`, `browser_snapshot`,
> `browser_click`, …). ESXP uses **SSO** via the existing Edge session — no credentials are entered. If a
> sign-in or session-timeout page appears, let SSO complete or click **Relaunch** and re-navigate; if it
> stalls, tell the user and stop.

### Step 1 — Navigate to the Availability Forecast

`browser_navigate` to
`https://esxp.microsoft.com/#/workbench/DeliveryResource?tab=AvailabilityForecast`. If redirected to Home,
click **Talent Workbench** in the left nav, then the **Availability Forecast** tab. `browser_snapshot` and
wait until the assignments have loaded before continuing.

### Step 2 — ⛔ Toggle "Review PJM forecast" ON

Locate the **Review PJM forecast** checkbox. If it reads **Off**, click it; confirm it reads **On**.
**This gate is mandatory** — the PjM forecast values and the **Match to PjM Forecast** row action are only
available when the toggle is **On**. Do not proceed to capture or resolution while it reads Off.

### Step 3 — Capture weeks 1–8

From the snapshot, record for each of the 8 visible weeks:

- **Week header** (e.g. "Jun 06-12").
- **Total Booked Hours** and **Total Available Hours**.
- **Per assignment**: name, customer, end date, **Resource ID** (the numeric link next to the customer
  name), **Allocated/Available** ratio (e.g. "36/27"), **My Forecast** (the spinbutton value), and
  **PjM Forecast** (the adjacent "Forecasts by PjMs" value).
- **Total Non-Project Hours** and **Total Time Away**.

### Step 4 — Capture weeks 9–12

Click **Go to next set of dates** and capture the same fields. Only weeks **9–12** (the first 4 of the 8
now shown) are needed to complete the 12-week window.

### Step 5 — Display the two tables

#### Table 1 — Weekly Totals

Columns: **#** · **Week** · **Total Booked Hrs** · **Total Available Hrs** · **Non-Project Hrs** ·
**Time Away Hrs**

- Format weeks as "D–D Mon", or "D Mon–D Mon" for cross-month weeks (e.g. "6–12 Jun", "27 Jun–3 Jul").
- Embed a status indicator in the **Total Booked Hrs** cell: 🔴 < 30 · 🟢 30–40 · 🟡 > 40.
- Add the footnote: `🔴 < 30 hrs · 🟢 30–40 hrs · 🟡 > 40 hrs`.
- If a 🟡 week is driven by time away rather than genuine overbooking, note it.

#### Table 2 — Assignment Detail

Columns: **Assignment** · **Customer** · **Ends** · **Alloc/Avail** · [12 week columns]

- **Alloc/Avail**: show "X/Y". Prefix ⚠️ if X > Y (over-allocated).
- **Week cells**: show "My \| PjM". Prefix ⚠️ if My ≠ PjM (discrepancy).
- Non-Project and Time Away rows show a single value (no PjM).
- Include a **Total Billable & Internal** subtotal row and a **Grand Total** row. The Grand Total must match
  Table 1's Total Booked.

### Step 6 — ⛔ Resolve discrepancies (one assignment at a time)

After both tables are shown, identify every assignment where **My Forecast ≠ PjM Forecast** in any
**editable** week (ignore past actuals — see Notes).

- **If there are none:** state this clearly and stop.
- **If there are any:** for each discrepant assignment, show a brief summary (which weeks, My vs PjM
  values), then ask the user what to do (e.g. via `m_ask_user`):

  > "What would you like to do for **[Assignment]** (**[Customer]**)?"
  >
  > 1. **Match to PjM Forecast** — update my forecast to the PjM's values
  > 2. **Notify PjM of variance** — send the PjM a Teams message
  > 3. **Do nothing** — skip

  Handle the chosen option below, then move to the next discrepant assignment. **Never** auto-pick — wait
  for the user's decision on each assignment.

#### Option 1 — Match to PjM Forecast

Confirm the **Review PJM forecast** toggle is **On** (Step 2). On the assignment row, click
**View row actions** (the `…` ellipsis) → **Match to PjM Forecast**, then click **Update forecast**
(Submit). Confirm the success alert appears.

#### Option 2 — Notify PjM of variance

1. **Capture engagement metadata** (for the TALOS-REF block). Navigate to
   `/workbench/Requestor?tab=Request&requestId={resource-id}`. Click the **Project** tab → capture the
   **Project ID** (e.g. `C.6908481937.002000`). Click the **Engagement** tab → capture the
   **Engagement ID** and **Engagement Name**. Return to the forecast tab.
2. **Find the PjM.** Click **Chat with PJM** on the assignment row — the Teams deep-link URL contains the
   PjM's email (e.g. `users=miosborne@microsoft.com`). Resolve their given name (e.g. via
   `m365_search_people`).
3. **⛔ Preview the message.** Build the message from the template in **Knowledge** and show it to the user.
   **Wait for explicit confirmation before sending.**
4. **Send.** Create the chat by email and send the message as HTML (e.g. `m365_create_chat_by_email` →
   `m365_send_chat_message` with `contentType: "html"`).

#### Option 3 — Do nothing

Skip this assignment and move to the next.

## Output Structure

1. **Table 1 — Weekly Totals** (12 rows, with the RAG status footnote).
2. **Table 2 — Assignment Detail** (per-assignment My \| PjM across 12 weeks, with subtotal and Grand Total
   rows).
3. A **discrepancy resolution** walk-through: either a clear "no discrepancies" statement, or a per-assignment
   summary plus the action taken (matched / notified / skipped).

## Behavioural Rules

- **⛔ Toggle ON first.** Never capture PjM values or attempt **Match to PjM Forecast** while the
  **Review PJM forecast** toggle reads **Off** — the data and the row action are unavailable.
- **Never auto-pick.** Resolve discrepancies one assignment at a time, and only after the user chooses an
  action for that assignment.
- **Never send without preview.** A Teams variance message is only sent after the user has seen it and
  explicitly confirmed.
- **Never edit the PjM's forecast.** The only forecast this skill writes is the user's own (and only on an
  explicit **Match** choice).
- **Skip past actuals.** Disabled spinbuttons are locked actuals — never treat them as discrepancies or try
  to edit them.
- **Don't invent data.** If a value can't be read from the snapshot, say so rather than guessing.

## Knowledge

### Field definitions

- **My Forecast** = the editable spinbutton value on the assignment row.
- **PjM Forecast** = the adjacent **Forecasts by PjMs** element value (only populated when the toggle is On).
- **Allocated/Available** = the "(Past Actuals + Future Forecast) / Total Requested hrs" value; store as
  "X/Y" (drop the "Hrs." suffix).
- **Total Booked** = billable + non-project + time away. A full-leave week shows 🟢 40 (leave, not billable).

### Teams variance message template

Only include `<tr>` rows for weeks where **My ≠ PjM**. `{{my-first-name}}` defaults to "Sam".

```html
Hi {{pjm-first-name}}<br>
I want to inform you of a variance between your forecast and mine for {{customer-name}} {{project-name}}.<br>
Can you please update the forecast in PjMXP accordingly?<br>
<br>
<table>
  <tr><th>Week</th><th>PjM Forecast</th><th>My Forecast</th></tr>
  {{discrepant-weeks-rows}}
</table><br>
<br>
Let me know if you have concerns or would like to discuss.<br>
Regards, {{my-first-name}}<br>
<i>Sent by Clawpilot</i><br>
<br>
[TALOS-REF]{"resource_id":"{{resource-id}}","project_id":"{{project-id}}","engagement_id":"{{engagement-id}}","engagement_name":"{{engagement-name}}"}[/TALOS-REF]
```

## Notes / Edge Cases

- **Past actuals are locked.** Spinbuttons marked `[disabled]` cannot be edited — skip them when checking
  discrepancies.
- **Match to PjM Forecast is disabled when the toggle is Off** — always ensure the **Review PJM forecast**
  toggle is **On** first.
- **Session timeout.** If a timeout dialog appears, click **Relaunch** and re-navigate to the forecast URL.
- **Stale Edge lockfile.** If the browser fails to launch, kill only the Playwright-owned `msedge` processes
  (those with a `--user-data-dir` pointing at `mcp-msedge`) and delete `SingletonLock` from
  `C:\Users\<user>\AppData\Local\ms-playwright\mcp-msedge\`. Do not kill the user's normal Edge processes.

## References

- ESXP Availability Forecast: `https://esxp.microsoft.com/#/workbench/DeliveryResource?tab=AvailabilityForecast`
- `../capture-financials-from-pjmxp/SKILL.md` — sibling browser-automation skill against ESXP/PjMXP
  (validate → navigate → snapshot pattern).
- `../build-project-status/SKILL.md` — owner of engagement context (Engagement ID / Project ID) referenced
  in the notify message's TALOS-REF block.

## Usage logging

When this skill finishes — **whether it succeeded or failed** (we track usage, not success) — make **one** best-effort call to log the run, then continue. Fire-and-forget telemetry: never block, retry, prompt, or surface errors; skip silently if `SKILL_USAGE_COOKIE` is unset (the endpoint is built in). Log once, after the work ends — not while planning or reading files, and not merely from reading this file.

**Send:** `skill_id`=`check-util-forecast`, `repo`=`mcaps-microsoft/talos`, `harness`=your agent (e.g. `GitHub Copilot`), `llm`=your model (e.g. `claude-opus-4.8`), and `usage_context_id`=an **Engagement ID** (e.g. `C.6908469353`) captured while resolving a discrepancy — IDs only, never customer/assignment names or resource IDs. This skill works on your own forecast and usually has no engagement context, so **omit it** unless an Engagement ID is already in context.

**Prefer the shared helper** (if `skills/_shared/log_usage.ps1` is present); else POST inline (drop `usage_context_id` if you don't have one):

```powershell
& "<repo>/skills/_shared/log_usage.ps1" -SkillId check-util-forecast -Repo mcaps-microsoft/talos -Harness "<harness>" -Llm <model> -UsageContextId <id>

# Inline fallback (no shared helper):
try { if ($env:SKILL_USAGE_COOKIE) {
  $b = @{ skill_id='check-util-forecast'; repo='mcaps-microsoft/talos'; harness='<harness>'; llm='<model>'; usage_context_id='<id>' } | ConvertTo-Json -Compress
  Invoke-RestMethod -Method Post -Uri 'https://zealous-flower-00f461900.1.azurestaticapps.net/api/skill-usage' -Headers @{ 'Content-Type'='application/json'; Cookie="StaticWebAppsAuthCookie=$env:SKILL_USAGE_COOKIE" } -Body $b -TimeoutSec 5 | Out-Null
} } catch { }
```
