Executive Summary
The feature — letting a back-office user see and select available packets from inside the agent app — is closer to done than it looks. Most of what it needs already exists in OnALead's back office; the real work is exposure and a disciplined entitlement boundary, not net-new domain logic.
Three findings shape everything below:
- Packets are built twice. OnALead (the back office) owns the authoritative
Agent.Packetmodel and already has server-side "available packets" and assignment operations. The AgentApp only exposes an agent's own packets and offers — read-mostly, no pool, no assignment. - The entitlement model already exists — in the wrong app. OnALead has a full hierarchical privilege/security-group system including a
LeadPacketprivilege. The AgentApp has only admin/non-admin string checks plus impersonation. The containment play is to reuse OnALead's privileges through the existing security-group bridge, not invent an entitlement layer inside the agent app. - The Snapshot dashboard is built and parked. A Phase-1 prototype (mock data) sits on a parked branch with KPI strip, charts, card grid, and table view already implemented — the wireframe round is refining something real, not starting from zero.
GetOpenPackets() / AssignAgentToPacket() through new AgentApp endpoints, gate them on the existing OnALead.Leads.LeadPacket[.Approve] privilege read via the security-group bridge, and reuse the parked Snapshot shell for the UI. Hold the line on scope: no field-level masking, no new admin screens, no trust/profitability dependency in v1 — those are the spiral.
Packet Domain Today
"Packet" is a real, mature concept — a named bundle of leads assigned to an agent, with a status lifecycle and two types. It exists end-to-end in OnALead and partially in the AgentApp.
Agent.Packettable, temporal/system-versioned, FK toPacketStatus&PacketType- PacketType ∈ {
BackOffice,Referral} - Lifecycle: Created → OutToAgent → Accepted / Rejected → Retracted / Deleted
- Already in
IAgentService:GetPackets(),GetOpenPackets(),AssignAgentToPacket(),Finalize / Retract / Delete - 18-month expiration window from
SentToAgentDate
PacketsController/OffersControllerexpose only the agent's own packets- Agents can accept / reject offered packets and act on leads (phone, door-knock)
- missing available-packet pool listing
- missing assignment endpoint
- missing any back-office / CSR view
OnALead.ServiceLibrary\Agent\IAgentService.cs already implements GetOpenPackets() and AssignAgentToPacket(). The build is exposure + UI, not new domain modeling.
Design intent already on record
Two in-repo specs define the UX philosophy and should anchor any build:
OnALead\docs\Harrison-CSR-Leadpacet.md— encodes the CSR's manual packet-building as a system. Its governing principle: "Design for selection, not submission" — present pre-built packets to choose from, don't make the user assemble carts.OnALead\docs\# Natural Language Feature Handof Lead Packetsf.md— the trust/profitability matrix, lead lifecycle color-coding (Green 0-31 / Yellow 32-61 / Orange 62-92 / Red 93+), and an inventory-snapshot drill-down concept (sections 5–6).
Entitlements & the Containment Strategy
This is where I9 warned the work "can spiral." The good news: the spiral is avoidable because the entitlement model is already built — it just lives in OnALead, not the agent app.
| Concern | OnALead (back office) | AgentApp (agent portal) |
|---|---|---|
| Model | Hierarchical Privilege (CRUD / visible / enabled / scope bits), SecurityGroup, PrivilegeSecurityGroups | Group-name string checks only |
| Packet privilege | OnALead.Leads.LeadPacket & .Approve (among 100+ generated PrivilegeNames) | None |
| Enforcement | ISecurityService.GetUserAccess() resolves effective privileges | "Administrators" / "On A Lead Manager" + impersonation block |
| Granularity | Per-privilege, per-group, scoped | Binary admin / non-admin |
OnALead.Leads.LeadPacket[.Approve] privilege, surfaced through the security-group bridge the AgentApp already calls (GetUserSecurityGroupsAsync). That's one new repository read and zero new entitlement schema — the agent app asks "does this user hold the packet privilege?" and never grows its own parallel permission model.
Explicitly out of scope for v1 (the spiral)
- defer Field-level masking — the 2024 user story ("so JE can create lead packets without seeing Lead Thumbnail, address, and age") is a real future requirement, noted but not built now.
- defer New admin/role-management UI — groups and privileges are administered in OnALead; the agent app only consumes them.
- defer Trust/profitability matrix — the access-classification system from the Natural-Language spec is a separate, larger effort; the packet-selection feature must not take a dependency on it.
Inventory Snapshot — State of Play
JJ asked to fold in the Inventory Snapshot work. It is not hypothetical: a working Phase-1 prototype is parked on a branch, and its mock data traces directly back to existing Excel count files.
The parked prototype
Branch parking/snapshot-phase-1-prototype in the onboard repo (commit 8cabed7 — "Park: snapshot inventory dashboard Phase 1 prototype (mock data)"). It already contains:
| File | Role | Lines |
|---|---|---|
src/pages/snapshot/SnapshotPage.jsx | Page shell — view-type tabs, filters, layout toggle | 267 |
SnapshotKpiStrip.jsx | Status-count tiles | 52 |
SnapshotCharts.jsx | Aging + SCF charts | 78 |
SnapshotCardGrid.jsx | Region cards, expand-in-place | 163 |
SnapshotTable.jsx | Dense table view | 79 |
store/slices/snapshotSlice.js | Redux state — filters, sort, expansion | 141 |
services/snapshotApi.js | Mock API (planned real endpoints noted inline) | 220 |
snapshotApi.js returns mock regions. Its header documents the planned real endpoints — GET /api/v1/inventory/snapshot, /:id, /kpi — which do not exist in the backend yet. Reviving this for production means building those endpoints in OnALead.WebApi (per the repo's stored-procedure convention in CLAUDE.md).
Data lineage — mock fields trace to real spreadsheets
The prototype's region shape (SCF / territory / county / route grouping; lead-type counts OAL, OAIC, PHI, SVQ, CSL, CSN, OTR, CLM, T65, MIX; aging buckets; response rate; premium YTD; CPA; close rate) mirrors the working count files in D:\repos\SnapShot\Counts\: target list counts.xlsx, analytics.xlsx, counts MS.xlsx, and JJ Penetration Data - Jan 2026.xlsx, plus the Issued and Not Taken NBR Blend Production extracts. Older UX references survive in LM system design notes\LEAD Packet App\ (the Binder PDF and Lead Assignment Layout) and the Nov-2025 back-office snapshot screenshot.
Wireframe Directions — Evaluation
Scoring the five Round-v1 directions against four constraints: distance from the parked prototype, data already available, the D3/D4 decisions (maps live in the back office; build order is matching → mapping → geography selector → snapshot), and the Harrison-CSR "design for selection" principle.
B Sidebar filters + dense rows Target shell
Why: Strongest team-lead fit. The permanent filter rail (grouping, status, age, lead-type, agent, saved quick views) maps almost one-to-one onto the filters already in snapshotSlice.js. Row-cards show eight regions at once, and an inline Assign control is exactly the "design for selection" move from Harrison-CSR.
Cost: Moderate — rebuild the card grid as rows + add the rail; the slice and data layer largely carry over.
D Heatmap matrix Phase-2 companion
Why: Best single view for triage — SCF × age-bucket, intensity = volume, hue = dominant status. It's literally the two charts we already have, crossed. But it's a companion to a region list, not a replacement, so it rides on top of Direction B rather than competing with it.
Cost: Additive — a second view mode once the data endpoints are real.
A Classic, refined Cheapest increment
Why: The parked prototype is already direction-A shaped (KPI strip → charts → expand-in-place cards). If the goal is the smallest shippable step, tighten what exists. Weakness the summary itself flags: expand-in-place blocks side-by-side region comparison — which is the team lead's core job.
Cost: Lowest — a polish pass on existing components.
C Map-first Defer
Why deferred: Decision D3 already places maps in the back office (React), and real SCF boundaries are a meaningful build. Per D4, mapping MVP precedes the snapshot in sequence — so geography-led snapshot UI should wait for that foundation rather than duplicate it.
E Assignment command-center Defer
Why deferred: Purpose-built for the highest-value action (drag region → agent lane, capacity bars), but it's a whole new page and it presumes assignment endpoints are already exposed to the app. Sequence it after Direction B proves the exposure layer and the entitlement gate.
Suggested Build Sequence
Not this taskThis is the path after the write-up is accepted — included so the discovery points somewhere concrete. No code is being written now.
- Backend snapshot endpoints. Build
GET /api/v1/inventory/snapshot[, /:id, /kpi]in OnALead.WebApi over stored procedures (per CLAUDE.md — no inline SQL), backed by the existing Counts data model. - Revive the parked branch. Bring
parking/snapshot-phase-1-prototypeforward and swapsnapshotApi.jsmocks for the real endpoints. - Reshape to Direction B. Filter rail + row-cards + saved quick views, reusing the existing slice filters.
- Add the entitlement gate. Surface OnALead's
LeadPacketprivilege through the security-group bridge; show packet-selection affordances only to holders. - Wire packet-selection actions. Connect Manual Order / Request Leads / Assign to the existing
IAgentServiceoperations (GetOpenPackets,AssignAgentToPacket). - Phase 2. Layer the Direction-D heatmap as a companion view once regions render reliably.
Artifact Index
Load-bearing artifacts surfaced by the sweep. Binary files (xlsx / docx / pdf) are flagged where manual extraction is still needed.