feat(post-audit): batch A+B quick-wins + audit-side residuals

Bundles the user-prioritised follow-ups from the post-audit punch-list.

Batch A — pipeline + EOI safety:
 - §1.1 timeline buildAuditDescription renders diff fields ("leadCategory → hot_lead").
 - §4.13 EOI rejection cascade: notification to assigned rep + audit row + rose banner.
 - §4.10b finish doc-detail: SigningProgress reuse, linked-entity names (server-resolved),
   per-event icons + tooltips + show-more in activity panel.
 - §7.2 stage guidance card replaces empty Payments slot pre-reservation.
 - §4.15 deal-pulse trigger audit (docs/deal-pulse-trigger-audit.md).

Batch B — UX consistency + docs:
 - §1.4 quick log-contact button on interest header.
 - §2.1 contact-log compose: Dialog → Sheet.
 - §7.1 docs/deal-pulse explainer page; /docs/ in PUBLIC_PATHS.
 - DocumentStatus now includes 'rejected' + 'declined' across constants, labels, tone maps.

Audit-side residuals:
 - M-NEW-1 /me/ports skips port-context requirement.
 - M-AU03 audit log CSV export endpoint + UI button.
 - M-IN03 dead receipt-scanner.ts deleted; live path already per-port.
 - M-P01 pg_trgm GIN indexes (migration 0071).
 - §10.1 webhook tests verified passing (was stale).

Deferred per user direction:
 - §11.3 email copy refactor (needs old-CRM reference).
 - M-EM03 IMAP bounce-to-interest linking.

Tests: 1374/1374. tsc + lint clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-18 14:22:11 +02:00
parent 4b5f85cb7d
commit 0f99f054b3
21 changed files with 1399 additions and 258 deletions

View File

@@ -0,0 +1,145 @@
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Deal Pulse & Heat — Port Nimara CRM',
description:
'How the deal pulse chip + heat score work: signals, calibration, and what to do when a deal goes cold.',
};
/**
* §7.1 — public explainer page for the Deal Pulse + Heat scoring model.
* Linked from the popover in `deal-pulse-chip.tsx` ("Full guide"). Kept
* intentionally text-heavy + jargon-free so a new sales rep can read
* once and internalize the model without bouncing back to the kanban.
*
* Public route (no auth) so external docs links and email signatures
* resolve cleanly. The route lives outside (dashboard) for that reason
* — middleware lets `/docs/...` through.
*/
export default function DealPulseDocsPage() {
return (
<div className="mx-auto max-w-3xl px-6 py-10 text-sm leading-relaxed text-foreground">
<header className="mb-8 space-y-2 border-b pb-6">
<p className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
Port Nimara CRM · Sales playbook
</p>
<h1 className="text-2xl font-semibold">Deal Pulse &amp; Heat</h1>
<p className="text-muted-foreground">
What the chip on every interest card actually means, how it&apos;s scored, and how to act
on it.
</p>
</header>
<section className="space-y-4">
<h2 className="text-lg font-semibold">The chip in one sentence</h2>
<p>
The colored chip on each interest is a fast read of{' '}
<strong>how hot the deal is right now</strong> based on what&apos;s been happening on it
lately not a prediction, not an AI score, just a mechanical rollup of recent activity.
</p>
</section>
<section className="mt-8 space-y-4">
<h2 className="text-lg font-semibold">Levels</h2>
<dl className="space-y-3">
<div className="rounded-md border bg-rose-50 p-3">
<dt className="font-semibold text-rose-900">Hot</dt>
<dd className="text-rose-900/90">
Recent inbound contact, multiple touches in the last week, or a milestone (EOI sent,
deposit received) inside the last 7 days. Default-sort lists put these at the top.
</dd>
</div>
<div className="rounded-md border bg-amber-50 p-3">
<dt className="font-semibold text-amber-900">Warm</dt>
<dd className="text-amber-900/90">
Activity in the last 1430 days. The deal isn&apos;t neglected but the cadence has
slowed usually means a follow-up reminder is the right next action.
</dd>
</div>
<div className="rounded-md border bg-slate-100 p-3">
<dt className="font-semibold text-slate-700">Cold</dt>
<dd className="text-slate-700/90">
No movement in 30+ days. Time to either re-engage with intent or close the deal as
lost so the kanban stays honest.
</dd>
</div>
</dl>
</section>
<section className="mt-8 space-y-4">
<h2 className="text-lg font-semibold">What feeds the score</h2>
<ul className="list-disc space-y-2 pl-5">
<li>
<strong>Recency of last contact log entry.</strong> Inbound (client replied) counts more
than outbound (rep reached out).
</li>
<li>
<strong>Furthest pipeline stage reached.</strong> A deal at EOI scores higher than one
at Enquiry, all else equal.
</li>
<li>
<strong>Count of related interests on the same client.</strong> Volume signals intent.
</li>
<li>
<strong>Count of EOIs sent.</strong> Multiple EOIs = multiple berths under serious
consideration.
</li>
<li>
<strong>Time at current stage.</strong> Stagnation drags the score down even if other
signals look good a deal stuck at Reservation for six weeks should not read hot.
</li>
</ul>
<p className="text-muted-foreground">
The exact weights live in <code className="text-xs">system_settings</code> under
<code className="text-xs"> heat_weight_*</code> keys and can be tuned per-port from the
admin Settings page.
</p>
</section>
<section className="mt-8 space-y-4">
<h2 className="text-lg font-semibold">What to do with each level</h2>
<ul className="list-disc space-y-2 pl-5">
<li>
<strong>Hot:</strong> Don&apos;t lose the momentum. Send the next document or schedule
the in-person visit while they&apos;re engaged.
</li>
<li>
<strong>Warm:</strong> Log a follow-up contact attempt; set a reminder if you&apos;re
waiting on them.
</li>
<li>
<strong>Cold:</strong> Either &quot;Reopen with a fresh hook&quot; (price drop, new
inventory, event invite) or close the deal so the pipeline reflects reality.
</li>
</ul>
</section>
<section className="mt-8 space-y-4">
<h2 className="text-lg font-semibold">FAQ</h2>
<details className="rounded-md border p-3">
<summary className="cursor-pointer font-medium">Does this use AI?</summary>
<p className="mt-2 text-muted-foreground">
No. It&apos;s a deterministic SQL rollup of contact logs, milestones, and stage
transitions. The same inputs always produce the same chip color.
</p>
</details>
<details className="rounded-md border p-3">
<summary className="cursor-pointer font-medium">
Can I override the chip on a specific deal?
</summary>
<p className="mt-2 text-muted-foreground">
Not directly the chip is a read-only summary. To change it, change the inputs: log a
contact, advance a stage, or close the deal.
</p>
</details>
<details className="rounded-md border p-3">
<summary className="cursor-pointer font-medium">How often does it refresh?</summary>
<p className="mt-2 text-muted-foreground">
On every interest write. The kanban + list pages query a live materialized rollup, so
you should see the chip move within a few seconds of any update.
</p>
</details>
</section>
</div>
);
}