Spaces:
Runtime error
Runtime error
Upload 4 files
Browse files- .gitattributes +1 -0
- app.py +290 -0
- requirements.txt +11 -0
- wedding_chunks.csv +4 -0
- wedding_docs/Cherry&Samuel Wedding plan version 1.1.docx +3 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
wedding_docs/Cherry&Samuel[[:space:]]Wedding[[:space:]]plan[[:space:]]version[[:space:]]1.1.docx filter=lfs diff=lfs merge=lfs -text
|
app.py
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os, io, glob, time, hashlib
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import numpy as np
|
| 4 |
+
from typing import List, Tuple
|
| 5 |
+
from dataclasses import dataclass
|
| 6 |
+
from dotenv import load_dotenv
|
| 7 |
+
|
| 8 |
+
# Telegram
|
| 9 |
+
from telegram import Update
|
| 10 |
+
from telegram.constants import ParseMode
|
| 11 |
+
from telegram.ext import Application, CommandHandler, MessageHandler, ContextTypes, filters
|
| 12 |
+
|
| 13 |
+
# OpenAI
|
| 14 |
+
from openai import OpenAI
|
| 15 |
+
|
| 16 |
+
# Files / parsing
|
| 17 |
+
from docx import Document as DocxDocument
|
| 18 |
+
from pypdf import PdfReader
|
| 19 |
+
|
| 20 |
+
# Vector store
|
| 21 |
+
import faiss
|
| 22 |
+
|
| 23 |
+
load_dotenv()
|
| 24 |
+
|
| 25 |
+
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
| 26 |
+
TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
|
| 27 |
+
OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4o-mini")
|
| 28 |
+
EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL", "text-embedding-3-small")
|
| 29 |
+
STRICT_DOC_MODE = (os.getenv("STRICT_DOC_MODE", "true").lower() == "true")
|
| 30 |
+
|
| 31 |
+
DOCS_DIR = os.getenv("DOCS_DIR", "wedding_docs")
|
| 32 |
+
INDEX_PATH = os.getenv("INDEX_PATH", "wedding.index")
|
| 33 |
+
META_CSV = os.getenv("META_CSV", "wedding_chunks.csv")
|
| 34 |
+
|
| 35 |
+
client = OpenAI(api_key=OPENAI_API_KEY)
|
| 36 |
+
|
| 37 |
+
# -----------------------------
|
| 38 |
+
# Utilities to read documents
|
| 39 |
+
# -----------------------------
|
| 40 |
+
|
| 41 |
+
def read_txt_md(path: str) -> str:
|
| 42 |
+
with open(path, "r", encoding="utf-8", errors="ignore") as f:
|
| 43 |
+
return f.read()
|
| 44 |
+
|
| 45 |
+
def read_docx(path: str) -> str:
|
| 46 |
+
doc = DocxDocument(path)
|
| 47 |
+
return "\n".join(p.text for p in doc.paragraphs)
|
| 48 |
+
|
| 49 |
+
def read_pdf(path: str) -> str:
|
| 50 |
+
reader = PdfReader(path)
|
| 51 |
+
texts = []
|
| 52 |
+
for page in reader.pages:
|
| 53 |
+
try:
|
| 54 |
+
texts.append(page.extract_text() or "")
|
| 55 |
+
except Exception:
|
| 56 |
+
pass
|
| 57 |
+
return "\n".join(texts)
|
| 58 |
+
|
| 59 |
+
def load_all_docs(folder: str) -> List[Tuple[str, str]]:
|
| 60 |
+
paths = []
|
| 61 |
+
for ext in ("*.md", "*.txt", "*.docx", "*.pdf"):
|
| 62 |
+
paths.extend(glob.glob(os.path.join(folder, ext)))
|
| 63 |
+
docs = []
|
| 64 |
+
for p in paths:
|
| 65 |
+
if p.endswith((".md", ".txt")):
|
| 66 |
+
text = read_txt_md(p)
|
| 67 |
+
elif p.endswith(".docx"):
|
| 68 |
+
text = read_docx(p)
|
| 69 |
+
elif p.endswith(".pdf"):
|
| 70 |
+
text = read_pdf(p)
|
| 71 |
+
else:
|
| 72 |
+
continue
|
| 73 |
+
docs.append((p, text))
|
| 74 |
+
return docs
|
| 75 |
+
|
| 76 |
+
# -----------------------------
|
| 77 |
+
# Chunk + Embed + Index
|
| 78 |
+
# -----------------------------
|
| 79 |
+
|
| 80 |
+
def chunk_text(text: str, source: str, chunk_size: int = 900, overlap: int = 150) -> List[dict]:
|
| 81 |
+
words = text.split()
|
| 82 |
+
chunks = []
|
| 83 |
+
i = 0
|
| 84 |
+
while i < len(words):
|
| 85 |
+
chunk_words = words[i:i+chunk_size]
|
| 86 |
+
chunk = " ".join(chunk_words)
|
| 87 |
+
chunks.append({
|
| 88 |
+
"source": source,
|
| 89 |
+
"chunk": chunk,
|
| 90 |
+
"hash": hashlib.md5((source + str(i) + chunk).encode("utf-8")).hexdigest()
|
| 91 |
+
})
|
| 92 |
+
i += (chunk_size - overlap)
|
| 93 |
+
return chunks
|
| 94 |
+
|
| 95 |
+
def embed_texts(texts: List[str]) -> np.ndarray:
|
| 96 |
+
# Returns an array of shape (n, d)
|
| 97 |
+
# Uses OpenAI embeddings
|
| 98 |
+
resp = client.embeddings.create(model=EMBEDDING_MODEL, input=texts)
|
| 99 |
+
vecs = [item.embedding for item in resp.data]
|
| 100 |
+
return np.array(vecs).astype("float32")
|
| 101 |
+
|
| 102 |
+
@dataclass
|
| 103 |
+
class RAGIndex:
|
| 104 |
+
index: faiss.IndexFlatIP
|
| 105 |
+
df: pd.DataFrame # columns: [source, chunk, hash, vector]
|
| 106 |
+
dim: int
|
| 107 |
+
|
| 108 |
+
def build_or_load_index(force_rebuild: bool = False) -> RAGIndex:
|
| 109 |
+
docs = load_all_docs(DOCS_DIR)
|
| 110 |
+
if not docs:
|
| 111 |
+
raise RuntimeError(f"No docs found in {DOCS_DIR}/. Put your itinerary files there.")
|
| 112 |
+
|
| 113 |
+
# Simple staleness check: if any file is newer than index, rebuild
|
| 114 |
+
def newest_mtime():
|
| 115 |
+
paths = []
|
| 116 |
+
for ext in ("*.md", "*.txt", "*.docx", "*.pdf"):
|
| 117 |
+
paths.extend(glob.glob(os.path.join(DOCS_DIR, ext)))
|
| 118 |
+
return max(os.path.getmtime(p) for p in paths)
|
| 119 |
+
|
| 120 |
+
index_exists = os.path.exists(INDEX_PATH) and os.path.exists(META_CSV)
|
| 121 |
+
need_rebuild = force_rebuild
|
| 122 |
+
if index_exists:
|
| 123 |
+
idx_mtime = min(os.path.getmtime(INDEX_PATH), os.path.getmtime(META_CSV))
|
| 124 |
+
need_rebuild = need_rebuild or (newest_mtime() > idx_mtime)
|
| 125 |
+
|
| 126 |
+
if index_exists and not need_rebuild:
|
| 127 |
+
df = pd.read_csv(META_CSV)
|
| 128 |
+
vecs = np.load(INDEX_PATH)
|
| 129 |
+
dim = vecs.shape[1]
|
| 130 |
+
index = faiss.IndexFlatIP(dim)
|
| 131 |
+
faiss.normalize_L2(vecs)
|
| 132 |
+
index.add(vecs)
|
| 133 |
+
return RAGIndex(index=index, df=df, dim=dim)
|
| 134 |
+
|
| 135 |
+
# Rebuild
|
| 136 |
+
all_chunks = []
|
| 137 |
+
for path, text in docs:
|
| 138 |
+
if not text.strip():
|
| 139 |
+
continue
|
| 140 |
+
all_chunks.extend(chunk_text(text, source=path))
|
| 141 |
+
|
| 142 |
+
if not all_chunks:
|
| 143 |
+
raise RuntimeError("Docs were read but produced no chunks. Check formats.")
|
| 144 |
+
|
| 145 |
+
df = pd.DataFrame(all_chunks)
|
| 146 |
+
vecs = embed_texts(df["chunk"].tolist())
|
| 147 |
+
# Normalize for cosine similarity via inner product
|
| 148 |
+
faiss.normalize_L2(vecs)
|
| 149 |
+
np.save(INDEX_PATH, vecs)
|
| 150 |
+
df.to_csv(META_CSV, index=False)
|
| 151 |
+
|
| 152 |
+
dim = vecs.shape[1]
|
| 153 |
+
index = faiss.IndexFlatIP(dim)
|
| 154 |
+
index.add(vecs)
|
| 155 |
+
return RAGIndex(index=index, df=df, dim=dim)
|
| 156 |
+
|
| 157 |
+
# -----------------------------
|
| 158 |
+
# Retrieval + Answering
|
| 159 |
+
# -----------------------------
|
| 160 |
+
|
| 161 |
+
def retrieve(query: str, rag: RAGIndex, k: int = 6) -> List[dict]:
|
| 162 |
+
qvec = embed_texts([query])
|
| 163 |
+
faiss.normalize_L2(qvec)
|
| 164 |
+
D, I = rag.index.search(qvec, k)
|
| 165 |
+
results = []
|
| 166 |
+
for score, idx in zip(D[0], I[0]):
|
| 167 |
+
if idx == -1:
|
| 168 |
+
continue
|
| 169 |
+
row = rag.df.iloc[int(idx)]
|
| 170 |
+
results.append({
|
| 171 |
+
"score": float(score),
|
| 172 |
+
"source": row["source"],
|
| 173 |
+
"chunk": row["chunk"]
|
| 174 |
+
})
|
| 175 |
+
return results
|
| 176 |
+
|
| 177 |
+
SYSTEM_PROMPT = (
|
| 178 |
+
"You are a helpful, concise wedding assistant for Samson’s brother’s wedding. "
|
| 179 |
+
"Answer ONLY using the provided context from the wedding documents. "
|
| 180 |
+
"If the answer isn’t in the docs, say you don’t have that info and suggest who to contact (e.g., Overall IC). "
|
| 181 |
+
"Keep answers under 6 bullets or 150 words when possible. Use SGT times."
|
| 182 |
+
)
|
| 183 |
+
|
| 184 |
+
async def answer_with_rag(question: str, rag: RAGIndex) -> str:
|
| 185 |
+
ctx = retrieve(question, rag, k=6)
|
| 186 |
+
context_blocks = []
|
| 187 |
+
for r in ctx:
|
| 188 |
+
# Keep brief context slices
|
| 189 |
+
text = r["chunk"]
|
| 190 |
+
if len(text) > 800:
|
| 191 |
+
text = text[:800] + "…"
|
| 192 |
+
context_blocks.append(f"[Source: {os.path.basename(r['source'])}]\n{text}")
|
| 193 |
+
|
| 194 |
+
context_text = "\n\n".join(context_blocks)
|
| 195 |
+
|
| 196 |
+
messages = [
|
| 197 |
+
{"role": "system", "content": SYSTEM_PROMPT},
|
| 198 |
+
{"role": "user", "content": f"Context from docs:\n\n{context_text}\n\nQuestion: {question}"}
|
| 199 |
+
]
|
| 200 |
+
|
| 201 |
+
completion = client.chat.completions.create(
|
| 202 |
+
model=OPENAI_MODEL,
|
| 203 |
+
messages=messages,
|
| 204 |
+
temperature=0.2,
|
| 205 |
+
)
|
| 206 |
+
answer = completion.choices[0].message.content.strip()
|
| 207 |
+
|
| 208 |
+
if STRICT_DOC_MODE and (not context_blocks or "I don’t have that info" in answer):
|
| 209 |
+
# If no context matched strongly, enforce honesty
|
| 210 |
+
if len(context_blocks) == 0:
|
| 211 |
+
return (
|
| 212 |
+
"I couldn’t find this in the wedding docs. Please check the Family Playbook or ask the Overall IC. "
|
| 213 |
+
"You can also /refresh to make sure I have the latest files."
|
| 214 |
+
)
|
| 215 |
+
return answer
|
| 216 |
+
|
| 217 |
+
# -----------------------------
|
| 218 |
+
# Telegram Handlers
|
| 219 |
+
# -----------------------------
|
| 220 |
+
RAG = None # lazy loaded
|
| 221 |
+
|
| 222 |
+
async def ensure_rag(force: bool = False):
|
| 223 |
+
global RAG
|
| 224 |
+
if RAG is None or force:
|
| 225 |
+
RAG = build_or_load_index(force_rebuild=force)
|
| 226 |
+
|
| 227 |
+
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
| 228 |
+
await ensure_rag(False)
|
| 229 |
+
msg = (
|
| 230 |
+
"👋 Hello! I’m the Wedding Q&A Bot. Ask me anything about roles, timings, addresses, and logistics.\n\n"
|
| 231 |
+
"Examples:\n"
|
| 232 |
+
"• What time is the solemnisation?\n"
|
| 233 |
+
"• What’s Mum’s role during tea ceremony?\n"
|
| 234 |
+
"• Where to park at the hotel?\n"
|
| 235 |
+
"• Who holds the ang bao box?\n\n"
|
| 236 |
+
"Admins can /refresh after updating the docs."
|
| 237 |
+
)
|
| 238 |
+
await update.message.reply_text(msg)
|
| 239 |
+
|
| 240 |
+
async def help_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
| 241 |
+
await update.message.reply_text(
|
| 242 |
+
"Send a normal question, or use:\n"
|
| 243 |
+
"/role <name> — quick role lookup\n"
|
| 244 |
+
"/refresh — rebuild knowledge from latest docs (admin only, but not enforced)"
|
| 245 |
+
)
|
| 246 |
+
|
| 247 |
+
async def role_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
| 248 |
+
await ensure_rag(False)
|
| 249 |
+
name = " ".join(context.args).strip()
|
| 250 |
+
if not name:
|
| 251 |
+
await update.message.reply_text("Usage: /role <name>")
|
| 252 |
+
return
|
| 253 |
+
q = f"What is the role and responsibilities of {name}? Include timings and contact if available."
|
| 254 |
+
ans = await answer_with_rag(q, RAG)
|
| 255 |
+
await update.message.reply_text(ans, parse_mode=ParseMode.MARKDOWN)
|
| 256 |
+
|
| 257 |
+
async def refresh_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
| 258 |
+
await ensure_rag(True)
|
| 259 |
+
await update.message.reply_text("✅ Refreshed. I’m now using the latest documents in wedding_docs/.")
|
| 260 |
+
|
| 261 |
+
async def on_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
| 262 |
+
await ensure_rag(False)
|
| 263 |
+
text = (update.message.text or "").strip()
|
| 264 |
+
if not text:
|
| 265 |
+
return
|
| 266 |
+
ans = await answer_with_rag(text, RAG)
|
| 267 |
+
# Telegram has 4096 char limit per message; be safe
|
| 268 |
+
if len(ans) > 3500:
|
| 269 |
+
ans = ans[:3500] + "…"
|
| 270 |
+
await update.message.reply_text(ans, parse_mode=ParseMode.MARKDOWN)
|
| 271 |
+
|
| 272 |
+
def main():
|
| 273 |
+
if not TELEGRAM_BOT_TOKEN:
|
| 274 |
+
raise RuntimeError("TELEGRAM_BOT_TOKEN missing")
|
| 275 |
+
if not OPENAI_API_KEY:
|
| 276 |
+
raise RuntimeError("OPENAI_API_KEY missing")
|
| 277 |
+
|
| 278 |
+
app = Application.builder().token(TELEGRAM_BOT_TOKEN).build()
|
| 279 |
+
|
| 280 |
+
app.add_handler(CommandHandler("start", start))
|
| 281 |
+
app.add_handler(CommandHandler("help", help_cmd))
|
| 282 |
+
app.add_handler(CommandHandler("role", role_cmd))
|
| 283 |
+
app.add_handler(CommandHandler("refresh", refresh_cmd))
|
| 284 |
+
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, on_message))
|
| 285 |
+
|
| 286 |
+
print("Bot running… Press Ctrl+C to stop.")
|
| 287 |
+
app.run_polling(drop_pending_updates=True)
|
| 288 |
+
|
| 289 |
+
if __name__ == "__main__":
|
| 290 |
+
main()
|
requirements.txt
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
python-telegram-bot==20.7
|
| 2 |
+
openai>=1.51.0
|
| 3 |
+
faiss-cpu==1.8.0.post1
|
| 4 |
+
python-dotenv==1.0.1
|
| 5 |
+
pydantic==2.9.2
|
| 6 |
+
pandas==2.2.3
|
| 7 |
+
numpy==1.26.4
|
| 8 |
+
python-docx==1.1.2
|
| 9 |
+
pypdf==5.0.0
|
| 10 |
+
unstructured==0.15.7
|
| 11 |
+
unstructured[pdf]==0.15.7
|
wedding_chunks.csv
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
source,chunk,hash
|
| 2 |
+
wedding_docs/Cherry&Samuel Wedding plan version 1.1.docx,"Notes: Bring personal / disposable towels for those staying at airbnb Distance from Groom’s Airbnb to Bride’s home is 1 min drive / 3 mins walking distance (Careful of Ipoh stray dogs) Address: Samuel (Groom) Airbnb (Adjacent) 67, Jalan Taman Jalong 2, sungai siput u perak (Samuel & friends) > 2 Keys: Samuel & Yuqi = Key holder Master bedroom – Samuel & Cherry Bedroom 2 (2 queen bed) – Andy + Kenneth + Zixian + Edwin Bedroom 3 (2 single bed) – Yuqi 68, Jalan Taman Jalong 2, sungai siput u perak (Samuel’s family) > 2 Keys: Samson & Sandra = Key holder Master bedroom – Mummy + Lilian Bedroom 2 (2 queen bed) – Auntie Wendy & Uncle Barry Bedroom 3 (2 single bed) – Either Samson & wife OR Sandra & Keyi Bedroom 4 (2 queen bed) – Either Samson & wife OR Sandra & Keyi Cherry (Bride) house: 17, Jalan Taman Jalong 4, Sungai siput u perak From groom’s airbnb to Bride’s home Chloe’s hotel : MH Sentral (5 mins drive to Groom’s Airbnb) Cherry’s non-Ipoh relatives & friends Airbnb No.232,Jalan Kemiri 8, Taman Kemiri, 31100 Sungai Siput (U) Perak (5 mins drive from Bride’s home) 3 rooms : 阿姨whole family (7 PPL) No. 250, Jalan Bistari 7, Taman Lintang Bistari, 31100 Sungai Siput (U) Perak (4 mins drive from Bride’s home) Master bedroom (1 queen bed) – Peiwen + Peiwen husband + Serene (Key holder) Bedroom 2 (1 queen bed) – Nab Bedroom 3 (1 queen bed) – Standby for photographer 2 ppl for edit video Bedroom 4 (3 single bed) – Jesc (Key holder) + Winky + Cat + Lce Restarant: Phongmun Restaurant Sdn Bhd (Guest to arrive 6.30 pm / Wedding dinner will start at 7 pm) Komersial Water pond, & 99-1, Persiaran Komersial 1, &, Persiaran Komersial 5, 31100 Sungai Siput, Perak, Malaysia Nice place to eat near Bride’s house (Check opening hours) 海燕茶室 Restaurant Hai Ying (Breakfast) 199, Taman Jalong Baru, 31100 Sungai Siput, Perak, Malaysia 后巷咖啡屋 (Breakfast) 201, Jalan Tjb 6, Taman Jalong Utara, 31100 Sungai Siput, Perak, Malaysia 啊姐Laksa & 炸蝦餅/年糕 (Snacks) 322, Taman Jalong Baru, 31100 Perak, Malaysia Selling delicious PO PO Cui / Crispy beancurd skin (Huang Dou Xian Shen) - 13 mins drive from bride’s home 354 lebuh kanthan baru kanthan 31200 chemor perak (Owner selling in own house) Contacts (Refer to excel list) Restaurant: lao ban / Buffet catering for 25th Lunch 大金姐: 小風哥 (In charge of wedding customs) SHE Live band : Alex / Emcee: James Photographer : Kenneth LED TV Screen: 喜哥 IPOH Deco Party : photobooth + reception table + photo album corner Deco for walkway + VIP table + Car Deco + Weddin g flower for bride: Michelle Buffet catering for 24th dinner: Iam Make up (Bride & Groom): Chloe Make up (Mothers): Sin Rou 23-Oct-25 (Wed) - (Cherry and Samuel to reach Ipoh in the evening) Tasks for Samuel & Cherry: Samuel to prepare remaining red packets including Jie Mei / Xiong di red packets (Exchange for balance $700 & $200 and put in sling bag (take from your mother) Collect back 1st batch of Red packets from DA GU. Final Decoration of Cherry’s home (include 4 flower beds) Cycle Count & Remove Doorgifts from toyogo bin which is to be used as cooler box for beers + other drinks for 24th dinner buffet Prepare Tupperware to freeze ice cubes at Cherry hse / Airbnb before 24th dinner buffet 23th Night to go restaurant test lighting + LED screen (To arrange with LED Guy) > Bring thumbdrive + discuss walk in aisle + remove table + table stands & cards Things to prepare to bring over to airbnb on 24th Glass cup x 2 Scratch Cards x 150 ea Clear glass box with test tubes filled with sand Groom’s Airbnb deco + tapes + scissors (Samuel’s fren to help deco) Groom’s Clothing, two brothers’ clothing & 8 paper fans (1 missing fan to buy from shopee) 1 Set Red bedsheet bundle + Red pajamas Small and medium mineral water bottles & Sweet packet drinks to put in fridge for 25th lunch buffet Things to bring to restaurant on 24th night. Wedding Poster Stand Door gift x 150 (to reconfirm qty) Registration counter items (Cherry purchased registration book from shopee) Restaurant drinks Thumbdrive (Capcut video + Wedding opening video + PPS) 24-Oct-25 (Thurs) 24th Morning to go restaurant to meet up Deco team (Michelle) Cherry & Samuel to check-in at Groom’s Airbnb earliest at 1 pm (unit 67 / 68) Cherry & Samuel to check-in at Relative + Friend’s Airbnb (earliest Timing TBC) Cherry & Samuel > Refer to Things to prepare to bring over to Airbnb & Restaurant today Samuel to makeup bed at airbnb with red bedsheets / red pillow casings etc. Samuel to make ice cubes at freezers. Friends and relative to arrive 1425 hrs (airport) – settle own lunch if required Take Grab / Train to designated Airbnb / hotel address provided Buffet dinner at Cherry’s address (6.30 - 7 pm onwards) Wedding Photo album to standby for Guests to view (With Cherry’s auntie from johor) Groom’s car (Lce) deco at 5 pm. Cherry to tag along. Samuel to stay. Decorate 3 xiong di’s car (Jake’s car + Ah San’s car + Cherry’s brother car) during buffet dinner. Wait for cherry to return Jake will bring beers",2f682817807a9331162eb12c0cf6f540
|
| 3 |
+
wedding_docs/Cherry&Samuel Wedding plan version 1.1.docx,"Airbnb earliest at 1 pm (unit 67 / 68) Cherry & Samuel to check-in at Relative + Friend’s Airbnb (earliest Timing TBC) Cherry & Samuel > Refer to Things to prepare to bring over to Airbnb & Restaurant today Samuel to makeup bed at airbnb with red bedsheets / red pillow casings etc. Samuel to make ice cubes at freezers. Friends and relative to arrive 1425 hrs (airport) – settle own lunch if required Take Grab / Train to designated Airbnb / hotel address provided Buffet dinner at Cherry’s address (6.30 - 7 pm onwards) Wedding Photo album to standby for Guests to view (With Cherry’s auntie from johor) Groom’s car (Lce) deco at 5 pm. Cherry to tag along. Samuel to stay. Decorate 3 xiong di’s car (Jake’s car + Ah San’s car + Cherry’s brother car) during buffet dinner. Wait for cherry to return Jake will bring beers to Cherry hse before dinner buffet (Reminder) Samuel to bring retrieve ice cubes & buy backup ice cubes from nearby mart when nearing buffet timing (toyogo bin + cooler bag) Glass cup x 2 (Kenneth / Yuqi / Andy / Zixian) to paste stickers at Airbnb on 24th Scratch Cards x 150 (Kenneth / Yuqi / Andy / Zixian) to paste stickers at Airbnb on 24th Kenneth / Yuqi / Andy / Zixian (Samuel’s friends) to help deco Groom’s Airbnb after buffet dinner on 24th After buffet dinner, Samuel & Cherry go feng man lou restaurant to check: bring along key personnel (Samson + wife / Sandra / Keyi / Pei Wen + Husband / Serene + Jessie) (Alphard + Cherry’s brother car) Things to bring to restaurant on 24th night. Wedding Poster Stand All the drinks Door gift x 150 (Qty to reconfirm) Kahoot game / Thumbdrive (Capcut video + Wedding opening video + PPS) (Test with LED guy) (if unable to finish testing by 23th) Deco1 (Michelle – walkway + vip seat) / Deco2 - stage Lighting (Jessie) Clarify with restaurant boss where are the table stands & cards 1st & 2nd march in process. (to remove the table) 25-Oct-25 (Saturday) AM Plan: Simple Breakfast Provided (Airbnb) Cherry’s brother to dapao (20 pax) All brothers to be at Groom’s Airbnb before 9 am. (Ah Lung, Ah San + Chloe to take note since not staying at airbnb) Set off from Groom’s Airbnb by 9.30 am (Issue red packets to Xiong di + Driver) Car arrangement Car 1 (Lce’s car 8 Seater alphard) – Driver: Samson Samuel + Xiao Feng (PIC of Wedding customs) Car 2 (Cherry’s brother car) – Driver: Ah long Kenneth + Andy + Yuqi Car 3 (Jake’s car) – Driver: Jake Zixian + Wen Qing + Chloe Car 4 (ah bang’s car 7 Seater): Driver: Ah San Pei Wen + Serene + Sandra + Keyi + Samson’s wife + Ah San’s wife Route – Detour for 5 mins & Reach Bride’s house by 9.45 am (Samson to take lead) (Distance from Groom to Bride home is 1 min drive) Samson (Groomsman in charge of slingbag with red packets) Cherry’s niece (NiNi) will open Groom’s car. (Issue red packet) Finish games by 10.30 am (Time may vary) Tea Ceremony at Bride’s house + Prayers > finish by 12 pm (Time may vary) Travel back to Airbnb by 12.30 pm (Time may vary) All brothers to be around by 11.45 pm (to standby to travel back to Groom’s airbnb with the same seating arrangement) Tea Ceremony at Groom’s Airbnb (Lunch will be provided) Photo taking will end by 2.30 pm. Pei Wei/Serene to keep 2 portions of lunch buffet catering food for the two photographers once food arrives Photographers will travel to another designated Airbnb to prepare photo editing PM Plan: (Restaurant) 6.30 pm (Guest Registration) / 7 pm Wedding dinner starts Makeup artist’s timings (Makeup to be done at Restaurant) / Samuel to Test Kahoot Drive Cherry’s brother car Makeup artist (Bride - 3.15 pm to 5.30 pm / Groom - 5.30 pm to 6 pm) Makeup artist (Bride’s mother - 3 pm to 4.30 pm / Groom’s mother – 4.30 pm to 6 pm) Restaurant to prepare early dinner for 6 pax Photographers x 2 Makeup artist x 1 Live band x 2 (6 pm consume) Emcee x 1 (6 pm consume) Manpower planning (Whatapps group chat will be setup) Pei Wen + Husband + Serene + Sandra + Keyi + Samson + Wife + Jessie drive over to restaurant using 8- seater Alphard for preparation. Reach restaurant by 4.50 pm Items to bring from Airbnb for setup on 25th Bride & Groom’s Cups (Wash and standby – Sandra & Keyi to fill up with Chinese tea and standby at entrance & handover to me & cherry during 2nd march in @ 8.30 pm) Samson to place Test tubes containing colored sand + clear glass box at registration counter table Pei Wen & Serene to place 4 beds of flowers to place in front of TV Screen (Check if the flowers will block the screen content) Items already at restaurant (brought over on 24th) for setup Samson to setup “Poster Stand” beside registration counter All - Place door gift x 150 pcs at each seat in the restaurant (already at restaurant) Sandra & Keyi to Tag table no. cards at each table stand for each table using seat plan (Cherry & Samuel to clarify 1",fe1d6dc783824173d5b0bc6e2fd03cef
|
| 4 |
+
wedding_docs/Cherry&Samuel Wedding plan version 1.1.docx,"preparation. Reach restaurant by 4.50 pm Items to bring from Airbnb for setup on 25th Bride & Groom’s Cups (Wash and standby – Sandra & Keyi to fill up with Chinese tea and standby at entrance & handover to me & cherry during 2nd march in @ 8.30 pm) Samson to place Test tubes containing colored sand + clear glass box at registration counter table Pei Wen & Serene to place 4 beds of flowers to place in front of TV Screen (Check if the flowers will block the screen content) Items already at restaurant (brought over on 24th) for setup Samson to setup “Poster Stand” beside registration counter All - Place door gift x 150 pcs at each seat in the restaurant (already at restaurant) Sandra & Keyi to Tag table no. cards at each table stand for each table using seat plan (Cherry & Samuel to clarify 1 day earlier with restaurant boss where are the stands/cards) Samson & Sandra to standby at reception before 6 pm to register guests using the guest list (Cherry’s cousin – Matthew will be assisting as well) Register guest (Tick their name according) > Cherry bought registration book from shopee Let them know their table no. Pass them 1 scratch card each (Ask them to Scratch after Emcee’s announcement) Receive red packets & indicate in the template (Check with guest if they are providing any red packets politely / 想请问一下,您这边会准备红包吗?) (Cherry & Samuel to confirm where to store these red packets) After registration, direct Guests to fill up the clear glass box using the test tubes colored sand Registration cut-off at 7 pm. Chloe & Husband to take grab directly to restaurant from hotel before 7 pm (come earlier to take photos) Andy / Zixian / Kenneth / Yuqi / Edwin to take grab directly to restaurant from Airbnb Cherry’s uncle will fetch them to restaurant Groom’s mother, Auntie Lilian, Uncle Barry, Auntie Wendy Restaurant Activities Live band / Emcee will prepare sound system at 5 pm & consume dinner at 6 pm Bride & Groom will join guests for photo taking at booth until 7 pm. (Photographer to standby at photo booth) 6:00pm~6:30pm-嘉宾开始入席 开始放暖场音乐而已 (先不要放婚纱照) 🎶📽️ + Dynamic wedding background Dynamic wedding background 婚礼动态背景to be presented on the screen at all times if no video/slides are being played (Non-active periods) 进场之前 放Wedding Opening video大屏幕婚礼 (Prepare by photographer) ➡️7:15pm-准备进场🤵♂️👰♀️/👨👩 •双方父母进场👨👩 进场歌曲: 戴祖儀 Joey - 分分鐘需要你 •一对新人进场🤵👰 进场歌曲: not my business 出菜的时候就可以放婚纱照片POWER POINT SLIDE ➡️7:30pm-出菜🥗 ➡️7:35pm-Live Band开始🎸🎤 (45分钟过后播放暖场音乐🎵) ➡️8:00pm:出第二道菜新人准备换礼服👰 ➡️8:30pm-新人第二场进场 (Sandra & keyi standby at entrance to handover the sticker cups to us) 进场歌曲: https://open.spotify.com/track/5pU2jicRzE4NAgXE7rOcKV?si=_tGqcjG-Sm6VZqrls6G9Fw ➡️8:35pm:新人上台切蛋糕/开香槟仪式 🎂🍾 -敬酒仪式🍻 歌曲: BSS(부석순)(SEVENTEEN) - The Reasons of My Smiles(자꾸만 웃게 돼) ➡️9:00pm-新人回主家席休息🤵👰 •准备播放capcut Video和Slide Show 早上敬茶的highlight photo 🎦 ➡️9:20pm-Emcee准备玩Game Game: Kahoot (VIP scanned QR code to join the game ) https://slempire.rhinopal.top?oid=2510059PQFQMUW https://create.kahoot.it/share/samuel-cherry-final-wedding-game/4d55c251-77e5-4269-8c70-4110c70148c4 When play Kahoot game, background song: https://open.spotify.com/track/4ppXYFIKT0GM6bGnxYk7qm?si=u3JXZSEgRNanLX94_B0HjQ 3 Winners (Red packets) Game 2 : 刮刮乐看谁刮到groom When play 刮刮乐 game, background song : ROSÉ & Bruno Mars - APT. (Official Music Video) – YouTube 10 winners (Red packets) 游戏完毕 ➡️9:45pm-新人到每一张宾桌敬酒🍻 ➡️9:45pm-Live Band开始直到最后🎸🎤 ➡️10:15pm-晚宴结束🔚 大概结束的时间是10:15pm~10:30pm左右的",5f0f309060593328cae1af586f275367
|
wedding_docs/Cherry&Samuel Wedding plan version 1.1.docx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:eba7558830349c8d0a90d6b3590df0eddf6fa36ae1f1f983f7bdff3197e2e998
|
| 3 |
+
size 5000198
|