""" Automatic Time Table Generator (Improved) - Gradio app for Hugging Face Spaces - Exports: Word (.docx) and Text (.txt) - Sunday = Holiday - Languages: English, Sindhi, Urdu """ import io import tempfile import random from typing import List, Dict import gradio as gr from docx import Document from datetime import time, timedelta, datetime # ------------------- # Localization labels # ------------------- LANG = { "English": { "app_title": "Automatic Time Table Generator", "subtitle": "Generate timetables with subjects, teachers, rooms and period times.", "days_label": "Working days (comma-separated, omit Sunday; Sunday will be shown as Holiday)", "subjects_label": "Subjects (comma-separated, e.g. Math, English, Science)", "teachers_label": "Teachers (comma-separated, will cycle if fewer than subjects)", "rooms_label": "Rooms (comma-separated or single numbers, e.g. 1,2,3 ... 10)", "classes_label": "Class list (comma-separated, e.g. 1,2,3,4,5)", "periods_label": "Periods per day (integer)", "duration_label": "Period duration (hours, integer)", "start_label": "Start time (24h, e.g. 8 for 08:00)", "generate_btn": "Generate & Export (Word / Text)", "preview_label": "Generated Timetable Preview", "holiday_text": "Holiday", "word_label": "Download Word (.docx)", "text_label": "Download Text (.txt)", "doc_title": "Timetable" }, "Sindhi": { "app_title": "خودڪار ٽائيم ٽيبل جنريٽر", "subtitle": "مضمون، استادن، ڪمري ۽ دورانيه سان ٽائيم ٽيبل ٺاهيو.", "days_label": "ڪم وارا ڏينهن (ڪاما سان الڳ، آچر شامل نه ڪريو؛ آچر موڪل ڏيکاريو ويندو)", "subjects_label": "مضمون (ڪاما سان الڳ ڪريو، مثال: Math, English, Science)", "teachers_label": "استاد (ڪاما سان الڳ ڪريو، جيڪڏهن گهٽ هجن ته ورجايا ويندا)", "rooms_label": "ڪمره (ڪاما سان يا انگ، مثال: 1,2,3 ... 10)", "classes_label": "ڪلاس لسٽ (ڪاما سان، مثال: 1,2,3,4,5)", "periods_label": "في ڏينهن دورا (عدد)", "duration_label": "هر دورو (گهنٽا)", "start_label": "شروعات جو وقت (24h، مثال: 8 لاءِ 08:00)", "generate_btn": "جنريٽ ۽ ٻاھ ڪڍو (Word / Text)", "preview_label": "پريوئو", "holiday_text": "موڪل", "word_label": "Word (.docx) ڊائونلوڊ ڪريو", "text_label": "Text (.txt) ڊائونلوڊ ڪريو", "doc_title": "ٽائيم ٽيبل" }, "Urdu": { "app_title": "خودکار ٹائم ٹیبل جنریٹر", "subtitle": "مضامین، اساتذہ، کمروں اور دورانیوں کے ساتھ جدول بنائیں۔", "days_label": "کام کے دن (کاما سے جدا کریں، اتوار شامل نہ کریں؛ اتوار تعطیل دکھایا جائے گا)", "subjects_label": "مضامین (کاما سے جدا کریں، مثال: Math, English, Science)", "teachers_label": "اساتذہ (کاما سے جدا کریں، اگر کم ہوں گے تو دوبارہ آئیں گے)", "rooms_label": "کمروں کے نمبر (کاما سے یا نمبر، مثال: 1,2,3 ... 10)", "classes_label": "کلاس فہرست (کاما سے، مثال: 1,2,3,4,5)", "periods_label": "روزانہ دورانیے (عدد)", "duration_label": "ہر دورانیہ (گھنٹے)", "start_label": "شروع وقت (24h، مثال: 8 = 08:00)", "generate_btn": "جنریٹ کریں اور ایکسپورٹ کریں (Word / Text)", "preview_label": "پریویو", "holiday_text": "چھٹی", "word_label": "Word (.docx) ڈاؤن لوڈ کریں", "text_label": "Text (.txt) ڈاؤن لوڈ کریں", "doc_title": "ٹائم ٹیبل" } } HOLIDAY = "Sunday" # ------------------- # Utilities # ------------------- def parse_csv(s: str) -> List[str]: return [p.strip() for p in s.split(",") if p.strip()] def ensure_days(days: List[str]) -> List[str]: # Normalize and ensure Sunday at end cleaned = [] for d in days: d2 = d.strip().capitalize() if d2 and d2.lower() != HOLIDAY.lower(): if d2 not in cleaned: cleaned.append(d2) cleaned.append(HOLIDAY) return cleaned def compute_period_times(start_hour: int, duration_hours: int, periods: int) -> List[str]: times = [] current = datetime(2000,1,1, start_hour, 0) delta = timedelta(hours=duration_hours) for _ in range(periods): end = current + delta times.append(f"{current.strftime('%H:%M')} - {end.strftime('%H:%M')}") current = end return times # ------------------- # Timetable generation # ------------------- def generate_timetable_for_class(days, subjects, teachers, rooms, period_times): """ returns: dict day -> list of {time, subject, teacher, room} """ mapping = {} # ensure rooms list has at least 1 item if not rooms: rooms = ["Room1"] if not teachers: teachers = ["TBD"] if not subjects: subjects = ["Free"] for day in days: if day.lower() == HOLIDAY.lower(): mapping[day] = [] # Holiday marker continue # shuffle subjects for uniqueness order = subjects.copy() random.shuffle(order) day_slots = [] for idx, t in enumerate(period_times): subj = order[idx % len(order)] teacher = teachers[idx % len(teachers)] room = rooms[idx % len(rooms)] day_slots.append({ "time": t, "subject": subj, "teacher": teacher, "room": room }) mapping[day] = day_slots return mapping def generate_full_timetable(class_list, days, subjects, teachers, rooms, period_times): full = {} for cls in class_list: full[cls] = generate_timetable_for_class(days, subjects, teachers, rooms, period_times) return full # ------------------- # Export helpers # ------------------- def export_docx_bytes(full_tt: Dict[str, Dict], labels) -> str: """ Create a temporary .docx file and return its path """ doc = Document() doc.add_heading(labels["doc_title"], level=0) for cls, tt in full_tt.items(): doc.add_heading(f"Class {cls}", level=1) for day, slots in tt.items(): doc.add_heading(day, level=2) if not slots: doc.add_paragraph(labels["holiday_text"]) else: table = doc.add_table(rows=1, cols=4) hdr = table.rows[0].cells hdr[0].text = "Time" hdr[1].text = "Subject" hdr[2].text = "Teacher" hdr[3].text = "Room" for s in slots: row = table.add_row().cells row[0].text = s["time"] row[1].text = s["subject"] row[2].text = s["teacher"] row[3].text = s["room"] doc.add_paragraph("") # small space tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".docx") doc.save(tmp.name) tmp.close() return tmp.name def export_text_bytes(full_tt: Dict[str, Dict], labels) -> str: lines = [] for cls, tt in full_tt.items(): lines.append(f"Class {cls}") lines.append("-" * (8 + len(str(cls)))) for day, slots in tt.items(): lines.append(f"{day}:") if not slots: lines.append(f" {labels['holiday_text']}") else: for s in slots: lines.append(f" {s['time']} | {s['subject']} | {s['teacher']} | Room: {s['room']}") lines.append("") txt = "\n".join(lines) tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".txt", mode="w", encoding="utf-8") tmp.write(txt) tmp.close() return tmp.name # ------------------- # Main Gradio function # ------------------- def build_and_export( language: str, days_input: str, subjects_input: str, teachers_input: str, rooms_input: str, classes_input: str, periods_per_day: int, duration_hours: int, start_hour: int, export_choice: str ): labels = LANG.get(language, LANG["English"]) # parse inputs days = parse_csv(days_input) days = ensure_days(days) subjects = [s.strip() for s in parse_csv(subjects_input)] teachers = [t.strip() for t in parse_csv(teachers_input)] rooms = [r.strip() for r in parse_csv(rooms_input)] or [f"Room{i}" for i in range(1, 11)] class_list = [c.strip() for c in parse_csv(classes_input)] or ["1","2","3","4","5"] # validation if periods_per_day <= 0: return "⚠️ Periods per day must be a positive integer.", None, None if duration_hours <= 0: return "⚠️ Period duration must be a positive integer (hours).", None, None if start_hour < 0 or start_hour > 23: return "⚠️ Start hour must be between 0 and 23.", None, None period_times = compute_period_times(start_hour, duration_hours, periods_per_day) full_tt = generate_full_timetable(class_list, days, subjects, teachers, rooms, period_times) # Build preview text preview_lines = [] for cls, tt in full_tt.items(): preview_lines.append(f"Class {cls}") preview_lines.append("-" * (8 + len(str(cls)))) for day, slots in tt.items(): preview_lines.append(f"{day}:") if not slots: preview_lines.append(f" {labels['holiday_text']}") else: for s in slots: preview_lines.append(f" {s['time']} | {s['subject']} | {s['teacher']} | Room: {s['room']}") preview_lines.append("") preview_text = "\n".join(preview_lines) # Exports docx_path = None txt_path = None if export_choice in ("Word", "Both"): docx_path = export_docx_bytes(full_tt, labels) if export_choice in ("Text", "Both"): txt_path = export_text_bytes(full_tt, labels) # return preview and file paths (Gradio File component accepts a filepath) # If a particular export wasn't requested, return None for its file output return preview_text, docx_path, txt_path # ------------------- # Gradio UI # ------------------- with gr.Blocks(title="Automatic Time Table Generator (Improved)") as demo: # Header with gr.Row(): lang = gr.Dropdown(list(LANG.keys()), value="English", label="Language") gr.Markdown("

