Panel Review · Hormozi × Ive × Wynn

RevParPro Sales Intelligence: why it feels like it sucks, and what to fix

Three reviewers, three lenses, one surface: the Sales Intelligence screen, the Prospect Pipeline, the outreach cadence engine, and the classification views feeding them. Each reviewer read the actual code before judging.

Generated 2026-07-16 · Sources: src/pages/SalesIntelligence.tsx, src/pages/ProspectPipeline.tsx, src/pages/SalesPlan.tsx, src/lib/salesCadence.ts, src/hooks/useSalesAccounts.ts, src/hooks/useProspects.ts, src/lib/nav-config.ts, migrations 223, 224, 228, 229 (revparpro repo, main @ 04a997c)

The one-paragraph consensus

All three reviewers landed on the same shape independently: the lead source is genuinely good and the everything-after leaks. Mining your own night audit for companies already sleeping in your beds on generic brand rates is the warmest lead list in hotels, and "no data entry" is the right instinct. But the screen shows counts instead of dollars, classifies accounts by keyword guess while the roster-truth view sits built and unwired, sends email through untracked mailto: links, assigns the work to nobody, and stops watching at exactly the moment that proves value: whether a won account ever produces on the new rate. None of the fixes require new data. Almost everything needed is already in the database and being thrown away at the display layer.

What is genuinely right and should not be touched: the nightly-audit-to-target pipeline, the NEPQ voice and copy rules in the cadence engine, and the roster-confirmed v2 classification view that is already written and waiting.

Where all three converge

Consensus 1

No money on the board

The stat row counts targets and nights. room_revenue flows through every row and is never aggregated, annualized, or shown as pipeline value. Account ADR (room_revenue / room_nights) is computable and appears nowhere. Nobody can say whether this feature earned a dollar.

Hormozi HIGH · Ive MED · Wynn HIGH
Consensus 2

Guessing at the one fact that matters

The live hook reads the keyword-only view (migration 224) while roster-confirmed v2 (migration 228) is unwired. Risk: pitching "your own rate" to a company that already holds one. The keyword fallback also silently flips real targets to owned_lnr on a single unrecognized rate plan.

Wynn HIGH ×2 · Hormozi implicit
Consensus 3

Outreach into a black hole

mailto: drafts leave no record of send, edit, or reply. "Mark done" advances the cadence without capturing what was said or answered. The pipeline is a picture of work, not a record of it.

Hormozi HIGH · Ive HIGH
Consensus 4

Nobody owns anything

assigned_to inserted null with no UI. Touches log user_id: null. No quota, no digest, no notification when a big new target appears, no consequence for an untouched queue. The follow-up queue is only seen if someone volunteers to look.

Hormozi HIGH + MED · Ive HIGH
Consensus 5

The loop never closes at Won

"Won" is a hand-moved card. Nothing checks account_production for the company producing on an owned rate afterward, no "won but not producing" alert, no pre/post ADR attribution. Ive: a Won card carries no rate and no commitment, so Won cannot answer what was won.

Hormozi MED · Ive HIGH · Wynn MED
Consensus 6

Cadence math punishes lateness

Next touch is scheduled from cadence_started_at + dayOffset, not from completion. Log step 2 late and steps 3 through 5 pile up as instantly overdue, which trains users to ignore the queue the cadence was built to run.

Hormozi MED · Wynn LOW

Hormozi the money machine lens

Judged as a revenue system: offer strength, follow-up volume, ownership, measurement.

