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
Supported analyses
- Will this member book another stay within 90 days?
predict_event(target="", window_days=90)
No event-type column is mapped, so every stay is an event and target "" refers to any stay. - What is the expected folio value of the next stay?
predict_value(target="", window_days=180)
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 groupvalue.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.