diff --git a/routers/database/tools/graph_tree_router.py b/routers/database/tools/graph_tree_router.py index c932cdc..7d20e4a 100644 --- a/routers/database/tools/graph_tree_router.py +++ b/routers/database/tools/graph_tree_router.py @@ -248,10 +248,32 @@ def _build_timetable_section(institute_db: Optional[str], teacher_uuid: Optional ).single() if rec: tt = rec["tt"] + tt_uuid = tt["uuid_string"] + classes = [] + try: + cls_result = session.run( + "MATCH (tt2:TeacherTimetable {uuid_string: $id})" + "-[:TIMETABLE_HAS_CLASS]->(c:SubjectClass) " + "RETURN c ORDER BY c.name", + id=tt_uuid, + ) + classes = [ + { + "neo4j_node_id": r["c"]["uuid_string"], + "label": r["c"].get("name") or "Class", + "node_type": "SubjectClass", + "neo4j_db_name": institute_db, + "is_section": False, + "has_children": True, + } + for r in cls_result + ] + except Exception: + pass return { **_section("timetable", "My Timetable", institute_db, "populated", - has_children=True), - "neo4j_node_id": tt["uuid_string"], + has_children=True, children=classes if classes else None), + "neo4j_node_id": tt_uuid, "node_type": "TeacherTimetable", "is_section": True, } @@ -379,6 +401,7 @@ def _get_children_for_node( neo4j_db_name: str, node_type: str, section_id: str = "", + user_email: str = "", ) -> List[Dict]: # Calendar if node_type == "CalendarYear": @@ -387,6 +410,57 @@ def _get_children_for_node( if node_type == "CalendarMonth": return _query_month_days(neo4j_node_id) + # TeacherTimetable lazy-load (fallback if not pre-loaded, or for By-Term view) + if node_type == "TeacherTimetable" and neo4j_db_name: + if section_id in ("", "timetable"): + try: + with driver_tools.get_session(database=neo4j_db_name) as session: + result = session.run( + "MATCH (tt:TeacherTimetable {uuid_string: $id})" + "-[:TIMETABLE_HAS_CLASS]->(c:SubjectClass) " + "RETURN c ORDER BY c.name", + id=neo4j_node_id, + ) + return [ + { + "neo4j_node_id": r["c"]["uuid_string"], + "label": r["c"].get("name") or "Class", + "node_type": "SubjectClass", + "neo4j_db_name": neo4j_db_name, + "is_section": False, + "has_children": True, + } + for r in result + ] + except Exception as e: + logger.warning(f"TeacherTimetable class children failed: {e}") + return [] + if section_id == "timetable-term": + try: + with driver_tools.get_session(database=neo4j_db_name) as session: + result = session.run( + "MATCH (tt:TeacherTimetable {uuid_string: $id}) " + "-[:ACADEMIC_TIMETABLE_HAS_ACADEMIC_YEAR]->(ay:AcademicYear) " + "-[:ACADEMIC_YEAR_HAS_ACADEMIC_TERM]->(t:AcademicTerm) " + "RETURN t ORDER BY toInteger(t.term_number)", + id=neo4j_node_id, + ) + return [ + { + "neo4j_node_id": r["t"]["uuid_string"], + "label": r["t"].get("term_name") or "Term {}".format(r["t"].get("term_number", "")), + "node_type": "AcademicTerm", + "neo4j_db_name": neo4j_db_name, + "section_id": "timetable-term", + "is_section": False, + "has_children": True, + } + for r in result + ] + except Exception as e: + logger.warning(f"TeacherTimetable term children failed: {e}") + return [] + # Section containers that need lazy loading if node_type == "Section": if section_id == "timetable" and neo4j_db_name: @@ -489,17 +563,46 @@ def _get_children_for_node( logger.warning(f"AcademicYear children failed: {e}") return [] - # AcademicWeek → days + # AcademicWeek → days (or TaughtLessons in timetable-term context) if node_type == "AcademicWeek" and neo4j_db_name: + if section_id == "timetable-term" and user_email: + try: + with driver_tools.get_session(database=neo4j_db_name) as session: + result = session.run( + "MATCH (w:AcademicWeek {uuid_string: $id}) " + "-[:ACADEMIC_WEEK_HAS_ACADEMIC_DAY]->(d:AcademicDay) " + "-[:ACADEMIC_DAY_HAS_PERIOD]->(p:AcademicPeriod) " + "-[:ACADEMIC_PERIOD_HAS_TAUGHT_LESSON]->(tl:TaughtLesson) " + "WHERE tl.teacher_email = " + "RETURN tl, d.date AS date ORDER BY d.date, p.start_time", + id=neo4j_node_id, + email=user_email, + ) + return [ + { + "neo4j_node_id": r["tl"]["uuid_string"], + "label": (r["tl"].get("period_code") or "") + + " — " + + (r["tl"].get("class_name") or r["tl"].get("subject_class") or "Lesson"), + "node_type": "TaughtLesson", + "neo4j_db_name": neo4j_db_name, + "is_section": False, + "has_children": False, + } + for r in result + ] + except Exception as e: + logger.warning(f"AcademicWeek timetable-term lessons failed: {e}") + return [] try: with driver_tools.get_session(database=neo4j_db_name) as session: result = session.run( - "MATCH (w:AcademicWeek {uuid_string: })" + "MATCH (w:AcademicWeek {uuid_string: $id}) " "-[:ACADEMIC_WEEK_HAS_ACADEMIC_DAY]->(d:AcademicDay) " "RETURN d ORDER BY d.date", id=neo4j_node_id, ) - days = [ + return [ { "neo4j_node_id": r["d"]["uuid_string"], "label": r["d"].get("date", ""), @@ -510,7 +613,6 @@ def _get_children_for_node( } for r in result ] - return days except Exception as e: logger.warning(f"AcademicWeek children failed: {e}") return [] @@ -528,9 +630,10 @@ def _get_children_for_node( return [ { "neo4j_node_id": r["w"]["uuid_string"], - "label": f"Week {r['w']['academic_week_number']}", + "label": "Week {}".format(r["w"].get("academic_week_number", r["w"].get("week_number", "?"))), "node_type": "AcademicWeek", "neo4j_db_name": neo4j_db_name, + "section_id": section_id if section_id == "timetable-term" else "", "is_section": False, "has_children": True, } @@ -620,7 +723,8 @@ async def get_node_children( section_id: str = "", credentials: dict = Depends(SupabaseBearer()), ) -> Dict[str, Any]: - children = _get_children_for_node(neo4j_node_id, neo4j_db_name, node_type, section_id) + user_email = credentials.get("email", "") + children = _get_children_for_node(neo4j_node_id, neo4j_db_name, node_type, section_id, user_email) return {"status": "success", "children": children}