Scope: Receipt STR Re-Stamp + Contamination Sweep

Why HGI Beaumont shows a $222 RevPAR that is really $118.51, how many other hotels have the same disease, and the exact fix. All figures queried from RPP Supabase (hotel_tax_receipts, hotel_tax_summary, str_hotel_summary) on 2026-07-14. TTM window = 2025-06 through 2026-05. Prepared by Woz; no data has been changed yet.

The bug

sift-sync.mjs (loadStrLookup, lines 100-115) builds its stamping map keyed by taxpayer_number alone, then stamps every receipt row of that taxpayer with that one STR number. A taxpayer that files hotel tax for multiple properties (management companies, owners with 2+ hotels) gets ALL of its locations stamped with a single hotel's STR number. str_hotel_summary then sums those receipts into one hotel's TTM and divides by that one hotel's rooms, inflating estimated_revpar.

Proven case: STR 54889 (Hilton Garden Inn Beaumont, 100 rooms). Its TTM receipts of $8,107,857 include $3,782,347 from Hilton Garden Inn Houston/Clear Lake NASA (750 W Texas Ave, Webster, 126 rooms), filed by shared taxpayer EPIC Hotel Group LLC (32065202775). Displayed RevPAR $222.13; Beaumont-only receipts of $4,325,510 give the true $118.51 (receipts query, 2026-07-14).

Blast radius (authoritative, from the location-grained join)

Joining every receipt row to hotel_tax_summary on (taxpayer_number + normalized location_address), which is the correct location-grained key (query run 2026-07-14, 960,124 receipt rows total):

