MBG0903 commited on
Commit
bceaec5
Β·
verified Β·
1 Parent(s): fe8fe3d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +165 -86
app.py CHANGED
@@ -1,8 +1,10 @@
1
- import gradio as gr
 
2
  import secrets
3
  from datetime import datetime
4
- from io import BytesIO
5
 
 
6
  import qrcode
7
  from PIL import Image
8
 
@@ -43,9 +45,14 @@ a, .procelevate-accent {{
43
  }}
44
  """
45
 
46
- # ----------------------------
47
- # In-memory stores (prototype)
48
- # ----------------------------
 
 
 
 
 
49
  BOOKINGS = [
50
  {
51
  "booking_ref": "RZQ12345",
@@ -69,14 +76,34 @@ BOOKINGS = [
69
  },
70
  ]
71
 
72
- # code -> record
73
- PRECHECKIN_RECORDS = {}
74
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
- # ----------------------------
77
- # Helpers
78
- # ----------------------------
79
- def _find_booking(booking_ref: str, last_name: str, checkin_date: str):
80
  booking_ref = (booking_ref or "").strip().upper()
81
  last_name = (last_name or "").strip().lower()
82
  checkin_date = (checkin_date or "").strip()
@@ -90,18 +117,14 @@ def _find_booking(booking_ref: str, last_name: str, checkin_date: str):
90
  return b
91
  return None
92
 
93
-
94
- def _generate_code(prefix="RZQ"):
95
- # Human-friendly: no confusing chars (I, O, 0, 1)
96
  alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
97
  token = "".join(secrets.choice(alphabet) for _ in range(6))
98
  return f"{prefix}-{token}"
99
 
100
-
101
- def _now():
102
  return datetime.now().strftime("%Y-%m-%d %H:%M")
103
 
104
-
105
  def _qr_image_from_text(text: str) -> Image.Image:
106
  qr = qrcode.QRCode(
107
  version=1,
@@ -111,13 +134,15 @@ def _qr_image_from_text(text: str) -> Image.Image:
111
  )
112
  qr.add_data(text)
113
  qr.make(fit=True)
114
- img = qr.make_image(fill_color="black", back_color="white").convert("RGB")
115
- return img
116
 
 
 
 
117
 
118
- # ----------------------------
119
- # Core actions
120
- # ----------------------------
121
  def verify_booking(booking_ref, last_name, checkin_date):
122
  b = _find_booking(booking_ref, last_name, checkin_date)
123
  if not b:
@@ -131,8 +156,8 @@ def verify_booking(booking_ref, last_name, checkin_date):
131
  )
132
  return (
133
  gr.update(visible=True, value=msg),
134
- gr.update(visible=False), # hide Step 2
135
- gr.update(value=None), # clear state
136
  )
137
 
138
  summary = (
@@ -146,20 +171,26 @@ def verify_booking(booking_ref, last_name, checkin_date):
146
  )
147
  return (
148
  gr.update(visible=True, value=summary),
149
- gr.update(visible=True), # show Step 2
150
- gr.update(value=b), # store booking dict
151
  )
152
 
153
-
154
- def complete_checkin(mode, booking_state, arrival_time, bed_pref, special_request, id_file):
155
- """
156
- mode: "Pre-Arrival" or "On-Arrival"
157
- booking_state: dict from verify_booking
158
- id_file: uploaded file (optional). OCR not included in v1.
159
- """
160
  if not booking_state:
161
  return "❌ Please verify your booking first.", None, "", gr.update(visible=False)
162
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  code = _generate_code("RZQ")
164
  qr_img = _qr_image_from_text(code)
165
 
@@ -172,17 +203,25 @@ def complete_checkin(mode, booking_state, arrival_time, bed_pref, special_reques
172
  "booking_ref": booking_state["booking_ref"],
173
  "checkin_date": booking_state["checkin_date"],
174
  "room_type": booking_state["room_type"],
175
- "arrival_time": arrival_time or "",
176
- "bed_pref": bed_pref or "",
177
- "special_request": special_request or "",
178
- "id_provided": bool(id_file),
179
- "id_filename": getattr(id_file, "name", "") if id_file else "",
 
180
  }
 
181
  PRECHECKIN_RECORDS[code] = record
 
 
 
 
 
182
 
183
  guest_msg = (
184
  f"βœ… {mode} Check-In Successful\n\n"
185
- f"Your Express Check-In Code:\n{code}\n\n"
 
186
  "Next step:\n"
187
  "β€’ Show this code (or QR) at the front desk for quick room key collection.\n"
188
  "β€’ Estimated counter time: under 1 minute.\n\n"
@@ -190,7 +229,7 @@ def complete_checkin(mode, booking_state, arrival_time, bed_pref, special_reques
190
  )
191
 
192
  staff_view = (
193
- f"βœ… FRONT DESK SUMMARY\n\n"
194
  f"Code: {record['code']}\n"
195
  f"Status: {record['status']}\n"
196
  f"Guest: {record['guest_name']}\n"
@@ -200,12 +239,36 @@ def complete_checkin(mode, booking_state, arrival_time, bed_pref, special_reques
200
  f"Arrival Time: {record['arrival_time']}\n"
201
  f"Preference: {record['bed_pref']}\n"
202
  f"Special Request: {record['special_request']}\n"
203
- f"ID Provided: {'🟑 Yes' if record['id_provided'] else '🟑 No'}\n"
204
  f"Submitted At: {record['created_at']}\n"
205
  )
206
 
207
  return guest_msg, qr_img, staff_view, gr.update(visible=True)
208
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
 
210
  def staff_lookup(code):
211
  code = (code or "").strip().upper()
@@ -215,44 +278,32 @@ def staff_lookup(code):
215
 
216
  view = (
217
  "βœ… Record found\n\n"
218
- f"Code: {rec['code']}\n"
219
- f"Status: {rec['status']}\n"
220
- f"Guest: {rec['guest_name']}\n"
221
- f"Booking Ref: {rec['booking_ref']}\n"
222
- f"Check-in Date: {rec['checkin_date']}\n"
223
- f"Room Type: {rec['room_type']}\n"
224
- f"Arrival Time: {rec['arrival_time']}\n"
225
- f"Preference: {rec['bed_pref']}\n"
226
- f"Special Request: {rec['special_request']}\n"
227
- f"ID Provided: {'Yes' if rec['id_provided'] else 'No'}\n"
228
- f"Submitted At: {rec['created_at']}\n"
229
  )
230
  return view
231
 
 
 
 
 
 
 
 
232
 
233
- def reset_form():
234
- return (
235
- "", "", "", # booking_ref, last_name, checkin_date
236
- gr.update(value="", visible=False), # verify_result
237
- gr.update(visible=False), # details_box
238
- None, # booking_state
239
- "Pre-Arrival", # mode
240
- "", "No preference", "", # arrival_time, bed_pref, special_request
241
- None, # id_file
242
- "", # guest_msg
243
- None, # qr_display
244
- "", # staff_preview
245
- gr.update(visible=False), # staff_preview_container
246
- )
247
-
248
-
249
- # ----------------------------
250
  # UI
251
- # ----------------------------
252
- with gr.Blocks(
253
- title="Smart Self Check-In (Prototype)",
254
- css=CUSTOM_CSS
255
- ) as demo:
256
  gr.Markdown(
257
  """
