fix(graph-tree): switch class/timetable data source to Supabase
- _query_teacher_classes: now queries class_teachers + class_students tables instead of non-existent Neo4j TEACHER_HAS_CLASS relationship - _build_classes_section: updated signature to (user_id, institute_id, institute_db) - _build_timetable_section: updated signature; loads classes from Supabase, not Neo4j TIMETABLE_HAS_CLASS - TeacherTimetable lazy handler: simplified (classes pre-loaded in section builder) - AcademicWeek timetable-term: Supabase taught_lessons query by date range instead of Neo4j - expose supabase_institute_id from _resolve_institute call Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
bf3df05632
commit
b71995f4fb
@ -166,27 +166,55 @@ def _query_month_days(month_uuid: str) -> List[Dict]:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def _query_teacher_classes(institute_db: str, teacher_uuid: str) -> List[Dict]:
|
def _query_teacher_classes(user_id: str, institute_id: str, institute_db: str) -> List[Dict]:
|
||||||
|
"""Query classes for a teacher or student from Supabase (source of truth)."""
|
||||||
try:
|
try:
|
||||||
with driver_tools.get_session(database=institute_db) as session:
|
sb = _sb()
|
||||||
result = session.run(
|
teacher_rows = (
|
||||||
"MATCH (t:Teacher {uuid_string: $uuid})-[:TEACHER_HAS_CLASS]->(c:SubjectClass) "
|
sb.supabase.table("class_teachers")
|
||||||
"RETURN c ORDER BY c.name",
|
.select("class_id, is_primary")
|
||||||
uuid=teacher_uuid,
|
.eq("teacher_id", user_id)
|
||||||
|
.execute()
|
||||||
|
.data or []
|
||||||
)
|
)
|
||||||
return [
|
teacher_class_ids = {r["class_id"] for r in teacher_rows}
|
||||||
{
|
student_rows = (
|
||||||
"neo4j_node_id": r["c"]["uuid_string"],
|
sb.supabase.table("class_students")
|
||||||
"label": r["c"].get("name") or "Class",
|
.select("class_id")
|
||||||
|
.eq("student_id", user_id)
|
||||||
|
.eq("status", "active")
|
||||||
|
.execute()
|
||||||
|
.data or []
|
||||||
|
)
|
||||||
|
student_class_ids = {r["class_id"] for r in student_rows}
|
||||||
|
all_ids = list(teacher_class_ids | student_class_ids)
|
||||||
|
if not all_ids:
|
||||||
|
return []
|
||||||
|
classes = (
|
||||||
|
sb.supabase.table("classes")
|
||||||
|
.select("id, name, code, subject")
|
||||||
|
.in_("id", all_ids)
|
||||||
|
.eq("institute_id", institute_id)
|
||||||
|
.eq("is_active", True)
|
||||||
|
.order("name")
|
||||||
|
.execute()
|
||||||
|
.data or []
|
||||||
|
)
|
||||||
|
result = []
|
||||||
|
for c in classes:
|
||||||
|
role = "teacher" if c["id"] in teacher_class_ids else "student"
|
||||||
|
result.append({
|
||||||
|
"neo4j_node_id": c["id"],
|
||||||
|
"label": c.get("name") or "Class",
|
||||||
"node_type": "SubjectClass",
|
"node_type": "SubjectClass",
|
||||||
"neo4j_db_name": institute_db,
|
"neo4j_db_name": institute_db,
|
||||||
"is_section": False,
|
"is_section": False,
|
||||||
"has_children": True,
|
"has_children": True,
|
||||||
}
|
"neo4j_props": {"role": role, "subject": c.get("subject") or ""},
|
||||||
for r in result
|
})
|
||||||
]
|
return result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Could not query classes for teacher {teacher_uuid}: {e}")
|
logger.warning(f"Could not query classes for user {user_id}: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
@ -235,8 +263,8 @@ def _build_calendar_section() -> Dict:
|
|||||||
return _section("calendar", "Calendar", "classroomcopilot", "empty")
|
return _section("calendar", "Calendar", "classroomcopilot", "empty")
|
||||||
|
|
||||||
|
|
||||||
def _build_timetable_section(institute_db: Optional[str], teacher_uuid: Optional[str]) -> Dict:
|
def _build_timetable_section(user_id: str, institute_id: Optional[str], institute_db: Optional[str], teacher_uuid: Optional[str]) -> Dict:
|
||||||
if not institute_db or not teacher_uuid:
|
if not institute_db or not teacher_uuid or not institute_id:
|
||||||
return _section("timetable", "My Timetable", "", "no_school")
|
return _section("timetable", "My Timetable", "", "no_school")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -249,27 +277,8 @@ def _build_timetable_section(institute_db: Optional[str], teacher_uuid: Optional
|
|||||||
if rec:
|
if rec:
|
||||||
tt = rec["tt"]
|
tt = rec["tt"]
|
||||||
tt_uuid = tt["uuid_string"]
|
tt_uuid = tt["uuid_string"]
|
||||||
classes = []
|
# Load classes from Supabase (source of truth)
|
||||||
try:
|
classes = _query_teacher_classes(user_id, institute_id, institute_db)
|
||||||
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 {
|
return {
|
||||||
**_section("timetable", "My Timetable", institute_db, "populated",
|
**_section("timetable", "My Timetable", institute_db, "populated",
|
||||||
has_children=True, children=classes if classes else None),
|
has_children=True, children=classes if classes else None),
|
||||||
@ -283,11 +292,11 @@ def _build_timetable_section(institute_db: Optional[str], teacher_uuid: Optional
|
|||||||
return _section("timetable", "My Timetable", institute_db, "empty")
|
return _section("timetable", "My Timetable", institute_db, "empty")
|
||||||
|
|
||||||
|
|
||||||
def _build_classes_section(institute_db: Optional[str], teacher_uuid: Optional[str]) -> Dict:
|
def _build_classes_section(user_id: str, institute_id: Optional[str], institute_db: Optional[str]) -> Dict:
|
||||||
if not institute_db or not teacher_uuid:
|
if not institute_db or not institute_id:
|
||||||
return _section("classes", "My Classes", "", "no_school")
|
return _section("classes", "My Classes", "", "no_school")
|
||||||
|
|
||||||
classes = _query_teacher_classes(institute_db, teacher_uuid)
|
classes = _query_teacher_classes(user_id, institute_id, institute_db)
|
||||||
if classes:
|
if classes:
|
||||||
return _section("classes", "My Classes", institute_db, "populated",
|
return _section("classes", "My Classes", institute_db, "populated",
|
||||||
has_children=True, children=classes)
|
has_children=True, children=classes)
|
||||||
@ -413,27 +422,8 @@ def _get_children_for_node(
|
|||||||
# TeacherTimetable lazy-load (fallback if not pre-loaded, or for By-Term view)
|
# TeacherTimetable lazy-load (fallback if not pre-loaded, or for By-Term view)
|
||||||
if node_type == "TeacherTimetable" and neo4j_db_name:
|
if node_type == "TeacherTimetable" and neo4j_db_name:
|
||||||
if section_id in ("", "timetable"):
|
if section_id in ("", "timetable"):
|
||||||
try:
|
# Classes are pre-loaded in _build_timetable_section via Supabase.
|
||||||
with driver_tools.get_session(database=neo4j_db_name) as session:
|
# Lazy expansion here is not needed in normal flow.
|
||||||
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 []
|
return []
|
||||||
if section_id == "timetable-term":
|
if section_id == "timetable-term":
|
||||||
try:
|
try:
|
||||||
@ -566,33 +556,52 @@ def _get_children_for_node(
|
|||||||
# AcademicWeek → days (or TaughtLessons in timetable-term context)
|
# AcademicWeek → days (or TaughtLessons in timetable-term context)
|
||||||
if node_type == "AcademicWeek" and neo4j_db_name:
|
if node_type == "AcademicWeek" and neo4j_db_name:
|
||||||
if section_id == "timetable-term" and user_email:
|
if section_id == "timetable-term" and user_email:
|
||||||
|
# Supabase: get week date range from Neo4j, then query taught_lessons
|
||||||
try:
|
try:
|
||||||
|
week_start = None
|
||||||
with driver_tools.get_session(database=neo4j_db_name) as session:
|
with driver_tools.get_session(database=neo4j_db_name) as session:
|
||||||
result = session.run(
|
rec = session.run(
|
||||||
"MATCH (w:AcademicWeek {uuid_string: $id}) "
|
"MATCH (w:AcademicWeek {uuid_string: $id}) "
|
||||||
"-[:ACADEMIC_WEEK_HAS_ACADEMIC_DAY]->(d:AcademicDay) "
|
"RETURN w.start_date AS start_date",
|
||||||
"-[: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,
|
id=neo4j_node_id,
|
||||||
email=user_email,
|
).single()
|
||||||
|
if rec:
|
||||||
|
week_start = rec["start_date"]
|
||||||
|
if week_start:
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
start_dt = datetime.strptime(str(week_start)[:10], "%Y-%m-%d")
|
||||||
|
end_dt = start_dt + timedelta(days=6)
|
||||||
|
sb = _sb()
|
||||||
|
prof = sb.supabase.table("profiles").select("id").eq("email", user_email).single().execute()
|
||||||
|
teacher_id = (prof.data or {}).get("id")
|
||||||
|
if teacher_id:
|
||||||
|
lessons = (
|
||||||
|
sb.supabase.table("taught_lessons")
|
||||||
|
.select("id, date, period_code, class_name, subject")
|
||||||
|
.eq("teacher_profile_id", teacher_id)
|
||||||
|
.gte("date", start_dt.strftime("%Y-%m-%d"))
|
||||||
|
.lte("date", end_dt.strftime("%Y-%m-%d"))
|
||||||
|
.order("date")
|
||||||
|
.order("period_code")
|
||||||
|
.execute()
|
||||||
|
.data or []
|
||||||
)
|
)
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"neo4j_node_id": r["tl"]["uuid_string"],
|
"neo4j_node_id": tl["id"],
|
||||||
"label": (r["tl"].get("period_code") or "")
|
"label": "{} — {}".format(
|
||||||
+ " — "
|
tl.get("period_code") or "",
|
||||||
+ (r["tl"].get("class_name") or r["tl"].get("subject_class") or "Lesson"),
|
tl.get("class_name") or tl.get("subject") or "Lesson"
|
||||||
|
),
|
||||||
"node_type": "TaughtLesson",
|
"node_type": "TaughtLesson",
|
||||||
"neo4j_db_name": neo4j_db_name,
|
"neo4j_db_name": neo4j_db_name,
|
||||||
"is_section": False,
|
"is_section": False,
|
||||||
"has_children": False,
|
"has_children": False,
|
||||||
}
|
}
|
||||||
for r in result
|
for tl in lessons
|
||||||
]
|
]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"AcademicWeek timetable-term lessons failed: {e}")
|
logger.warning(f"AcademicWeek timetable-term Supabase lessons failed: {e}")
|
||||||
return []
|
return []
|
||||||
try:
|
try:
|
||||||
with driver_tools.get_session(database=neo4j_db_name) as session:
|
with driver_tools.get_session(database=neo4j_db_name) as session:
|
||||||
@ -691,12 +700,12 @@ async def get_teacher_graph_tree(
|
|||||||
"has_children": True,
|
"has_children": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, institute_db, teacher_node_uuid = _resolve_institute(user_id, user_email)
|
supabase_institute_id, institute_db, teacher_node_uuid = _resolve_institute(user_id, user_email)
|
||||||
|
|
||||||
sections = [
|
sections = [
|
||||||
_build_calendar_section(),
|
_build_calendar_section(),
|
||||||
_build_timetable_section(institute_db, teacher_node_uuid),
|
_build_timetable_section(user_id, supabase_institute_id, institute_db, teacher_node_uuid),
|
||||||
_build_classes_section(institute_db, teacher_node_uuid),
|
_build_classes_section(user_id, supabase_institute_id, institute_db),
|
||||||
_build_curriculum_section(institute_db),
|
_build_curriculum_section(institute_db),
|
||||||
_build_journal_section(teacher_db),
|
_build_journal_section(teacher_db),
|
||||||
_build_planner_section(teacher_db),
|
_build_planner_section(teacher_db),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user