RevParPro / Woz / Fri 2026-07-17 / back to roadmap

Page-Depth Audit: 16-Agent Fleet

One agent per left-menu page, each armed with today's live screenshots, the page source, and read-only prod queries. Every "data verified" quick win cites the actual query the agent ran. Synthesis first, then the per-page detail.

Model routing (capacity constraint)

Fable usage is at 80%+ with access ending Sunday 2026-07-19 5am (per Ace, 2026-07-17). Everything below routes to Sonnet 5 or Opus 4.8 by default; Fable is rationed to review gates and the highest-risk correctness work only.

Why the product feels generalized (6 themes)

Ranked build order

#SlotEffortModelPageItem
1weekendSOpus 4.8/app/sales-planStop scenario wipe + import GM Budget into V2 modelHighest urgency in the audit set: any edit today destroys the stored GM Budget, and fixing it simultaneously makes the real plan of record render in Forecast and Actuals vs Plan for all 3 planned hotels.
2weekendSSonnet 5/app/budgetRevenue-completeness banner (night audit vs posted QBO 41xx)One banner stops the page from presenting July as a catastrophic loss month; kills the single most trust-destroying artifact a comptroller would see.
3weekendSSonnet 5/app/data-healthScheduled jobs health card from cron_execution_logsThe green 'all checks passed' banner is currently lying while QBO sync has failed 8/8 runs; one select makes the page honest about the whole pipeline.
4weekendSSonnet 5/app/compsetsRGI trend arrow (MoM) + rank column on list rowsThe hook already fetches the data and discards it; trend plus '2 of 5' rank is the first thing a STAR reader looks for and transforms bare index numbers into a verdict.
5weekendSSonnet 5/app/dashboardFreshness strip (night audit / rates / pace / STR staleness)Four head queries; complements the already-committed money band by making the stale STR cards honest portfolio-wide and setting the freshness pattern other pages will copy.
6weekendSSonnet 5/app/compsets/:idRate-move deltas on calendar cells + per-night market rankZero new queries (captures already on the wire); turns the rate mirror into a 'what changed since yesterday' shop, the core daily RM question.
7next-weekMOpus 4.8 + Fable review/app/hotel-intel/:strNumberGeo compset cohort (radius + tier +/-1 + rooms +/-50%) replacing tax tractThe named fix: one data-source swap corrects the ranking panel, gap cards, gauges, and thesis simultaneously; verified 18-candidate query already works against str_hotel_summary.
8next-weekSSonnet 5/app/hotel-intel/:strNumberExclude distressed hotels from averages + same-league gap leader + honest labels/n= samplesShips with the cohort fix: kills the fake +$10 outperformance story, the 303-room Marriott 'peer', and the three contradictory verdicts on one screen.
9next-weekSSonnet 5/app/daily-opsBooking Pace 'vs 7d ago' pickup column + revenue OTB68 banked snapshots make true pace a clean same-stay-date join; converts the misnamed OTB tab into the ahead/behind signal RMs actually ask for.
10next-weekSSonnet 5/app/daily-opsNight Audit LY same-date + trailing same-weekday contextThe useNightAuditLY hook already exists unused; delta chips answer 'was 70% on a Wednesday good?' with data going back to 2023.
11next-weekSOpus 4.8 + Fable review/app/deal-stagingPromote carries identity into deal_references + $/key and revenue-multiple columnsEnds manual re-keying on every promotion and puts the first two numbers Ace computes mentally directly on the triage row.
12next-weekSSonnet 5/app/dealsPipeline card upgrades: broker line, open-questions badge, staleness chipThree small selects turn the board into a call sheet that shows who to call, what is blocking, and which deals (HGI N Dallas, 35d idle) are rotting.
13next-weekSOpus 4.8/app/budgetLabor tab fixed to actual wage accounts + real flow-through KPIReplaces total-opex-labeled-as-labor with real wage lines and gives BvA the 50%-rule flow-through metric M3 users expect; both computed from rows already loaded.
14next-weekMSonnet 5/app/sales-intelligenceLast-seen + 30d momentum chips, honest win-back email routing, data-bearing default propertyStops drafting 'stayed with us recently' to accounts dead since March and re-ranks the queue by momentum instead of lifetime totals; the credibility fix for the whole sales lane.
15next-weekSSonnet 5/app/taxAssessment YoY column + 5yr sparkline, and FIPS derived from assessment rowsHistory is already fetched and discarded; surfaces the +53%/+25% protest triggers and restores millage coverage for the 3 missing counties (7 of 15 hotels).
16next-weekMSonnet 5/app/compsetsFill Avg RevPAR card + subject-vs-compset RevPAR, replace dead Rate Alerts card with computed Undercuts TonightKills the 'Coming soon' card with data one query away and makes the permanently-zero alerts card honest by computing undercuts from tonight's fresh snapshots.
17next-weekMOpus 4.8/app/compsets/:idSubject OTB pace column in the rate calendar (HOUUS/HOURP/HOUZN)The join that makes it a pricing tool: OTB occupancy beside comp median flags exactly the soft-and-overpriced nights, using the existing useBookingPace hook.
18next-weekMSonnet 5/app/prospectsNo-next-action badge + reschedule, value-weighted columns, stage-aging chipsEnforces the one pipeline-discipline rule (35 of 36 open prospects have no next touch) and exposes that the entire 225-card board is 90+ days untouched.
19next-weekSSonnet 5/app/daily-opsAdjustments staff and reason rollups194 rows with staff and reason 100% populated; two client-side rollups answer the actual loss-prevention question owners have.
20next-weekMSonnet 5/app/data-healthOther-feeds freshness strip + CLLSB/CLLAL coverage, unattributed-file drill-downExtends health beyond the 3-property flash lane to rate shop, pace, tax, and the 2 invisible email-feed properties; step toward the SLA-board deep feature.
21next-weekMSonnet 5/app/leadsReview-queue aging pills, reject-reason capture, outcomes visible to capturerCloses the agent feedback loop before launch traffic arrives; all fields already fetched, and pre-launch (0 rows) is the cheapest moment to ship it.
22next-weekMSonnet 5/app/pipMobile card layout + staleness chips + vendor rollupThe page is unusable on its persona's device today; cards, a 96d-stale flag, and vendor exposure make it minimally real, though the demo-seed data needs replacing before it earns daily opens.

