Buildd From Ideas · Revenue Management Services

RMS Pipeline — Database Architecture

Complete map of all 34+ tables in AWS PostgreSQL (hotel-intelligence-db) · OAuth2 live · ETL pipelines running · CoStar STR loaded · Pricing engine tables designed

Owner: Jay Vekaria
Company: Buildd From Ideas — RMS
Database: postgres (PostgreSQL / AWS RDS)
Total Tables: 34+ (9 ETL + 4 STR + 21 App + Pricing Engine)
Scale Target: 500+ properties
Last Updated: 2026-04-06
LIVE DASHBOARD | Last refresh: April 24, 2026 at 13:16 UTC | 349,557 total rows | 25 properties | 115 hotels | 396 rate plans | Prices: 2026-03-10 → 2026-07-28

🔄 Data Flow — End to End

📊 Google Sheets
3 sheets / tabs
Comp data + Property Master
🐍 Python ETL
✅ OAuth2 live
comp_prices daily via GitHub Actions
property_master on-demand
🗄️ ETL Tables (9)
✅ Raw → Clean → History
291,563 history rows
🧠 Feature Store
Unified feature vectors
per property × date × run
+
🏨 PMS Systems
Choice Hotels, Wyndham
Scaling to 500+ properties
🤖 Scraper Bots
Engineer's system
OTP + auto-login
🗄️ App Tables (21)
Live operational data
700 pricing records
🧠 Feature Store
Unified feature vectors
per property × date × run
+
📈 CoStar STR
Monthly STAR Excel files
7 hotels × Live + Static
🐍 STR Parser
✅ costar_str.py
Occ/ADR/RevPAR/MPI/ARI/RGI
🗄️ STR Tables (4)
str_properties + monthly
+ daily + day-of-week
🧠 Feature Store
Unified feature vectors
per property × date × run
🧠 Feature Store
⚡ FT-Transformer
Neural Network
+ Rule Engine Ensemble
👤 Rev Manager
Human-in-the-Loop
Accept / Override
🤖 AI Reasoning
Claude API
Explain + Communicate
📤 PMS Push
API or Manual
Price goes live

✅ Pipeline Status — Live as of 2026-04-24

Authentication
OAuth2 Desktop App — larry@buildfromideas.com
Token auto-refreshes. Service account blocked by org policy.
Daily Automation
3 ETL jobs daily via GitHub Actions (7AM Central). 21 comp_prices batches, 12 property_master batches completed.
CoStar STR monthly
Scale Target
500+ properties · 20+ active clients · Growing daily
Universal key: bfi_property_id
Pricing Engine
7-step model designed: Rank Waterfall → Base Price → Heat Signal → Multipliers → Floor/Ceiling
Room category matrix live · FT-Transformer + AI reasoning layer
📥 ETL Pipeline Tables — from Google Sheets 9 TABLES · ✅ LIVE — OAUTH2 CONNECTED · COMP_PRICES DAILY AT 7AM

property_master_raw

Raw copy of Sheet 1 — ✅ 30 rows (2026-04-24). On-demand refresh.

✅ 30 rows
ColumnTypeNotes
idBIGSERIALAuto-assigned row ID
scraped_atTIMESTAMPWhen this row was loaded from Sheets
+ all columns from Property_Master_Raw_AWS tab (dynamic)

active_properties_raw → clean → history

🟢 SOURCE OF TRUTH — Only active hotels (is_active=1). Raw/clean/history pattern. All downstream scripts JOIN to active_properties_clean.

✅ 25 active
TablePurposeNotes
active_properties_rawUnfiltered active rowsTruncated daily
active_properties_cleanNormalized, deduplicatedPRIMARY KEY on property_id
active_properties_historyDaily snapshotsload_batch_id = YYYYMMDD, never overwritten
active_propertiesLegacy aliasSynced from clean — all JOINs use this
property_master_historyALL properties archiveActive + inactive, daily snapshots

property_master_clean