Verdict
You built a lead list, not a money machine. And the lead list part is genuinely good: pulling companies off your own night audit that are already sleeping in your beds on generic brand rates is the warmest lead source that exists in hotels. But everything after the list leaks. There is not a single dollar sign on the scoreboard, nobody owns a number, the emails offer a rate without ever naming a rate or a savings figure, the send button is a mailto link that disappears into the void, and when you finally win an account the system just stops watching. A machine you cannot measure is a machine you cannot improve, and a salesperson with no quota, no owner, and no consequence does exactly what this UI expects of them: nothing, quietly.
HIGH
No money anywhere on the scoreboard. Stats show "Conversion targets", "Room nights in play", counts only. room_revenue is selected and rendered per row as fine print, never aggregated. estimated_adr and fit_score hardcoded null at insert.SalesIntelligence.tsx:374-379, buildProspectInput at SalesIntelligence.tsx:39
HIGH
The offer is unquantified. Step 1 offers "a rate just for your people" with no number and no savings figure. The step 8 "Proof email" contains a claim, not proof. The system holds each company's actual paid revenue and nights and could truthfully state the savings; the drafts never receive it.salesCadence.ts businessEdgeEmail steps 1 and 8
HIGH
mailto is a black hole. No send record, no reply detection, no sequencing enforcement. The Sales Intelligence "Draft intro email" link logs nothing at all; only "Work this account" writes anything. mailtoFromDraft happily produces a recipient-less mailto:?subject=....salesCadence.ts:480, SalesIntelligence.tsx:236
HIGH
Nobody owns anything. assigned_to null with no UI to set it; every logged touch inserts user_id: null; no quota, no leaderboard across the properties, no escalation. Everyone's job is nobody's job.useProspects.ts useLogTouch, buildProspectInput
MED
Speed to lead is zero because nothing pushes. New targets sit until someone visits the page. The repo already has a discord-notify edge function used elsewhere; the sales queue does not use it.supabase/functions/discord-notify
MED
Won is where the machine stops watching. The true win signal (account flips to owned_lnr in production data) is computable and unused. No "won 60 days ago, zero production since" alert.sales_account_summary_v2 has_owned, useProspects.ts:276
MED
Corporate Gold play is a badge with no workflow. "Business case via IHG Key Account Director" has no KAD contact, no business-case artifact, nothing to do.SalesIntelligence.tsx TYPE config
LOW
lost_reason is never captured. The column exists; moving a card to Lost never asks why. Win-back then re-engages blind.prospects.lost_reason, ProspectPipeline.tsx

Ive the experience lens

Judged as a designed object: coherence, reduction, honesty of materials, the morning ritual.

Verdict
There is genuine care in the deepest layer of this product: the cadence engine is written with real respect for the person who will send those emails, and the nightly audit turning rate codes into a hit list is an honest, almost beautiful idea. But the experience wrapped around that idea is not designed, it is accumulated. Five navigation items promise a sales department; two of them are empty rooms; the two that work are the same job split in half, each with its own memory of which hotel you were looking at. The one object that matters each morning, the follow-up due today, is buried mid-page behind the third item in the group. The user senses this even if they cannot name it. That is why it feels like it sucks. The idea deserves one inevitable screen, and it is perhaps two focused weeks away.
HIGH
Five nav items for one job, two of them empty. Compsets, Sales Plan, Sales Intelligence, Prospects, Leads. Leads.tsx is an 8-line stub shipped in mvp phase, and front desk roles see ONLY this group, so their app includes a dead page. Prospects.tsx is a dead 8-line file whose route actually renders ProspectPipeline.nav-config.ts:69-73, Leads.tsx, Prospects.tsx, App.tsx:78
HIGH
The morning ritual is inverted. "Follow-ups due today" renders below the header and two selects, inside the third nav item. The nav has a badgeCount hook and nothing uses it, so the nav cannot say "3 due." The user assembles the picture the tool should have assembled.ProspectPipeline.tsx:342, nav-config.ts:44
HIGH
Dishonest material: a mailto wearing the costume of a CRM. The product learns nothing from a draft: sent, edited, or abandoned. "Mark done" records no outcome. Won cards carry no rate and no room-night commitment, so Won cannot answer what was won.ProspectPipeline.tsx:285, :299
MED
The two halves do not share memory. Separate localStorage keys (rpp-si-last-hotel vs rpp-pp-last-hotel) mean crossing pages can silently switch hotels. The per-row "worked" flag is component state; reload and the screen re-offers an account already in the pipeline.SalesIntelligence.tsx:14,143; ProspectPipeline.tsx:25
MED
The kanban is scaffolding. One gesture per card ("Move to Contacted", a button, no drag), no card detail, no touch history, no notes ever surfaced. fit_score renders as a naked "67" with no scale, and "pts" appears on mobile only.ProspectPipeline.tsx:84, :67, :187
LOW
Controls that lecture instead of sequence. The gray "Draft intro email" button exists solely to scold: "Find contacts first, then you can draft an intro email." If the sequence is find-then-draft, one button should perform the sequence.SalesIntelligence.tsx:246
LOW
titleCase mangles names in outbound email. Words of 3 or fewer characters go uppercase: "The Home Depot" becomes "THE Home Depot", and the mangled form is injected into email subjects. Implemented twice.SalesIntelligence.tsx:30, salesCadence.ts:219
LOW
Guest names disclosed with a shrug. "Who stayed" lists guest names labeled "(internal)" in tiny gray text, and a guest first name is piped into outbound email copy. A real privacy posture decision rendered as an afterthought.SalesIntelligence.tsx:268, salesCadence.ts guestClause
LOW
Three pages, three loading vocabularies. Centered "Loading..." text vs skeletons vs pulse bars. The good empty-state copy on Sales Intelligence makes the inconsistency elsewhere more visible.SalesIntelligence.tsx:400, ProspectPipeline.tsx:478