Per-page audits

/app/dashboard (Portfolio Dashboard) - src/pages/Dashboard.tsx shallow

An owner opening this at 7am on 2026-07-17 sees March 2026 STR numbers for 2 of 15 hotels, "No STR data" on the other 13 cards, and fifteen compset-drift chips, which are a data-plumbing signal, not an operating signal. Meanwhile prod already holds last night's FINAL night-audit for all 5 fed properties (business_date 2026-07-16, finality=final), rate shops captured today at 19:16 UTC for ~25 hotels, and pace reports dated today for 3 properties, and none of it appears. There is no money number for today, no MTD, no YoY, no budget pacing, and no freshness indicator beyond a tiny "Mar 2026" label, so a STAR/ProfitSword-grade user gets nothing they would act on this morning.

Quick wins

The deep feature

A per-property "today ledger" row for the fed properties that fuses all three live feeds into the revenue manager's morning one-liner: last night final (occ/ADR/RevPAR with same-day-last-year delta where LY exists), tonight on-the-books from booking_pace (rooms_sold, avg_rate, group_not_sold as of today's report), and tonight's shopped rate vs compset median from rate_snapshots, ending in left-to-sell. Concretely for HOURP this morning every input is already in prod: 89 of 190 sold for tonight at $115.51 OTB (booking_pace report_date 2026-07-17), shopped rate captured today vs a 10-member comp median around $146, 90 rooms left after last night's 52% close. That single fused row is what Lighthouse/Duetto-class tools open with, it is the difference between a dashboard that reports and one that prices, and it needs zero new pipelines for the 3 Houston properties (booking_pace, rate_snapshots, night_audit_current are all dated today). It also gives the money band a forward edge without violating the no-projection rule, because everything shown is an observed value.

