Open customer-support tickets that need an AM response, stale-by-business-day breakdown, per-AM workload. Data is read live from HubSpot via a Cloudflare Pages Function (server-side, key not exposed).
Pipeline scope: Sales Pipeline only (HubSpot id 28263059). The "Support Pipeline" (id 0) is mostly auto form-submission tickets and is excluded from v1. Despite the confusing name, the Sales Pipeline is where real customer-support work lives at PYC today.
Open stages included: New, Waiting on customer, Waiting on us, Store being setup, In Progress (Trello). Closed stage 64204641 is excluded.
Stale (ball in our court) = stage is Waiting on us AND hs_last_message_from_visitor = true AND business days since hs_last_message_received_date ≥ threshold.
Needs follow-up (ball in their court) = stage is Waiting on customer AND hs_last_message_from_visitor = false AND business days since hs_lastactivitydate ≥ threshold. We replied; the customer has gone quiet and a nudge is overdue.
Very stale = either bucket above but ≥ 7 business days.
Orphaned = hubspot_owner_id is empty AND in an open stage AND last message was from the visitor.
Business days = weekday count strictly after the relevant timestamp through today, weekends not counted; same-day = 0 (matches CLAUDE.md fulfillment-timing convention).
"Days waiting" column reflects whichever side is currently waiting: BD since the visitor's last message when stage = Waiting on us, BD since our last activity when stage = Waiting on customer, BD since last modification otherwise.
Inactive owner flag: AMs whose HubSpot user is marked archived are labelled "inactive" so reassignment is obvious.
Architecture: Static page on Cloudflare Pages calls two Pages Functions (/api/owners, /api/open-tickets) that proxy HubSpot. The HubSpot Service Key lives only as an encrypted Cloudflare environment variable and is never sent to the browser. Both endpoints are read-only and have hard-coded query payloads. Cloudflare Access gates the entire site to @printyourcause.com emails.
Data freshness: live HubSpot calls every Refresh, with a 60s edge cache to dampen rate-limit pressure when multiple AMs reload simultaneously. v1 is capped at the 200 most-recently-modified open tickets — if "Open tickets" hits exactly 200, the underlying queue is larger than what we're loading and v2 needs Supabase sync.