Wynn the revenue intelligence lens

Judged as intelligence: classification integrity, rate math, windows and trends, the closed loop. A number without a comparison is noise.

Verdict
The pipeline under this screen is real: nightly rate-report rows, alias-resolved companies, an authoritative ECS roster sitting in the database. But the screen itself is a lead list, not intelligence. Every number on it is an all-time cumulative with no window, no trend, no ADR, and no compset context. Worse, the live view still classifies accounts by keyword regex while the roster-confirmed v2 view sits built and unwired, so the tool is guessing at the one fact the whole play depends on: whether the hotel already owns a rate with this company. And nowhere does it answer the CRO question that decides if this is worth doing at all: what happens to ADR if we convert this Business Edge account to an LNR. As shipped, it can tell a DOS who to email. It cannot tell them whether they should, at what rate, or whether it worked.
HIGH
Classification runs on keyword guesses while roster truth is unwired. The hook reads sales_account_summary (224, keyword-only). Migration 228 built v2 with the roster join precisely because keywords are guesses; its header says cutover is a one-line hook change pending the README_dq_p5.md validation queries, which have apparently not been acted on. Risk: offering "your own rate" to a company that already holds a loaded LNR.useSalesAccounts.ts:32, migrations 224 and 228, README_dq_p5.md
HIGH
The keyword fallback misclassifies by construction. Any unrecognized non-empty rate plan becomes owned_lnr, and owned_lnr outranks everything over all history: one stay on a promo or crew block permanently hides a genuine target. ~* 'GOLD' is a substring match, so any plan name containing GOLD becomes corporate_gold. The "Plus N accounts you already hold a local rate with" line is an unaudited number.migration 228 rows_typed CASE, SalesIntelligence.tsx:423
HIGH
No window, no recency, no trend. Both views aggregate all of account_production with no date predicate; "$X over N nights" states no period. A company that stopped staying five months ago is indistinguishable from one staying weekly. Win-back exists as a cadence but lapse detection is manual eyeballing of a number that only ever grows.migrations 224/228 (no business_date filter), SalesIntelligence.tsx:198
HIGH
The rate math is absent. No ADR on screen despite being trivially computable, no proposed-LNR field, no BAR or compset ADR beside the account, no dilution check. Business Edge is already discounted; an LNR set below the account's effective rate converts revenue you were getting anyway into a deeper discount. The compset context was built (useAggregateGap, commit 4d22803) and then dropped in the 620e830 rework.useProspects.ts useAggregateGap, git log
MED
No closed outcome loop. Won stamps won_at and nothing ever checks production data for the company producing on an owned rate. No conversion verification, no post-win ADR delta, so the tool can never report what it made.useProspects.ts:276
MED
Prioritization is a single-key sort. order by room_nights desc, period. Rewards big stale accounts over growing recent ones; ignores rate upside and classification confidence.useSalesAccounts.ts:37
LOW
The pipeline discards the intelligence at handoff. buildProspectInput nulls estimated_adr and drops room_revenue, so pipeline cards show a company with no economics attached.SalesIntelligence.tsx:39
LOW
Company-name casing contract is inconsistent. Sales Intelligence keys contacts by raw company_name while the pipeline needed a lowercase fallback for the same lookup. One canonical normalization is owed.SalesIntelligence.tsx:414, ProspectPipeline.tsx normCompany