Broken / misleading

/app/compsets shallow

A cluster RM (Aadil) opens a compset tool daily to answer "where am I priced tonight vs my comps, and is my index trending up or down." This page only answers "what did the March STAR report say," and shows it as bare index numbers with no trend arrows, no rank, no RevPAR dollars, and a stale-data header the KPI cards ignore. The entire rate-shopping layer that would make it a Lighthouse/OTA-Insight peer is inert: the alerts card has literally never had data (rate_alerts table is empty, 0 rows ever), and 11 of 16 compsets show no rate scan at all because the daily flash scan only covers Houston. Meanwhile 498 fresh snapshots across 25 Houston hotels sit in rate_snapshots every 24h and none of that reaches this page.

Quick wins

The deep feature

A nightly rate-position engine that turns the already-flowing rate_snapshots into per-compset positioning, which is the core of what Lighthouse sells: for each compset-night over the next 30-90 days, compute the subject's rank among active members on lowest available BAR, days undercut, and spread to market median, persist it, and generate real undercut/parity rows into the currently empty rate_alerts table so the entire alert UI (card, filters, RM triage banner, severity rails) stops being decoration. The list page row then reads like a rate shop: 'Tonight #3 of 7, undercut on 12 of next 30 nights, avg gap -4%' next to the monthly RGI. The blocking gap is coverage, not code: the flash scan feeds only 25 Houston hotels, so DFW, BCS, and Midland compsets (11 of 16) have zero forward rates; extending the scan roster to those compset members is the single highest-leverage move because every downstream feature on this page starves without it. With that in place, Aadil gets one screen that answers both the monthly question (STAR index, rank, trend) and the daily question (position tonight and forward), which is exactly the two-tool workflow (STR + rate shop) this page is supposed to collapse.

Broken / misleading

/app/compsets/:id (CompsetDetail: overview / rates / parity / alerts / discovery) shallow

A revenue manager opening this daily gets one number per hotel per night (current lowest scraped rate) plus an STR verdict frozen at March 2026 (verified: latest compset_metrics monthly period = 2026-03 for every compset). The two questions a pro tool answers on screen one, "what changed since yesterday" and "am I priced right given what is on the books", are unanswerable here despite both datasets sitting in prod: twice-daily capture history per check-in date and daily 91-day booking_pace for 3 subject properties. Meanwhile Parity and Alerts, two of the five tabs, are structurally dead with current data (single source, zero alerts ever), so the page reads as a rate mirror wrapped in empty scaffolding rather than a pricing tool.

Quick wins

The deep feature

A per-night pricing decision view that fuses the three daily-fresh datasets this page already has but never joins: for each of the next 9+ nights, show subject on-the-books occupancy and pickup (booking_pace rooms_sold deltas across consecutive report_dates; verified HOURP stay 7/25 picked up 70 to 83 rooms over 4 report days), the comp median and subject price rank from rate_snapshots, and peer rate momentum from the 2x-daily capture history, then emit a defensible per-night call: push (high pickup, priced below median), hold, or drop (soft OTB, priced above a falling market). That is precisely the screen Lighthouse and OTA Insight charge hundreds per property per month for, and for HOUUS, HOURP, and HOUZN every input is already landing in Supabase daily with zero new pipelines. It converts the page persona from 'reading a rate mirror' to 'making tonight's pricing decision with evidence', which is the depth Ace says was lost.

Broken / misleading

/app/sales-plan shallow