BucketRowsRe-stamp action
Stamp agrees with the location-grained summary919,612none
No summary row exists for the (taxpayer, address) pair (address spelling variants, dead filings)33,589none (fail-safe: leave untouched, log)
Stamped, but the summary says that specific location is unmatched (inherited another location's STR)6,811strip stamp to NULL (this is the contamination)
Unstamped, but the summary has an STR for that location104gain correct stamp
Stamped with a different STR than the summary assigns8move to correct STR

So the write touches 6,923 rows of 960,124 (0.7%). The Clear Lake NASA rows fall in the strip-to-NULL bucket; they regain their own STR (59355) later through the existing match-str-to-tax pipeline, which is unchanged by this work.

Contaminated hotels (TTM window, cross-city confirmed)

Sizing by two detectors: a name-aware cross-city classifier confirms 30 hotels with $27,074,410 of foreign TTM receipts (lower bound; it misses same-brand cases like Beaumont itself, where "Hilton Garden Inn" tokens defeat name matching). The city-only classifier finds 39 hotels with $58,373,834 foreign (upper bound; it false-positives on mailing-city quirks like The Westin at The Woodlands, whose own receipts file under Spring, TX). The exact per-hotel deltas fall out of the re-stamp dry run, which uses the authoritative key and no fuzzy matching. The 30 name-aware-confirmed hotels, worst first:

STRHotel (census identity)RoomsTTM shownForeign $RevPAR nowRevPAR cleanForeign locations (city, TTM $)
32784Heritage Inn & Suites The Woodlands, Trademark by Wyndham150$7,796,314$4,671,375$142.40$57.08DRURY INN AND SUITES HOUSTON GALLERIA (HOUSTON, $4,671,375)
33720Sonesta Select Austin North120$4,796,859$3,437,843$109.52$31.03AMERISUITES - DN (DALLAS, $3,437,843)
39328Sonesta Select Dallas Richardson123$5,087,922$2,611,072$113.33$55.17SONESTA ES SUITES SAN ANTONIO DOWNTOWN (SAN ANTONIO, $2,611,072)
21472Fairfield Inn & Suites San Antonio Airport/North Star Mall120$5,761,936$2,223,223$131.55$80.79RESIDENCE INN AUSTIN ROUND ROCK (ROUND ROCK, $2,223,223)
21268Days Inn by Wyndham Shenandoah93$2,538,197$1,991,389$74.77$16.11HOLIDAY INN EXPRESS HOTEL & SUITES (SPRING, $1,991,389)
36555HomeTowne Studios Houston West Oaks145$2,987,340$1,596,933$56.44$26.27CROSSLAND - DALLAS - MESQUITE - 3544 (MESQUITE, $954,812); CROSSLANDS AUSTIN - WEST #3545 (AUSTIN, $642,121)
57792Aloft Las Colinas136$8,680,455$1,559,462$174.87$143.45HYATT PLACE FORT WORTH/TCU (FORT WORTH, $1,559,462)
67861Homewood Suites by Hilton Harlingen89$5,726,497$1,100,412$176.28$142.41HOLIDAY INN EXPRESS (ZAPATA, $1,100,412)
58934Fairfield by Marriott Inn & Suites Texarkana83$2,195,427$1,031,347$72.47$38.42COMFORT SUITES FRISCO (FRISCO, $1,031,347)
33191SpringHill Suites Houston Hobby Airport122$2,889,745$886,067$64.89$45.00FAIRFIELD & SUITES DALLAS MEDICAL/MARKET CENTER (DALLAS, $886,067)
31431Spark by Hilton San Antonio Northwest near Six Flags124$3,464,715$831,159$76.55$58.19HOLIDAY INN EXPRESS (BAY CITY, $831,159)
82140Home2 Suites by Hilton Denton120$4,879,862$629,062$111.41$97.05FAIRFIELD INN & SUITES WICHITA FALLS, TX (WICHITA FALLS, $629,062)
57527Best Western Littlefield Inn & Suites45$1,690,648$556,965$102.93$69.02WESTON INN & SUITES (RUSK, $556,965)
58502Overton Hotel & Conference Center303$13,228,223$469,256$119.61$115.37INN OF THE HILLS RESORT & CONFERENCE CENTER (KERRVILLE, $438,756); SOUTH SHORE HARBOUR RESORT (LEAGUE CITY, $30,500)
74211WoodSpring Suites Round Rock-Austin North122$1,199,686$450,960$26.94$16.81ECHO SUITES-FORT WORTH ALLIANCE (FORT WORTH, $210,804); ECHO SUITES-DALLAS PLANO RICHARDSON (PLANO, $190,828); ECHO SUITES-HOUSTON PASADENA (PASADENA, $49,328)
3533Americas Best Value Inn Bonham53$1,171,422$344,350$60.55$42.75SAGAMAR INN (SEYMOUR, $344,350)
33474Best Western Lindale Inn55$761,744$338,847$37.94$21.07CANDLEWOOD SUITES (GRAND PRAIRIE, $338,847)
54732Courtyard Austin Airport151$7,826,144$297,119$142.00$136.61FAIRFIELD INN SAN ANTONIO DOWNTOWN (SAN ANTONIO, $297,119)
62936Comfort Inn Midland61$1,582,823$286,081$71.09$58.24ECONOMY INN (BROWNFIELD, $286,081)
54385Comfort Suites University Drive79$1,310,087$270,173$45.43$36.06HOME 2 SUITES (LUFKIN, $270,173)
61983Sleep Inn & Suites Center55$1,539,733$244,288$76.70$64.53EXECUTIVE INN & SUITES (LUFKIN, $244,288)
54867Red Roof PLUS+ San Antonio Northeast57$455,927$228,968$21.91$10.91BUDGET INN (BIG SPRING, $228,968)
30970Hotel O Luxury Inn Channelview Baytown I-1030$411,991$220,602$37.62$17.48ROSE GARDEN (HOUSTON, $220,602)
57199Days Inn by Wyndham Salado41$1,267,084$144,380$84.67$75.02817 E VETERANS MEMORIAL BLVD (KILLEEN, $144,380)
82077Kasa at The Waller Austin19$593,189$137,195$85.54$65.75KASA MOCKINGBIRD FLATS DALLAS APARTMENTS (DALLAS, $109,008); BAYOU PARK (HOUSTON, $28,187)
45597Hotel Lucine61$2,720,194$129,773$122.17$116.34MUSTANG INN (HUMBLE, $129,773)
5959Motel 6 North Richland Hills82$1,719,433$122,755$57.45$53.35GARDEN INN & SUITES (DALLAS, $122,755)
45736Economy Inn By OYO Lamesa, TX27$207,828$91,886$21.09$11.76ROTAN INN (ROTAN, $91,886)
40169Comfort Suites Fort Worth North Fossil Creek75$818,801$90,116$29.91$26.62INN OF THE CONCHO'S (SAN ANGELO, $90,116)
67428Studio 6 Corpus Christi, TX - North71$1,563,096$81,352$60.32$57.18MANSA DALLAS HOTEL (DALLAS, $81,352)

Add Beaumont (54889, $3,782,347 foreign, $222.13 to $118.51) which the name detector misses but the authoritative key catches. A same-city review bucket of 69 locations across 53 STR numbers (e.g. Drury Inn Houston Galleria receipts of $4,671,375 sitting under Heritage Inn & Suites The Woodlands, STR 32784) resolves mechanically in the dry run via the same key.

The fix (three code sites + one data pass)

  1. scripts/sift-sync.mjs (upstream, prevents recurrence). loadStrLookup keys change from taxpayer_number to taxpayer_number + normalized address (upper, collapse whitespace), reading taxpayer_number, location_address, str_number from hotel_tax_summary. Fallback: taxpayer-only match allowed ONLY when the taxpayer has exactly one location in the summary, which preserves stamping through minor address drift for single-property taxpayers.
  2. scripts/backfill-receipt-str-number.mjs (the re-contamination trap). This script fills NULL stamps from hotel_tax_summary by taxpayer. Run after the re-stamp, it would put the bad stamps straight back. It gets the same composite-key change, in the same PR, before any nightly-cron wiring of it happens.
  3. New scripts/restamp-receipt-str-by-location.mjs (the data pass). Recomputes the stamp for every receipt row from the (taxpayer + address) summary join. Dry-run by default: prints per-hotel before/after RevPAR deltas, row counts per bucket, and a list of any THM-owned or deal-pipeline STR numbers touched. --execute applies in batches with keyset pagination (id-ordered; PostgREST range-without-order silently drops rows). Idempotent: second run reports zero changes.
  4. Guard rail (post-fix monitor). The sweep query from this scope (2+ distinct material addresses under one STR in the trailing 12 months) becomes a data-health check; expected steady-state result is zero confirmed cross-city rows, so any regression pages the Data Health screen.
Risk notes. (1) Stripping the 6,811 bad stamps LOWERS displayed RevPAR for the 30 to 39 affected hotels; anything downstream that consumed the inflated numbers (compset RM tables, tax intel, distress scoring, any deal underwriting that cited a contaminated comp) shifts the day this lands. (2) The strip-to-NULL bucket can contain a small number of address-variant edge cases where the location really is the stamped hotel; the dry-run report lists every stripped location name alongside its census hotel for eyeball sign-off before --execute. (3) Per the repo rule, SIFT and hotel-identity changes are high risk: the plan is dry-run, review the report, then execute, then verify.

Verification plan

Effort