Source: Skill Shack v1.2 (Clawpilot) | Skill ID: msx-earnings-snapshot
What's new in v1.2
- Adds soft-cap upside modeling for estimating payout if the 300% soft cap is removed.
- Captures observed FY26-Q4 earning-potential curve anchors for Total ACR, AI Biz Solutions, Security, All MS Cloud, and Usage.
- Documents how to find and scrape the earning-potential chart icon on each bucket card.
- Tightens capture reliability guidance for hidden Show more controls, bucket parsing, and per-section screenshots.
Build (or operate) a daily MSX Earnings snapshot tracker for the user.
What this skill does
- Captures the user's MSX Earnings page (microsoftsales.crm.dynamics.com → msp_earnings list view) every night.
- Saves: full-page screenshot, per-bucket clipped screenshots, raw page text/html, and parsed JSON (weighted attainment + 5 buckets with sub-metrics).
- Builds a "time machine" website: Plotly graphs + date slider + screenshot gallery to view any historical day.
- Supports soft-cap upside modeling by reading each bucket's earning-potential curve and estimating payout with the 300% soft cap removed.
One-time setup (first invocation)
- Ask the user for: install dir (default
~/.copilot/earnings-tracker), capture time (default 11pm local), end date (default end of fiscal quarter). - Create
bin/,captures/,site/,profile/subdirs. npm init -y && npm i playwrightin the install dir. Confirm msedge channel is available.- Write
bin/setup.mjsthat launcheschromium.launchPersistentContext(profileDir, { channel: 'msedge', headless: false }), navigates to the Earnings list view URL, and pauses on stdin until the user signs in via SSO. Tell the user to ALSO click "Show more buckets" before pressing Enter (the page persists this preference per-profile). - Run setup interactively. Have the user sign in.
Capture script (bin/capture.mjs)
Headless Playwright with the persistent profile. Key implementation notes:
- URL:
https://microsoftsales.crm.dynamics.com/main.aspx?appid=<guid>&forceUCI=1&pagetype=entitylist&etn=msp_earnings&viewid=<guid>&viewType=1039 - Wait for
Weighted attainmenttext + a\d+\.\d+%to appear, plus 3s settle. - "Show more buckets" / per-bucket "Show more": standard button selectors do NOT find these. Pattern: query all elements whose
textContent.trim()exactly equals the label and that have NO children (leaf), then climb up to 8 ancestors looking for a clickable (BUTTON, A, [role=button], or onclick). Click the ancestor; fallback to clicking the leaf. - Bucket names: Total ACR, AI Biz Solutions, Security, All MS Cloud, Usage.
- Per-bucket screenshot: scroll card into view, RE-MEASURE bounding rect (page geometry shifts after show-more clicks), clamp clip box to actual
document.documentElement.scrollHeight/scrollWidth(otherwise screenshot fails with "Clipped area is empty"). - Parse text dump with regex. Each bucket block layout:
<Name>\nTotal earnings\n<value> USD\nAttainment\n<pct>\nWeight\n<pct>\nTotal metrics\n<n>\nAttainment is <status>\n<sub>\n<pct>\n...until next bucket name OR "Show more buckets" / "YTD performance breakdown" / "Performance summary". - Save to
captures/<YYYY-MM-DD>/:full.png,sections/<slug>.png,page.txt,page.html,data.json.
Earning-potential / soft-cap upside model
Use this section when the user asks about "soft caps", "no soft cap", "remove caps", "uncapped payout", or "what would I gain once soft caps are removed".
How to get curve settings
- Each bucket card has an earning-potential chart icon at the right edge of the bucket status/message bar. It is NOT the generic "Attainment Open tooltip" info icon.
- In the DOM it may render as a custom HE-BUTTON_MEM/HE-ICON_MEM element with pointer cursor and no text, positioned near the right edge of the bucket card status row.
- Click that icon per bucket and scrape the earning-potential panel for: threshold, target, excellence, payout at threshold, soft cap, hard cap, target-to-excellence rate, and threshold-to-target rate.
- The chart text may be sparse; if the rates are not visible as numbers, derive them from the curve anchors below.
Observed FY26-Q4 curve settings (captured May 21 2026)
All buckets had target = 100%, payout at threshold = 0%, soft cap = 300%, and hard cap = 500%.
| Bucket | Threshold | Target | Excellence | Soft cap | Hard cap |
|---|---|---|---|---|---|
| Total ACR | 60% | 100% | 135% | 300% | 500% |
| AI Biz Solutions | 30% | 100% | 170% | 300% | 500% |
| Security | 20% | 100% | 180% | 300% | 500% |
| All MS Cloud | 60% | 100% | 140% | 300% | 500% |
| Usage | 55% | 100% | 140% | 300% | 500% |
Refresh these values by clicking the chart icons if the fiscal period, role, or plan rules changed.
Payout formula
if attainment < threshold:
payout_percent = 0
else if attainment < target:
payout_percent = (attainment - threshold) * (100 / (target - threshold))
else:
payout_percent = 100 + (attainment - target) * ((soft_cap - 100) / (excellence - target))
current_payout_percent = min(payout_percent, soft_cap)
no_soft_cap_payout_percent = min(payout_percent, hard_cap)
With current FY26-Q4 anchors, the target-to-excellence slope is: - Total ACR: 200 / (135 - 100) = 5.714 payout points per attainment point - AI Biz Solutions: 200 / (170 - 100) = 2.857 payout points per attainment point - Security: 200 / (180 - 100) = 2.500 payout points per attainment point - All MS Cloud: 200 / (140 - 100) = 5.000 payout points per attainment point - Usage: 200 / (140 - 100) = 5.000 payout points per attainment point
Converting payout percent to dollars
- If the bucket already has nonzero earnings, infer target bucket dollars as:
target_bucket_value = current_bucket_earnings / (current_payout_percent / 100). - Then estimate no-soft-cap dollars as:
target_bucket_value * (no_soft_cap_payout_percent / 100). - Gain is
no_soft_cap_dollars - current_bucket_earnings. - For buckets below threshold, current and no-soft-cap payout are both zero unless attainment crosses threshold.
- For buckets below target/excellence but under the soft cap, removing the soft cap does not change dollars.
- Subtract previous payments/advances only when converting total YTD earnings into projected payment; the soft-cap gain itself is unchanged by previous payments.
Bundle + site (bin/bundle.ps1 + site/index.html)
bundle.ps1mirrorscaptures/*/full.pngandsections/*intosite/captures/, buildssite/data/all.json(array of all daily data.json). PowerShell gotcha:ConvertTo-Json -InputObject @($index)to force array on single-element.site/index.html: self-contained dark Clawpilot-themed page. Plotly v2.35.2 from CDN. Components: headline (current weighted attainment), 5 bucket cards, 3 charts (weighted-attainment trend, all-buckets trend, current sub-metric horizontal bars), screenshot gallery with date picker + slider + Latest button + arrow-key nav.
Schedule (Windows)
$action1 = New-ScheduledTaskAction -Execute "node.exe" -Argument "bin\capture.mjs" -WorkingDirectory "<INSTALL_DIR>"
$action2 = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -File <INSTALL_DIR>\bin\deploy.ps1" -WorkingDirectory "<INSTALL_DIR>"
$trigger = New-ScheduledTaskTrigger -Daily -At 11pm
$trigger.EndBoundary = "<END_DATE>T23:59:59"
Register-ScheduledTask -TaskName "MSXEarningsCapture" -Action @($action1,$action2) -Trigger $trigger -RunLevel Highest
Optional deploy (if user has an App Service or other host)
Wrap deploy as bin/deploy.ps1. For Azure App Service with basic-auth disabled, use AAD bearer with management.azure.com resource and Kudu VFS PUT. Don't use /api/zipdeploy (silently nukes wwwroot on Linux App Service plans).
Operating mode (subsequent invocations)
- "show me today's snapshot" → read
captures/<today>/data.json, summarize. - "compare to yesterday/last week" → diff data.json files, report deltas.
- "estimate soft-cap removal" / "what do I gain if soft caps are removed" → use latest data.json plus the earning-potential / soft-cap upside model above; refresh chart-icon curve settings when plan/period changed or numbers seem stale.
- "the capture failed" → check
captures/<date>/error.pngandcapture-debug.log; first remediation is re-runbin/setup.mjs(cookies expire). - "rebuild the site" → run
bin/bundle.ps1thenbin/deploy.ps1.
Constraints
- The
msp_earningsDataverse entity exists but returns 0 rows via API (UI-rendered/computed). Browser scraping is the only path. - Cookies in the persistent profile last for months but eventually expire. Re-run
setup.mjs. - Each daily snapshot is ~1.2MB. Plan for ~80MB by end of quarter.