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.
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.
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.
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.
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.
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.
"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.
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.
Judged as a revenue system: offer strength, follow-up volume, ownership, measurement.
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:39mailtoFromDraft happily produces a recipient-less mailto:?subject=....salesCadence.ts:480, SalesIntelligence.tsx:236assigned_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, buildProspectInputdiscord-notify edge function used elsewhere; the sales queue does not use it.supabase/functions/discord-notifyowned_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:276Judged as a designed object: coherence, reduction, honesty of materials, the morning ritual.
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:44rpp-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:25fit_score renders as a naked "67" with no scale, and "pts" appears on mobile only.ProspectPipeline.tsx:84, :67, :187Judged as intelligence: classification integrity, rate math, windows and trends, the closed loop. A number without a comparison is noise.
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.mdowned_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:423account_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:198useAggregateGap, commit 4d22803) and then dropped in the 620e830 rework.useProspects.ts useAggregateGap, git logwon_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:276order by room_nights desc, period. Rewards big stale accounts over growing recent ones; ignores rate upside and classification confidence.useSalesAccounts.ts:37buildProspectInput nulls estimated_adr and drops room_revenue, so pipeline cards show a company with no economics attached.SalesIntelligence.tsx:39company_name while the pipeline needed a lowercase fallback for the same lookup. One canonical normalization is owed.SalesIntelligence.tsx:414, ProspectPipeline.tsx normCompanyAll 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.
| Phase | Fix | First engineering step | Championed by |
|---|---|---|---|
| P0 | Cut 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 |
| P0 | Dollars 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 |
| P0 | Numbers 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 |
| P0 | Small 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 |
| P1 | One 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 |
| P1 | Owner + 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 |
| P2 | Window 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 |
| P2 | Target 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 |
| P2 | Close 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.