import json from datetime import datetime # PDF courses: (SP, Name, Dozent, SWS, CP, Turnus, shortname(s) in WebUntis) # Source: 260806_WPF_TI_FKE_WiSe2026.pdf (Wahlpflichtmodule FK Elektrotechnik, WiSe 2026/27) # Full list of all modules. The last field is either a single shortname, a list # of shortnames (module split over several timetable entries, e.g. lecture + # Praktikum), or None if no matching timetable entry was found. # Existing shortnames were kept; new ones were looked up in timetables.json, # preferring the entry that actually carries scheduled dates. PDF_COURSES = [ # Aufbau ("Aufbau", "Elektrische Messtechnik", "Frey", 4, 5, "WiSe", "E-EMT"), ("Aufbau", "Elektromagnetische Verträglichkeit (EMV)", "Stolle", 4, 5, "WiSe", "E-EMV"), ("Aufbau", "Feldlehre", "Meyer", 4, 5, "WiSe", "E-ET4"), ("Aufbau", "Messtechnik Praktikum", "Großmann", 2, 2, "SoSe", "E-MT.PR"), # Autom./Robotik ("Autom./Robotik", "Antriebstechnik", "Meyer", 4, 5, "SoSe", "E-ANT"), ("Autom./Robotik", "Antriebstechnik Praktikum", "Meyer", 2, 2, "WiSe", "E-ANT.PR"), ("Autom./Robotik", "Automatisierungstechnik 1", "Zeller", 4, 5, "WiSe/SoSe", "E-AUT.1"), ("Autom./Robotik", "Automatisierungstechnik 2", "Zeller / Danzer", 4, 5, "WiSe", "E-AUT.2"), ("Autom./Robotik", "Automatisierungstechnik Praktikum", "Zeller", 2, 2, "WiSe/SoSe", "E-AUT.PR"), ("Autom./Robotik", "Robot Systems Engineering", "Dietrich", 4, 5, "SoSe", "E-RSE"), ("Autom./Robotik", "Robot Systems Engineering Praktikum", "Dietrich", 2, 2, "WiSe", "E-RSE.PR"), # Elektronik ("Elektronik", "Automobilelektronik", "Schurk", 2, 2, "WiSe/SoSe", "E-%AUTOM"), ("Elektronik", "Formula Student Electric", "Markgraf", 4, 5, "WiSe/SoSe", None), ("Elektronik", "Fortgeschrittene Messtechnik", "Großmann", 4, 5, "SoSe", "E-FMT"), ("Elektronik", "IoT - Methoden der industriellen Bildverarbeitung", "Zeuke / Steyer", 2, 2, "WiSe", "E-IOT"), ("Elektronik", "Mikroelektronik", "Beckmann", 4, 5, "WiSe", "E-MIKEL"), ("Elektronik", "Schaltungstechnik", "Zedler", 4, 5, "WiSe/SoSe", "E-SCHT"), ("Elektronik", "Schaltungstechnik Praktikum", "Zedler", 2, 2, "WiSe", "E-SCHT.PR"), ("Elektronik", "System-on-Chip Design", "Beckmann", 4, 5, "WiSe", ["E-SOC", "E-SOC.PR"]), # Energietechnik ("Energietechnik", "Elektrische Maschinen", "Meyer", 4, 5, "SoSe", "E-ELMA"), ("Energietechnik", "Elektrische Maschinen Praktikum", "Meyer", 2, 2, "WiSe", "E-ELMA.PR"), ("Energietechnik", "Energiespeicher", "Schwägerl", 2, 2, "WiSe", "E-ENSP"), ("Energietechnik", "Energietechnische Anlagen", "Finkel", 4, 5, "WiSe", "E-ENAN"), ("Energietechnik", "Erneuerbare Energien", "Schwägerl", 4, 5, "SoSe", "E-EREN"), ("Energietechnik", "Erneuerbare Energien Praktikum", "Schwägerl", 2, 2, "WiSe/SoSe", "E-EREN.PR"), ("Energietechnik", "Hochspannungstechnik", "Finkel", 4, 5, "SoSe", "E-HST"), ("Energietechnik", "Hochspannungstechnik Praktikum", "Finkel", 2, 2, "WiSe", "E-HST.PR"), ("Energietechnik", "Leistungselektronik", "Ritter", 4, 5, "SoSe", "E-LE"), ("Energietechnik", "Leistungselektronik Praktikum", "Ritter", 2, 2, "WiSe", "E-LE.PR"), ("Energietechnik", "Smart Grid Fundamentals", "Finkel", 2, 2, "WiSe", "E-%SGridF"), # Information ("Information", "Digitale Zwillinge: Grundkonzepte und Anwendungen", "Legat", 4, 5, "SoSe", "E-DIGTWIN"), ("Information", "Edge AI – Grundlagen und Anwendungen", "Dietrich", 2, 2, "SoSe", None), ("Information", "Eingebettete Echtzeitsysteme mit Praktikum", "Werthschulte", 4, 5, "SoSe", "E-EES"), ("Information", "Industrial Security Basics", "Hollmann", 4, 5, "SoSe", "E-IS1C5"), ("Information", "Künstliche Intelligenz: Grundlagen und Anwendungen", "Legat", 4, 5, "WiSe", "E-KI"), ("Information", "Matlab Simulink", "Werthschulte", 2, 2, "WiSe/SoSe", "E-%MATLAB"), ("Information", "Smartphone und IoT-Sicherheit", "Werthschulte", 2, 2, "WiSe", "E-SEC"), # Kommunikation ("Kommunikation", "Digitale Kommunikation mit Praktikum", "Kamuf", 4, 5, "SoSe", "E-DIGK"), ("Kommunikation", "Entwurf moderner Kommunikationssysteme mit Praktikum", "Kamuf", 4, 5, "WiSe", ["E-EMK", "E-EMK.PR"]), ("Kommunikation", "Funksysteme", "Stolle", 4, 5, "WiSe", "E-FUSYS"), ("Kommunikation", "Funktechnik in der Praxis", "Bögl", 2, 2, "SoSe", "E-%FUNKT"), ("Kommunikation", "Hochfrequenz-Schaltungstechnik mit Praktikum", "Stolle", 4, 5, "SoSe", "E-HFSCH"), ("Kommunikation", "Hochfrequenzsysteme mit Praktikum", "Stolle", 4, 5, "SoSe", "E-HFSYS"), # Übergreifend ("Übergreifend", "Advanced Topics in Electrical Engineering", "Gastdozierende", 2, 2, "WiSe/SoSe", None), ("Übergreifend", "Amateurfunk", "Stolle / Peter", 2, 2, "WiSe/SoSe", "E-%AF"), ("Übergreifend", "Betriebsorganisation", "Zeller / Tammer", 2, 2, "WiSe", "E-%BO"), ("Übergreifend", "Betriebswirtschaftslehre für Ingenieure", "Finkel", 2, 2, "WiSe", "E-BWL_I"), ("Übergreifend", "Elektrokonstruktion mit E-Plan", "Danzer / Voicau-Ottlik", 2, 2, "SoSe", "E-%EPLAN"), ("Übergreifend", "Elektronikproduktion", "Dietrich / Baur", 2, 2, "SoSe", "E-EP"), ("Übergreifend", "Existenzgründung", "Adam", 2, 2, "WiSe", "E-EXIGRUE"), ("Übergreifend", "Fertigungstechnik", "Dietrich", 4, 5, "SoSe", "E-FT"), ("Übergreifend", "Kinematik und Kinetik", "Eckert", 4, 5, "WiSe", "E-KIN"), ("Übergreifend", "Multiphysics Simulation", "Frey", 2, 2, "WiSe", "E-MPHY.SIM"), ("Übergreifend", "Nachhaltiges Management industrieller Produktion", "Dietrich", 2, 2, "WiSe", "E-FTN"), ("Übergreifend", "Optimale Produkte und Prozesse", "Frommelt", 2, 2, "WiSe", "E-OPRO"), ("Übergreifend", "Projekt \"Technische Informatik\"", "", 2, 2, "WiSe/SoSe", None), ("Übergreifend", "Projektseminar \"Technische Informatik\"", "", 2, 2, "WiSe/SoSe", None), ("Übergreifend", "Ringvorlesung „Energie und Ökologie\"", "Schwägerl", 2, 2, "WiSe", "E-ENOEK"), ("Übergreifend", "Systemdenken im Produktentstehungsprozess", "Königbauer", 4, 5, "SoSe", "E-THINK"), ("Übergreifend", "Systems Engineering", "Frommelt", 4, 5, "WiSe/SoSe", None), ("Übergreifend", "Zeit- und Selbstmanagement", "Manhardt", 2, 2, "SoSe", "E-ZEITM"), ] DAY_NAMES = { 0: "Montag", 1: "Dienstag", 2: "Mittwoch", 3: "Donnerstag", 4: "Freitag", 5: "Samstag", } with open("timetables.json", 'r') as f: timetables = json.load(f) # Build lookup by shortname by_shortname = {} for course in timetables: by_shortname[course["shortname"]] = course def get_slots(course_data): """Extract (day_name, start_time, end_time, room) tuples from timetable data.""" slots = [] for day in course_data["timetable"].get("days", []): for grid in day.get("gridEntries", []): start = datetime.fromisoformat(grid["duration"]["start"]) end = datetime.fromisoformat(grid["duration"]["end"]) day_name = DAY_NAMES[start.weekday()] start_time = start.strftime("%H:%M") end_time = end.strftime("%H:%M") rooms = [] for room in (grid.get("position2") or []): if room.get("current"): rooms.append(room["current"]["displayName"]) room_str = "/".join(rooms) if rooms else "" slots.append((day_name, start_time, end_time, room_str)) return slots def merge_slots(slots): """Merge consecutive slots on the same day into single blocks.""" if not slots: return [] # Group by day by_day = {} for day, start, end, room in slots: by_day.setdefault(day, []).append((start, end, room)) merged = [] for day in DAY_NAMES.values(): if day not in by_day: continue entries = sorted(by_day[day]) # Merge consecutive entries (end of one == start of next, same day) current_start, current_end, current_room = entries[0] rooms = {current_room} if current_room else set() for start, end, room in entries[1:]: if start <= current_end: current_end = max(current_end, end) if room: rooms.add(room) else: merged.append((day, current_start, current_end, "/".join(sorted(rooms)))) current_start, current_end = start, end rooms = {room} if room else set() merged.append((day, current_start, current_end, "/".join(sorted(rooms)))) return merged kurse = [] no_data = [] for sp, name, dozent, sws, cp, turnus, shortname in PDF_COURSES: # Turnus -> Semester: WiSe/SoSe -> WS/SS, WiSe -> WS, SoSe -> SS semester = {"WiSe/SoSe": "WS/SS", "WiSe": "WS", "SoSe": "SS"}[turnus] # shortname may be a single shortname, a list of shortnames, or None. # A module can have several timetable entries (e.g. lecture + Praktikum # under different shortnames); we collect the slots of all of them. if shortname is None: shortnames = [] elif isinstance(shortname, (list, tuple)): shortnames = list(shortname) else: shortnames = [shortname] slots = [] for sn in shortnames: course_data = by_shortname.get(sn) if course_data: slots.extend(get_slots(course_data)) merged = merge_slots(slots) if not merged: no_data.append(name) entry = { "Vertiefung": sp, "Name": name, "Dozent": dozent, "Semester": semester, "SWS": str(sws), "CP": str(cp), "Tag1": "", "Startzeit1": "", "Ende1": "", "Raum1": "", "Tag2": "", "Startzeit2": "", "Ende2": "", "Raum2": "", "Tag3": "", "Startzeit3": "", "Ende3": "", "Raum3": "", "Tag4": "", "Startzeit4": "", "Ende4": "", "Raum4": "", } for i, slot in enumerate(merged[:4], 1): entry[f"Tag{i}"] = slot[0] entry[f"Startzeit{i}"] = slot[1] entry[f"Ende{i}"] = slot[2] entry[f"Raum{i}"] = slot[3] if len(merged) > 4: print(f" WARNING: {name} has {len(merged)} time blocks, only first 4 used") kurse.append(entry) with open("kurse.json", 'w') as f: json.dump(kurse, f, ensure_ascii=False, indent=2) print(f"Generated kurse.json with {len(kurse)} courses") if no_data: print(f"\nNo timetable data for {len(no_data)} courses:") for n in no_data: print(f" - {n}")