Skip to content

Commit 2e4a791

Browse files
committed
Add support for existing room assignments in calculations
Enhanced room assignment logic to consider existing assignments and prevent double-booking. Introduced an overloaded `CalculateAsync` method in `CalculateProposal.cs` to handle existing assignments. Updated `Calculations.cs` to separate rows needing assignments from those with existing assignments and pass them to the new method. Improved logging throughout the codebase to track the handling of existing assignments, calculation results, and potential errors. Added logic to return room options even when no calculations are needed, with error handling for loading capacity data. Refactored and cleaned up code for better readability and maintainability, including adding comments and reorganizing logic for handling requests and assignments.
1 parent d58fbcf commit 2e4a791

2 files changed

Lines changed: 91 additions & 4 deletions

File tree

RFPResponsePOC/RFPResponsePOC.Client/Services/CalculateProposal.cs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,27 @@ public CalculateProposal(string basePath = "/RFPResponseAPP", LogService logServ
6464
/// <param name="rfpText">JSON string containing an array of room requests</param>
6565
/// <returns>List of room assignments with original requests and assigned room names</returns>
6666
public async Task<List<RoomAssignment>> CalculateAsync(string rfpText)
67+
{
68+
return await CalculateAsync(rfpText, null);
69+
}
70+
71+
/// <summary>
72+
/// Main calculation method that processes RFP text and assigns rooms to requests.
73+
/// This overload considers existing room assignments to prevent double-booking during recalculation.
74+
/// Implements a first-fit algorithm where rooms are assigned to requests in order.
75+
/// Rooms are sorted by capacity (lowest to highest) to optimize space usage.
76+
/// </summary>
77+
/// <param name="rfpText">JSON string containing an array of room requests</param>
78+
/// <param name="existingAssignments">Optional list of existing room assignments to consider during scheduling</param>
79+
/// <returns>List of room assignments with original requests and assigned room names</returns>
80+
public async Task<List<RoomAssignment>> CalculateAsync(string rfpText, List<RoomAssignment> existingAssignments)
6781
{
6882
// Initialize the result list to ensure we always return a valid collection
6983
var assignments = new List<RoomAssignment>();
7084

7185
try
7286
{
73-
await _logService.WriteToLogAsync($"[{DateTime.Now}] CalculateProposale.CalculateAsync started");
87+
await _logService.WriteToLogAsync($"[{DateTime.Now}] CalculateProposale.CalculateAsync started with {existingAssignments?.Count ?? 0} existing assignments");
7488

7589
// Validate input - early return if RFP text is empty or null
7690
if (string.IsNullOrWhiteSpace(rfpText))
@@ -184,6 +198,42 @@ public async Task<List<RoomAssignment>> CalculateAsync(string rfpText)
184198
// Value: List of time intervals when the room is booked
185199
var schedule = new Dictionary<string, List<(DateTime start, DateTime end)>>();
186200

201+
// Pre-populate schedule with existing assignments if provided
202+
if (existingAssignments?.Any() == true)
203+
{
204+
await _logService.WriteToLogAsync($"[{DateTime.Now}] Pre-populating schedule with {existingAssignments.Count} existing assignments");
205+
206+
foreach (var existingAssignment in existingAssignments)
207+
{
208+
if (existingAssignment?.Request != null && !string.IsNullOrWhiteSpace(existingAssignment.AssignedRoom))
209+
{
210+
try
211+
{
212+
// Calculate actual start and end times for existing assignment
213+
var existingStart = existingAssignment.Request.StartDate.Date.Add(existingAssignment.Request.StartTime);
214+
var existingEnd = existingAssignment.Request.EndDate.Date.Add(existingAssignment.Request.EndTime);
215+
216+
// Find the room object for this assignment
217+
var existingRoom = capacity.Rooms.FirstOrDefault(r => r.Name == existingAssignment.AssignedRoom);
218+
if (existingRoom != null)
219+
{
220+
// Block the room in the schedule
221+
BlockRoom(existingRoom, existingStart, existingEnd, schedule, capacity.Rooms);
222+
await _logService.WriteToLogAsync($"[{DateTime.Now}] Pre-blocked room '{existingAssignment.AssignedRoom}' for existing assignment '{existingAssignment.Request.Name}' from {existingStart} to {existingEnd}");
223+
}
224+
else
225+
{
226+
await _logService.WriteToLogAsync($"[{DateTime.Now}] WARNING: Could not find room '{existingAssignment.AssignedRoom}' in capacity data for existing assignment '{existingAssignment.Request.Name}'");
227+
}
228+
}
229+
catch (Exception ex)
230+
{
231+
await _logService.WriteToLogAsync($"[{DateTime.Now}] ERROR: Failed to pre-block existing assignment for room '{existingAssignment.AssignedRoom}' - {ex.Message}");
232+
}
233+
}
234+
}
235+
}
236+
187237
// Put requests in order based on start date and time
188238
requests = requests.OrderBy(req => req.StartDate.Date.Add(req.StartTime)).ToList();
189239

RFPResponsePOC/RFPResponsePOC.Client/Services/Calculations.cs

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -272,9 +272,13 @@ public static async Task<List<string>> CalculateRoomsForExistingRows(List<Propos
272272
return new List<string>();
273273
}
274274

275+
// Find rows that need room assignments (no manual room and no selected room)
275276
var rowsNeedingRooms = proposalRows.Where(row => string.IsNullOrWhiteSpace(row.ManualRoom) && string.IsNullOrWhiteSpace(row.SelectedRoom)).ToList();
276277

277-
await logService.WriteToLogAsync($"[{DateTime.Now}] Found {rowsNeedingRooms.Count} rows needing room assignments");
278+
// Find rows that already have room assignments (either manual or selected)
279+
var rowsWithAssignments = proposalRows.Where(row => !string.IsNullOrWhiteSpace(row.ManualRoom) || !string.IsNullOrWhiteSpace(row.SelectedRoom)).ToList();
280+
281+
await logService.WriteToLogAsync($"[{DateTime.Now}] Found {rowsNeedingRooms.Count} rows needing room assignments and {rowsWithAssignments.Count} rows with existing assignments");
278282

279283
if (!rowsNeedingRooms.Any())
280284
{
@@ -286,9 +290,22 @@ public static async Task<List<string>> CalculateRoomsForExistingRows(List<Propos
286290
Detail = "All rows already have room assignments or manual room overrides.",
287291
Duration = 4000
288292
});
289-
return new List<string>();
293+
294+
// Still return room options even if no calculations are needed
295+
try
296+
{
297+
var capacityJson = await File.ReadAllTextAsync("/RFPResponseAPP/Capacity.json");
298+
var capacity = JsonConvert.DeserializeObject<CapacityRoot>(capacityJson);
299+
return capacity?.Rooms?.Select(r => r.Name).OrderBy(n => n).ToList() ?? new List<string>();
300+
}
301+
catch (Exception ex)
302+
{
303+
await logService.WriteToLogAsync($"[{DateTime.Now}] WARNING: Unable to load Capacity.json - {ex.Message}");
304+
return new List<string>();
305+
}
290306
}
291307

308+
// Convert rows needing rooms to RoomRequest objects
292309
var requests = rowsNeedingRooms.Select(row => new RoomsRequest
293310
{
294311
Name = row.Name ?? "Unknown",
@@ -301,11 +318,31 @@ public static async Task<List<string>> CalculateRoomsForExistingRows(List<Propos
301318
Notes = row.Notes ?? ""
302319
}).ToList();
303320

321+
// Convert rows with existing assignments to RoomAssignment objects
322+
var existingAssignments = rowsWithAssignments.Select(row => new RoomAssignment
323+
{
324+
Request = new RoomsRequest
325+
{
326+
Name = row.Name ?? "Unknown",
327+
StartDate = row.StartDate,
328+
StartTime = row.StartTime,
329+
EndDate = row.EndDate,
330+
EndTime = row.EndTime,
331+
RoomType = row.RoomType ?? "",
332+
Attendance = row.Attendance,
333+
Notes = row.Notes ?? ""
334+
},
335+
AssignedRoom = !string.IsNullOrWhiteSpace(row.ManualRoom) ? row.ManualRoom : row.SelectedRoom
336+
}).ToList();
337+
304338
var requestsJson = JsonConvert.SerializeObject(requests);
305339
await logService.WriteToLogAsync($"[{DateTime.Now}] Created requests JSON for calculation: {requestsJson.Substring(0, Math.Min(500, requestsJson.Length))}");
340+
await logService.WriteToLogAsync($"[{DateTime.Now}] Existing assignments to consider: {string.Join(", ", existingAssignments.Select(ea => $"{ea.Request?.Name}:{ea.AssignedRoom}"))}");
306341

307342
var calculator = new CalculateProposal("/RFPResponseAPP", logService);
308-
var assignments = await calculator.CalculateAsync(requestsJson);
343+
344+
// Use the new overloaded method that considers existing assignments
345+
var assignments = await calculator.CalculateAsync(requestsJson, existingAssignments);
309346
await logService.WriteToLogAsync($"[{DateTime.Now}] Calculator returned {assignments?.Count ?? 0} assignments");
310347

311348
var assignmentDict = assignments.ToDictionary(a => a.Request?.Name ?? "", a => a.AssignedRoom);

0 commit comments

Comments
 (0)