258
  # 🏨 Smart Self Check-In (Prototype)
@@ -293,14 +344,22 @@ Step 1: Verify Booking β†’ Step 2: Confirm Details β†’ Step 3: Get Express C
293
  placeholder="e.g., baby cot, late check-out request, allergy notes",
294
  lines=3,
295
  )
296
- id_file = gr.File(label="Upload ID (optional for v1)", file_types=["image", "pdf"])
 
 
 
 
 
 
 
 
 
297
  submit_btn = gr.Button("Submit Check-In", variant="primary")
298
 
299
  gr.Markdown("### Step 3: Receive your Express Check-In Code")
300
- guest_msg = gr.Textbox(label="Guest Confirmation", lines=7)
301
  qr_display = gr.Image(label="Express Check-In QR Code", type="pil", height=220)
302
 
303
- # Hide staff preview until submitted (looks more professional)
304
  staff_preview_container = gr.Column(visible=False)
305
  with staff_preview_container:
306
  staff_preview = gr.Textbox(label="(Demo) Front Desk Preview", lines=13)
@@ -315,7 +374,7 @@ Step 1: Verify Booking β†’ Step 2: Confirm Details β†’ Step 3: Get Express C
315
 
316
  submit_btn.click(
317
  complete_checkin,
318
- inputs=[mode, booking_state, arrival_time, bed_pref, special_request, id_file],
319
  outputs=[guest_msg, qr_display, staff_preview, staff_preview_container],
320
  )
321
 
