# Explainable Opportunity Prioritisation Implementation Plan

> **READ FIRST — [`2026-08-06-plan-review-corrections.md`](../specs/2026-08-06-plan-review-corrections.md) is a BINDING addendum to this document.** Five independent reviewers found defects that were then verified against the repository. Where the addendum conflicts with anything below, the addendum wins.

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Help staff focus on the strongest and highest-value recorded opportunities using transparent intent, stage, value and completeness signals—without fabricating conversion probabilities.

**Architecture:** A pure presenter derives display signals from normalized conversation analysis plus the confirmed opportunity link/value. A query-order helper applies the same explicit rank order to paginated Zoom lists and a small Dashboard focus card. No model call, composite probability or causal-uplift calculation is introduced.

**Tech Stack:** Laravel/PHP, Eloquent/MySQL JSON expressions, Vue 3/Inertia, existing Zoom/Engagement services from Plan B, PHPUnit/Vitest.

## Global Constraints

- Complete and integrate both preceding plans before starting.
- Read `docs/superpowers/specs/2026-08-06-ai-sales-follow-up-workspace-design.md` completely.
- Work in an isolated branch/worktree from updated local `dev-chen`; integrate verified commits locally only.
- Do not push or create a PR.
- `features.zoom_opportunity_demo` gates every new signal, sort, card and endpoint.
- Never display `conversion probability`, `% likely`, `expected uplift`, `30→70`, `AI confidence = buying intent`, or equivalent wording.
- `data_confidence` means completeness of analysis + confirmed link + known value, not customer likelihood.
- Meeting duration may appear in evidence but never changes rank by itself.
- Unknown commercial value is null/Unknown, not zero.
- Add no request-rate limit and no new AI call.
- TDD, atomic commits and independent review after every task.

---

## File map

- Create `src/Zoom/Services/ZoomOpportunitySignals.php` — pure signal presenter.
- Create `src/Zoom/Queries/OrdersZoomOpportunities.php` — reusable SQL ordering/filter scope.
- Modify `app/Http/Requests/Manage/Zoom/RecordingsQueryRequest.php` — signal/value filters and sort validation.
- Modify `app/Http/Controllers/Manage/Zoom/RecordingsController.php` — eager-load/present/filter/sort.
- Modify `resources/js/Pages/Manage/Zoom/Recordings/Index.vue` — Intent, Stage, Value, Confidence and evidence UI.
- Create `app/Http/Controllers/Manage/Zoom/ZoomPriorityOpportunitiesController.php` — lazy Dashboard focus payload.
- Modify `app/Http/Controllers/Manage/DashboardController.php`, `resources/js/Pages/Manage/Dashboard.vue`, `routes/web.php` — feature-gated focus card.
- Modify `resources/js/Components/TodaysChecklistModal.vue` — show confirmed Project/value beside relevant tasks.

---

### Task 1: Create a pure, honest opportunity-signal presenter

**Files:**
- Create: `src/Zoom/Services/ZoomOpportunitySignals.php`
- Test: `tests/Feature/Zoom/ZoomOpportunitySignalsTest.php`

**Interfaces:**
- `ZoomOpportunitySignals::present(ZoomMeeting $meeting): array`.
- Output: `intent`, `buying_stage`, `commercial_value`, `commercial_value_estimated`, `data_confidence`, `evidence`, `missing`.

- [ ] **Step 1: Write the full failing signal matrix**

Cover every interest/stage enum, missing/invalid legacy analysis, confirmed/suggested/no link, known/unknown value, confidence thresholds and meeting duration as non-ranking evidence only.

```php
$result = $signals->present($meeting);

$this->assertSame('high', $result['intent']);
$this->assertSame('consideration', $result['buying_stage']);
$this->assertSame(39000.0, $result['commercial_value']);
$this->assertSame('high', $result['data_confidence']);
$this->assertArrayNotHasKey('conversion_probability', $result);
$this->assertArrayNotHasKey('uplift', $result);
```