Validated & cleaned version — all properties (active + inactive). Column names normalized to lowercase.

29 rows
ColumnTypeNotes
idBIGSERIALAuto-assigned
scraped_atTIMESTAMPLoad timestamp
+ cleaned columns (nulls removed, types cast)

comp_set_mapping_raw

Raw copy of COMP_MAPPING tab — which hotels compete with which

119 rows
ColumnTypeNotes
idBIGSERIALAuto-assigned
scraped_atTIMESTAMPLoad timestamp
+ all columns from COMP_MAPPING tab

comp_set_mapping_clean

Validated & cleaned version of competitor mapping

106 rows
ColumnTypeNotes
idBIGSERIALAuto-assigned
scraped_atTIMESTAMPLoad timestamp
+ cleaned comp mapping columns

comp_prices_raw

Raw copy of COMP_ALL_PRICES_EXTENDED tab — ✅ 15,725 rows (2026-04-24)

✅ 15,725 rows
ColumnTypeNotes
property_idVARCHARbfi_property_id — universal connector
bdc_hotel_idVARCHARBooking.com hotel ID
bdc_property_nameVARCHARCompetitor hotel name
checkinVARCHARCheck-in date
priceVARCHARRoom price (raw — text from sheet)
currencyVARCHARCurrency code
+ 23 more columns: rateplan_id, rateplan_name, is_available, no_guests, meal_plan, cancellation_policy, last_scrapped_at_IN_UTC, queueId, rateshopId, roomDescription, roomAmenities, sourceUrl, isPromo, closed, discount, promoName, taxStatus, taxType, taxAmount, isRatePerStay, minStay, optionalExtras, payPlan

comp_prices_clean

Cleaned competitor pricing — ✅ 15,725 rows, prices as NUMERIC, nulls handled

✅ 15,725 rows
ColumnTypeNotes
property_idVARCHARbfi_property_id — universal connector
bdc_hotel_idVARCHARBooking.com hotel ID
bdc_property_nameVARCHARCompetitor hotel name
checkinVARCHARCheck-in date
priceNUMERICRoom price (cleaned — cast to number)
currencyVARCHARCurrency code
+ 23 more columns (same as raw, with prices/amounts cast to NUMERIC, empty strings → NULL)

comp_prices_history

📸 Daily snapshot archive — one batch per day. Never overwritten. 291,563 rows as of 2026-04-24.

291,563 rows · Grows daily
ColumnTypeNotes
load_timestampTIMESTAMPExact UTC time of snapshot
load_batch_idVARCHARDate-based batch ID (e.g. "20260323") — used for idempotent daily reloads
property_idVARCHARbfi_property_id — universal connector
bdc_hotel_idVARCHARBooking.com hotel ID
bdc_property_nameVARCHARCompetitor hotel name
checkinVARCHARCheck-in date
priceNUMERICRoom price
currencyVARCHARCurrency code (e.g. USD)
last_scrapped_at_in_utcVARCHARWhen price was scraped from source
+ 20 more columns: rateplan, availability, meal plan, cancellation, tax, promo, room details

agg_market_percentiles_by_scrape

Market percentile distribution per scrape run

11,818 rows
ColumnTypeNotes
idBIGSERIALAuto-assigned
scrape_dateDATEDate of aggregation
+ P10, P25, P50, P75, P90 price percentiles

agg_market_price_statistics_by_scrape

Market-wide price statistics per scrape run

11,818 rows
ColumnTypeNotes
idBIGSERIALAuto-assigned
scrape_dateDATEDate of aggregation
+ avg_price, min_price, max_price, std_dev, count
🏨 Core Business Tables FROM ENGINEER'S APP · LIVE OPERATIONAL DATA

properties

Master list of all 5 hotels. Central table — everything links to this.

