musawar32ali's picture
Create app.py
1e2a4ce verified
"""
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)