Automatic Time Table Generator (Improved)

") with gr.Row(): days_in = gr.Textbox(value="Monday, Tuesday, Wednesday, Thursday, Friday, Saturday", label=LANG["English"]["days_label"]) classes_in = gr.Textbox(value="1,2,3,4,5", label=LANG["English"]["classes_label"]) subjects_in = gr.Textbox(value="Math, English, Science, History, Art", label=LANG["English"]["subjects_label"]) teachers_in = gr.Textbox(value="Mr. Ali, Ms. Fatima, Mr. Khan, Ms. Sara", label=LANG["English"]["teachers_label"]) rooms_in = gr.Textbox(value="1,2,3,4,5,6,7,8,9,10", label=LANG["English"]["rooms_label"]) with gr.Row(): periods_in = gr.Number(value=6, label=LANG["English"]["periods_label"]) duration_in = gr.Number(value=1, label=LANG["English"]["duration_label"]) start_in = gr.Number(value=8, label=LANG["English"]["start_label"]) export_choice = gr.Dropdown(choices=["Word", "Text", "Both"], value="Both", label="Export option") generate_btn = gr.Button(LANG["English"]["generate_btn"], variant="primary") preview = gr.Textbox(label=LANG["English"]["preview_label"], lines=20, interactive=False) download_docx = gr.File(label=LANG["English"]["word_label"]) download_txt = gr.File(label=LANG["English"]["text_label"]) # When language changes, update labels (client-side) def update_ui_labels(selected_lang): labels = LANG.get(selected_lang, LANG["English"]) return ( gr.update(label=labels["days_label"]), gr.update(label=labels["subjects_label"]), gr.update(label=labels["teachers_label"]), gr.update(label=labels["rooms_label"]), gr.update(label=labels["classes_label"]), gr.update(label=labels["periods_label"]), gr.update(label=labels["duration_label"]), gr.update(label=labels["start_label"]), gr.update(value=labels["generate_btn"]), gr.update(label=labels["preview_label"]), gr.update(label=labels["word_label"]), gr.update(label=labels["text_label"]) ) lang.change( fn=update_ui_labels, inputs=[lang], outputs=[days_in, subjects_in, teachers_in, rooms_in, classes_in, periods_in, duration_in, start_in, generate_btn, preview, download_docx, download_txt] ) generate_btn.click( fn=build_and_export, inputs=[lang, days_in, subjects_in, teachers_in, rooms_in, classes_in, periods_in, duration_in, start_in, export_choice], outputs=[preview, download_docx, download_txt] ) if __name__ == "__main__": demo.launch(server_name="0.0.0.0", share=False)