5 rows
ColumnTypeNotes
idBIGSERIALPrimary key — referenced by 5+ tables
uuidCHAR(36)Unique ID used in app URLs
property_codeVARCHAR(50)PA672, CA322, 33517, NA, NA2
hotel_nameVARCHAR(255)Comfort Inn, Quality Inn, Days Inn, etc.
bfi_property_idVARCHAR(100)Build From Ideas internal ID
pms_name / pms_idVARCHARProperty Management System identifier
bdc_property_name/idVARCHARBooking.com property name & ID
city / stateVARCHARLocation
owner_nameVARCHAR(255)Hotel owner's name

update_records ⭐

THE CORE TABLE. Every pricing decision per hotel per date. This is where the algorithm lives.

700 rows
ColumnTypeNotes
idBIGSERIALPrimary key
property_idBIGINT→ properties.id
scraping_run_idBIGINT→ scraping_runs.id (nullable)
record_dateDATEDate this pricing record applies to
algo_output_priceDECIMAL(10,4)💡 Algorithm's recommended price
standard_priceDECIMAL(10,4)Actual price set by revenue manager
competitor_set_avg_priceDECIMAL(10,4)Average of comp set prices
occupancy_percentDECIMAL(5,4)Current occupancy (0-1)
forecasted_occupancy_percentDECIMAL(5,4)Predicted occupancy
actual_adr / actual_revparDECIMAL(10,2)Actual ADR & RevPAR for the day
ly_adr / ly_occupancy_percentDECIMALLast year comparisons
mtd_occupancy/adr/revparDECIMALMonth-to-date running totals
otb_revenue / ly_revenue / budget_revenueDECIMAL(12,2)Revenue tracking vs LY vs budget
direct/ota/gds/other channel revenueDECIMAL(10,2)Revenue by booking channel
mpi / ari / rgiDECIMAL(5,2)Market Penetration / Rate / Revenue indices
market_occupancy/adr/revparDECIMALMarket averages for comparison
total_rooms / ooo / otb_rooms / avl_roomsSMALLINTRoom inventory breakdown
goppar / trevpar / alosDECIMALGOPPAR, TRevPAR, Avg Length of Stay
updated_by_rmSMALLINT1 = revenue manager manually overrode algo

weather_snapshots

Intraday pricing intelligence. Multiple snapshots per day (morning/afternoon) showing market movement.

Live data
ColumnTypeNotes
idBIGSERIALPrimary key
property_idBIGINT→ properties.id
snapshot_dateDATEDate of the snapshot
snapshot_timeTIMETime taken (e.g., 8:05 AM, 4:15 PM)
snapshot_labelVARCHAR(50)RC, LC-1, LC-2 (Revenue Check labels)
comp_avg_priceDECIMAL(10,2)Competitor set average at this moment
adr_ly / adr_tyDECIMAL(10,2)ADR Last Year vs This Year
occupancy_ly / occupancy_tyDECIMAL(5,2)Occupancy % LY vs TY
forecasted_occupancy_ly/tyDECIMAL(5,2)Forecast LY vs TY
revenue_ly / revenue_tyDECIMAL(12,2)Revenue LY vs TY
saleable_rooms / ooo / sold_roomsSMALLINTCurrent room availability
rate_to_pmr_for_the_dayDECIMAL(10,2)Recommended rate to property manager

properties_characteristics_history

Daily room inventory record — how many rooms available/sold/OOO each day

Daily records
ColumnTypeNotes
idBIGSERIALPrimary key
property_idBIGINT→ properties.id
record_dateDATEDate of this inventory snapshot
physical_capacityINTEGERTotal rooms in hotel
out_of_orderINTEGERRooms unavailable (maintenance, etc.)
on_bookINTEGERRooms currently with reservations
saleable_roomsINTEGERRooms available for sale

deleted_properties

Archive of removed hotels with full history snapshot preserved

1 row
ColumnTypeNotes
idBIGSERIALPrimary key
original_idBIGINTOriginal property ID before deletion
property_code / hotel_nameVARCHARPreserved from original
deleted_atTIMESTAMPWhen property was removed
characteristics_dataJSONBFull history of room data at deletion
update_records_dataJSONBFull pricing history at deletion
⚙️ PMS Integration & Scraping Tables CHOICE HOTELS · WYNDHAM · AUTOMATED DATA COLLECTION

