RJuro commited on
Commit
f1baba0
·
verified ·
1 Parent(s): 0d26629

Upload 3 files

Browse files
Files changed (3) hide show
  1. Dockerfile +20 -0
  2. requirements.txt +4 -0
  3. src/streamlit_app.py +169 -0
Dockerfile ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.13.5-slim
2
+
3
+ WORKDIR /app
4
+
5
+ RUN apt-get update && apt-get install -y \
6
+ build-essential \
7
+ curl \
8
+ git \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ COPY requirements.txt ./
12
+ COPY src/ ./src/
13
+
14
+ RUN pip3 install -r requirements.txt
15
+
16
+ EXPOSE 8501
17
+
18
+ HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
19
+
20
+ ENTRYPOINT ["streamlit", "run", "src/streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"]
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ streamlit
2
+ requests
3
+ pandas
4
+ numpy
src/streamlit_app.py ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import datetime as dt
3
+ import numpy as np
4
+ import pandas as pd
5
+ import requests
6
+ import streamlit as st
7
+
8
+ # --- CONFIG ---
9
+ API_URL = os.getenv("API_URL", "https://rjuro-hotel-cancel-api.hf.space/predict_batch")
10
+
11
+ NUMERIC_FEATURES = [
12
+ 'lead_time','arrival_date_week_number','arrival_date_day_of_month',
13
+ 'stays_in_weekend_nights','stays_in_week_nights','adults','children',
14
+ 'babies','is_repeated_guest','previous_cancellations',
15
+ 'previous_bookings_not_canceled','booking_changes','agent',
16
+ 'days_in_waiting_list','adr','required_car_parking_spaces',
17
+ 'total_of_special_requests','total_guests','total_nights','is_summer',
18
+ 'previous_cancellation_rate'
19
+ ]
20
+ CATEGORICAL_FEATURES = [
21
+ 'hotel','meal','market_segment','distribution_channel',
22
+ 'reserved_room_type','deposit_type','customer_type'
23
+ ]
24
+ ALL_FEATURES = NUMERIC_FEATURES + CATEGORICAL_FEATURES
25
+
26
+ st.set_page_config(page_title="Weekly Cancellation Predictions", layout="wide")
27
+
28
+ # --- SIMPLE SYNTH GENERATOR ---
29
+ def synth_week(n_per_day=300, seed=42):
30
+ rng = np.random.default_rng(seed)
31
+ today = dt.date.today()
32
+ all_rows = []
33
+ for i in range(1, 8):
34
+ arr = today + dt.timedelta(days=i)
35
+ week = int(arr.isocalendar().week)
36
+ dom = arr.day
37
+ is_summer = int(arr.month in [6,7,8])
38
+
39
+ n = n_per_day
40
+ lead_time = np.clip(rng.gamma(2.0, 60.0, n).astype(int), 1, 365)
41
+ wkd = rng.poisson(1.0, n)
42
+ wk = rng.poisson(3.0, n)
43
+ adults = np.maximum(1, rng.poisson(1.5, n)+1)
44
+ children = rng.binomial(2, 0.15, n)
45
+ babies = rng.binomial(1, 0.05, n)
46
+ is_repeated_guest = rng.binomial(1, 0.12, n)
47
+ prev_canc = rng.binomial(2, 0.05, n)
48
+ prev_notc = rng.binomial(3, 0.15, n)
49
+ booking_changes = rng.poisson(0.2, n)
50
+ agent = rng.integers(0, 5, n) # 0≈direct
51
+ wait_list = rng.binomial(5, 0.05, n)
52
+ adr = np.clip(rng.normal(120, 35, n), 30, 450)
53
+ parking = rng.binomial(1, 0.12, n)
54
+ special_req = rng.poisson(0.6, n)
55
+ total_nights = (wkd + wk).astype(int)
56
+ total_guests = (adults + children + babies).astype(int)
57
+ prev_rate = prev_canc / np.maximum(1e-6, (prev_canc + prev_notc + 1e-6))
58
+
59
+ def choice(vals, probs):
60
+ p = np.array(probs, dtype=float); p = p / p.sum()
61
+ return rng.choice(vals, p=p, size=n)
62
+
63
+ hotel = choice(['City Hotel','Resort Hotel'], [0.7, 0.3])
64
+ meal = choice(['BB','HB','FB','SC'], [0.75,0.15,0.03,0.07])
65
+ market = choice(['Online TA','Direct','Corporate','Offline TA/TO'], [0.45,0.30,0.15,0.10])
66
+ channel = choice(['TA/TO','Direct','Corporate','GDS'], [0.5,0.3,0.15,0.05])
67
+ roomtype = choice(list("ABCDEFG"), [0.35,0.25,0.15,0.1,0.08,0.05,0.02])
68
+ deposit = choice(['No Deposit','Non Refund','Refundable'], [0.75,0.15,0.10])
69
+ cust = choice(['Transient','Contract','Group','Transient-Party'], [0.7,0.15,0.08,0.07])
70
+
71
+ df = pd.DataFrame({
72
+ 'lead_time': lead_time,
73
+ 'arrival_date_week_number': week,
74
+ 'arrival_date_day_of_month': dom,
75
+ 'stays_in_weekend_nights': wkd,
76
+ 'stays_in_week_nights': wk,
77
+ 'adults': adults,
78
+ 'children': children,
79
+ 'babies': babies,
80
+ 'is_repeated_guest': is_repeated_guest,
81
+ 'previous_cancellations': prev_canc,
82
+ 'previous_bookings_not_canceled': prev_notc,
83
+ 'booking_changes': booking_changes,
84
+ 'agent': agent,
85
+ 'days_in_waiting_list': wait_list,
86
+ 'adr': adr,
87
+ 'required_car_parking_spaces': parking,
88
+ 'total_of_special_requests': special_req,
89
+ 'total_guests': total_guests,
90
+ 'total_nights': total_nights,
91
+ 'is_summer': is_summer,
92
+ 'previous_cancellation_rate': prev_rate,
93
+ 'hotel': hotel,
94
+ 'meal': meal,
95
+ 'market_segment': market,
96
+ 'distribution_channel': channel,
97
+ 'reserved_room_type': roomtype,
98
+ 'deposit_type': deposit,
99
+ 'customer_type': cust
100
+ })
101
+ df.insert(0, "arrival_date", pd.Timestamp(arr))
102
+ all_rows.append(df)
103
+ return pd.concat(all_rows, ignore_index=True)
104
+
105
+ def call_api(df: pd.DataFrame) -> np.ndarray:
106
+ payload = {"data": df[ALL_FEATURES].to_dict(orient="records")}
107
+ r = requests.post(API_URL, json=payload, timeout=60)
108
+ r.raise_for_status()
109
+ return np.array(r.json()["probabilities"])
110
+
111
+ # --- UI ---
112
+ st.title("Weekly Booking Predictions")
113
+ st.caption("API: " + API_URL)
114
+
115
+ with st.sidebar:
116
+ st.header("Simulation")
117
+ n_per_day = st.slider("Synthetic bookings per day", 50, 2000, 400, 50)
118
+ t_low = st.slider("Reminder threshold", 0.05, 0.60, 0.30, 0.01)
119
+ t_high = st.slider("Perk (prepay upgrade) threshold", 0.30, 0.95, 0.65, 0.01)
120
+ seed = st.number_input("Random seed", 0, 99999, 42, 1)
121
+ st.caption("Rules: p ≥ t_high → Perk; t_low ≤ p < t_high → Reminder; else → None.")
122
+
123
+ cols = st.columns(2)
124
+ with cols[0]:
125
+ if st.button("Generate & Predict", use_container_width=True):
126
+ df = synth_week(n_per_day=n_per_day, seed=int(seed))
127
+ probs = call_api(df)
128
+ df['pred_cancel_prob'] = probs
129
+ df['action'] = np.where(
130
+ probs >= t_high, "Perk-Upgrade (Prepay)",
131
+ np.where(probs >= t_low, "Reminder", "None")
132
+ )
133
+
134
+ daily = (
135
+ df.groupby(df['arrival_date'].dt.date)
136
+ .agg(n_bookings=('arrival_date','count'),
137
+ mean_risk=('pred_cancel_prob','mean'),
138
+ p75=('pred_cancel_prob', lambda x: np.quantile(x, 0.75)),
139
+ n_perk=('action', lambda s: (s=="Perk-Upgrade (Prepay)").sum()),
140
+ n_reminder=('action', lambda s: (s=="Reminder").sum()),
141
+ n_none=('action', lambda s: (s=="None").sum()))
142
+ .reset_index()
143
+ .rename(columns={'arrival_date':'date'})
144
+ )
145
+
146
+ st.subheader("Daily Summary (Next 7 Days)")
147
+ st.dataframe(daily, use_container_width=True, hide_index=True)
148
+
149
+ st.subheader("Preview: First 200 Bookings with Suggested Actions")
150
+ st.dataframe(
151
+ df[['arrival_date','hotel','market_segment','deposit_type','lead_time',
152
+ 'total_nights','total_guests','pred_cancel_prob','action']].head(200),
153
+ use_container_width=True, hide_index=True
154
+ )
155
+
156
+ st.download_button(
157
+ "Download Full Weekly Predictions (CSV)",
158
+ df.to_csv(index=False).encode("utf-8"),
159
+ file_name="weekly_predictions_with_actions.csv",
160
+ mime="text/csv"
161
+ )
162
+
163
+ with cols[1]:
164
+ st.subheader("How it works")
165
+ st.markdown(
166
+ "- Synthetic bookings for the next 7 days\n"
167
+ "- Calls the public FastAPI to get cancellation probabilities\n"
168
+ "- Simple rules pick suggested actions"
169
+ )