A GM or DOS opening this page today sees either an empty state (default property CLLAL has no plan and no STR rows) or, on HOURP/HOUUS/HOUZN, a 'forecast' that is just last year flat, because the V2 UI reads adjustment columns that are empty on every prod row while the actual GM Budget scenarios sit unrendered in the same table. Annual plan vs actuals is the one job of this page and the plan shown is not the plan the GM built. Worse, touching any cell silently destroys the stored GM Budget scenarios. Compared to a professional package (budget workbook plus STAR trend plus pace-to-plan), it has the bones of all three but connects none of them to the live data (night audit and booking pace) that already lands daily for exactly the three planned properties.

Quick wins

The deep feature

A monthly pace-to-plan bridge that turns the annual plan into a living forecast: for each month show Plan (GM Budget), then MTD actual from night_audit_entries (daily occ/ADR/room_revenue, current through 2026-07-16 for HOURP/HOUUS/HOUZN), then on-the-books for remaining days from booking_pace (daily snapshots through 2026-07-17, ~90 days forward, per-stay-date rooms_sold/avg_rate/room_revenue for the same 3 planned hotels), and finally 'pickup needed': rooms and ADR required over remaining days to hit the plan month. This is what Duetto/Lighthouse-grade tools give a DOS and it is the difference between a plan you file and a plan you manage; the GM opens it Monday and knows July needs 412 more room-nights at $118+ to make budget. All three data feeds already land daily in Supabase for exactly the three properties that have plans, so no new pipeline is needed, just the join and the arithmetic. It also gives the page a reason to be opened weekly instead of twice a year (budget season and year-end).

Broken / misleading

/app/sales-intelligence shallow

A DOS reading a Hotelligence/Agency360-style report gets pace and trend per account; this page gives lifetime totals over a Mar 1 to Jun 17 window with no time axis. The number-one ranked target (fresenius medical care, 126 nights) last stayed 2026-03-10, and 45 of 87 accounts have not been seen since May 15, yet all count equally toward "598 room nights in play" and get the same "stayed with us recently" intro email, which would burn credibility on the first call. The account ADR is shown but never positioned against the hotel's live public rate (fresh in rate_snapshots today), so the DOS has no anchor for what LNR rate to offer. And the page renders real data for exactly 1 of 15 properties while defaulting to aloft College Station (zero rows), so the first impression is an empty shell with IHG-only jargon on a Marriott property.

Quick wins

The deep feature

An account lifecycle model that turns the page from a roster into a ranked play sheet: classify every target as growing, steady, fading, or gone from the account_production daily grain (trailing 30d vs prior 30d nights plus last-seen), rank the queue by convertible revenue at risk (trailing-30d nights times paid ADR) instead of lifetime nights, and auto-route each class to the right cadence: growing and steady get the Business Edge or Corporate Gold intro, fading and gone get the existing win_back sequence. Pair it with a feed-freshness banner (the feed currently ends 2026-06-17, 30 days stale, and the page says nothing) so the DOS always knows what window she is selling from. This is exactly the pace-versus-history distinction that separates Agency360/Hotelligence-grade corporate reporting from a static production dump, and every input already exists in account_production plus salesCadence.ts.

Broken / misleading

/app/prospects (ProspectPipeline.tsx) shallow

A DOS opening this page sees a bare six-column card board: no dollar value anywhere, no stage aging, no owner, no cadence status on the cards, and for 13 of 15 properties the board is literally empty. The underlying cadence engine (scripts, outcome logging, dilution-guard Won dialog) is genuinely thoughtful, but it is disconnected from the board: manually moving a card to Interested schedules nothing, so 35 of 36 open-stage prospects in prod have no next_touch_at and never surface in the Today queue. Prod usage confirms the tool is not being worked: 3 activity rows ever, 0 prospects assigned to anyone, 12 wins recorded with zero won_rate captured.

Quick wins

The deep feature

