# AI Sales Follow-up Workspace — Design

> **READ FIRST — [`2026-08-06-plan-review-corrections.md`](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.

**Date:** 2026-08-06

**Status:** Approved direction; implementation is split into three reviewed plans

**Target:** local `dev-chen` / `petav3.test`

**Production behavior:** disabled and invisible behind the existing demo flags until a later release decision

## 1. Outcome

Turn the existing Zoom AI Action Plan demo into a trustworthy sales work system:

1. AI proposes concrete follow-up work and explains its reasoning.
2. A human reviews every proposed step, edits it, assigns the right person and confirms its priority.
3. Assigned staff work from one persistent checklist on the Manage Dashboard.
4. Zoom conversations are linked to the authoritative Sales Pipeline engagement and project instead of carrying disconnected project text.
5. Opportunity views show recorded intent, pipeline value and data confidence without presenting uncalibrated AI guesses as conversion probabilities.

The existing Action Plan approval transaction, Lead Action Items, personal checklist, Lead Sales Coach, per-recording chat and step feedback remain the foundation. This design changes their granularity and connects them to the existing Engagement/Project/Booking domain; it does not replace them.

## 2. Evidence and product decisions

### Confirmed from the 2026-08-05 meeting

- AI-authored steps are not trusted enough for automatic approval.
- Every step must remain editable before approval.
- Different steps may be assigned to different staff members.
- AI may suggest High / Medium / Low priority, but a human confirms it.
- Salespeople need their own open and completed checklist; Super Admins need a team view.
- Contact tasks should expose quick Call and WhatsApp actions.
- The Action Plan review should retain an AI assistant that explains and proposes revisions.
- Assigned staff should be able to agree or disagree with an AI-written step and explain a disagreement.
- A Zoom meeting should connect to the relevant Pipeline engagement and interested project.
- Commercial value must come from the authoritative Project/Booking commission calculation.
- Staff time should be focused using both recorded customer intent and commercial value.

### Decisions added after independent review

- No due date or scheduling field is added in this release. The UI is renamed from **Today's Checklist** to **My Checklist**, with **Current Tasks** meaning every still-open assigned task.
- `suggested_priority` and confirmed `priority` are separate values. Overwriting the suggestion would destroy the evidence needed to assess AI quality.
- AI priority, opportunity intent, AI confidence and conversion probability are different concepts and must not share one field or badge.
- The product does not display precise conversion percentages or claims such as “30% to 70% after these actions.” There is no calibrated outcome model or causal evidence for those numbers.
- Long call duration is supporting engagement evidence only. It cannot, by itself, establish buying intent.
- Analyst incentive percentages are compensation configuration. They are not hard-coded into Zoom or Action Plan code.
- The existing `Brief` column remains the post-analysis WhatsApp/email delivery ledger; it is not repurposed as an opportunity score.
- No automatic assignment, automatic approval, automatic Pipeline mutation or automatic prompt tuning is introduced.
- No new request-rate limit is added in this scope, per the product decision made on 2026-08-05.
- New local-demo prompt keys use the existing AI Prompt model pinning mechanism and are configured locally for OpenAI `gpt-5.6-terra`; provider/model names are not hard-coded in controllers or committed as environment-specific database state.

## 3. Scope decomposition

### Plan A — trusted Action Plan workflow

- Add AI-suggested step priority and action type without breaking legacy `next_steps` consumers.
- Let each draft step carry its own assignee and confirmed priority.
- Materialise the per-step metadata into the existing `LeadActionItem` rows on approval.
- Preserve prompt-improvement evidence when staff rate a step.
- Add an explicit AI revision proposal that can be applied to the editor but never saved or approved without a human action.
- Rename and expand the checklist into Sales “My Tasks” and Super Admin “Team Tasks.”
- Reuse the existing call-intent log and safe phone/WhatsApp handoff for contact tasks.

### Plan B — opportunity linkage and value

- Add one reviewed opportunity-link record per Zoom meeting.
- AI may suggest one of the Lead's existing Engagements, with confidence and evidence; a Super Admin confirms or rejects it.
- Never let the model invent an Engagement UUID or create a Pipeline record.
- Show the confirmed Project, Pipeline stage and commission value on Zoom surfaces.
- Read commission through existing Engagement/Booking/Project rules and label estimated versus priced values.

### Plan C — explainable prioritisation

- Present separate Intent, Buying Stage, Commercial Value and Data Confidence signals.
- Default-sort by recorded intent, then buying stage, then known commercial value, then recency.
- Show short evidence, missing-data warnings and “Needs product match” states.
- Do not emit a composite percentage or expected uplift.
- Preserve the data relationships and timestamps needed for a later calibrated model; do not build that model in this release.

## 4. Action Plan data contract

### 4.1 Conversation analysis remains backward compatible

`meeting_report.next_steps` remains an array of strings because Calls, F2F, Zoom briefs, queues and chase commands already consume it.

The prompt gains an additive field:

```json
{
  "meeting_report": {
    "next_steps": ["Send the financing comparison"],
    "recommended_actions": [
      {
        "body": "Send the financing comparison and ask which monthly repayment range is comfortable",
        "priority": "high",
        "action_type": "whatsapp",
        "reason": "The customer raised monthly instalment affordability as an unresolved concern"
      }
    ]
  }
}
```

Allowed priority codes are `high`, `medium`, `low`.

Allowed action-type codes are `call`, `whatsapp`, `send_information`, `schedule`, `internal`, `other`.

The normalizer must always return `recommended_actions` as a bounded, validated list. A missing or invalid field becomes `[]`. Existing analyses remain valid.

### 4.2 Review snapshot

Internally, each `zoom_meeting_action_plans.items` entry becomes:

```php
[
    'key' => 'step-xxxxxxxx',
    'body' => 'Send the financing comparison…',
    'reason' => 'Recorded financing concern',
    'suggested_priority' => LeadActionItem::PRIORITY_HIGH,
    'priority' => LeadActionItem::PRIORITY_HIGH,
    'action_type' => LeadActionItem::TYPE_WHATSAPP,
    'assignee_id' => 123,
]
```

The JSON stores internal integer IDs. The controller maps `assignee_id` to/from UUID at the HTTP boundary. The existing plan-level `assignee_id` remains the **default assignee** used by “Apply to all” and by legacy draft migration; it is no longer the assertion that every step has one owner.

Legacy `{key, body}` rows are upgraded on read/save with:

- `suggested_priority = null`
- `priority = medium`
- `action_type = other`
- `assignee_id = plan.assignee_id`
- `reason = null`

### 4.3 Approved Lead Action Item

Add nullable integer fields to `lead_action_items`:

- `suggested_priority`
- `priority`
- `action_type`

Generated items require all three values during Action Plan approval. Existing/manual items may remain null and keep their existing UI behavior.

Use unsigned integer model constants and metadata arrays, matching project conventions. Do not store free-form status or priority strings in database columns.

### 4.4 Approval rules

Approval remains one row-locked database transaction and retains the existing unique `(source_action_plan_id, source_step_key)` idempotency boundary.

Before any generated item is written, the server validates every step:

- non-empty body;
- known priority constant;
- known action-type constant;
- active manage-portal assignee who can currently view the Lead;
- unique/issued step key.

If any step fails, the entire approval rolls back. A repeated or concurrent approval still creates nothing.

## 5. Human review and AI revision

The review modal exposes, per row:

- editable task body;
- AI reason, visually labelled as an AI suggestion;
- suggested-priority badge;
- confirmed priority selector;
- action-type selector;
- assignee selector.

The plan-level default assignee selector gains **Apply to all**. Changing the default alone does not silently overwrite row overrides.

The normal chat stays advisory. A separate **Generate revision proposal** action sends the current unsaved rows plus the reviewer's instruction to a JSON-only prompt. The response is a proposed full list; it is never persisted by the AI endpoint.

The UI shows Current versus Proposed and requires **Apply proposal**. Applying changes only Vue editor state. The reviewer must still Save Draft or Approve.

Unknown, duplicated or forged step keys from the model are discarded/reissued by the existing server key issuer.

## 6. Feedback provenance

The current feedback relationship and one-row-per-item/user uniqueness remain.

Add immutable snapshots written when a rating is submitted:

- `body_snapshot`
- `suggested_priority_snapshot`
- `priority_snapshot`
- `action_type_snapshot`
- `analysis_hash_snapshot`

`analysis_hash_snapshot` is produced by one shared canonical JSON helper so object-key ordering cannot change the hash for semantically identical analysis.

Changing a verdict updates verdict/reason and refreshes these snapshots to the item state the user is rating now. The admin listing marks deleted source items as it does today and shows the snapshots even when the live item has changed.

The source plan/meeting provides the AI request attribution. The listing may show the latest successful `conversation_analysis` request for that meeting (prompt key, provider and model), but must label it “analysis request” and must not claim a prompt version that was not persisted.

Feedback remains human-reviewed training evidence. It never automatically edits a prompt or enters a provider request.

## 7. Checklist workspace and contact actions

### Sales

- Auto-open **My Checklist** after login when the signed-in user has open generated items.
- Show Current Tasks and Completed History.
- Sort Current Tasks by confirmed priority, then newest plan.
- Show Lead, meeting date, priority, action type and progress.
- Only the authenticated assignee can see/complete the item, subject to current Lead visibility.

### Super Admin

- The same modal includes `My Tasks` and `Team Tasks`.
- Team Tasks are lazy-loaded, never included wholesale in the initial Dashboard payload.
- Filter by assignee, priority and completion state.
- Team rows are Lead-visibility scoped and paginated/cursor-paginated.
- No non-Super Admin can call the team endpoint.

### Call and WhatsApp

- `call` tasks show Call; `whatsapp` tasks show WhatsApp; other types do not guess from task prose.
- The server includes a callable phone only when the viewer may see the Lead.
- Call reuses the existing `POST /manage/leads/{lead}/call-clicks` intent log with `fetch(..., {keepalive: true})` before `tel:` handoff.
- WhatsApp normalizes digits and opens `https://wa.me/{digits}` in a new tab with `noopener`.
- Missing/invalid phone hides the button; the task remains completable.
- This scope does not automatically start recording. It reuses the existing call workflow and its recording behavior.

## 8. Opportunity-link domain

Create `zoom_meeting_opportunity_links` as a separate 1:1 workflow entity rather than adding more workflow columns to `zoom_meetings`.

Fields:

- standard id/uuid/blame/timestamps/soft delete;
- unique `zoom_meeting_id`;
- nullable confirmed `engagement_id`;
- nullable `suggested_engagement_id`;
- integer `suggested_confidence` (`high`, `medium`, `low` constants);
- nullable `suggested_reason`;
- integer status (`unreviewed`, `confirmed`, `rejected` constants);
- nullable `reviewed_by`, `reviewed_at`.

Only Engagements belonging to the Zoom meeting's linked Lead are eligible.

AI suggestion input contains:

- the bounded meeting analysis/transcript context already used by the recording chat;
- a server-produced candidate array containing only eligible Engagement UUID, Project name, Pipeline stage and status.

The model returns one candidate UUID or null, confidence and evidence. The server rejects any UUID not present in the candidate set. Suggestion writes no confirmed relationship.

Confirmation is a Super Admin write. Rejection records a reviewed state and does not delete the suggestion evidence. Reanalysis never overwrites a confirmed link.

## 9. Commercial value

Commercial value is read from the confirmed Engagement:

1. explicit booking commission, when present;
2. otherwise booking commission-basis price × Project commission rate;
3. otherwise Project `price_from` × Project commission rate, labelled estimated;
4. otherwise unknown.

Use the existing `Booking::commissionAt()` / commission-basis rules; do not calculate value from transcript text.

Display:

- Project name;
- Pipeline stage/status;
- `RM …` value;
- `Priced` or `Estimated` label;
- `Needs product match` when no confirmed Engagement exists.

The Analyst/share amount is read from existing Engagement commission assignments when configured. No `5%` default is introduced here.

## 10. Explainable opportunity ordering

Every eligible Zoom meeting row may expose:

```json
{
  "intent": "high",
  "buying_stage": "consideration",
  "commercial_value": 39000.0,
  "commercial_value_estimated": true,
  "data_confidence": "high",
  "evidence": [
    "Customer interest level was recorded as high",
    "Buying stage was recorded as consideration",
    "Pipeline match was confirmed by an admin"
  ],
  "missing": []
}
```

`data_confidence` describes completeness, not likelihood:

- high: analysis plus confirmed Engagement plus known commission value;
- medium: any two of those three;
- low: zero or one.

Default ordering:

1. interest `high`, `medium`, `low`, `none`;
2. buying stage `decision`, `consideration`, `interest`, `awareness`, `unknown`;
3. known commercial value descending;
4. latest meeting first.

The UI must state that these are recorded signals for prioritisation, not a predicted probability.

## 11. Authorization and feature isolation

- Existing `features.zoom_action_plan_demo` continues to gate Action Plan review, generated checklist, feedback and review AI.
- Add a separate `features.zoom_opportunity_demo` for opportunity link/value/prioritisation surfaces.
- Both committed defaults are false.
- Dedicated endpoints return 404 when their flag is off.
- Draft review, AI revision proposals, opportunity suggestion/confirmation and Team Tasks require Super Admin.
- Sales users see only items assigned to themselves and only Leads allowed by `LeadVisibility`.
- Every UUID received from AI or the browser is re-resolved and scope-validated server-side.
- Provider context excludes phone, email and media URLs unless the existing bounded context explicitly permits a field. Candidate IDs are opaque UUIDs.

## 12. Failure and edge behavior

- Legacy draft: upgrade metadata in memory and persist on the next review save; do not rewrite during GET.
- AI analysis lacks `recommended_actions`: build legacy steps from `next_steps`, default Medium/Other.
- A per-step assignee becomes inactive/ineligible before approval: reject that step and roll back all.
- Lead visibility changes after approval: existing checklist read-time filter keeps the row hidden; Team view surfaces an admin-only “stranded assignment” count without exposing it to the old assignee.
- Revision AI fails or returns invalid JSON: keep editor unchanged and show retryable error.
- Opportunity has no candidate Engagement: show manual link state; do not create an Engagement.
- Suggested Engagement is deleted or moves to a different Lead before confirmation: reject confirmation and refresh candidates.
- No commission rate/price: show Unknown, never zero.
- All generated tasks deleted: retain the existing no-active-steps state.

## 13. Non-goals and future gates

- No automatic approval or assignment.
- No automatic Pipeline creation or project match confirmation.
- No due dates, overdue tasks, reminders or escalation.
- No auto-generated customer message is sent.
- No hard-coded compensation percentage.
- No precise conversion probability.
- No “expected uplift after task” number.
- No prompt self-modification from feedback.
- No production enablement, push or PR in this work.

A later probability model requires, at minimum, a versioned outcome dataset, a defined conversion event, time horizon, training/validation split, calibration measurement and monitoring for drift. A later action-uplift claim additionally requires causal evidence or a controlled experiment. Those are release gates, not implementation placeholders.

## 14. Acceptance journey

1. Reanalyse a linked Zoom meeting and receive suggested action metadata while legacy `next_steps` still render everywhere.
2. Open Review Action Plan as Super Admin.
3. Edit a step, change its priority/type, assign different staff per row, and use Apply to all without erasing an override unintentionally.
4. Ask AI for a revision, preview it, apply it only to the editor, then save.
5. Approve once; verify one Lead Action Item per step with its own assignee and metadata.
6. Approve again/concurrently; verify no duplicates.
7. Log in as each assignee and see only their Current Tasks in My Checklist.
8. Use Call/WhatsApp only on the matching task types and complete tasks.
9. Submit Agree/Disagree feedback and verify the rated snapshot survives later edits/deletion.
10. As Super Admin, inspect Team Tasks and filter by assignee/priority.
11. Ask AI to suggest an existing Engagement for a Zoom meeting, inspect evidence and confirm it manually.
12. See the confirmed Project, Pipeline stage and authoritative estimated/priced commission on Zoom surfaces.
13. Sort opportunities by recorded intent/value and verify no probability or uplift claim appears.
14. Disable each flag and verify every dedicated route/prop/UI entry disappears or 404s.
