vertiq
Travel and hospitality

Travel and hospitality

Forecast repeat stays, estimate the value of the next booking, and measure campaign response against control groups.

Data mapping

Entity
guest or loyalty member
Group
property
Profile
generic; configure time windows with verticals.register
SOURCE COLUMNSVERTIQ DATA CONTRACTMember IDCheck-in DateFolio AmountPropertyChannelRoom TypeRate PlanEntity · identifierEvent · timestampEvent · valueEntity · groupEvent · attributes%d/%m/%Y
Vertiq LM can propose this mapping from the header and sample rows. Column names, value types and the date format are validated against the data before use.

Supported analyses

Reference implementation

from datetime import datetime, timedelta
from vertiq import plugins, rules
from vertiq.mapping import Mapping, apply, repair
from vertiq.tasks import predict_event, predict_value

# rows: the source export as a list of dicts, e.g. list(csv.DictReader(open("export.csv")))
mapping = Mapping.parse({"entity": "Member ID", "time": "Check-in Date", "type": None, "value": "Folio Amount", "group": "Property", "attrs": ["Channel", "Room Type", "Rate Plan"], "date_format": "%d/%m/%Y"})
m, notes = repair(mapping, rows)      # validates column names, value types and date format against the rows
ds, problems = apply(m, rows)         # rows that fail to load are reported, not imputed

@plugins.feature("hospitality")
def direct_share(entity, before, cut):   # receives only events recorded before the cutoff
    recent = [e for e in before if e.at >= cut - timedelta(days=365)]
    direct = sum(1 for e in recent if e.attrs.get("Channel") == "direct")
    return {"direct_share_365": direct / len(recent) if recent else 0.0}

task = predict_event(ds, target="", window_days=90, pack="hospitality")
print(task.status, task.report())     # "trained" only if the model outperformed the baseline on held-out entities
if task.status == "trained":
    print(task.score("M-104233"))  # probability with contributing factors

size = predict_value(ds, target="", window_days=180, pack="hospitality")
print(size.status, size.report())

flags = rules.run(ds, [
    rules.repeat(1),   # two stays recorded for the same member on one day
])
print(flags["summary"])

This code is executed by the test suite (tests/test_guides.py) against generated records with these column names.

Recommended plugin

direct_share: Guests who book directly behave differently from guests who book through intermediaries.

Validation rules

  • rules.repeat(1): two stays recorded for the same member on one day

Related modules

  • uplift: expected response to an offer, estimated from a randomised hold-out group
  • value.train_bill: the effect of an offer on the value of the stay it generates

Implementation notes

  • Withhold a randomised control group from every campaign. Uplift cannot be estimated without one.
  • Train only on members with documented consent.

Validation

  • Customer retention, intervention uplift and offer-effect benchmarks, 5 runs each
  • Hospitality tables in the Vertiq LM mapping evaluation
  • Every deployment: the evaluation gate measures each model on the organisation's own held-out records before promotion.
← Retail and consumer goodsBanking and financial services →