Closed-loop won-account verification: turn the Won column from a graveyard into a contract scoreboard. Join won prospects (won_rate, won_expected_nights from migration 258) against actual production in sales_account_summary_v2 and account guest stays for the 5 night-audit properties, and render each won account's pace: committed nights vs actualized nights year-to-date, realized ADR vs negotiated rate, with a red flag and a one-click win-back-style re-touch when an account is producing under ~50% of its commitment. This is what separates Delphi/Amadeus-grade sales systems from card boards: the DOS is held accountable for revenue that actually materialized, not cards that moved. The WonDialog already captures the two inputs needed, so the remaining work is the production join plus backfilling estimated_adr from account revenue/nights at prospect creation (the prospectInputFromAccount path already computes it) so the dilution guard and the scoreboard both have a floor to measure against.

Broken / misleading

/app/leads (Capture, My Leads, Review Queue, Scoreboard, Points Setup) adequate

The capture side is genuinely strong (30-second form, direct-contact coaching, server-stamped points), but the loop back to the agent is half-built: a rejected lead dies silently because the reject button never collects a reason and reviewer_notes/dos_outcome_notes are fetched but rendered nowhere, and a Salesforce-grade user would immediately notice there is no duplicate detection, so five agents can capture the same crew company five times with no rollup. The Review Queue has no aging or SLA signal, so Matthew cannot see that a hot crew lead has been sitting in Awaiting review for four days. Converted revenue is a self-typed number with no tie to actual room-night production, which weakens the leaderboard the moment real money rides on tiers.

Quick wins

The deep feature

Company account rollup with production verification. Entity-resolve captures into a company account (normalized company name) so repeated captures of the same crew or corporate name across shifts and properties merge into one record with combined estimated room nights, first-seen and last-seen dates, and every capturer credited. Then, for the 5 properties with live night-audit feeds, verify conversions against actual production instead of trusting the self-typed converted_revenue number: when a reviewer marks a lead won, show the company's actual room-night and revenue trend in the weeks after conversion so payout-bearing leaderboard revenue is defensible. This turns the page from a suggestion box into a real pipeline source that can feed Prospects and Sales Intelligence, and it is exactly what separates a gamified form from a Salesforce-grade lead product.

Broken / misleading

/app/daily-ops shallow

The Flash tab is genuinely strong, but the other three tabs are data plumbing dressed as reports. Night Audit is a clerk's entry form: last night renders with zero context even though the same table holds up to 3 years of history and an unused useNightAuditLY hook; a GM cannot see "was 70% occupancy on a Wednesday good?" The Booking Pace tab is misnamed: it shows on-the-books, not pace; with 68 daily snapshots banked since February it never answers the only question a revenue manager asks, "am I ahead or behind of where I was last week / last year, and should I move rate?" Adjustments lists refunds one by one but never aggregates by staff or reason, which is the actual loss-prevention question.

Quick wins

The deep feature

Turn Booking Pace into a real pace ladder: for each forward stay date show OTB occ, 1-day and 7-day pickup, the final actual from the same date last year (night_audit_current holds 2023-2026 actuals for all 3 pace hotels, so STLY-final is a straight join), and the currently shopped own rate from rate_snapshots (verified fresh for 10 forward days on all 3 hotels). From those, compute a defensible need-date flag: OTB materially below the trailing same-weekday sell pattern for that days-to-arrival, with rate context beside it. That converts the tab from "here is what is on the books" into "these 4 dates are behind and here is the rate you are showing," which is the decision Lighthouse/Duetto users pay for, and every input is already in Supabase with no new pipeline.

Broken / misleading

/app/budget shallow

The skeleton is genuinely good (USALI-grouped P&L, flexed BvA, transaction drill-down drawer), but a comptroller cannot trust or close from it: the revenue budget is double-counted in BvA (4100 total plus 4110-4190 segment splits both carry the full rooms budget, so 4100 shows a perpetual -100% variance and total revenue budget is 2x), the Labor tab calls all of departments 51-64 "labor" so Labor % of Revenue is really total opex, and the page happily renders July 2026 as a catastrophic loss month because expenses posted but revenue has not, with no completeness warning. What M3/ProfitSword users expect and this page never shows: per-occupied-room costs on every line, real flow-through (delta NOI over delta revenue), and any trend beyond a single month. Two of seven tabs (Alerts, and the STR/RGI columns) are wired to tables or compsets that do not exist in prod at all.

