diff --git a/database/migrations/20260724_g1_homework_field.sql b/database/migrations/20260724_g1_homework_field.sql new file mode 100644 index 0000000..10eb97c --- /dev/null +++ b/database/migrations/20260724_g1_homework_field.sql @@ -0,0 +1,14 @@ +-- G1: Homework as a first-class lesson/planner field. +-- Apply to DEV Supabase only before promoting app/api changes. + +alter table if exists public.taught_lessons + add column if not exists homework text not null default ''; + +alter table if exists public.planned_lessons + add column if not exists homework text not null default ''; + +comment on column public.taught_lessons.homework is + 'Teacher-authored homework for this taught lesson; surfaced in lesson/timetable views.'; + +comment on column public.planned_lessons.homework is + 'Default homework carried by a lesson plan and copied to a taught lesson when delivered.'; diff --git a/modules/database/schemas/nodes/workers/timetable.py b/modules/database/schemas/nodes/workers/timetable.py index 819d66a..1c28f6f 100644 --- a/modules/database/schemas/nodes/workers/timetable.py +++ b/modules/database/schemas/nodes/workers/timetable.py @@ -53,6 +53,7 @@ class PlannedLessonNode(CCBaseNode): subject: str teacher_code: str planning_status: str + homework: Optional[str] = None topic_code: Optional[str] = None topic_name: Optional[str] = None lesson_code: Optional[str] = None diff --git a/routers/database/tools/lesson_plans_router.py b/routers/database/tools/lesson_plans_router.py index 77143fc..f631544 100644 --- a/routers/database/tools/lesson_plans_router.py +++ b/routers/database/tools/lesson_plans_router.py @@ -83,6 +83,7 @@ class CreatePlanRequest(BaseModel): estimated_duration_minutes: Optional[int] = None objectives: Optional[List[Dict[str, Any]]] = None activities: Optional[List[Dict[str, Any]]] = None + homework: Optional[str] = None status: Optional[str] = "draft" tags: Optional[List[str]] = None topic_code: Optional[str] = None @@ -99,6 +100,7 @@ class UpdatePlanRequest(BaseModel): estimated_duration_minutes: Optional[int] = None objectives: Optional[List[Dict[str, Any]]] = None activities: Optional[List[Dict[str, Any]]] = None + homework: Optional[str] = None status: Optional[str] = None tags: Optional[List[str]] = None topic_code: Optional[str] = None @@ -249,6 +251,7 @@ async def create_plan( "status": body.status or "draft", "objectives": body.objectives or [], "activities": body.activities or [], + "homework": body.homework or "", "tags": body.tags or [], } for field in ( @@ -362,7 +365,7 @@ async def update_plan( updates: Dict[str, Any] = {} for field in ( "title", "class_id", "subject", "year_group", "estimated_duration_minutes", - "objectives", "activities", "status", "tags", "topic_code", + "objectives", "activities", "homework", "status", "tags", "topic_code", "whiteboard_room_id", "course_id", "sequence_number", ): val = getattr(body, field) @@ -425,7 +428,7 @@ async def deliver_plan( plan_res = ( sb.supabase.table("planned_lessons") - .select("id, whiteboard_room_id") + .select("id, whiteboard_room_id, homework") .eq("id", plan_id) .eq("institute_id", institute_id) .single() @@ -456,6 +459,19 @@ async def deliver_plan( res = sb.supabase.table("lesson_deliveries").insert(row).execute() delivery = (res.data or [{}])[0] + + # If the plan has default homework and was delivered into a taught lesson, + # copy it onto the lesson so homework appears in daily/timetable views. + plan_homework = plan_res.data.get("homework") + if body.taught_lesson_id and plan_homework is not None: + try: + sb.supabase.table("taught_lessons").update({ + "homework": plan_homework, + "updated_at": datetime.utcnow().isoformat(), + }).eq("id", body.taught_lesson_id).eq("teacher_id", user_id).execute() + except Exception as e: + logger.warning(f"Could not copy homework to taught_lesson {body.taught_lesson_id}: {e}") + logger.info(f"Lesson delivery created: {delivery.get('id')} for plan {plan_id} by {user_id}") return delivery diff --git a/routers/database/tools/taught_lessons_router.py b/routers/database/tools/taught_lessons_router.py index 46a33f0..1da4942 100644 --- a/routers/database/tools/taught_lessons_router.py +++ b/routers/database/tools/taught_lessons_router.py @@ -4,7 +4,7 @@ Taught Lessons Router — materialization and lesson CRUD. POST /materialize — slot template × academic_periods → taught_lessons rows GET /lessons — teacher's lessons for a date range GET /lessons/{id} — single lesson detail -PATCH /lessons/{id} — update lesson_plan, notes, status (teacher-owned) +PATCH /lessons/{id} — update lesson_plan, homework, notes, status (teacher-owned) """ import os from collections import defaultdict @@ -44,6 +44,7 @@ def _require_institute(user_id: str) -> str: class UpdateLessonRequest(BaseModel): lesson_plan: Optional[Dict[str, Any]] = None + homework: Optional[str] = None notes: Optional[str] = None status: Optional[str] = None # planned | in_progress | completed | cancelled | substituted @@ -310,7 +311,7 @@ async def get_lessons( lessons = ( sb.supabase.table("taught_lessons") .select( - "id,date,period_code,week_cycle,day_of_week,status,lesson_plan,notes,whiteboard_room_id," + "id,date,period_code,week_cycle,day_of_week,status,lesson_plan,homework,notes,whiteboard_room_id," "class_id,academic_period_id" ) .eq("teacher_id", user_id) @@ -418,13 +419,15 @@ async def update_lesson( body: UpdateLessonRequest, credentials: dict = Depends(SupabaseBearer()), ) -> Dict[str, Any]: - """Teacher updates their own lesson content: plan, notes, status.""" + """Teacher updates their own lesson content: plan, homework, notes, status.""" user_id = credentials.get("sub", "") sb = _sb() updates: Dict[str, Any] = {} if body.lesson_plan is not None: updates["lesson_plan"] = body.lesson_plan + if body.homework is not None: + updates["homework"] = body.homework if body.notes is not None: updates["notes"] = body.notes if body.status is not None: @@ -508,7 +511,7 @@ async def get_student_lessons( lessons = ( sb.supabase.table("taught_lessons") .select( - "id,date,period_code,week_cycle,day_of_week,status,lesson_plan,notes,whiteboard_room_id," + "id,date,period_code,week_cycle,day_of_week,status,lesson_plan,homework,notes,whiteboard_room_id," "class_id,academic_period_id,teacher_id" ) .in_("class_id", class_ids) diff --git a/routers/database/tools/timetable_builder_router.py b/routers/database/tools/timetable_builder_router.py index 6db4a24..5692c7f 100644 --- a/routers/database/tools/timetable_builder_router.py +++ b/routers/database/tools/timetable_builder_router.py @@ -675,7 +675,7 @@ def _sync_taught_lessons_to_neo4j( """ lessons = ( sb.supabase.table("taught_lessons") - .select("id,neo4j_node_id,academic_period_id,teacher_id,date,period_code,week_cycle,day_of_week,status") + .select("id,neo4j_node_id,academic_period_id,teacher_id,date,period_code,week_cycle,day_of_week,status,homework") .eq("institute_id", institute_id) .execute() .data or [] @@ -728,6 +728,7 @@ def _sync_taught_lessons_to_neo4j( SET tl.date = date($date), tl.period_code = $pcode, tl.week_cycle = $wc, tl.day_of_week = $dow, tl.status = $status, + tl.homework = $homework, tl.node_storage_path = $path """, id=tl_id, @@ -736,6 +737,7 @@ def _sync_taught_lessons_to_neo4j( wc=lesson.get("week_cycle", ""), dow=lesson.get("day_of_week", ""), status=lesson.get("status", "planned"), + homework=lesson.get("homework") or "", path=f"taught_lessons/{tl_id}", ) count += 1