242 lines
16 KiB
Python
242 lines
16 KiB
Python
from dotenv import load_dotenv, find_dotenv
|
|
load_dotenv(find_dotenv())
|
|
import os
|
|
import modules.logger_tool as logger
|
|
log_name = 'api_modules_database_init_init_worker_timetable'
|
|
log_dir = os.getenv("LOG_PATH", "/logs") # Default path as fallback
|
|
logging = logger.get_logger(
|
|
name=log_name,
|
|
log_level=os.getenv("LOG_LEVEL", "DEBUG"),
|
|
log_path=log_dir,
|
|
log_file=log_name,
|
|
runtime=True,
|
|
log_format='default'
|
|
)
|
|
import pandas as pd
|
|
import re
|
|
import modules.database.tools.neo4j_driver_tools as driver
|
|
import modules.database.tools.neontology_tools as neon
|
|
import modules.database.tools.neo4j_session_tools as session
|
|
from modules.database.tools.filesystem_tools import ClassroomCopilotFilesystem
|
|
from modules.database.schemas.nodes.schools.schools import SubjectClassNode
|
|
from modules.database.schemas.nodes.workers.workers import TeacherNode
|
|
from modules.database.schemas.nodes.schools.timetable import AcademicPeriodNode, RegistrationPeriodNode
|
|
from modules.database.schemas.nodes.workers.timetable import TeacherTimetableNode, TimetableLessonNode, PlannedLessonNode
|
|
from modules.database.schemas.nodes.schools.pastoral import YearGroupSyllabusNode
|
|
from modules.database.schemas.relationships.planning_relationships import TimetableLessonBelongsToPeriod, TimetableLessonHasPlannedLesson, TeacherHasTimetable, TimetableHasClass, ClassHasLesson, TimetableLessonFollowsTimetableLesson, PlannedLessonFollowsPlannedLesson, SubjectClassBelongsToYearGroupSyllabus
|
|
|
|
def init_worker_timetable(timetable_df: pd.DataFrame, school_worker_node: TeacherNode):
|
|
logging.info(f"School worker node: {school_worker_node}")
|
|
worker_node = TeacherNode(**school_worker_node)
|
|
logging.info(f"Worker node: {worker_node}")
|
|
worker_db_name = worker_node.worker_db_name
|
|
|
|
logging.info(f"Initialising filesystem handler...")
|
|
fs_handler = ClassroomCopilotFilesystem(db_name=worker_db_name, init_run_type="user")
|
|
_, worker_timetable_path = fs_handler.create_teacher_timetable_directory(worker_node.path)
|
|
|
|
logging.info(f"Initialising neo4j connection...")
|
|
neon.init_neontology_connection()
|
|
|
|
try:
|
|
timetable_unique_id = f"TeacherTimetable_{worker_node.teacher_code}"
|
|
worker_timetable = TeacherTimetableNode(
|
|
unique_id=timetable_unique_id,
|
|
path=worker_timetable_path
|
|
)
|
|
neon.create_or_merge_neontology_node(worker_timetable, database=worker_db_name, operation='merge')
|
|
fs_handler.create_default_tldraw_file(worker_timetable.path, worker_timetable.to_dict())
|
|
neon.create_or_merge_neontology_relationship(
|
|
TeacherHasTimetable(source=worker_node, target=worker_timetable),
|
|
database=worker_db_name, operation='merge'
|
|
)
|
|
logging.info(f"Teacher timetable node created: {worker_timetable}")
|
|
|
|
# Group the timetable by class
|
|
class_groups = timetable_df.groupby('Class')
|
|
for class_name, class_df in class_groups:
|
|
if pd.notna(class_name):
|
|
class_name_safe = re.sub(r'[^A-Za-z0-9_ ]+', '', class_name)
|
|
_, class_path = fs_handler.create_teacher_class_directory(worker_timetable.path, class_name_safe)
|
|
|
|
subject_class_node_unique_id = f"SubjectClass_{class_name}"
|
|
subject_class_node = SubjectClassNode(
|
|
unique_id=subject_class_node_unique_id,
|
|
subject_class_code=class_name,
|
|
year_group=str(int(class_df['YearGroup'].iloc[0])), # TODO: Hacky fix for the year group being a float
|
|
subject=str(class_df['Subject'].iloc[0]),
|
|
subject_code=str(class_df['SubjectCode'].iloc[0]),
|
|
path=class_path
|
|
)
|
|
neon.create_or_merge_neontology_node(subject_class_node, database=worker_db_name, operation='merge')
|
|
logging.info(f"Class node created: {subject_class_node}")
|
|
# Create the tldraw file for the node
|
|
fs_handler.create_default_tldraw_file(subject_class_node.path, subject_class_node.to_dict())
|
|
|
|
# Link ClassNode to TeacherTimetableNode
|
|
neon.create_or_merge_neontology_relationship(
|
|
TimetableHasClass(source=worker_timetable, target=subject_class_node),
|
|
database=worker_db_name, operation='merge'
|
|
)
|
|
logging.info(f"Relationship created from {worker_timetable.unique_id} to {subject_class_node.unique_id}")
|
|
|
|
# Link class to corresponding YearGoupSyllabus
|
|
|
|
year_group_syllabus_search_driver = driver.get_driver(worker_db_name)
|
|
year_group_syllabus_search_session = year_group_syllabus_search_driver.session(database=worker_db_name)
|
|
year_group_syllabus = session.find_nodes_by_label_and_properties(year_group_syllabus_search_session, "YearGroupSyllabus", {"yr_syllabus_year_group": subject_class_node.year_group, "yr_syllabus_subject_code": subject_class_node.subject_code})
|
|
if year_group_syllabus:
|
|
year_group_syllabus_node_data = year_group_syllabus[0]
|
|
year_group_syllabus_node = YearGroupSyllabusNode(**year_group_syllabus_node_data)
|
|
neon.create_or_merge_neontology_relationship(
|
|
SubjectClassBelongsToYearGroupSyllabus(source=subject_class_node, target=year_group_syllabus_node),
|
|
database=worker_db_name, operation='merge'
|
|
)
|
|
logging.info(f"Relationship created from {subject_class_node.unique_id} to {year_group_syllabus_node.unique_id}")
|
|
else:
|
|
logging.warning(f"No YearGroupSyllabus found for class {class_name} with year group {subject_class_node.year_group} and subject code {subject_class_node.subject_code}")
|
|
|
|
class_lesson_nodes = []
|
|
planned_lesson_nodes = []
|
|
lesson_number = 0
|
|
for _, row in class_df.iterrows():
|
|
properties = {
|
|
"period_code": row['PeriodCode']
|
|
}
|
|
class_lessons_search_driver = driver.get_driver(worker_db_name)
|
|
class_lessons_search_session = class_lessons_search_driver.session(database=worker_db_name)
|
|
# If the period code contains "Rg" then we want to find the corresponding registration period and use its unique id
|
|
if "Rg" in row['PeriodCode']: # TODO: This is hacky and not very flexible. We are assuming that any period code containing "Rg" is a registration period. We should probably find a more robust way to identify registration periods
|
|
logging.info(f"Registration period found for class {class_name} with period code {row['PeriodCode']}")
|
|
class_lessons = session.find_nodes_by_label_and_properties(class_lessons_search_session, "RegistrationPeriod", properties)
|
|
else:
|
|
logging.info(f"Academic period found for class {class_name} with period code {row['PeriodCode']}")
|
|
class_lessons = session.find_nodes_by_label_and_properties(class_lessons_search_session, "AcademicPeriod", properties)
|
|
if class_lessons:
|
|
lesson_of_same_period = 0
|
|
number_of_lessons = len(class_lessons)
|
|
while lesson_of_same_period < number_of_lessons:
|
|
class_lesson = class_lessons[lesson_of_same_period]
|
|
if "Rg" in row['PeriodCode']:
|
|
period_node = RegistrationPeriodNode(**class_lesson)
|
|
else:
|
|
period_node = AcademicPeriodNode(**class_lesson)
|
|
lesson_period_code = row['PeriodCode']
|
|
date = class_lesson['date']
|
|
date_safe = date.strftime("%Y-%m-%d")
|
|
# Clean the class_name to make it directory-safe (catch all for invalid characters)
|
|
timetable_lesson_unique_id = f"TimetableLesson_{timetable_unique_id}_Class_{class_name}_Lesson_{lesson_number}_{date_safe}_{lesson_period_code}"
|
|
|
|
timetable_lesson_node = TimetableLessonNode(
|
|
unique_id=timetable_lesson_unique_id,
|
|
subject_class=class_name,
|
|
date=date,
|
|
start_time=class_lesson['start_time'].time(), # TODO: This is probably how we should format the start and end time properties for all such nodes
|
|
end_time=class_lesson['end_time'].time(),
|
|
period_code=lesson_period_code,
|
|
path="Not set"
|
|
)
|
|
neon.create_or_merge_neontology_node(timetable_lesson_node, database=worker_db_name, operation='merge')
|
|
logging.info(f"TimetableLessonNode created: {timetable_lesson_node}")
|
|
class_lesson_nodes.append(timetable_lesson_node)
|
|
|
|
neon.create_or_merge_neontology_relationship(
|
|
TimetableLessonBelongsToPeriod(source=timetable_lesson_node, target=period_node),
|
|
database=worker_db_name, operation='merge'
|
|
)
|
|
logging.info(f"Relationship created from {timetable_lesson_node.unique_id} to {period_node.unique_id}")
|
|
|
|
# Link TimetableLessonNode to ClassNode
|
|
neon.create_or_merge_neontology_relationship(
|
|
ClassHasLesson(source=subject_class_node, target=timetable_lesson_node),
|
|
database=worker_db_name, operation='merge'
|
|
)
|
|
logging.info(f"Relationship created from {subject_class_node.unique_id} to {timetable_lesson_node.unique_id}")
|
|
|
|
# Create PlannedLessonNode
|
|
planned_lesson_unique_id = f"PlannedLesson_{timetable_unique_id}_Class_{class_name}_Lesson_{lesson_number}_{date_safe}_{lesson_period_code}"
|
|
planned_lesson_node = PlannedLessonNode(
|
|
unique_id=planned_lesson_unique_id,
|
|
date=date,
|
|
start_time=class_lesson['start_time'].time(),
|
|
end_time=class_lesson['end_time'].time(),
|
|
period_code=lesson_period_code,
|
|
subject_class=class_name,
|
|
year_group=subject_class_node.year_group,
|
|
subject=subject_class_node.subject,
|
|
teacher_code=worker_node.teacher_code,
|
|
planning_status="Unplanned",
|
|
topic_code=None,
|
|
topic_name=None,
|
|
lesson_code=None,
|
|
lesson_name=None,
|
|
learning_statement_codes=None,
|
|
learning_statements=None,
|
|
learning_resource_codes=None,
|
|
learning_resources=None,
|
|
path="Not set"
|
|
)
|
|
# Create the PlannedLessonNode
|
|
neon.create_or_merge_neontology_node(planned_lesson_node, database=worker_db_name, operation='merge')
|
|
logging.info(f"PlannedLessonNode created: {planned_lesson_node}")
|
|
planned_lesson_nodes.append(planned_lesson_node)
|
|
|
|
# Link PlannedLessonNode to TimetableLessonNode
|
|
neon.create_or_merge_neontology_relationship(
|
|
TimetableLessonHasPlannedLesson(source=timetable_lesson_node, target=planned_lesson_node),
|
|
database=worker_db_name, operation='merge'
|
|
)
|
|
logging.info(f"Relationship created from {timetable_lesson_node.unique_id} to {planned_lesson_node.unique_id}")
|
|
lesson_of_same_period += 1
|
|
lesson_number += 1
|
|
else:
|
|
logging.warning(f"No class periods found for class {class_name} on day {row['DayOfWeek']}")
|
|
# Sort the nodes by date and start time
|
|
class_lesson_nodes.sort(key=lambda x: (x.date, x.start_time))
|
|
planned_lesson_nodes.sort(key=lambda x: (x.date, x.start_time))
|
|
|
|
# Create sequential relationships and directories for TimetableLessonNodes
|
|
for i in range(1, len(class_lesson_nodes)):
|
|
previous_node = class_lesson_nodes[i - 1]
|
|
current_node = class_lesson_nodes[i]
|
|
i_safe = f"{i:02d}"
|
|
_, class_lesson_path = fs_handler.create_teacher_timetable_lesson_directory(class_path, f"{i_safe}_{current_node.date}_{current_node.period_code}")
|
|
current_node.path = class_lesson_path
|
|
neon.create_or_merge_neontology_node(current_node, database=worker_db_name, operation='merge')
|
|
logging.info(f"TimetableLessonNode directory created and node merged into database: {current_node}")
|
|
# Create the tldraw file for the node
|
|
fs_handler.create_default_tldraw_file(current_node.path, current_node.to_dict())
|
|
if previous_node:
|
|
neon.create_or_merge_neontology_relationship(
|
|
TimetableLessonFollowsTimetableLesson(source=previous_node, target=current_node),
|
|
database=worker_db_name, operation='merge'
|
|
)
|
|
logging.info(f"Sequential relationship created between {previous_node.unique_id} and {current_node.unique_id}")
|
|
|
|
# Create sequential relationships for PlannedLessonNodes
|
|
for i in range(1, len(planned_lesson_nodes)):
|
|
previous_node = planned_lesson_nodes[i - 1]
|
|
current_node = planned_lesson_nodes[i]
|
|
i_safe = f"{i:02d}"
|
|
_, planned_lesson_path = fs_handler.create_teacher_planned_lesson_directory(class_path, f"{i_safe}_{current_node.date}_{current_node.period_code}")
|
|
current_node.path = planned_lesson_path
|
|
neon.create_or_merge_neontology_node(current_node, database=worker_db_name, operation='merge')
|
|
logging.info(f"PlannedLessonNode directory created and node merged into database: {current_node}")
|
|
# Create the tldraw file for the node
|
|
fs_handler.create_default_tldraw_file(current_node.path, current_node.to_dict())
|
|
if previous_node:
|
|
neon.create_or_merge_neontology_relationship(
|
|
PlannedLessonFollowsPlannedLesson(source=previous_node, target=current_node),
|
|
database=worker_db_name, operation='merge'
|
|
)
|
|
logging.info(f"Sequential relationship created between {previous_node.unique_id} and {current_node.unique_id}")
|
|
logging.info(f"Successfully initialized worker timetable for worker {worker_node.teacher_code}")
|
|
return {"status": "success", "message": "Worker timetable initialized successfully"}
|
|
|
|
except Exception as e:
|
|
logging.error(f"Error initializing worker timetable: {str(e)}")
|
|
return {"status": "error", "message": f"Error initializing worker timetable: {str(e)}"}
|
|
|
|
|
|
|