Quick wins

The deep feature

A trailing-13-month trend P&L, which is the report a comptroller actually lives in during close (M3's Trend Report): every USALI line as columns Jun-25 through Jun-26 with budget overlay, CPOR per line, and GOP margin sparkline, so Junaid can see that Utilities crept from $6.2k to $8.9k over three months instead of eyeballing one month at a time. The data is fully present: vw_pl_monthly_summary serves any month range in one query (HOUUS has 55 continuous months, HOUZN 36, HOURP 21), budget_entries covers all 12 months of 2026, and night_audit_current provides actual occupied-room denominators for every month since 2023. This one view would also make the missing-revenue months self-evident (a $0 revenue column screams) and would convert the page from a single-period viewer into the close-and-forecast workbench the Budget page is named for.

Broken / misleading

/app/deals (Deal Pipeline list + DealDetail) adequate

DealDetail is genuinely deep (13 panels, model sync, PIP, proforma), but the LIST is a table of contents with money sprinkled on. A pipeline operator opens a board to answer three questions the page cannot answer: which deal is stalling (no staleness or days-in-stage; HGI N Dallas has sat untouched in Underwriting since Jun 12), who do I call next (broker_name/firm populated on 11 of 14 deals in prod but never selected or rendered at list level), and what is blocking each deal (90 tracked open questions across 10 deals, zero visible on cards). There is also no aggregate view: 5 LOIs out totaling $87.8M with roughly $36M combined cash-to-close, and no column totals anywhere. Worse, the trailing cap on LOI cards is computed against the ASK while the card leads with the LOI price, so Crowne Plaza shows "-2.3% cap" beside "LOI $15.0M" when the model's going-in cap at the offer is 2.2 percent, a misleading basis a lender or partner would catch instantly.

Quick wins

The deep feature

A deal_stage_events log written by useUpdateDealStage (deal_id, from_stage, to_stage, actor, timestamp) is the single unlock that makes this page expert-grade. It gives true days-in-stage per card instead of last-touch proxies, column-level aging norms (median days in Underwriting, LOI age before it goes stale with a seller), and funnel discipline over time: pass rate by stage, prospecting-to-LOI velocity, and which brokers' deals actually convert. Salesforce-grade pipelines live on exactly this table; without it every aging or conversion number on the board is unverifiable. It is one small table plus two lines in the existing mutation, then the list page reads it with one grouped query, and it starts accruing history the day it ships, which is why shipping it early matters more than any rendering change.

Broken / misleading

/app/hotel-intel/:strNumber (SiftDetail, Compset tab focus) shallow

The tab looks analytical (pecking order, gap math, index chart, percentile gauges, auto-thesis) but every conclusion is computed on the wrong cohort: the STR tax tract, which I verified spans 0 to 16.6 miles and pits Sugar Land/Stafford hotels against a Westchase subject while omitting the actual across-the-street compset (DoubleTree Westchase 0.4mi, Home2 1.2mi, Courtyard 1.7mi). A domain expert doing STAR-report work would spot in seconds that the "+$10 outperformance" is an artifact of a $6.56-RevPAR distressed Wyndham dragging the tract average, that the $61-vs-$145 comparison is a 112-room SpringHill against a 303-room full-service Marriott, and that three mutually contradictory verdicts render on one screen. Wrong-cohort analytics is worse than no analytics for an acquisition persona.

Quick wins

The deep feature

A STAR-style compset report on demand for any of the 4,227 hotels: once the geo cohort is right, aggregate monthly hotel_tax_receipts (statewide, monthly report_period granularity verified in prod) across the cohort to produce a RevPAR index (subject / cohort avg x 100), rank n of m, and a 24-month index trend line, plus receipts correlation to flag true revenue twins per the persona-weighted scoring already specced in docs/compset-discovery-redesign-prompt.md and running in scripts/precompute-discovery.mjs for portfolio hotels. That converts the tab from tract trivia into the sentence Ace actually needs when underwriting: this SpringHill indexes 84 against its real Westchase compset and has shed 9 index points in 18 months. It is the exact artifact STR sells, rebuilt from tax data THM already ingests for all of Texas.

Broken / misleading

/app/deal-staging shallow

This is a generic CRUD inbox (name, city, rooms, price, four status chips) when the database underneath already knows almost everything needed to triage. Every staged row's source_ref resolves to hotel_tax_summary with 6+ years of actual taxed room revenue current through 2026-05, yet the row shows none of it: no price per key, no revenue multiple, no implied RevPAR, no segment sanity check. An acquisitions person cannot decide promote-vs-dismiss from the row; proof in prod is SAIDA TOWERS III, a 1-room vacation_rental condo with $4.6K annual revenue, sitting as a New hotel lead for 3 months next to a 1,000-room Marriott Marquis that is wildly outside THM's select-service box, and the page gives zero visual signal that either is junk.

Quick wins

The deep feature

A one-glance "does it pencil" screen per row: join the SIFT trailing revenue (verified present for every staged ref) and render three numbers next to the asking price: implied RevPAR, price-to-trailing-revenue multiple, and $/key vs the $150K/key replacement-cost anchor already stored in the underwriting model_summary. Color-band them against THM's own promoted-deal precedent (the three Central Expy deals cluster around 4.5x revenue and $129-150K/key, going-in cap ~7.2-7.4%), so a new lead lands on the page pre-scored against deals Ace actually signed off on rather than as a bare name and price. Add days-in-status aging and a duplicate check against deal_references/hotel_tax_summary so re-listed properties surface their prior pass/pursue history. That converts the page from a to-do list into the pre-underwriting screen a professional acquisitions pipeline (Dealpath-style) puts at intake, using zero new pipelines.

Broken / misleading

/app/tax (TaxIntel.tsx) shallow

The page is a CAD data dump, not tax intelligence: it shows only the latest assessment per parcel with zero trend, zero benchmarking, and no appeal math, even though prod holds 5 years of assessment history (52 rows, 2021-2025) and 12-month SIFT revenue through May 2026 for all 15 hotels. A comptroller opening this before the May 15 Texas protest deadline cannot answer the one question the page exists for: which properties are over-assessed and by how much. Meanwhile the numbers it does show are wrong: the Est. Tax Bill sums every levying body in the county (Harris 2025 sum is 67.7 per $100, an implied 67.7% tax rate), 3 of 5 THM counties are missing from the hardcoded FIPS list, and the $93.9M Total Assessed card silently drops a $9.6M property because a sliver parcel was linked. The Appeals tab shows real protest rows but its Total Savings card is permanently blank (all tax_savings null), and the Exemptions tab has exactly 1 row in the entire prod table.

Quick wins

The deep feature

A per-property protest recommendation engine, which is exactly what O'Connor and Popp Hutcheson (both already appearing as agents in prod tax_appeals rows) charge 25-40% contingency for. For each hotel it combines the verified inputs into one verdict line: 5-year assessment trajectory and latest YoY jump, assessed-per-key and assessed-to-revenue multiple ranked against the county peer distribution from SIFT (Harris n=234, Dallas n=184), the yearly_data revenue trajectory as counter-evidence, and the county millage stack to convert any over-assessment into dollars at stake per year. Output per property: "CLLAL: assessment +53% in 2025 while revenue fell 3%; assessed at $93.5K/key; estimated over-assessment $2.4M; roughly $46K/yr at stake; protest deadline May 15; status: not protested." That last field matters because hotel_tax_summary already tracks tax_protested and protest_agent, so the page can show which properties the tax agents are covering and which are exposed with nobody protesting.

Broken / misleading

/app/pip (PipProjects.tsx + PipDetail.tsx) hollow

Every project on the page is April 11 seed data (all 4 visible pip_projects share created_at 2026-04-11T22:01:59, zero item updates since; vendors are demo names like FloorCraft LLC), so the marquee "3 overdue / 108d overdue" stat is demo-data rot, not portfolio status; THM's actual live PIP work (SpringHill Houston FF&E) is not in the system at all. The one analytical element, "Recommended Renovation Windows (Based on Occupancy)", is a hardcoded JSX strip identical for every project and never touches occupancy, while a purpose-built pip_schedule_windows table with avg_occupancy sits unused. For the persona, a chief engineer on a phone, the page is unusable: the 8-column table sits in an overflow-hidden wrapper, so Budget, Deadline, % Complete, and Days Left are clipped off-screen with no scroll (confirmed in the mobile screenshot), and the only write control (status dropdown) lives in that clipped table. No photos, no delivery dates, no install scheduling, no change history, despite install_status, planned_start_date, estimated_delivery_date, attachments, and pip_documents all existing in the schema, populated with nothing.

Quick wins

The deep feature

Turn each PIP item into a real procurement record with a mobile status-advance flow and ledger-true actuals. The schema is already built for it: pip_items carries install_status, planned_start_date/planned_end_date, estimated_delivery_date, vendor_quotes and attachments jsonb; pip_documents exists for brand letters and invoices; and pip_match_rules (pattern, pattern_type=vendor_name, pip_item_id, hit_count) is clearly designed to auto-match vendor charges from the pl_imports/credit-card feed onto PIP items so 'Actual' comes from the ledger instead of hand-typed numbers. Ship the loop: Mohammed taps an item on his phone, advances it one status (quoted to awarded to ordered to received to installing to complete), attaches a photo on complete, and sets a delivery date; the page then flags delivery-vs-install-window conflicts against pip_phases and shows a per-item audit trail. That single loop converts the page from a static spreadsheet mirror into the reason field staff open the app daily, and it is what separates a Procore-grade tool from decoration.

Broken / misleading

/app/data-health shallow

The page equates "data health" with one pipeline: the 3-property flash verifier. Prod cron_execution_logs shows qbo-sync-pl-summary-daily has failed all 8 runs since Jul 10 (vault secret missing) yet the page banner says "all 27 checks passed" in green. Two more properties (CLLSB Staybridge, CLLAL aloft College Station) have live email_parse night-audit feeds and land 100+ files a week in report_inbox but are invisible here, and the rate-shop, booking-pace, and SIFT tax feeds have zero freshness representation. An ops person trusting this page would believe the number pipeline is fully healthy while a financial sync has been dark for a week.

Quick wins

The deep feature

Turn the page into a unified pipeline SLA board: every feed is a monitored lane (5 night-audit email feeds, morning verifier, each cron job, rate-shop scans, booking pace, QBO P&L sync, SIFT tax loads, STR uploads) with its expected cadence declared once, then last-success time, staleness vs cadence, and consecutive-failure streak rendered per lane. The headline banner becomes an aggregate across all lanes rather than just the flash verifier, so a week of silent QBO sync failures or a stale rate-scan lane flips the page red the way Innrly or a Datadog monitor board would. Fold in data_quality_issues (1,000 unresolved rows today, including 131 high-severity chain_integrity and 28 str_collision) as a triage count per lane so hygiene debt is visible next to feed health. Everything needed already lands in Supabase (cron_execution_logs, report_inbox, night_audit_current, rate_snapshots, booking_pace, hotel_tax_receipts, str_uploads, data_quality_issues); the work is the lane abstraction plus a cadence manifest, roughly 2 to 3 days.

Fleet run wf_bab25735-3fc, 16 agents, 2026-07-17. Every quantity cited from prod queries or page source by the diving agent; spot-verify before acting on any single number. Woz / Claude.