-
+
Add Classroom
+
+ Manage Commissions
+
New Reservation
Today's Schedule
+
+ AI Room Optimizer
+
View Calendar
diff --git a/app/templates/genetic_optimizer.html b/app/templates/genetic_optimizer.html
new file mode 100644
index 0000000..ed4c759
--- /dev/null
+++ b/app/templates/genetic_optimizer.html
@@ -0,0 +1,434 @@
+{% extends "base.html" %}
+
+{% block title %}AI Room Reservation Optimizer{% endblock %}
+
+{% block extra_css %}
+
+{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
+
+ AI Room Reservation Optimizer
+
+
+ Use genetic algorithms to automatically optimize room assignments based on capacity, availability, and student enrollment.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
System Status
+
+ Available Classrooms
+ --
+
+
+ Active Commissions
+ --
+
+
+ Existing Reservations
+ --
+
+
+
+
+
Algorithm Configuration
+
+ Population Size
+ 50
+
+
+ Generations
+ 100
+
+
+ Mutation Rate
+ 10%
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Loading commissions...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Optimizing...
+
+
Running genetic algorithm optimization...
+
This may take a few moments
+
+
+
+
+
+{% endblock %}
+
+{% block extra_js %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/app/templates/schedule/add.html b/app/templates/schedule/add.html
index d847d55..4498918 100644
--- a/app/templates/schedule/add.html
+++ b/app/templates/schedule/add.html
@@ -183,12 +183,13 @@
{% block extra_js %}
+{% endblock %}
\ No newline at end of file
diff --git a/app/templates/schedule/today.html b/app/templates/schedule/today.html
index 6895dd0..2437441 100644
--- a/app/templates/schedule/today.html
+++ b/app/templates/schedule/today.html
@@ -11,13 +11,19 @@
Today's Schedule
{{ today.strftime('%B %d, %Y') }}
-
@@ -338,13 +344,19 @@
There are no classroom reservations for {{ today.strftime('%B %d, %Y') }}.
-
diff --git a/tests/test_genetic_algorithm.py b/tests/test_genetic_algorithm.py
new file mode 100644
index 0000000..9051104
--- /dev/null
+++ b/tests/test_genetic_algorithm.py
@@ -0,0 +1,219 @@
+import unittest
+from datetime import datetime, timedelta
+from app.models.genetic_algorithm import GeneticAlgorithm, ReservationOptimizer, ReservationRequest, Gene, Individual
+from app.models.classroom import Classroom
+from app.models.subject import Commission
+
+
+class TestGeneticAlgorithm(unittest.TestCase):
+ """Test cases for the Genetic Algorithm implementation"""
+
+ def setUp(self):
+ """Set up test fixtures"""
+ self.ga = GeneticAlgorithm(population_size=10, generations=5)
+
+ # Mock classrooms
+ self.classrooms = [
+ Classroom(id=1, code="A101", building="A", capacity=30, is_active=True),
+ Classroom(id=2, code="A102", building="A", capacity=50, is_active=True),
+ Classroom(id=3, code="B101", building="B", capacity=25, is_active=True),
+ Classroom(id=4, code="B102", building="B", capacity=40, is_active=True),
+ ]
+
+ # Mock reservation requests
+ start_time = datetime(2024, 1, 15, 9, 0, 0)
+ end_time = datetime(2024, 1, 15, 11, 0, 0)
+
+ self.requests = [
+ ReservationRequest(
+ commission_id=1,
+ expected_attendees=25,
+ purpose="Math Class",
+ preferred_start_time=start_time,
+ preferred_end_time=end_time,
+ priority=1,
+ flexibility_hours=2
+ ),
+ ReservationRequest(
+ commission_id=2,
+ expected_attendees=35,
+ purpose="Physics Lab",
+ preferred_start_time=start_time,
+ preferred_end_time=end_time,
+ priority=1,
+ flexibility_hours=2
+ ),
+ ReservationRequest(
+ commission_id=3,
+ expected_attendees=20,
+ purpose="Chemistry Class",
+ preferred_start_time=start_time,
+ preferred_end_time=end_time,
+ priority=1,
+ flexibility_hours=2
+ ),
+ ]
+
+ def test_individual_fitness_calculation(self):
+ """Test fitness calculation for an individual"""
+ # Create an individual with some genes
+ genes = [
+ Gene(
+ commission_id=1,
+ classroom_id=1, # Capacity 30 for 25 students
+ start_time=datetime(2024, 1, 15, 9, 0, 0),
+ end_time=datetime(2024, 1, 15, 11, 0, 0),
+ expected_attendees=25,
+ purpose="Math Class"
+ ),
+ Gene(
+ commission_id=2,
+ classroom_id=2, # Capacity 50 for 35 students
+ start_time=datetime(2024, 1, 15, 9, 0, 0),
+ end_time=datetime(2024, 1, 15, 11, 0, 0),
+ expected_attendees=35,
+ purpose="Physics Lab"
+ )
+ ]
+
+ individual = Individual(genes)
+ classrooms_dict = {c.id: c for c in self.classrooms}
+ requests_dict = {r.commission_id: r for r in self.requests}
+
+ # Calculate fitness
+ fitness = individual.calculate_fitness(classrooms_dict, requests_dict)
+
+ # Fitness should be positive
+ self.assertGreater(fitness, 0)
+ self.assertEqual(individual.fitness, fitness)
+
+ def test_conflict_detection(self):
+ """Test time conflict detection"""
+ genes = [
+ Gene(
+ commission_id=1,
+ classroom_id=1,
+ start_time=datetime(2024, 1, 15, 9, 0, 0),
+ end_time=datetime(2024, 1, 15, 11, 0, 0),
+ expected_attendees=25,
+ purpose="Math Class"
+ ),
+ Gene(
+ commission_id=2,
+ classroom_id=1, # Same classroom
+ start_time=datetime(2024, 1, 15, 10, 0, 0), # Overlapping time
+ end_time=datetime(2024, 1, 15, 12, 0, 0),
+ expected_attendees=35,
+ purpose="Physics Lab"
+ )
+ ]
+
+ individual = Individual(genes)
+ classrooms_dict = {c.id: c for c in self.classrooms}
+ requests_dict = {r.commission_id: r for r in self.requests[:2]}
+
+ # Calculate fitness - should detect conflicts
+ fitness = individual.calculate_fitness(classrooms_dict, requests_dict)
+
+ # Should have conflicts
+ self.assertGreater(len(individual.conflicts), 0)
+ self.assertLess(fitness, 2.0) # Lower fitness due to conflicts
+
+ def test_crossover_operation(self):
+ """Test crossover operation"""
+ parent1_genes = [
+ Gene(1, 1, datetime.now(), datetime.now() + timedelta(hours=2), 25, "Class 1"),
+ Gene(2, 2, datetime.now(), datetime.now() + timedelta(hours=2), 30, "Class 2")
+ ]
+ parent2_genes = [
+ Gene(3, 3, datetime.now(), datetime.now() + timedelta(hours=2), 20, "Class 3"),
+ Gene(4, 4, datetime.now(), datetime.now() + timedelta(hours=2), 35, "Class 4")
+ ]
+
+ parent1 = Individual(parent1_genes)
+ parent2 = Individual(parent2_genes)
+
+ child1, child2 = self.ga._crossover(parent1, parent2)
+
+ # Children should have genes from both parents
+ self.assertGreater(len(child1.genes), 0)
+ self.assertGreater(len(child2.genes), 0)
+
+ # Total genes should equal sum of parents
+ total_genes = len(child1.genes) + len(child2.genes)
+ self.assertEqual(total_genes, len(parent1_genes) + len(parent2_genes))
+
+ def test_tournament_selection(self):
+ """Test tournament selection"""
+ population = []
+ for i in range(5):
+ genes = [
+ Gene(i, 1, datetime.now(), datetime.now() + timedelta(hours=2), 25, f"Class {i}")
+ ]
+ individual = Individual(genes)
+ individual.fitness = i * 0.1 # Different fitness values
+ population.append(individual)
+
+ selected = self.ga._tournament_selection(population)
+
+ # Should return an individual from the population
+ self.assertIn(selected, population)
+
+ def test_capacity_score_calculation(self):
+ """Test capacity score calculation"""
+ # Perfect match
+ individual = Individual([])
+ score = individual._calculate_capacity_score(self.classrooms[0], 30) #capacity 30, students 30
+ self.assertEqual(score, 1.0)
+
+ # Underfilled but efficient (27/30 = 11.1% waste, goes to next category)
+ score = individual._calculate_capacity_score(self.classrooms[0], 27) # capacity 30, students 27
+ self.assertEqual(score, 0.8)
+
+ # Overfilled
+ score = individual._calculate_capacity_score(self.classrooms[0], 35) # capacity 30, students 35
+ self.assertEqual(score, 0.0)
+
+ def test_optimization_process(self):
+ """Test the complete optimization process"""
+ result = self.ga.optimize_reservations(
+ self.requests,
+ self.classrooms,
+ datetime(2024, 1, 15),
+ datetime(2024, 1, 20)
+ )
+
+ # Should return best individual
+ self.assertIsInstance(result, Individual)
+ self.assertGreaterEqual(result.fitness, 0)
+ self.assertGreater(len(result.genes), 0)
+
+ def test_reservation_optimizer_interface(self):
+ """Test the high-level optimizer interface"""
+ optimizer = ReservationOptimizer()
+
+ # Test the interface methods exist
+ self.assertTrue(hasattr(optimizer, 'optimize_schedule'))
+ self.assertTrue(hasattr(optimizer, 'apply_optimized_reservations'))
+ self.assertTrue(hasattr(optimizer, '_calculate_preferred_time'))
+
+ def test_reservation_request_validation(self):
+ """Test reservation request validation"""
+ request = ReservationRequest(
+ commission_id=1,
+ expected_attendees=25,
+ purpose="Test Class",
+ preferred_start_time=datetime.now(),
+ preferred_end_time=datetime.now() + timedelta(hours=2)
+ )
+
+ # Should have required attributes
+ self.assertEqual(request.commission_id, 1)
+ self.assertEqual(request.expected_attendees, 25)
+ self.assertEqual(request.purpose, "Test Class")
+ self.assertEqual(request.priority, 1) # Default value
+ self.assertEqual(request.flexibility_hours, 2) # Default value
+
+
+if __name__ == '__main__':
+ unittest.main()
\ No newline at end of file