hotel_pms_platforms

PMS login credentials and config for each connected system (Choice, Wyndham)

2 rows
ColumnTypeNotes
idBIGSERIALPrimary key
platform_nameVARCHAR(100)Choice Max, Wyndham
pms_typeVARCHAR(50)choice | wyndham
username / emailVARCHAR(255)Login credentials
passwordTEXTEncrypted password
statusVARCHAR(50)active | inactive
last_scraped_atTIMESTAMPLast successful data pull
configJSONBExtra config (e.g., last 4 digits of card)

property_hotel_pms_platform

Maps which hotel is managed in which PMS system

4 rows
ColumnTypeNotes
idBIGSERIALPrimary key
property_idBIGINT→ properties.id
hotel_pms_platform_idBIGINT→ hotel_pms_platforms.id

PA672, CA322, NA2 → Choice Max | 33517 → Wyndham

scraping_runs

Log of every automated data collection job — success, failure, how many records created

115 rows
ColumnTypeNotes
idBIGSERIALPrimary key
hotel_pms_platform_idBIGINT→ hotel_pms_platforms.id
started_at / completed_atTIMESTAMPJob timing
statusVARCHAR(50)running | completed | failed
days_scrapedINTEGERHow many dates were pulled
records_createdINTEGERHow many rows added to update_records
error_messageTEXTError details if status = failed
metadataJSONBExtra scraping context

pms_otp

One-time password (OTP) management for PMS 2-factor login. Choice & Wyndham require OTP.

2 rows
ColumnTypeNotes
idBIGSERIALPrimary key
pms_typeVARCHAR(50)choice | wyndham
otpVARCHAR(10)Current OTP code (nullable)
expires_atTIMESTAMPOTP expiry time
is_used / requested / is_wrongSMALLINTOTP status flags
retry_countSMALLINTFailed OTP attempts
pathVARCHAR(500)Path to Python scraper script
👤 Client Management Tables WHO HAS ACCESS TO WHICH HOTELS

clients

Hotel clients who log in to the RMS app to see their property dashboards

1 row
ColumnTypeNotes
idBIGSERIALPrimary key
name / emailVARCHAR(255)Client's name and login email
is_activeSMALLINT1 = active, 0 = deactivated
invitation_tokenVARCHAR(255)Token for first-time login invite
invitation_sent/accepted_atTIMESTAMPInvitation tracking
last_login_atTIMESTAMPMost recent login

client_property

Maps which clients can see which hotels. One client can have multiple hotels.

3 rows
ColumnTypeNotes
idBIGSERIALPrimary key
client_idBIGINT→ clients.id
property_idBIGINT→ properties.id

roles

User permission roles for the app (admin, revenue_manager, client, etc.)

0 rows (defined)
ColumnTypeNotes
idBIGSERIALPrimary key
name / display_nameVARCHAR(255)Role identifier and friendly name
descriptionTEXTWhat this role can do
is_activeSMALLINT1 = active role

users

Internal system users (admins, revenue managers at Build From Ideas)

2 rows
ColumnTypeNotes
idBIGSERIALPrimary key
name / emailVARCHAR(255)Staff member name and login
roleVARCHAR(255)user | admin
passwordVARCHAR(255)Hashed (bcrypt)
📈 CoStar STR Tables — Monthly STAR Reports 4 TABLES · ✅ PARSER BUILT · 7 HOTELS × LIVE + STATIC

str_properties

Hotel registry: STR ID, name, owner, owner_group, bfi_property_id link

7 rows
ColumnTypeNotes
str_idINTEGERCoStar STR property ID (Primary Key)
property_nameVARCHARHotel name from CoStar
owner_nameVARCHARHotel owner (extracted from Comp sheet)
owner_groupVARCHARPortfolio grouping for dashboard rollup
bfi_property_idVARCHAR→ Universal connector to all other tables
market_name / classVARCHARCoStar market classification