- [ ] **Step 2: Verify failure**

```bash
php artisan test tests/Feature/Zoom/ZoomOpportunitySignalsTest.php
```

- [ ] **Step 3: Implement one explicit presenter**

Read intent/stage from normalized `ai_analysis`; confirmed link/value from Plan B. Confidence count:

```php
$complete = (int) $hasAnalysis + (int) $hasConfirmedEngagement + (int) $hasKnownValue;
$confidence = match ($complete) {
    3 => 'high',
    2 => 'medium',
    default => 'low',
};
```

Evidence must state recorded facts; missing lists actionable data gaps. Do not convert duration into interest.

- [ ] **Step 4: Review and commit**

Require product-logic review for misleading labels and fact/inference mixing. Commit:

```bash
git add src/Zoom/Services/ZoomOpportunitySignals.php tests/Feature/Zoom/ZoomOpportunitySignalsTest.php
git commit -m "feat(zoom): present explainable opportunity signals"
```

### Task 2: Add stable database ordering and filters

**Files:**
- Create: `src/Zoom/Queries/OrdersZoomOpportunities.php`
- Modify: `app/Http/Requests/Manage/Zoom/RecordingsQueryRequest.php`
- Modify: `app/Http/Controllers/Manage/Zoom/RecordingsController.php`
- Test: `tests/Feature/Manage/Zoom/RecordingsOpportunityOrderTest.php`

**Interfaces:**
- Sort code `opportunity` applies intent → stage → value → start time.
- Filters: `intent` allowed canonical codes; `opportunity_match=confirmed|needs_review|missing`; `value=known|unknown`.

- [ ] **Step 1: Write failing paginated ordering/filter tests**

Create rows whose expected order distinguishes every tie-break. Prove unknown value sorts after known at equal intent/stage, filters remain Lead-visible, and pagination does not reshuffle ties.

- [ ] **Step 2: Implement reusable SQL ordering**

Use CASE expressions matching the spec and explicit final `zoom_meetings.id DESC` stability. Join only the confirmed-link/Engagement/Project/Booking rows needed for value. Keep existing webinar/global scopes intact. Do not sort a paginated page in PHP.

- [ ] **Step 3: Explain query plan and prevent N+1**

Run the relevant SQL/explain tooling or query-count assertion. Add indexes only if the measured plan needs them; do not speculate.

- [ ] **Step 4: Test, database review and commit**

```bash
php artisan test tests/Feature/Manage/Zoom/RecordingsOpportunityOrderTest.php
```

Commit with `feat(zoom): order recordings by opportunity signals`.

### Task 3: Display transparent signals on Zoom Recordings

**Files:**
- Modify: `app/Http/Controllers/Manage/Zoom/RecordingsController.php`
- Modify: `resources/js/Pages/Manage/Zoom/Recordings/Index.vue`
- Test: `tests/Feature/Manage/Zoom/RecordingsOpportunityColumnsTest.php`
- Add focused Vue utility/component tests if signal formatting is extracted.

**Interfaces:**
- Row includes the exact Task 1 signal shape under `opportunity_signals` only when flag enabled.

- [ ] **Step 1: Write failing payload/flag/legacy tests**

Prove flag-off key absence, canonical labels, null value, estimated badge, suggestion-vs-confirmation state and no percentage fields.

- [ ] **Step 2: Implement columns and filters**

Add compact columns/badges for Intent, Stage, Pipeline Product, Est. Commission and Data Confidence. Evidence appears in a popover/expanded row with recorded/missing wording. Use `—`/Unknown instead of zero.

- [ ] **Step 3: Add an explicit disclaimer**

Use concise copy: `Prioritisation signals from recorded conversations and pipeline value; not a predicted conversion probability.`

- [ ] **Step 4: Run tests/build, Vue review and commit**

```bash
php artisan test tests/Feature/Manage/Zoom/RecordingsOpportunityColumnsTest.php tests/Feature/Manage/Zoom/RecordingsOpportunityOrderTest.php
npm run build
```

