diff --git a/@platform/codebase/@features/ai-copilot/docs/prospect-detail.screen.md b/@platform/codebase/@features/ai-copilot/docs/prospect-detail.screen.md index 8f01699..2c54164 100644 --- a/@platform/codebase/@features/ai-copilot/docs/prospect-detail.screen.md +++ b/@platform/codebase/@features/ai-copilot/docs/prospect-detail.screen.md @@ -12,6 +12,18 @@ Implementation breakdown of [brief B](./B-drawers.brief.md) §B3 — the cross-s │ first DM Mar 12 · last contact 2h ago │ │ [ funnel: PPV ▾ ] value: $1,420 ytd │ ← funnel chip + value-to-date strip ├────────────────────────────────────────┤ +│ Touchpoint chain · [ Time-decay ▾ ] │ ← attribution model picker (per T-attr-model) +│ ┌──────────────────────────────────┐ │ +│ │ Tryst (Apr 8 14:02) │ │ +│ │ ↓ 19h │ │ +│ │ iMessage (Apr 9 09:14) │ │ +│ │ ↓ 13h │ │ +│ │ OF subscription (Apr 9 22:11) │ │ +│ │ ↓ ongoing · 3 tips $47 │ │ +│ │ │ │ +│ │ Credit: Tryst 60% · X 0% · OF40%│ │ +│ └──────────────────────────────────┘ │ +├────────────────────────────────────────┤ │ ⚠ Coop intel · 2 peer reports │ ← coop callout (plain register) │ Berlin Providers · UNSAFE │ │ [ open report ] │ @@ -53,6 +65,9 @@ Implementation breakdown of [brief B](./B-drawers.brief.md) §B3 — the cross-s | `ReplyComposer` | new (B3 + P) | Channel picker (defaults to last-contact channel) + text + "ask Cocotte to draft" — routes through `mcp__quinn-prospector__draft_message`. | | `ShareWithOrgButton` | new (W) | Cross-context handoff affordance; gated on org membership + share scope. | | `UnresolvedIdentifierBanner` | new (B3 + P §prospect-resolver) | Renders when `prospect-resolver` has unmerged candidate identifiers for this prospect. | +| `TouchpointChain` | new (B3 + T §T-attribution) | Reads `prospect_touchpoints` for this `prospect_id`, oldest-first. Each touchpoint shows surface + kind + occurred_at; arrows show the elapsed time between consecutive touchpoints. Anonymous touchpoints never appear here (per [_engineering-surface-metrics.md](./_engineering-surface-metrics.md) §9 invariant). | +| `AttributionModelPicker` | shared (T §T-attribution-model) | Dropdown: First-touch / Last-touch / Time-decay (default) / Position-based. Same component used in T2 funnel `Top paths`. Persists to `users.attribution_model_pref`. | +| `AttributionCreditStrip` | new (B3 + T §T-attribution-model) | Per-surface credit weights for this prospect under the selected model. Shows `—` (unmeasured) for surfaces with tier-gated analytics off, never `0%` (which would falsely imply "no influence" rather than "we can't tell"). | ## States to render @@ -62,7 +77,9 @@ Implementation breakdown of [brief B](./B-drawers.brief.md) §B3 — the cross-s 4. **Funnel-stage cold** — value strip muted, hearth chip suggests "On the back burner. Want strategist to draft a re-engagement?" Reply composer still available; no auto-draft pre-filled. 5. **Peer-coop reports present** (per brief N) — `CoopIntelCallout` rendered above the timeline in plain register. Reply composer **soft-warns** but is not disabled; Quinn judges. 6. **Blocked in K1** — drawer renders for replay/audit purposes only. Composer + approval strips are disabled with a plain-register notice: "This prospect is on your blocklist. Reply disabled. Manage in safety settings." -7. **Cross-surface unresolved** — `prospect-resolver` hasn't merged yet. Banner above the timeline: "2 unmerged identifiers — Cocotte is still confirming this is the same person. Showing what's matched so far." Identity line shows resolved channels only. +7. **Cross-surface unresolved** — `prospect-resolver` hasn't merged yet. Banner above the timeline: "2 unmerged identifiers — Cocotte is still confirming this is the same person. Showing what's matched so far." Identity line shows resolved channels only. **Touchpoint chain stays empty** until merge resolves — anonymous touchpoints never render per-prospect (surface-metrics §9 invariant). +9. **Single-touch prospect** — only one resolved touchpoint exists. `TouchpointChain` renders the single node without arrows; `AttributionCreditStrip` shows 100% credit to that surface under any model. Strategist insight chip (working): "Single-touch prospect. Worth nudging onto a second surface — typical Felix-types convert at 3× after the second touchpoint." +10. **Chain-confidence hedge** — `prospect-resolver` linked touchpoints via `identifier_hash` match but didn't confirm. Chain renders with dotted arrows + plain-register hint: "Linked by identifier hash — Cocotte's 78% confident this is the same person." Quinn can confirm/break the link from a long-press menu on any arrow. 8. **Share-with-org pending** (per brief W) — header `share` button shows a confirmation sheet before any data crosses scopes. Org-context voice shift applies once confirmed. ## Gestures @@ -71,6 +88,8 @@ Implementation breakdown of [brief B](./B-drawers.brief.md) §B3 — the cross-s - **Swipe left on a timeline row**: set aside the draft (same semantics as A's chat cards). - **Tap a timeline row**: opens the source's raw thread (audit-shadow view). - **Long-press the funnel chip**: opens funnel-stage history sheet (when did this prospect move from free → tip, etc.). +- **Tap the attribution-model picker**: switches model; credit strip + insight chip recompute instantly (all 4 models pre-computed per [T §T-attribution-model](./T-analytics-dashboard.brief.md)). +- **Long-press a chain arrow**: opens "confirm / break link" sheet for hedged identifier-hash links (state 10). - **Tap the coop callout**: opens [`coop-intel-detail.screen.md`](./coop-intel-detail.screen.md). - **Swipe down**: dismiss to the surface that opened this drawer (chat / inbox / search / audit). @@ -125,3 +144,7 @@ Implementation breakdown of [brief B](./B-drawers.brief.md) §B3 — the cross-s - [brief W](./W-org-overlay.brief.md) — share-with-org cross-context handoff. - [brief K](./K-safety-blocklist.brief.md) §K1 (blocked-in-drawer), §K3c-1 (govt-name hard rule), §K3h (cross-channel identifier guard). - [voice](./00-system-voice.md) §V2b working register · §V2c plain on coop callout + safety stubs. +- [T-analytics-dashboard.brief.md §T-attribution](./T-analytics-dashboard.brief.md) — per-prospect chain is the drilldown surface from T2 `Top paths`. +- [T-analytics-dashboard.brief.md §T-attribution-model](./T-analytics-dashboard.brief.md) — model picker shared with this screen. +- [specialist-prospect-resolver.contract.md](./specialist-prospect-resolver.contract.md) — writes the `prospect_touchpoints` rows this screen renders; handles state-10 hedged links. +- [_engineering-surface-metrics.md](./_engineering-surface-metrics.md) §4 (touchpoints schema) + §9 (privacy: anonymous-aggregate-only invariant). diff --git a/@platform/codebase/@features/ai-copilot/docs/specialist-prospect-resolver.contract.md b/@platform/codebase/@features/ai-copilot/docs/specialist-prospect-resolver.contract.md index 26951f1..df1ce83 100644 --- a/@platform/codebase/@features/ai-copilot/docs/specialist-prospect-resolver.contract.md +++ b/@platform/codebase/@features/ai-copilot/docs/specialist-prospect-resolver.contract.md @@ -8,31 +8,40 @@ Cross-surface identity glue. Never speaks to anyone. ## Does -Cross-surface prospect dedup (same person on Tryst + OF + iMessage gets one `prospects` row). Intent classification (browsing / engaged / paying / lapsed). Scoring (heat tier, churn risk, LTV proxy). Feeds `strategist` and `triage` with resolved prospect context. +Cross-surface prospect dedup (same person on Tryst + OF + iMessage gets one `prospects` row). Intent classification (browsing / engaged / paying / lapsed). Scoring (heat tier, churn risk, LTV proxy). **Touchpoint linking**: every new `engagement_event` is matched against existing prospects to write a corresponding `prospect_touchpoints` row (per [_engineering-surface-metrics.md §4](./_engineering-surface-metrics.md)) — including setting `attributed_to_touchpoint_id` to build the attribution chain. **Identifier hashing**: writes `identifier_hash = sha256(lower(surface || ':' || raw_identifier) || user_salt)` (per surface-metrics §9) when storing touchpoints with no resolved `prospect_id`. Feeds `strategist` and `triage` with resolved prospect context. ## Auto - Dedup writes (matching new event to existing prospect record). - Score updates. - Cross-surface link assertions when match confidence > threshold. +- **Touchpoint linking**: for every new `engagement_event`, write a `prospect_touchpoints` row with `prospect_id` (resolved) or `identifier_hash` only (anonymous). +- **Chain inference**: when a resolved touchpoint follows a prior anonymous touchpoint with matching `identifier_hash`, retroactively set `attributed_to_touchpoint_id` to thread the chain. (Backward-fill is bounded — only within the past 90 days per surface-metrics §10 immutability rule.) +- **Photo-hash dedup**: when a new touchpoint carries a photo (e.g. profile_view from a surface that exposes the prospect's avatar), run perceptual-hash match against existing prospects' known photos via the ported `photo-hasher.ts` (per [_engineering-talent-scout-port.md](./_engineering-talent-scout-port.md) §"Detection module"). ## Proposes -Nothing user-facing directly. Surfaces high-confidence dedup proposals through `strategist` ("This Tryst inquiry looks like the same person as last week's OF DM — link?"). +Nothing user-facing directly. Surfaces high-confidence dedup proposals through `strategist` ("This Tryst inquiry looks like the same person as last week's OF DM — link?"). Surfaces **chain hypotheses** through `strategist` ("3 anonymous Tryst profile-views in the past 48h match Felix's identifier-hash — the chain that led to his OF subscription likely starts there. Backfill?"). ## Never - Contacts a prospect. - Sends a DM. - Posts. - Crosses K3h channel-vs-surface separation. +- **Exposes anonymous-touchpoint rows in any prospect-scoped query** (per surface-metrics §9 invariant 2). Anonymous touchpoints are aggregate-only. +- **Stores plaintext identifiers** in `prospect_touchpoints` when no resolved `prospect_id` exists. Only `identifier_hash` is permitted. ## Correction lens - False-dedup (Quinn says "no, these are two different people"). - Wrong tier (Quinn corrects the heat classification). - Missed link (Quinn manually links two prospect records). +- **False chain inference** (Quinn rejects a proposed `attributed_to_touchpoint_id` link — the touchpoint chain is unset on that row). ## Surfaces -None directly — reads `engagement_events` across all surfaces. +None directly — reads `engagement_events` across all surfaces; writes `prospect_touchpoints`. ## Related - [brief L](./L-specialists-fleet.brief.md) §L3h — original contract. - [brief I](./I-audit-trust-replay.brief.md) — dedup decisions land in audit. +- [_engineering-surface-metrics.md](./_engineering-surface-metrics.md) §4 (touchpoints schema) + §9 (privacy invariants). +- [_engineering-talent-scout-port.md](./_engineering-talent-scout-port.md) §"Detection module" — `photo-hasher.ts` is the ported photo-dedup primitive. +- [T-analytics-dashboard.brief.md §T-attribution](./T-analytics-dashboard.brief.md) — UX consumer of touchpoint chains. - v2 `quinn-prospector` MCP per `_engineering/v2-port-map.md`. diff --git a/@platform/codebase/@features/ai-copilot/docs/specialist-strategist.contract.md b/@platform/codebase/@features/ai-copilot/docs/specialist-strategist.contract.md index 579324f..98f3859 100644 --- a/@platform/codebase/@features/ai-copilot/docs/specialist-strategist.contract.md +++ b/@platform/codebase/@features/ai-copilot/docs/specialist-strategist.contract.md @@ -8,7 +8,7 @@ The reader of patterns. Speaks in plans, not actions. ## Does -Reads recent `engagement_events`, `content_posts` performance, `agent_actions` history, prospect funnel data. Produces weekly content plans, tour-timing windows, prospect-follow-up clustering recommendations, cohort warmth reads, OF-X funnel reads. Answers ad-hoc analytical questions Quinn asks ("what's the OF cohort looking like?"). +Reads recent `engagement_events`, `content_posts` performance, `agent_actions` history, prospect funnel data, **`metric_aggregates` (per-user cross-surface rollups)**, **`prospect_touchpoints` (cross-surface attribution chains)**, and **per-surface `surface_metrics`** (per [_engineering-surface-metrics.md](./_engineering-surface-metrics.md)). Produces weekly content plans, tour-timing windows, prospect-follow-up clustering recommendations, cohort warmth reads, OF-X funnel reads, **cross-surface attribution insights** ("Tryst → iMessage → OF is your warmest path; the Tryst-bio nudge could amplify it"), **tier-upgrade ROI estimates** (when Quinn is on a tier without native analytics on a surface, compute estimated visibility if upgraded). Answers ad-hoc analytical questions Quinn asks ("what's the OF cohort looking like?", "what did Tryst contribute this month?", "which model attributes Tryst lowest?"). ## Auto Nothing. Pure proposal layer. @@ -18,15 +18,21 @@ Nothing. Pure proposal layer. - Tour timing windows ("Berlin warmer in early Oct than mid-Sep based on prior tours"). - Prospect-follow-up clusters ("12 OF subs cooled since Berlin tease — send a follow-up cluster Friday"). - Cohort warmth digest entries. +- **Cross-surface attribution narratives** for analytics-dashboard insight chips (per [T-analytics-dashboard.brief.md §T-attribution](./T-analytics-dashboard.brief.md)). +- **Top-path drilldowns** when Quinn taps a path row in T2 funnel `Top paths` view. +- **Anomaly chat-surfacing** when a cross-surface aggregate or top-path delta crosses thresholds (per [T6c](./T-analytics-dashboard.brief.md)). +- **Tier-upgrade prompts** when computed metrics suggest a surface's hidden contribution (e.g. "Your Tryst search rank is unmeasured at Basic — upgrade to Standard for ~$47/mo would unlock 30-day analytics and probably reveal $X in Tryst-attributed revenue"). ## Never -Posts. Sends DMs. Mutates anything. Acts directly on a surface. Engages a prospect. +Posts. Sends DMs. Mutates anything. Acts directly on a surface. Engages a prospect. **Writes to `surface_metrics`, `metric_aggregates`, or `prospect_touchpoints`** (read-only on all three — write authority lives at surface adapters + `prospect-resolver`). ## Correction lens - Bad reads of cohort warmth. - Missed seasonality. - Wrong-frame analytics (looking at the wrong cohort, wrong window). - Plan proposals that ignore declared vacations (brief H1) or tours (brief H3). +- **Wrong attribution model framing** (Quinn corrects: "Don't anchor on Time-decay for this question, use Last-touch — I want to see what closed."). +- **Over-weighting unmeasured surfaces** in narratives (e.g. claiming "X is 30% of your reach" when X's analytics aren't tier-enabled; strategist must use `—` not estimates in those cases). ## Surfaces None directly — reads across all. @@ -35,3 +41,7 @@ None directly — reads across all. - [brief I](./I-audit-trust-replay.brief.md) — strategist's proposals are tracked + corrected through audit. - [brief H](./H-recurring-chores.brief.md) §H3 — tour-timing inputs. - [brief L](./L-specialists-fleet.brief.md) §L3b — original contract. +- [T-analytics-dashboard.brief.md](./T-analytics-dashboard.brief.md) — strategist's front-window; consumer of all read tables. +- [_engineering-surface-metrics.md](./_engineering-surface-metrics.md) — schema reference for read paths. +- [specialist-prospect-resolver.contract.md](./specialist-prospect-resolver.contract.md) — sibling specialist that writes `prospect_touchpoints` strategist reads. +- [surface-tryst.brief.md §canonical-facts](./surface-tryst.brief.md) — tier-gating fact source; informs tier-upgrade prompts.