str_monthly_performance

18 trailing months × 7 hotels: Occ / ADR / RevPAR + MPI / ARI / RGI + ranks

~252 rows
ColumnTypeNotes
str_idINTEGER→ str_properties.str_id
year / monthINTEGERReport period
report_typeVARCHARlive | static
occ_my / occ_comp / occ_indexNUMERICOccupancy: My Property, Comp Set, MPI
adr_my / adr_comp / adr_indexNUMERICADR: My Property, Comp Set, ARI
revpar_my / revpar_comp / revpar_indexNUMERICRevPAR: My Property, Comp Set, RGI
occ_rank / adr_rank / revpar_rankINTEGERRank within competitive set

str_daily_performance

Daily Occ / ADR / RevPAR for report month — per-day detail

~392 rows
ColumnTypeNotes
str_idINTEGER→ str_properties.str_id
record_dateDATESpecific day in report month
occ_my / occ_comp / occ_indexNUMERICDaily occupancy metrics
adr_my / adr_comp / adr_indexNUMERICDaily ADR metrics
revpar_my / revpar_comp / revpar_indexNUMERICDaily RevPAR metrics

str_dow_performance

Day-of-week aggregations: Sun-Sat × 4 time periods × 7 hotels

~392 rows
ColumnTypeNotes
str_idINTEGER→ str_properties.str_id
day_of_weekVARCHARSunday through Saturday
time_periodVARCHARCurrent Month, 3-Month, Running 12, etc.
occ_my / occ_comp / occ_indexNUMERICDOW occupancy metrics
adr_my / adr_comp / adr_indexNUMERICDOW ADR metrics
revpar_my / revpar_comp / revpar_indexNUMERICDOW RevPAR metrics
🧠 Pricing Engine Tables — AI + Human-in-the-Loop NEW · DESIGNED 2026-04-01 · CREATED IN DB — Session 6

property_pricing_config

Per-property algorithm configuration: anchor percentile, quality rank, floors/ceilings, ensemble weights

1 per property
ColumnTypeNotes
bfi_property_idVARCHARPrimary Key — universal connector
quality_rankINTEGERRank within compset (e.g. 3 of 10)
compset_sizeINTEGERTotal competitors in set
default_anchor_pctlVARCHARDefault comparison percentile (e.g. p30)
min_anchor_pctlVARCHARFloor when competitors sell out
max_anchor_pctlVARCHARCeiling when competitors sell out
pricing_algoVARCHARAI_comp_anchor, rule_only, etc.
delivery_modeVARCHARapi_push | manual_input
ensemble_rule_weightNUMERICCurrent rule engine weight (0-1)
ensemble_nn_weightNUMERICCurrent FT-Transformer weight (0-1)

pricing_recommendations

Every pricing recommendation from the model — the audit trail. One row per property × date × run.

Grows 4x/day
ColumnTypeNotes
idBIGSERIALPrimary key
bfi_property_idVARCHAR→ property_pricing_config
stay_dateDATEDate this recommendation is for
run_timestampTIMESTAMPWhen this pricing run executed
rule_engine_priceNUMERICRule-based engine output
nn_priceNUMERICFT-Transformer output
ensemble_priceNUMERICFinal blended recommendation
adjustment_factorNUMERICMultiplier vs current rate
anchor_price_usedNUMERICCompset percentile anchor
anchor_pctl_usedVARCHARWhich percentile was selected
pct_compset_sold_outNUMERIC% of competitors sold out
top_features_jsonJSONBSHAP values — top 5 feature attributions
explanation_textTEXTAI-generated plain English explanation

pricing_overrides

Every rev manager override with reason. The human feedback loop — this is how the model learns.