The synthesized action plan

All three reviewers' fixes, deduplicated and sequenced. P0 items are days each and mostly one-line or one-view changes; P1 is the restructure; P2 builds on P0 data foundations.

PhaseFixFirst engineering stepChampioned by
P0Cut over to roster truth. Validate then switch the hook to sales_account_summary_v2; badge each row roster-confirmed vs keyword-guess.Run the two README_dq_p5.md validation queries against prod, record the disagreement rate, then change one string in useSalesAccounts.ts:32.Wynn #1
P0Dollars on the board. Annualized value per target, pipeline $ by stage, headline stat in dollars ("$ on generic rates"), ADR column per account.Aggregate room_revenue already flowing through the page; add adr to the view select and an ADR column to TargetRow.Hormozi #1, Ive, Wynn #3
P0Numbers in the emails. Extend buildEmailDraft with the account's actual paid ADR and the proposed LNR so step 1 and step 8 state a concrete, truthful savings figure.Pass room_revenue and proposed rate into EmailDraftInput; rewrite steps 1 and 8 to use them.Hormozi #2
P0Small honest fixes. Schedule next touch from completion date not cadence start; require a one-tap lost_reason; fix titleCase; delete the dead Leads and Prospects stubs; one shared hotel-selector key.Each is a contained edit in useLogTouch, ProspectPipeline, salesCadence, nav-config.All three
P1One Sales screen opening on Today. Merge Sales Intelligence + Pipeline: follow-up queue on top (overdue first, outcome capture required on Mark done), dollar-ranked targets in the middle, pipeline as stage list with touch history. Kill the kanban columns. Won transition asks for negotiated rate and expected nights.New combined route; nav badgeCount wired to follow-ups due.Ive redesign, Hormozi #5
P1Owner + morning push. Set assigned_to at "Work this account", record user_id on every touch, morning Discord digest per property: follow-ups due, new targets, overdue count, by name.Reuse the existing discord-notify edge function on a scheduled trigger.Hormozi #3
P2Window and trend everything. Rolling 90 days vs prior 90, last-stay date, direction arrows; state the window in the UI header.Windowed variant of the view exposing room_nights_t90, room_nights_prior90, last_stay_date.Wynn #2
P2Target score replaces the nights sort. Recent nights × recency decay × rate-gap upside × roster-confirmation weight, components visible on hover.Computed column on the windowed view; depends on roster cutover and ADR landing first.Wynn #4, Hormozi #8
P2Close the loop. Nightly: flag won-but-not-producing within 30 days; surface 30/60-day lapses into a win-back queue; report pre/post nights and ADR delta per verified conversion as the screen's own scoreboard.View joining prospects (stage = won) to account_production on company_name_norm, owned-classified plans since won_at.Hormozi #4, Wynn #5, Ive

Sequencing logic: P0 makes the current screens truthful with data already in the database. P1 is the structural redesign (Ive's "one inevitable screen") plus accountability. P2 turns a lead list into intelligence: trend, score, and a closed loop that lets the feature report its own revenue.