Commit with `feat(zoom): show explainable opportunity priority`.

### Task 4: Add a role-scoped Dashboard focus card

**Files:**
- Create: `app/Http/Controllers/Manage/Zoom/ZoomPriorityOpportunitiesController.php`
- Modify: `app/Http/Controllers/Manage/DashboardController.php`
- Modify: `routes/web.php`
- Modify: `resources/js/Pages/Manage/Dashboard.vue`
- Modify: `resources/js/Components/TodaysChecklistModal.vue`
- Test: `tests/Feature/Manage/Zoom/ZoomPriorityOpportunitiesTest.php`

**Interfaces:**
- Lazy GET `/manage/dashboard/priority-opportunities`.
- Super Admin sees all Lead-visible opportunities.
- Non-Super users see a meeting only when they are its agent, hold an assignment on the confirmed Engagement, or hold an open generated Action Item for its Lead/plan.
- Response capped to 10 rows and uses the same query order/presenter.

- [ ] **Step 1: Write failing role/visibility/order tests**

Cover Super Admin all-team view, unrelated salesperson exclusion, meeting-agent inclusion, Engagement-assignment inclusion, Action-Item inclusion, Lead visibility, flag 404 and no initial Dashboard bulk load.

- [ ] **Step 2: Implement one lazy endpoint**

Reuse `OrdersZoomOpportunities` and `ZoomOpportunitySignals`; do not copy ranking logic into DashboardController. Return only data needed by the card.

- [ ] **Step 3: Render the Focus Opportunities card**

Load on demand/visibility. Show Lead, confirmed Project, intent/stage/value/confidence and link to the existing Zoom/Lead detail. Keep My Checklist the task-execution workspace. Show Project/value in checklist plan headers when confirmed.

- [ ] **Step 4: Test/build/security review and commit**

```bash
php artisan test tests/Feature/Manage/Zoom/ZoomPriorityOpportunitiesTest.php tests/Feature/Manage/DashboardChecklistTest.php
npm run build
```

Commit with `feat(manage): add priority opportunities workspace`.

### Task 5: Verify claims, performance and local integration

- [ ] **Step 1: Scan UI/code for forbidden claims**

```bash
rg -n "conversion probability|conversion rate|uplift|% likely|30.?70|70.?90" app src resources/js resources/prompts
```

Expected: no new user-facing probability/uplift claims; legitimate historical analytics uses must be reviewed, not blindly changed.

- [ ] **Step 2: Run focused suites**

```bash
php artisan test tests/Feature/Zoom/ZoomOpportunitySignalsTest.php tests/Feature/Manage/Zoom/RecordingsOpportunityOrderTest.php tests/Feature/Manage/Zoom/RecordingsOpportunityColumnsTest.php tests/Feature/Manage/Zoom/ZoomPriorityOpportunitiesTest.php tests/Feature/Manage/DashboardChecklistTest.php
npm test
npm run build
```

- [ ] **Step 3: Validate list performance**

Compare query count and response time for the 265-record local Zoom dataset before/after. Investigate measured regressions; do not hide them with arbitrary caches.

- [ ] **Step 4: Manual role rehearsal**

Verify Super Admin team ranking, Sales own card, unmatched/missing-value states, ties, filters, flag-off disappearance and no percentage language.

- [ ] **Step 5: Final reviews and integration**

Require integration, database/performance, security, Vue and product-logic reviews. Fix findings, rerun affected checks, then cherry-pick only this plan's commits into clean local `dev-chen` after a backup ref. No push/PR.

## Deferred evidence gate (not an implementation task)

Do not add a probability model until Product defines the conversion event and horizon and the team has a versioned outcome dataset large enough for train/validation separation and calibration measurement. Do not add action-uplift claims without causal evidence or a controlled experiment. This gate is a deliberate completed decision, not unfinished work in this plan.