Training signal
ColumnTypeNotes
idBIGSERIALPrimary key
recommendation_idBIGINT→ pricing_recommendations.id
bfi_property_idVARCHARProperty
stay_dateDATEDate affected
algo_recommended_priceNUMERICWhat the model said
manager_set_priceNUMERICWhat the manager chose
override_reasonVARCHARrate_too_high, rate_too_low, owner_directive, event, stale_comp, other
override_noteTEXTFree-text explanation
manager_idVARCHARWho overrode (aaryan, roshan, etc.)
override_timestampTIMESTAMPWhen the override happened

pricing_outcomes

Next-day reconciliation: actual RevPAR vs model recommendation vs manager override. The truth table.

Daily backfill
ColumnTypeNotes
idBIGSERIALPrimary key
bfi_property_idVARCHARProperty
stay_dateDATEThe date that passed
algo_recommended_priceNUMERICWhat algo said
final_published_priceNUMERICWhat actually went live
was_overriddenBOOLEANDid manager change it?
actual_revparNUMERICReal RevPAR outcome
actual_occNUMERICReal occupancy
actual_adrNUMERICReal ADR
algo_would_have_revparNUMERICEstimated RevPAR if algo price was used
override_improved_revparBOOLEANDid manager's override beat algo?
🔧 System / Infrastructure Tables (Laravel Framework) 7 TABLES · APP PLUMBING — NOT BUSINESS DATA

cache & cache_locks

Laravel application cache for fast page loads

Empty
ColumnTypeNotes
"key"VARCHAR(255)Cache key (quoted — reserved word in PG)
value / ownerTEXTCached data / lock owner
expirationINTEGERUnix timestamp when cache expires

sessions

Active user sessions (who is logged in right now)

3 rows
ColumnTypeNotes
idVARCHAR(255)Session token (Primary Key)
user_idBIGINTLogged-in user (nullable = anonymous)
ip_addressVARCHAR(45)Client IP
payloadTEXTBase64-encoded session data
last_activityINTEGERUnix timestamp

jobs, job_batches & failed_jobs

Laravel queue system — background task processing

Empty
ColumnTypeNotes
id / uuidBIGSERIAL/VARCHARJob identifier
queueVARCHAR(255)Which queue (default, emails, etc.)
payloadTEXTSerialized job data
attemptsSMALLINTHow many times it tried to run

migrations & password_reset_tokens

Schema version tracking & password reset tokens

41 / 0
ColumnTypeNotes
id / migrationSERIAL/VARCHAR41 database migrations tracked
batchINTEGERDeployment batch number (1-28)
email / tokenVARCHAR(255)Password reset token storage