@@ -325,8 +384,10 @@ Step 1: Verify Booking β†’ Step 2: Confirm Details β†’ Step 3: Get Express C
325
  outputs=[
326
  booking_ref, last_name, checkin_date,
327
  verify_result, details_box, booking_state,
328
- mode, arrival_time, bed_pref, special_request, id_file,
329
- guest_msg, qr_display, staff_preview, staff_preview_container
 
 
330
  ],
331
  )
332
 
@@ -339,13 +400,31 @@ Step 1: Verify Booking β†’ Step 2: Confirm Details β†’ Step 3: Get Express C
339
  )
340
 
341
  with gr.Tab("Front Desk Validation"):
342
- gr.Markdown(
343
- "Enter the guest's **Express Check-In Code** to retrieve their pre-check-in record."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
344
  )
345
- staff_code = gr.Textbox(label="Express Check-In Code", placeholder="e.g., RZQ-ABC123")
346
- staff_lookup_btn = gr.Button("Lookup", variant="primary")
347
- staff_view = gr.Textbox(label="Front Desk Result", lines=14)
348
 
349
  staff_lookup_btn.click(staff_lookup, inputs=[staff_code], outputs=[staff_view])
350
 
 
 
351
  demo.launch()
 
1
+ import os
2
+ import json
3
  import secrets
4
  from datetime import datetime
5
+ from typing import Dict, Any, Optional
6
 
7
+ import gradio as gr
8
  import qrcode
9
  from PIL import Image
10
 
 
45
  }}
