| """Mock data & business logic for Streamlit Hostel MS.""" |
|
|
| import copy |
| import math |
| import random |
| from datetime import date, datetime |
|
|
| DAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] |
| STATUS_MAP = {0: "Pending", 1: "In Progress", 2: "Resolved"} |
|
|
|
|
| def seed_data(): |
| random.seed(42) |
| rooms = [] |
| for i in range(15): |
| ac = i % 3 == 0 |
| cap = 2 if i % 2 == 0 else 4 |
| rooms.append({ |
| "id": i + 1, "number": f"R-{101 + i:03d}", "floor": i // 5 + 1, |
| "type": "2-seater" if i % 2 == 0 else "4-seater", "ac": ac, |
| "capacity": cap, "occupied": 0, "rent": 18000 if ac else 12000, |
| "active": True, |
| }) |
|
|
| names = [ |
| "Ali Hassan", "Fatima Malik", "Usman Ahmed", "Ayesha Khan", "Bilal Raza", |
| "Hamza Younas", "Abdullah Khan", "Sana Iqbal", "Zain Butt", "Hira Nawaz", |
| ] |
| students = [] |
| for i, name in enumerate(names): |
| students.append({ |
| "id": i + 1, "name": name, "roll": f"UCP-BSAI-{1001 + i}", |
| "phone": f"0300-{1000000 + i:07d}", "emergency": f"Parent {i + 1}", |
| "password": "student123", "room_id": i + 1, "active": True, |
| "rent_due": rooms[i]["rent"], "mess_due": 8500, "utility_due": 1500, |
| "rent_paid": i % 3 != 0, "mess_paid": i % 4 != 0, |
| "last_pay_date": "2026-05-15", "mess_off_days": 0, |
| }) |
| rooms[i]["occupied"] = 1 |
|
|
| mess = [] |
| for d in range(7): |
| mess.append({ |
| "day": DAYS[d], |
| "breakfast": "Paratha + Omelette + Tea", |
| "lunch": "Biryani + Raita", |
| "dinner": "Karahi + Naan", |
| }) |
|
|
| complaints = [ |
| {"id": 1, "student_idx": 0, "text": "Wi-Fi not working in Room R-101", "status": 0, |
| "created": "2026-06-10"}, |
| {"id": 2, "student_idx": 3, "text": "Ceiling fan making noise", "status": 1, |
| "created": "2026-06-12"}, |
| {"id": 3, "student_idx": 5, "text": "Water heater not heating properly", "status": 0, |
| "created": "2026-06-14"}, |
| ] |
|
|
| gate_logs = [ |
| {"id": 1, "student_idx": 0, "type": "OUT", "time": "2026-06-15 08:30"}, |
| {"id": 2, "student_idx": 0, "type": "IN", "time": "2026-06-15 18:45"}, |
| {"id": 3, "student_idx": 5, "type": "OUT", "time": "2026-06-15 09:15"}, |
| ] |
|
|
| attendance = [ |
| {"student_idx": i, "date": str(date.today()), "present": i % 5 != 0} |
| for i in range(10) |
| ] |
|
|
| announcements = [ |
| {"id": 1, "title": "Mess Timings Updated", |
| "body": "Dinner will be served 7:00β9:00 PM during exam week.", |
| "date": "2026-06-14", "priority": "high"}, |
| {"id": 2, "title": "Fee Deadline Reminder", |
| "body": "Please clear pending dues before June 30 to avoid fine.", |
| "date": "2026-06-13", "priority": "medium"}, |
| {"id": 3, "title": "Guest Policy", |
| "body": "Visitors allowed only in common room until 6 PM.", |
| "date": "2026-06-10", "priority": "low"}, |
| ] |
|
|
| payment_history = [ |
| {"student_idx": 1, "type": "Rent", "amount": 18000, "date": "2026-05-01"}, |
| {"student_idx": 1, "type": "Mess", "amount": 8500, "date": "2026-05-01"}, |
| {"student_idx": 4, "type": "Rent", "amount": 12000, "date": "2026-05-03"}, |
| ] |
|
|
| return { |
| "rooms": rooms, |
| "students": students, |
| "mess": mess, |
| "complaints": complaints, |
| "gate_logs": gate_logs, |
| "attendance": attendance, |
| "announcements": announcements, |
| "payment_history": payment_history, |
| } |
|
|
|
|
| def fresh_state(): |
| return copy.deepcopy(seed_data()) |
|
|
|
|
| def find_student(students, roll): |
| for i, s in enumerate(students): |
| if s["active"] and s["roll"] == roll: |
| return i |
| return -1 |
|
|
|
|
| def find_room(rooms, number): |
| for i, r in enumerate(rooms): |
| if r["active"] and r["number"].upper() == number.upper(): |
| return i |
| return -1 |
|
|
|
|
| def room_status(r): |
| if r["occupied"] == 0: |
| return "Vacant" |
| if r["occupied"] < r["capacity"]: |
| return "Partial" |
| return "Full" |
|
|
|
|
| def pay_label(paid): |
| return "Paid" if paid else "Due" |
|
|
|
|
| def calc_score(student, work=80000): |
| total = 0.0 |
| for w in range(work): |
| total += math.sin(student["rent_due"] * w * 0.0001) |
| total += math.cos(student["mess_due"] + w * 0.001) |
| if not student["rent_paid"]: |
| total += 5000 |
| if not student["mess_paid"]: |
| total += 3000 |
| return total |
|
|
|
|
| def match_student(student, key): |
| if not student["active"]: |
| return False |
| k = key.lower() |
| return k in student["name"].lower() or k in student["roll"].lower() |
|
|
|
|
| def record_payment(db, idx, pay_rent, pay_mess): |
| s = db["students"][idx] |
| today = str(date.today()) |
| if pay_rent and not s["rent_paid"]: |
| s["rent_paid"] = True |
| db["payment_history"].append({ |
| "student_idx": idx, "type": "Rent", "amount": s["rent_due"], "date": today, |
| }) |
| if pay_mess and not s["mess_paid"]: |
| s["mess_paid"] = True |
| db["payment_history"].append({ |
| "student_idx": idx, "type": "Mess", "amount": s["mess_due"], "date": today, |
| }) |
| s["last_pay_date"] = today |
|
|
|
|
| def add_student(db, name, roll, phone, room_number, password="student123"): |
| if find_student(db["students"], roll) >= 0: |
| return False, "Roll number already exists" |
| ri = find_room(db["rooms"], room_number) |
| if ri < 0: |
| return False, "Room not found" |
| room = db["rooms"][ri] |
| if room["occupied"] >= room["capacity"]: |
| return False, "Room is full" |
| idx = len(db["students"]) |
| db["students"].append({ |
| "id": idx + 1, "name": name, "roll": roll, "phone": phone, |
| "emergency": "Not set", "password": password, "room_id": room["id"], |
| "active": True, "rent_due": room["rent"], "mess_due": 8500, |
| "utility_due": 1500, "rent_paid": False, "mess_paid": False, |
| "last_pay_date": "-", "mess_off_days": 0, |
| }) |
| room["occupied"] += 1 |
| return True, f"Student {name} enrolled in {room['number']}" |
|
|
|
|
| def assign_room(db, roll, room_number): |
| si = find_student(db["students"], roll) |
| if si < 0: |
| return False, "Student not found" |
| ri = find_room(db["rooms"], room_number) |
| if ri < 0: |
| return False, "Room not found" |
| student = db["students"][si] |
| new_room = db["rooms"][ri] |
| if new_room["occupied"] >= new_room["capacity"]: |
| return False, "Target room is full" |
| if student["room_id"] > 0: |
| for r in db["rooms"]: |
| if r["id"] == student["room_id"] and r["occupied"] > 0: |
| r["occupied"] -= 1 |
| student["room_id"] = new_room["id"] |
| student["rent_due"] = new_room["rent"] |
| new_room["occupied"] += 1 |
| return True, f"Assigned {student['name']} to {new_room['number']}" |
|
|
|
|
| def add_gate_log(db, student_idx, log_type): |
| db["gate_logs"].append({ |
| "id": len(db["gate_logs"]) + 1, |
| "student_idx": student_idx, |
| "type": log_type, |
| "time": datetime.now().strftime("%Y-%m-%d %H:%M"), |
| }) |
|
|
|
|
| def mark_attendance(db, student_idx, present): |
| today = str(date.today()) |
| for rec in db["attendance"]: |
| if rec["student_idx"] == student_idx and rec["date"] == today: |
| rec["present"] = present |
| return |
| db["attendance"].append({"student_idx": student_idx, "date": today, "present": present}) |
|
|
|
|
| def add_announcement(db, title, body, priority="medium"): |
| db["announcements"].insert(0, { |
| "id": len(db["announcements"]) + 1, |
| "title": title, |
| "body": body, |
| "date": str(date.today()), |
| "priority": priority, |
| }) |
|
|
|
|
| def bulk_mark_attendance(db, present=True): |
| today = str(date.today()) |
| for i, s in enumerate(db["students"]): |
| if not s["active"]: |
| continue |
| mark_attendance(db, i, present) |
|
|
|
|
| def get_student_gate_logs(db, student_idx, limit=20): |
| return [g for g in db["gate_logs"] if g["student_idx"] == student_idx][-limit:] |
|
|
|
|
| def get_student_attendance_history(db, student_idx, limit=14): |
| return [a for a in db["attendance"] if a["student_idx"] == student_idx][-limit:] |
|
|
|
|
| def get_activity_feed(db, limit=8): |
| """Recent events for dashboard activity stream.""" |
| events = [] |
| for g in reversed(db["gate_logs"][-5:]): |
| name = db["students"][g["student_idx"]]["name"] |
| events.append((g["time"], "πͺ", f"{name} β Gate {g['type']}", g["time"])) |
| for p in reversed(db["payment_history"][-3:]): |
| name = db["students"][p["student_idx"]]["name"] |
| events.append((p["date"], "π°", f"{name} paid {p['type']}", f"PKR {p['amount']:,}")) |
| for c in reversed(db["complaints"][-3:]): |
| name = db["students"][c["student_idx"]]["name"] |
| events.append((c.get("created", ""), "π", f"Ticket #{c['id']} β {name}", c["text"][:50])) |
| for a in db["announcements"][:2]: |
| events.append((a["date"], "π’", a["title"], a["body"][:60])) |
| events.sort(key=lambda x: x[0], reverse=True) |
| return [(icon, title, meta) for _, icon, title, meta in events[:limit]] |
|
|
|
|
| def export_summary(db): |
| stats = get_analytics(db) |
| return { |
| "generated": str(datetime.now()), |
| "analytics": stats, |
| "students": len([s for s in db["students"] if s["active"]]), |
| "rooms": len([r for r in db["rooms"] if r["active"]]), |
| "complaints_open": stats["complaint_counts"].get("Pending", 0), |
| "gate_logs": len(db["gate_logs"]), |
| } |
|
|
|
|
| def get_analytics(db): |
| rooms = [r for r in db["rooms"] if r["active"]] |
| students = [s for s in db["students"] if s["active"]] |
| vacant = sum(1 for r in rooms if r["occupied"] == 0) |
| partial = sum(1 for r in rooms if 0 < r["occupied"] < r["capacity"]) |
| full = sum(1 for r in rooms if r["occupied"] >= r["capacity"]) |
| rent_clear = sum(1 for s in students if s["rent_paid"]) |
| mess_clear = sum(1 for s in students if s["mess_paid"]) |
| defaulters = sum(1 for s in students if not s["rent_paid"] or not s["mess_paid"]) |
| total_dues = sum( |
| s["rent_due"] + s["mess_due"] + s["utility_due"] |
| for s in students if not s["rent_paid"] or not s["mess_paid"] |
| ) |
| complaint_counts = {STATUS_MAP[k]: 0 for k in STATUS_MAP} |
| for c in db["complaints"]: |
| complaint_counts[STATUS_MAP[c["status"]]] += 1 |
| present_today = sum( |
| 1 for a in db["attendance"] |
| if a["date"] == str(date.today()) and a["present"] |
| ) |
| return { |
| "rooms_total": len(rooms), |
| "students_total": len(students), |
| "vacant": vacant, |
| "partial": partial, |
| "full": full, |
| "rent_clear": rent_clear, |
| "mess_clear": mess_clear, |
| "defaulters": defaulters, |
| "total_dues": total_dues, |
| "complaint_counts": complaint_counts, |
| "present_today": present_today, |
| "occupancy_pct": round( |
| sum(r["occupied"] for r in rooms) / max(sum(r["capacity"] for r in rooms), 1) * 100, 1 |
| ), |
| } |
|
|