🔗 App Table Relationships (Engineer's 21 Tables)

properties → update_records

update_records.property_id → properties.id
Every pricing record belongs to one hotel. 700 rows across 5 properties (IDs 2–6).

properties → weather_snapshots

weather_snapshots.property_id → properties.id
Intraday pricing snapshots (morning/afternoon revenue checks) per hotel.

properties → properties_characteristics_history

properties_characteristics_history.property_id → properties.id
Daily room inventory records per property.

hotel_pms_platforms → scraping_runs

scraping_runs.hotel_pms_platform_id → hotel_pms_platforms.id
Each scraping job is linked to the PMS system it pulled data from.

scraping_runs → update_records

update_records.scraping_run_id → scraping_runs.id
Pricing records created by a scraper are linked to the run that created them.

properties ↔ hotel_pms_platforms

Via property_hotel_pms_platform join table.
PA672/CA322/NA2 → Choice Max (id=3). Days Inn (33517) → Wyndham (id=4).

clients ↔ properties

Via client_property join table.
1 client currently has access to 3 hotels.

🌉 Cross-Dataset Connections — How ETL & App Tables Join

⚠️ Architectural Decision: What is the Universal Property Identifier?

properties.id (integers 2–6) is a meaningless serial number — it only exists inside the engineer's system and has no value across datasets.

bfi_property_id is the true universal identifier (e.g. US_76126_001). It is the same field used in your ETL / comp mapping data as property_id, and it uniquely identifies each hotel across both systems.

Currently bfi_property_id only lives in the properties table. All child tables in the engineer's system (update_records, weather_snapshots, etc.) only store the integer property_id. This means every cross-dataset join must route through properties as a lookup bridge.

-- Canonical join path: engineer data ↔ ETL data
update_records ur
  JOIN properties p ON ur.property_id = p.id
  JOIN comp_set_mapping_clean csm ON p.bfi_property_id = csm.property_id
  JOIN comp_prices_clean cp ON csm.property_id = cp.property_id AND ur.record_date = cp.price_date
-- bfi_property_id is the bridge between the two worlds

Future option (Phase 2, with engineer agreement): Add bfi_property_id directly as a column to update_records, weather_snapshots, and properties_characteristics_history. This would make every table directly joinable without needing the lookup, and make bfi_property_id the true primary connector across all 30 tables.

The two datasets — your ETL pipeline data (from Google Sheets) and the engineer's app data (from PMS scrapers) — are linked through the properties table using bfi_property_id as the universal bridge key.

Hotel properties.id
(used in update_records, etc.)
properties.bfi_property_id
= comp_set_mapping.property_id
properties.bdc_property_id
= comp_set_mapping.Agg_int_unique_id
PMS System
Comfort Inn PA672 2 us_19464_001 null — not on Booking.com Choice Max
Quality Inn CA322 3 US_76126_001 171693 Choice Max
City Center Inn NA 4 US_23601_001 306176 Choice Max
Days Inn Gilroy 33517 5 US_95020_002 259410 Wyndham
Quality Inn Mobile NA2 6 US_36619_001 179470 Choice Max

🔑 Primary ID Bridge

properties.bfi_property_id = comp_set_mapping_clean.property_id

This links each hotel's live operational data (occupancy, ADR, RevPAR from PMS) to its competitor set (which hotels to track) defined in your Google Sheet.

🔑 Booking.com ID Bridge

properties.bdc_property_id = comp_set_mapping_clean.Agg_int_unique_id

Links each managed hotel to its Booking.com listing ID. Used to match comp pricing data scraped from BDC against your own hotel's BDC page. Note: PA672 has no BDC ID.

📊 The Pricing Algorithm Join (Phase 2)

The full join that powers pricing recommendations:

update_records (hotel's own ADR, occupancy, RevPAR, OTB rooms)
+ comp_prices_clean (what competitors are charging)
+ properties_characteristics_history (room inventory)
+ weather_snapshots (intraday market signals)

All joined via properties.id / bfi_property_id / date.

⚠️ Data Overlap Note

Both datasets contain competitor pricing fields. update_records.competitor_set_avg_price is populated by the PMS scraper. comp_prices_clean is your manually-tracked comp data from Google Sheets.

These are complementary, not duplicates — the scraper catches live rate changes; your Sheets data holds a curated historical record. The algorithm should use both.

📤 Live Google Sheet Outputs — Session 7 LIVE · OAUTH2 WRITE · SHARED WITH LARRY@BUILDFROMIDEAS.COM

Room Category Mapping Matrix

One tab per market. Rows = room categories, Cols = competitor hotels. Exact matches only.

18 markets
FeatureDetail
Scriptrun_room_category_matrix.py
LayoutCategory | Our Hotel | Competitor 1 | Competitor 2 | ...
MatchingExact category only (score=100). No adjacent/fuzzy.
ClassificationHierarchy-based with word-boundary regex + modifier stripping
PurposeRM review before pricing engine goes live

Comp Mapping Output Sheet

3 tabs: Full Mapping, Unique Markets, Market Stats. Auto-refreshed after comp_mapping ETL.

Session 6
FeatureDetail
Scriptrun_comp_mapping_to_sheets.py
Sourcecomp_set_mapping_clean (from Compset_mapping tab)
Auto-triggerRuns after etl/comp_mapping.py completes