46
  """
47
 
48
+ # ============================
49
+ # Settings (Demo / Prototype)
50
+ # ============================
51
+ FRONT_DESK_PIN = os.environ.get("FRONT_DESK_PIN", "2580")
52
+
53
+ DATA_DIR = "data"
54
+ DATA_FILE = os.path.join(DATA_DIR, "checkins.json")
55
+
56
  BOOKINGS = [
57
  {
58
  "booking_ref": "RZQ12345",
 
76
  },
77
  ]
78
 
79
+ # ============================
80
+ # Persistence helpers
81
+ # ============================
82
+ def _ensure_data_dir() -> None:
83
+ os.makedirs(DATA_DIR, exist_ok=True)
84
+
85
+ def _load_records() -> Dict[str, Dict[str, Any]]:
86
+ _ensure_data_dir()
87
+ if not os.path.exists(DATA_FILE):
88
+ return {}
89
+ try:
90
+ with open(DATA_FILE, "r", encoding="utf-8") as f:
91
+ data = json.load(f)
92
+ return data if isinstance(data, dict) else {}
93
+ except Exception:
94
+ return {}
95
+
96
+ def _save_records(records: Dict[str, Dict[str, Any]]) -> None:
97
+ _ensure_data_dir()
98
+ with open(DATA_FILE, "w", encoding="utf-8") as f:
99
+ json.dump(records, f, ensure_ascii=False, indent=2)
100
+
101
+ PRECHECKIN_RECORDS: Dict[str, Dict[str, Any]] = _load_records()
102
 
103
+ # ============================
104
+ # Core helpers
105
+ # ============================
106
+ def _find_booking(booking_ref: str, last_name: str, checkin_date: str) -> Optional[Dict[str, Any]]:
107
  booking_ref = (booking_ref or "").strip().upper()
108
  last_name = (last_name or "").strip().lower()
109
  checkin_date = (checkin_date or "").strip()
 
117
  return b
118
  return None
119
 
120
+ def _generate_code(prefix="RZQ") -> str:
 
 
121
  alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
122
  token = "".join(secrets.choice(alphabet) for _ in range(6))
123
  return f"{prefix}-{token}"
124
 
125
+ def _now() -> str:
 
126
  return datetime.now().strftime("%Y-%m-%d %H:%M")
127
 
 
128
  def _qr_image_from_text(text: str) -> Image.Image:
129
  qr = qrcode.QRCode(
130
  version=1,
 
134
  )
135
  qr.add_data(text)
136
  qr.make(fit=True)
137
+ return qr.make_image(fill_color="black", back_color="white").convert("RGB")
 
138
 
139
+ def _file_name(file_obj) -> str:
140
+ # Gradio File may be a TemporaryFile or object with .name
141
+ return getattr(file_obj, "name", "") if file_obj else ""
142
 
143
+ # ============================
144
+ # Guest actions
145
+ # ============================
146
  def verify_booking(booking_ref, last_name, checkin_date):
147
  b = _find_booking(booking_ref, last_name, checkin_date)
148
  if not b:
 
156
  )
157
  return (
158
  gr.update(visible=True, value=msg),
159
+ gr.update(visible=False),
160
+ gr.update(value=None),
161
  )
162
 
163
  summary = (
 
171
  )
172
  return (
173
  gr.update(visible=True, value=summary),
174
+ gr.update(visible=True),
175
+ gr.update(value=b),
176
  )
177
 
178
+ def complete_checkin(mode, booking_state, arrival_time, bed_pref, special_request, id_type, id_file):
 
 
 
 
 
 
179
  if not booking_state:
180
  return "❌ Please verify your booking first.", None, "", gr.update(visible=False)
181
 
182
+ # --- ID validation rules (prototype) ---
183
+ id_type = (id_type or "Not provided").strip()
184
+ has_file = bool(id_file)
185
+
186
+ # If user selected an ID type, require upload
187
+ if id_type != "Not provided" and not has_file:
188
+ return "❌ Please upload the selected ID proof file to continue.", None, "", gr.update(visible=False)
189
+
190
+ # If user uploaded a file but did not select type, require type
191
+ if has_file and id_type == "Not provided":
192
+ return "❌ Please select the ID proof type (e.g., Passport) to continue.", None, "", gr.update(visible=False)
193
+
194
  code = _generate_code("RZQ")
195
  qr_img = _qr_image_from_text(code)
196
 
 
203
  "booking_ref": booking_state["booking_ref"],
204
  "checkin_date": booking_state["checkin_date"],
205
  "room_type": booking_state["room_type"],
206
+ "arrival_time": (arrival_time or "").strip(),
207
+ "bed_pref": (bed_pref or "").strip(),
208
+ "special_request": (special_request or "").strip(),
209
+ "id_type": id_type if id_type != "Not provided" else "",
210
+ "id_provided": has_file,
211
+ "id_filename": _file_name(id_file) if has_file else "",
212
  }
213
+
214
  PRECHECKIN_RECORDS[code] = record
215
+ _save_records(PRECHECKIN_RECORDS)
216
+
217
+ id_line = ""
218
+ if record["id_provided"]:
219
+ id_line = f"\nID Proof: {record['id_type']} uploaded"
220
 
221
  guest_msg = (
222
  f"βœ… {mode} Check-In Successful\n\n"
223
+ f"Your Express Check-In Code:\n{code}\n"
224
+ f"{id_line}\n\n"
225
  "Next step:\n"
226
  "β€’ Show this code (or QR) at the front desk for quick room key collection.\n"
227
  "β€’ Estimated counter time: under 1 minute.\n\n"
 
229
  )
230
 
231
  staff_view = (
232
+ "βœ… FRONT DESK SUMMARY\n\n"
233
  f"Code: {record['code']}\n"
234
  f"Status: {record['status']}\n"
235
  f"Guest: {record['guest_name']}\n"
 
239
  f"Arrival Time: {record['arrival_time']}\n"
240
  f"Preference: {record['bed_pref']}\n"
241
  f"Special Request: {record['special_request']}\n"
242
+ f"ID Proof: {record['id_type'] if record['id_provided'] else 'Not provided'}\n"
243
  f"Submitted At: {record['created_at']}\n"
244
  )
245
 
246
  return guest_msg, qr_img, staff_view, gr.update(visible=True)
247
 
248
+ def reset_form():
249
+ return (
250
+ "", "", "",
251
+ gr.update(value="", visible=False),
252
+ gr.update(visible=False),
253
+ None,
254
+ "Pre-Arrival",
255
+ "", "No preference", "",
256
+ "Not provided",
257
+ None,
258
+ "",
259
+ None,
260
+ "",
261
+ gr.update(visible=False),
262
+ )
263
+
264
+ # ============================
265
+ # Front desk actions (PIN gated)
266
+ # ============================
267
+ def staff_unlock(entered_pin: str):
268
+ entered_pin = (entered_pin or "").strip()
269
+ if entered_pin == FRONT_DESK_PIN:
270
+ return gr.update(visible=False), gr.update(visible=True), "βœ… Access granted."
271
+ return gr.update(visible=True), gr.update(visible=False), "❌ Incorrect PIN."
272
 
273
  def staff_lookup(code):
274
  code = (code or "").strip().upper()
 
278
 
279
  view = (
280
  "βœ… Record found\n\n"
281
+ f"Code: {rec.get('code','')}\n"
282
+ f"Status: {rec.get('status','')}\n"
283
+ f"Guest: {rec.get('guest_name','')}\n"
284
+ f"Booking Ref: {rec.get('booking_ref','')}\n"
285
+ f"Check-in Date: {rec.get('checkin_date','')}\n"
286
+ f"Room Type: {rec.get('room_type','')}\n"
287
+ f"Arrival Time: {rec.get('arrival_time','')}\n"
288
+ f"Preference: {rec.get('bed_pref','')}\n"
289
+ f"Special Request: {rec.get('special_request','')}\n"
290
+ f"ID Proof: {rec.get('id_type','') if rec.get('id_provided') else 'Not provided'}\n"
291
+ f"Submitted At: {rec.get('created_at','')}\n"
292
  )
293
  return view
294
 
295
+ def staff_clear_all(entered_pin: str):
296
+ entered_pin = (entered_pin or "").strip()
297
+ if entered_pin != FRONT_DESK_PIN:
298
+ return "❌ Incorrect PIN. Cannot clear records."
299
+ PRECHECKIN_RECORDS.clear()
300
+ _save_records(PRECHECKIN_RECORDS)
301
+ return "βœ… All stored check-in records cleared."
302
 
303
+ # ============================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
  # UI
305
+ # ============================
306
+ with gr.Blocks(title="Smart Self Check-In (Prototype)", css=CUSTOM_CSS) as demo:
 
 
 
307
  gr.Markdown(
308
  """
