api/modules/database/init/init_school_timetable.py
2025-11-14 14:47:19 +00:00

534 lines
30 KiB
Python

import os
from modules.logger_tool import initialise_logger
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
from datetime import timedelta, datetime
import pandas as pd
from modules.database.schemas.structures import structures
import modules.database.schemas.nodes.schools.timetable as timetable
import modules.database.schemas.relationships.timetables as tt_rels
import modules.database.schemas.relationships.entity_timetable_rels as entity_tt_rels
import modules.database.schemas.relationships.calendar_timetable_rels as cal_tt_rels
import modules.database.init.init_calendar as init_calendar
import modules.database.tools.neontology_tools as neon
def create_school_timetable(dataframes, db_name, school_node=None, filesystem=None):
logger.info(f"Creating school timetable for {db_name}")
if dataframes is None:
raise ValueError("Data is required to create the calendar and timetable.")
logger.info(f"Initialising neo4j connection...")
neon.init_neontology_connection()
school_df = dataframes['school']
if school_node is None:
logger.info(f"School node is None, using school data from dataframe")
school_uuid_string = school_df[school_df['Identifier'] == 'SchoolID']['Data'].iloc[0]
else:
logger.info(f"School node is not None, using school data from school node: {school_node}")
school_uuid_string = school_node.uuid_string
terms_df = dataframes['terms']
weeks_df = dataframes['weeks']
days_df = dataframes['days']
periods_df = dataframes['periods']
school_df_year_start = school_df[school_df['Identifier'] == 'AcademicYearStart']['Data'].iloc[0]
school_df_year_end = school_df[school_df['Identifier'] == 'AcademicYearEnd']['Data'].iloc[0]
if isinstance(school_df_year_start, str):
school_year_start_date = datetime.strptime(school_df_year_start, '%Y-%m-%d')
else:
school_year_start_date = school_df_year_start
if isinstance(school_df_year_end, str):
school_year_end_date = datetime.strptime(school_df_year_end, '%Y-%m-%d')
else:
school_year_end_date = school_df_year_end
# Create a dictionary to store the timetable nodes
timetable_nodes = {
'timetable_node': None,
'academic_year_nodes': [],
'academic_term_nodes': [],
'academic_week_nodes': [],
'academic_day_nodes': [],
'academic_period_nodes': []
}
# Create AcademicTimetable Node
school_timetable_uuid_string = f"{school_uuid_string}_{school_year_start_date.year}_{school_year_end_date.year}"
# Generate storage path for timetable node
if filesystem:
timetable_dir_created, timetable_path = filesystem.create_school_timetable_directory()
node_storage_path = os.path.relpath(timetable_path, filesystem.base_path)
logger.info(f"Generated timetable node_storage_path: {node_storage_path}")
else:
node_storage_path = ""
logger.warning("No filesystem provided, using empty storage path")
school_timetable_node = timetable.SchoolTimetableNode(
school_timetable_id=school_timetable_uuid_string,
uuid_string=school_timetable_uuid_string,
start_date=school_year_start_date,
end_date=school_year_end_date,
node_storage_path=node_storage_path
)
neon.create_or_merge_neontology_node(school_timetable_node, database=db_name, operation='merge')
timetable_nodes['timetable_node'] = school_timetable_node
if school_node:
logger.info(f"Creating calendar for {school_uuid_string} from Neo4j SchoolNode: {school_node.uuid_string}")
calendar_nodes = init_calendar.create_calendar(db_name, school_year_start_date, school_year_end_date, attach_to_calendar_node=True, owner_node=school_node, filesystem=filesystem)
# Link the school node to the timetable node
neon.create_or_merge_neontology_relationship(
entity_tt_rels.SchoolHasTimetable(source=school_node, target=school_timetable_node),
database=db_name, operation='merge'
)
timetable_nodes['calendar_nodes'] = calendar_nodes
else:
logger.info(f"Creating calendar for {school_uuid_string} from dataframe SchoolID: {school_uuid_string}")
calendar_nodes = init_calendar.create_calendar(db_name, school_year_start_date, school_year_end_date, attach_to_calendar_node=False, owner_node=None, filesystem=filesystem)
# Create AcademicYear nodes for each year within the range
for year in range(school_year_start_date.year, school_year_end_date.year + 1):
year_str = str(year)
academic_year_uuid_string = f"{school_timetable_uuid_string}_{year}"
# Generate storage path for academic year node
if filesystem:
year_dir_created, year_path = filesystem.create_school_timetable_year_directory(timetable_path, year)
node_storage_path = os.path.relpath(year_path, filesystem.base_path)
else:
node_storage_path = ""
academic_year_node = timetable.AcademicYearNode(
uuid_string=academic_year_uuid_string,
year=year_str,
node_storage_path=node_storage_path
)
neon.create_or_merge_neontology_node(academic_year_node, database=db_name, operation='merge')
timetable_nodes['academic_year_nodes'].append(academic_year_node)
logger.info(f'Created academic year node: {academic_year_node.uuid_string}')
neon.create_or_merge_neontology_relationship(
tt_rels.AcademicTimetableHasAcademicYear(source=school_timetable_node, target=academic_year_node),
database=db_name, operation='merge'
)
logger.info(f"Created school timetable relationship from {school_timetable_node.uuid_string} to {academic_year_node.uuid_string}")
# Link the academic year with the corresponding calendar year node
for year_node in calendar_nodes['calendar_year_nodes']:
if year_node.year == year:
neon.create_or_merge_neontology_relationship(
cal_tt_rels.AcademicYearIsCalendarYear(source=academic_year_node, target=year_node),
database=db_name, operation='merge'
)
logger.info(f"Created school timetable relationship from {academic_year_node.uuid_string} to {year_node.uuid_string}")
break
# Create Term and TermBreak nodes linked to AcademicYear
term_number = 1
academic_term_number = 1
for _, term_row in terms_df.iterrows():
term_node_class = timetable.AcademicTermNode if term_row['TermType'] == 'Term' else timetable.AcademicTermBreakNode
term_name = term_row['TermName']
term_name_no_spaces = term_name.replace(' ', '')
term_start_date = term_row['StartDate']
if isinstance(term_start_date, pd.Timestamp):
term_start_date = term_start_date.strftime('%Y-%m-%d')
term_end_date = term_row['EndDate']
if isinstance(term_end_date, pd.Timestamp):
term_end_date = term_end_date.strftime('%Y-%m-%d')
# Generate storage path for term node
if filesystem:
if term_row['TermType'] == 'Term':
term_dir_created, term_path = filesystem.create_school_timetable_academic_term_directory(timetable_path, term_name, academic_term_number)
else:
term_dir_created, term_path = filesystem.create_school_timetable_academic_term_break_directory(timetable_path, term_name)
node_storage_path = os.path.relpath(term_path, filesystem.base_path)
else:
node_storage_path = ""
if term_row['TermType'] == 'Term':
term_node_uuid_string = f"{school_timetable_uuid_string}_{academic_term_number}_{term_name_no_spaces}"
academic_term_number_str = str(academic_term_number)
term_node = term_node_class(
uuid_string=term_node_uuid_string,
term_name=term_name,
term_number=academic_term_number_str,
start_date=datetime.strptime(term_start_date, '%Y-%m-%d'),
end_date=datetime.strptime(term_end_date, '%Y-%m-%d'),
node_storage_path=node_storage_path
)
academic_term_number += 1
else:
term_break_node_uuid_string = f"{school_timetable_uuid_string}_{term_name_no_spaces}"
term_node = term_node_class(
uuid_string=term_break_node_uuid_string,
term_break_name=term_name,
start_date=datetime.strptime(term_start_date, '%Y-%m-%d'),
end_date=datetime.strptime(term_end_date, '%Y-%m-%d'),
node_storage_path=node_storage_path
)
neon.create_or_merge_neontology_node(term_node, database=db_name, operation='merge')
logger.info(f'Created academic term break node: {term_node.uuid_string}')
timetable_nodes['academic_term_nodes'].append(term_node)
term_number += 1 # We don't use this but we could
# Link term node to the correct academic year
term_years = set()
term_years.update([term_node.start_date.year, term_node.end_date.year])
for academic_year_node in timetable_nodes['academic_year_nodes']:
if int(academic_year_node.year) in term_years:
relationship_class = tt_rels.AcademicYearHasAcademicTerm if term_row['TermType'] == 'Term' else tt_rels.AcademicYearHasAcademicTermBreak
neon.create_or_merge_neontology_relationship(
relationship_class(source=academic_year_node, target=term_node),
database=db_name, operation='merge'
)
logger.info(f"Created school timetable relationship from {academic_year_node.uuid_string} to {term_node.uuid_string}")
# Create Week nodes
academic_week_number = 1
for _, week_row in weeks_df.iterrows():
week_node_class = timetable.HolidayWeekNode if week_row['WeekType'] == 'Holiday' else timetable.AcademicWeekNode
week_start_date = week_row['WeekStart']
if isinstance(week_start_date, pd.Timestamp):
week_start_date = week_start_date.strftime('%Y-%m-%d')
week_node_uuid_string = f"{school_timetable_uuid_string}_{week_row['WeekNumber']}_{week_row['WeekType']}Week"
# Generate storage path for week node
if filesystem:
week_dir_created, week_path = filesystem.create_school_timetable_academic_week_directory(timetable_path, week_row['WeekNumber'])
node_storage_path = os.path.relpath(week_path, filesystem.base_path)
else:
node_storage_path = ""
if week_row['WeekType'] == 'Holiday':
week_node = week_node_class(
uuid_string=week_node_uuid_string,
start_date=datetime.strptime(week_start_date, '%Y-%m-%d'),
node_storage_path=node_storage_path
)
else:
academic_week_number_str = str(academic_week_number)
week_type = week_row['WeekType']
week_node = week_node_class(
uuid_string=week_node_uuid_string,
academic_week_number=academic_week_number_str,
start_date=datetime.strptime(week_start_date, '%Y-%m-%d'),
week_type=week_type,
node_storage_path=node_storage_path
)
academic_week_number += 1
neon.create_or_merge_neontology_node(week_node, database=db_name, operation='merge')
timetable_nodes['academic_week_nodes'].append(week_node)
logger.info(f"Created week node: {week_node.uuid_string}")
for calendar_node in calendar_nodes['calendar_week_nodes']:
if calendar_node.start_date == week_node.start_date:
if isinstance(week_node, timetable.AcademicWeekNode):
neon.create_or_merge_neontology_relationship(
cal_tt_rels.AcademicWeekIsCalendarWeek(source=week_node, target=calendar_node),
database=db_name, operation='merge'
)
logger.info(f"Created school timetable relationship from {calendar_node.uuid_string} to {week_node.uuid_string}")
elif isinstance(week_node, timetable.HolidayWeekNode):
neon.create_or_merge_neontology_relationship(
cal_tt_rels.HolidayWeekIsCalendarWeek(source=week_node, target=calendar_node),
database=db_name, operation='merge'
)
logger.info(f"Created school timetable relationship from {calendar_node.uuid_string} to {week_node.uuid_string}")
break
# Link week node to the correct academic term
for term_node in timetable_nodes['academic_term_nodes']:
if term_node.start_date <= week_node.start_date <= term_node.end_date:
relationship_class = tt_rels.AcademicTermHasAcademicWeek if week_row['WeekType'] != 'Holiday' else tt_rels.AcademicTermBreakHasHolidayWeek
neon.create_or_merge_neontology_relationship(
relationship_class(source=term_node, target=week_node),
database=db_name, operation='merge'
)
logger.info(f"Created school timetable relationship from {term_node.uuid_string} to {week_node.uuid_string}")
break
# Link week node to the correct academic year
for academic_year_node in timetable_nodes['academic_year_nodes']:
if int(academic_year_node.year) == week_node.start_date.year:
relationship_class = tt_rels.AcademicYearHasAcademicWeek if week_row['WeekType'] != 'Holiday' else tt_rels.AcademicYearHasHolidayWeek
neon.create_or_merge_neontology_relationship(
relationship_class(source=academic_year_node, target=week_node),
database=db_name, operation='merge'
)
logger.info(f"Created school timetable relationship from {academic_year_node.uuid_string} to {week_node.uuid_string}")
break
# Create Day nodes
day_number = 1
academic_day_number = 1
for _, day_row in days_df.iterrows():
date_str = day_row['Date']
if isinstance(date_str, pd.Timestamp):
date_str = date_str.strftime('%Y-%m-%d')
day_node_class = {
'Academic': timetable.AcademicDayNode,
'Holiday': timetable.HolidayDayNode,
'OffTimetable': timetable.OffTimetableDayNode,
'StaffDay': timetable.StaffDayNode
}[day_row['DayType']]
# Generate storage path for day node
if filesystem:
day_dir_created, day_path = filesystem.create_school_timetable_academic_day_directory(timetable_path, academic_day_number)
node_storage_path = os.path.relpath(day_path, filesystem.base_path)
else:
node_storage_path = ""
# Format the unique ID as {day_node_class.__name__}Day
day_node_data = {
'uuid_string': f"{school_timetable_uuid_string}_{day_number}_{day_node_class.__name__}Day",
'date': datetime.strptime(date_str, '%Y-%m-%d'),
'day_of_week': datetime.strptime(date_str, '%Y-%m-%d').strftime('%A'),
'node_storage_path': node_storage_path
}
if day_row['DayType'] == 'Academic':
day_node_data['academic_day'] = str(academic_day_number)
day_node_data['day_type'] = day_row['WeekType']
day_node = day_node_class(**day_node_data)
for calendar_node in calendar_nodes['calendar_day_nodes']:
if calendar_node.date == day_node.date:
neon.create_or_merge_neontology_node(day_node, database=db_name, operation='merge')
timetable_nodes['academic_day_nodes'].append(day_node)
logger.info(f"Created day node: {day_node.uuid_string}")
if isinstance(day_node, timetable.AcademicDayNode):
relationship_class = cal_tt_rels.AcademicDayIsCalendarDay
elif isinstance(day_node, timetable.HolidayDayNode):
relationship_class = cal_tt_rels.HolidayDayIsCalendarDay
elif isinstance(day_node, timetable.OffTimetableDayNode):
relationship_class = cal_tt_rels.OffTimetableDayIsCalendarDay
elif isinstance(day_node, timetable.StaffDayNode):
relationship_class = cal_tt_rels.StaffDayIsCalendarDay
neon.create_or_merge_neontology_relationship(
relationship_class(source=day_node, target=calendar_node),
database=db_name, operation='merge'
)
logger.info(f'Created relationship from {calendar_node.uuid_string} to {day_node.uuid_string}')
break
# Link day node to the correct academic week
for academic_week_node in timetable_nodes['academic_week_nodes']:
if academic_week_node.start_date <= day_node.date <= (academic_week_node.start_date + timedelta(days=6)):
if day_row['DayType'] == 'Academic':
relationship_class = tt_rels.AcademicWeekHasAcademicDay
elif day_row['DayType'] == 'Holiday':
if hasattr(academic_week_node, 'week_type') and academic_week_node.week_type in ['A', 'B']:
relationship_class = tt_rels.AcademicWeekHasHolidayDay
else:
relationship_class = tt_rels.HolidayWeekHasHolidayDay
elif day_row['DayType'] == 'OffTimetable':
relationship_class = tt_rels.AcademicWeekHasOffTimetableDay
elif day_row['DayType'] == 'Staff':
relationship_class = tt_rels.AcademicWeekHasStaffDay
else:
continue # Skip linking for other day types
neon.create_or_merge_neontology_relationship(
relationship_class(source=academic_week_node, target=day_node),
database=db_name, operation='merge'
)
logger.info(f"Created relationship from {academic_week_node.uuid_string} to {day_node.uuid_string}")
break
# Link day node to the correct academic term
for term_node in timetable_nodes['academic_term_nodes']:
if term_node.start_date <= day_node.date <= term_node.end_date:
if day_row['DayType'] == 'Academic':
relationship_class = tt_rels.AcademicTermHasAcademicDay
elif day_row['DayType'] == 'Holiday':
if isinstance(term_node, timetable.AcademicTermNode):
relationship_class = tt_rels.AcademicTermHasHolidayDay
else:
relationship_class = tt_rels.AcademicTermBreakHasHolidayDay
elif day_row['DayType'] == 'OffTimetable':
relationship_class = tt_rels.AcademicTermHasOffTimetableDay
elif day_row['DayType'] == 'Staff':
relationship_class = tt_rels.AcademicTermHasStaffDay
else:
continue # Skip linking for other day types
neon.create_or_merge_neontology_relationship(
relationship_class(source=term_node, target=day_node),
database=db_name, operation='merge'
)
logger.info(f"Created relationship from {term_node.uuid_string} to {day_node.uuid_string}")
break
# Create Period nodes for each academic day
if day_row['DayType'] == 'Academic':
logger.info(f"Creating periods for {day_node.uuid_string}")
period_of_day = 1
academic_or_registration_period_of_day = 1
for _, period_row in periods_df.iterrows():
period_node_class = {
'Academic': timetable.AcademicPeriodNode,
'Registration': timetable.RegistrationPeriodNode,
'Break': timetable.BreakPeriodNode,
'OffTimetable': timetable.OffTimetablePeriodNode
}[period_row['PeriodType']]
logger.info(f"Creating period node for {period_node_class.__name__} Period: {period_of_day}")
period_node_uuid_string = f"{school_timetable_uuid_string}_{academic_day_number}_{period_of_day}_{period_node_class.__name__}Period"
logger.debug(f"Period node unique id: {period_node_uuid_string}")
# Generate storage path for period node
if filesystem:
period_dir_created, period_path = filesystem.create_school_timetable_period_directory(timetable_path, academic_day_number, period_row['PeriodCode'])
node_storage_path = os.path.relpath(period_path, filesystem.base_path)
else:
node_storage_path = ""
period_node_data = {
'uuid_string': period_node_uuid_string,
'name': period_row['PeriodName'],
'date': day_node.date,
'start_time': datetime.combine(day_node.date, period_row['StartTime']),
'end_time': datetime.combine(day_node.date, period_row['EndTime']),
'node_storage_path': node_storage_path
}
logger.debug(f"Period node data: {period_node_data}")
if period_row['PeriodType'] in ['Academic', 'Registration']:
week_type = day_row['WeekType']
day_name_short = day_node.day_of_week[:3]
period_code = period_row['PeriodCode']
period_code_formatted = f"{week_type}{day_name_short}{period_code}"
period_node_data['period_code'] = period_code_formatted
academic_or_registration_period_of_day += 1
period_node = period_node_class(**period_node_data)
neon.create_or_merge_neontology_node(period_node, database=db_name, operation='merge')
timetable_nodes['academic_period_nodes'].append(period_node)
logger.info(f'Created period node: {period_node.uuid_string}')
relationship_class = {
'Academic': tt_rels.AcademicDayHasAcademicPeriod,
'Registration': tt_rels.AcademicDayHasRegistrationPeriod,
'Break': tt_rels.AcademicDayHasBreakPeriod,
'OffTimetable': tt_rels.AcademicDayHasOffTimetablePeriod
}[period_row['PeriodType']]
neon.create_or_merge_neontology_relationship(
relationship_class(source=day_node, target=period_node),
database=db_name, operation='merge'
)
logger.info(f"Created relationship from {day_node.uuid_string} to {period_node.uuid_string}")
period_of_day += 1 # We don't use this but we could
academic_day_number += 1 # This is a bit of a hack but it works to keep the directories aligned (reorganise)
day_number += 1 # We don't use this but we could
def create_school_timetable_node_sequence_rels(timetable_nodes):
def sort_and_create_relationships(nodes, relationship_map, sort_key):
sorted_nodes = sorted(nodes, key=sort_key)
for i in range(len(sorted_nodes) - 1):
source_node = sorted_nodes[i]
target_node = sorted_nodes[i + 1]
node_type_pair = (type(source_node), type(target_node))
relationship_class = relationship_map.get(node_type_pair)
if relationship_class:
# Avoid self-referential relationships
if source_node.uuid_string != target_node.uuid_string:
neon.create_or_merge_neontology_relationship(
relationship_class(
source=source_node,
target=target_node
),
database=db_name, operation='merge'
)
logger.info(f"Created relationship from {source_node.uuid_string} to {target_node.uuid_string}")
else:
logger.warning(f"Skipped self-referential relationship for node {source_node.uuid_string}")
# Relationship maps for different node types
academic_year_relationship_map = {
(timetable.AcademicYearNode, timetable.AcademicYearNode): tt_rels.AcademicYearFollowsAcademicYear
}
academic_term_relationship_map = {
(timetable.AcademicTermNode, timetable.AcademicTermBreakNode): tt_rels.AcademicTermBreakFollowsAcademicTerm,
(timetable.AcademicTermBreakNode, timetable.AcademicTermNode): tt_rels.AcademicTermFollowsAcademicTermBreak
}
academic_week_relationship_map = {
(timetable.AcademicWeekNode, timetable.AcademicWeekNode): tt_rels.AcademicWeekFollowsAcademicWeek,
(timetable.HolidayWeekNode, timetable.HolidayWeekNode): tt_rels.HolidayWeekFollowsHolidayWeek,
(timetable.AcademicWeekNode, timetable.HolidayWeekNode): tt_rels.HolidayWeekFollowsAcademicWeek,
(timetable.HolidayWeekNode, timetable.AcademicWeekNode): tt_rels.AcademicWeekFollowsHolidayWeek
}
academic_day_relationship_map = {
(timetable.AcademicDayNode, timetable.AcademicDayNode): tt_rels.AcademicDayFollowsAcademicDay,
(timetable.HolidayDayNode, timetable.HolidayDayNode): tt_rels.HolidayDayFollowsHolidayDay,
(timetable.OffTimetableDayNode, timetable.OffTimetableDayNode): tt_rels.OffTimetableDayFollowsOffTimetableDay,
(timetable.StaffDayNode, timetable.StaffDayNode): tt_rels.StaffDayFollowsStaffDay,
(timetable.AcademicDayNode, timetable.HolidayDayNode): tt_rels.HolidayDayFollowsAcademicDay,
(timetable.AcademicDayNode, timetable.OffTimetableDayNode): tt_rels.OffTimetableDayFollowsAcademicDay,
(timetable.AcademicDayNode, timetable.StaffDayNode): tt_rels.StaffDayFollowsAcademicDay,
(timetable.HolidayDayNode, timetable.AcademicDayNode): tt_rels.AcademicDayFollowsHolidayDay,
(timetable.HolidayDayNode, timetable.OffTimetableDayNode): tt_rels.OffTimetableDayFollowsHolidayDay,
(timetable.HolidayDayNode, timetable.StaffDayNode): tt_rels.StaffDayFollowsHolidayDay,
(timetable.OffTimetableDayNode, timetable.AcademicDayNode): tt_rels.AcademicDayFollowsOffTimetableDay,
(timetable.OffTimetableDayNode, timetable.HolidayDayNode): tt_rels.HolidayDayFollowsOffTimetableDay,
(timetable.OffTimetableDayNode, timetable.StaffDayNode): tt_rels.StaffDayFollowsOffTimetableDay,
(timetable.StaffDayNode, timetable.AcademicDayNode): tt_rels.AcademicDayFollowsStaffDay,
(timetable.StaffDayNode, timetable.HolidayDayNode): tt_rels.HolidayDayFollowsStaffDay,
(timetable.StaffDayNode, timetable.OffTimetableDayNode): tt_rels.OffTimetableDayFollowsStaffDay,
}
academic_period_relationship_map = {
(timetable.AcademicPeriodNode, timetable.AcademicPeriodNode): tt_rels.AcademicPeriodFollowsAcademicPeriod,
(timetable.AcademicPeriodNode, timetable.BreakPeriodNode): tt_rels.BreakPeriodFollowsAcademicPeriod,
(timetable.AcademicPeriodNode, timetable.RegistrationPeriodNode): tt_rels.RegistrationPeriodFollowsAcademicPeriod,
(timetable.AcademicPeriodNode, timetable.OffTimetablePeriodNode): tt_rels.OffTimetablePeriodFollowsAcademicPeriod,
(timetable.BreakPeriodNode, timetable.AcademicPeriodNode): tt_rels.AcademicPeriodFollowsBreakPeriod,
(timetable.BreakPeriodNode, timetable.BreakPeriodNode): tt_rels.BreakPeriodFollowsBreakPeriod,
(timetable.BreakPeriodNode, timetable.RegistrationPeriodNode): tt_rels.RegistrationPeriodFollowsBreakPeriod,
(timetable.BreakPeriodNode, timetable.OffTimetablePeriodNode): tt_rels.OffTimetablePeriodFollowsBreakPeriod,
(timetable.RegistrationPeriodNode, timetable.AcademicPeriodNode): tt_rels.AcademicPeriodFollowsRegistrationPeriod,
(timetable.RegistrationPeriodNode, timetable.RegistrationPeriodNode): tt_rels.RegistrationPeriodFollowsRegistrationPeriod,
(timetable.RegistrationPeriodNode, timetable.BreakPeriodNode): tt_rels.BreakPeriodFollowsRegistrationPeriod,
(timetable.RegistrationPeriodNode, timetable.OffTimetablePeriodNode): tt_rels.OffTimetablePeriodFollowsRegistrationPeriod,
(timetable.OffTimetablePeriodNode, timetable.OffTimetablePeriodNode): tt_rels.OffTimetablePeriodFollowsOffTimetablePeriod,
(timetable.OffTimetablePeriodNode, timetable.AcademicPeriodNode): tt_rels.AcademicPeriodFollowsOffTimetablePeriod,
(timetable.OffTimetablePeriodNode, timetable.BreakPeriodNode): tt_rels.BreakPeriodFollowsOffTimetablePeriod,
(timetable.OffTimetablePeriodNode, timetable.RegistrationPeriodNode): tt_rels.RegistrationPeriodFollowsOffTimetablePeriod,
}
# Sort and create relationships
sort_and_create_relationships(timetable_nodes['academic_year_nodes'], academic_year_relationship_map, lambda x: int(x.year))
sort_and_create_relationships(timetable_nodes['academic_term_nodes'], academic_term_relationship_map, lambda x: x.start_date)
sort_and_create_relationships(timetable_nodes['academic_week_nodes'], academic_week_relationship_map, lambda x: x.start_date)
sort_and_create_relationships(timetable_nodes['academic_day_nodes'], academic_day_relationship_map, lambda x: x.date)
sort_and_create_relationships(timetable_nodes['academic_period_nodes'], academic_period_relationship_map, lambda x: (x.start_time, x.end_time))
# Call the function with the created timetable nodes
create_school_timetable_node_sequence_rels(timetable_nodes)
logger.info(f'Created timetable: {timetable_nodes["timetable_node"].uuid_string}')
# Log the directory structure after creation
# root_timetable_directory = fs_handler.root_path # Access the root directory of the filesystem handler
# fs_handler.log_directory_structure(root_timetable_directory)
return {
'school_node': school_node,
'school_calendar_nodes': calendar_nodes,
'school_timetable_nodes': timetable_nodes
}