Spaces:
Sleeping
Sleeping
| """ | |
| 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("<h2 style='margin-left:12px'>Automatic Time Table Generator (Improved)</h2>") | |
| 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) | |