309
  # 🏨 Smart Self Check-In (Prototype)
 
344
  placeholder="e.g., baby cot, late check-out request, allergy notes",
345
  lines=3,
346
  )
347
+
348
+ gr.Markdown("#### ID Proof (optional for demo, recommended for realistic flow)")
349
+ with gr.Row():
350
+ id_type = gr.Dropdown(
351
+ ["Not provided", "Passport", "National ID", "Driving License", "Other"],
352
+ value="Not provided",
353
+ label="ID Proof Type",
354
+ )
355
+ id_file = gr.File(label="Upload ID Proof (image or PDF)", file_types=["image", "pdf"])
356
+
357
  submit_btn = gr.Button("Submit Check-In", variant="primary")
358
 
359
  gr.Markdown("### Step 3: Receive your Express Check-In Code")
360
+ guest_msg = gr.Textbox(label="Guest Confirmation", lines=8)
361
  qr_display = gr.Image(label="Express Check-In QR Code", type="pil", height=220)
362
 
 
363
  staff_preview_container = gr.Column(visible=False)
364
  with staff_preview_container:
365
  staff_preview = gr.Textbox(label="(Demo) Front Desk Preview", lines=13)
 
374
 
375
  submit_btn.click(
376
  complete_checkin,
377
+ inputs=[mode, booking_state, arrival_time, bed_pref, special_request, id_type, id_file],
378
  outputs=[guest_msg, qr_display, staff_preview, staff_preview_container],
379
  )
380
 
 
384
  outputs=[
385
  booking_ref, last_name, checkin_date,
386
  verify_result, details_box, booking_state,
387
+ mode, arrival_time, bed_pref, special_request,
388
+ id_type, id_file,
389
+ guest_msg, qr_display,
390
+ staff_preview, staff_preview_container
391
  ],
392
  )
393
 
 
400
  )
401
 
402
  with gr.Tab("Front Desk Validation"):
403
+ gr.Markdown("### Staff access (PIN protected)")
404
+
405
+ pin_box = gr.Textbox(label="Enter Front Desk PIN", placeholder="PIN", type="password")
406
+ unlock_btn = gr.Button("Unlock Staff Tools", variant="primary")
407
+ unlock_status = gr.Markdown("")
408
+
409
+ staff_tools = gr.Column(visible=False)
410
+ with staff_tools:
411
+ gr.Markdown("Enter the guest's **Express Check-In Code** to retrieve their record.")
412
+ staff_code = gr.Textbox(label="Express Check-In Code", placeholder="e.g., RZQ-ABC123")
413
+ staff_lookup_btn = gr.Button("Lookup", variant="primary")
414
+ staff_view = gr.Textbox(label="Front Desk Result", lines=14)
415
+
416
+ gr.Markdown("β€”")
417
+ clear_btn = gr.Button("Clear All Demo Records (PIN required)")
418
+ clear_result = gr.Markdown("")
419
+
420
+ unlock_btn.click(
421
+ staff_unlock,
422
+ inputs=[pin_box],
423
+ outputs=[pin_box, staff_tools, unlock_status],
424
  )
 
 
 
425
 
426
  staff_lookup_btn.click(staff_lookup, inputs=[staff_code], outputs=[staff_view])
427
 
428
+ clear_btn.click(staff_clear_all, inputs=[pin_box], outputs=[clear_result])
429
+
430
  demo.launch()