Do0rMaMu commited on
Commit
dda1cd6
·
verified ·
1 Parent(s): a5580dd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +238 -72
app.py CHANGED
@@ -2,14 +2,44 @@ import pandas as pd
2
  from itertools import permutations, combinations
3
  from fastapi import FastAPI, File, UploadFile, Form, HTTPException, Request, Response
4
  from fastapi.middleware.cors import CORSMiddleware
5
- from pydantic import BaseModel
6
- from typing import List, Dict, Any
7
  import uuid
8
  import requests
9
  import json
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  app = FastAPI()
12
  stored_combinations = {}
 
13
  processed_data_store: Dict[str, Dict[str, Any]] = {} # Store processed data with session_id
14
 
15
  app.add_middleware(
@@ -31,12 +61,13 @@ async def preflight_handler(request: Request, rest_of_path: str) -> Response:
31
 
32
  # Box and Truck classes
33
  class Box:
34
- def __init__(self, length, width, height, quantity, box_type):
35
  self.length = length
36
  self.width = width
37
  self.height = height
38
  self.quantity = quantity if quantity > 0 else 1
39
  self.type = box_type
 
40
 
41
  def rotated_dimensions(self):
42
  possible_rotations = [
@@ -158,6 +189,10 @@ def find_max_consignments_combinations(truck, consignments, combination_limit=10
158
  for r in range(1, len(consignments_sorted) + 1):
159
  for combo in combinations(consignments_sorted, r):
160
  combo_count += 1
 
 
 
 
161
  print(f"Checking combination {combo_count}: {[f'Consignment {boxes[0].type}' for boxes, _ in combo]}")
162
 
163
  if combo_count > combination_limit:
@@ -236,17 +271,19 @@ def suggest_truck(consignments):
236
  return None
237
 
238
 
239
- # Models
240
  class BoxData(BaseModel):
241
  PieceLength: float
242
  PieceBreadth: float
243
  PieceHeight: float
244
  Priority: int
 
245
 
246
 
247
  class ConsignmentData(BaseModel):
248
  ConsignmentNo: str
249
  Priority: int
 
250
  boxes: List[BoxData]
251
 
252
 
@@ -254,30 +291,36 @@ class SubmitDataRequest(BaseModel):
254
  truck_name: str
255
  autoSuggest: bool
256
  consignments_data: List[ConsignmentData]
 
257
 
258
- from fastapi.responses import RedirectResponse
259
 
260
  @app.post("/submit_data/")
261
  async def submit_data(request: SubmitDataRequest):
262
  """
263
  Endpoint to receive data from the client via API.
264
  """
 
 
265
  truck_name = request.truck_name
266
  autoSuggest = request.autoSuggest
267
  consignments_data = request.consignments_data # This is already parsed from JSON
 
268
 
269
- global stored_combinations, processed_data_store
 
 
270
 
271
  # Parse consignments and calculate total quantity based on box dimensions
272
  consignments = []
273
  for consignment_data in consignments_data:
274
  consignment_no = consignment_data.ConsignmentNo # ConsignmentNo is now accessible as a string
275
  is_priority = consignment_data.Priority
 
276
 
277
- # Aggregate the boxes with same dimensions
278
  box_aggregation = {}
279
  for box_data in consignment_data.boxes:
280
- dimensions = (box_data.PieceLength, box_data.PieceBreadth, box_data.PieceHeight)
281
 
282
  if dimensions in box_aggregation:
283
  box_aggregation[dimensions]['Quantity'] += 1 # Increment the count if dimensions are the same
@@ -286,6 +329,7 @@ async def submit_data(request: SubmitDataRequest):
286
  'PieceLength': box_data.PieceLength,
287
  'PieceBreadth': box_data.PieceBreadth,
288
  'PieceHeight': box_data.PieceHeight,
 
289
  'Quantity': 1 # Initialize count to 1
290
  }
291
 
@@ -296,7 +340,8 @@ async def submit_data(request: SubmitDataRequest):
296
  box['PieceBreadth'] / 2.54,
297
  box['PieceHeight'] / 2.54,
298
  box['Quantity'],
299
- f"Consignment {consignment_no} ({'Priority' if is_priority else 'Non-Priority'})"
 
300
  )
301
  for box in box_aggregation.values()
302
  ]
@@ -370,8 +415,9 @@ async def submit_data(request: SubmitDataRequest):
370
  "combination_number": index + 1,
371
  "consignments": [
372
  {
373
- "consignment_number": f"{boxes[0].type.split(' ')[1]}",
374
- "priority_status": boxes[0].type.split('(')[1].strip(')')
 
375
  }
376
  for boxes, _ in combo
377
  ],
@@ -392,11 +438,12 @@ async def submit_data(request: SubmitDataRequest):
392
  "height": height
393
  }
394
  },
395
- "consignments_data": [consignment.dict() for consignment in consignments_data] # **Added**
 
396
  }
397
 
398
  # Redirect to the visualization page
399
- return RedirectResponse(url=f"https://66eafada5f1ccb4a5f161808--quiet-nougat-e4fc4b.netlify.app?session_id={session_id}", status_code=302)
400
 
401
  # Prepare box data for visualization
402
  box_data = []
@@ -411,7 +458,8 @@ async def submit_data(request: SubmitDataRequest):
411
  'height': box.height,
412
  'type': box.type,
413
  'quantity': 1, # Since we're iterating over each individual box
414
- 'position': position
 
415
  })
416
 
417
  # Generate a unique session_id
@@ -425,83 +473,201 @@ async def submit_data(request: SubmitDataRequest):
425
  "width": width,
426
  "height": height
427
  }
428
- }
 
429
  }
 
 
430
 
431
  # Redirect to the visualization page
432
- return RedirectResponse(url=f"https://66eafada5f1ccb4a5f161808--quiet-nougat-e4fc4b.netlify.app?session_id={session_id}", status_code=302)
433
-
434
 
435
-
436
- @app.post("/load_combination/")
437
- async def load_combination(
438
- index: int = Form(...),
439
- truck_length: float = Form(...),
440
- truck_width: float = Form(...),
441
- truck_height: float = Form(...),
442
- truck_name: str = Form(...)
443
- ):
444
  """
445
- Endpoint to visualize the selected combination.
446
  """
447
- print(f"Received index: {index}")
448
- print(f"Received truck details: {truck_length}L x {truck_width}W x {truck_height}H, Name: {truck_name}")
449
-
450
- # Ensure the index is within the valid range
451
- if index >= len(stored_combinations):
452
- raise HTTPException(status_code=400, detail="Invalid combination index")
453
-
454
- selected_combination = stored_combinations[index]
455
-
456
- # Create a Truck object using the received truck dimensions
457
- truck = Truck(truck_length * 12, truck_width * 12, truck_height * 12) # Convert feet to inches
458
-
459
- # Unpack and structure the combination data for the pack_boxes_ffd function
460
- consignments = [
461
- (boxes, priority) for boxes, priority in selected_combination # Unpack the combination as expected
462
- ]
463
 
464
- # Generate the box data for this combination
465
- packed_positions = pack_boxes_ffd(truck, consignments)
466
 
467
- # Prepare the box data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
468
  box_data = []
469
- for box, pos in packed_positions:
470
  if pos is not None:
471
  position = {'x': pos[0], 'y': pos[1], 'z': pos[2]}
472
  else:
473
- position = None
474
  box_data.append({
475
- 'length': box.length,
476
- 'width': box.width,
477
- 'height': box.height,
478
- 'type': box.type,
479
- 'quantity': 1,
480
- 'position': position
 
481
  })
482
 
483
- print("Data sent to the frontend:", len(box_data))
484
-
485
- # Return the box data and truck information to the frontend
486
- return {
487
  "boxes": box_data,
488
  "truck": {
489
  "name": truck_name,
490
  "dimensions": {
491
- "length": truck_length,
492
- "width": truck_width,
493
- "height": truck_height
494
  }
495
- }
 
496
  }
497
 
498
-
499
- @app.get("/get_visualization/{session_id}/")
500
- async def get_visualization(session_id: str):
501
- """
502
- Endpoint for the frontend to fetch processed data using session_id.
503
- """
504
- if session_id not in processed_data_store:
505
- raise HTTPException(status_code=404, detail="Session ID not found")
506
-
507
- return processed_data_store[session_id]
 
2
  from itertools import permutations, combinations
3
  from fastapi import FastAPI, File, UploadFile, Form, HTTPException, Request, Response
4
  from fastapi.middleware.cors import CORSMiddleware
5
+ from pydantic import BaseModel, Field
6
+ from typing import List, Dict, Any, Optional
7
  import uuid
8
  import requests
9
  import json
10
 
11
+ from fastapi.responses import RedirectResponse
12
+ import pyodbc
13
+ import os
14
+ from dotenv import load_dotenv
15
+ from datetime import datetime
16
+
17
+ # Load environment variables from .env file
18
+ load_dotenv()
19
+
20
+ # Fetch credentials from environment variables
21
+ DB_DRIVER = os.getenv('DB_DRIVER')
22
+ DB_SERVER = os.getenv('DB_SERVER')
23
+ DB_DATABASE = os.getenv('DB_DATABASE')
24
+ DB_UID = os.getenv('DB_UID')
25
+ DB_PWD = os.getenv('DB_PWD')
26
+ DB_ENCRYPT = os.getenv('DB_ENCRYPT')
27
+ DB_NAME = os.getenv('DB_name')
28
+ DB_TABLE_NAME = os.getenv('DB_tableName')
29
+ frontend_url = os.getenv('FRONTEND_URL')
30
+ # Define connection string
31
+ conn_str = (
32
+ f"Driver={DB_DRIVER};"
33
+ f"Server={DB_SERVER};"
34
+ f"Database={DB_NAME};" # Use the target database
35
+ f"UID={DB_UID};"
36
+ f"PWD={DB_PWD};"
37
+ f"Encrypt={DB_ENCRYPT};"
38
+ )
39
+
40
  app = FastAPI()
41
  stored_combinations = {}
42
+ destination_mapping_global = {}
43
  processed_data_store: Dict[str, Dict[str, Any]] = {} # Store processed data with session_id
44
 
45
  app.add_middleware(
 
61
 
62
  # Box and Truck classes
63
  class Box:
64
+ def __init__(self, length, width, height, quantity, box_type, destination):
65
  self.length = length
66
  self.width = width
67
  self.height = height
68
  self.quantity = quantity if quantity > 0 else 1
69
  self.type = box_type
70
+ self.destination = destination # Added Destination
71
 
72
  def rotated_dimensions(self):
73
  possible_rotations = [
 
189
  for r in range(1, len(consignments_sorted) + 1):
190
  for combo in combinations(consignments_sorted, r):
191
  combo_count += 1
192
+ # Corrected line: Access the first box's type
193
+ if len(combo) == 0 or any(len(consignment[0]) == 0 for consignment in combo):
194
+ # Skip if any consignment has no boxes
195
+ continue
196
  print(f"Checking combination {combo_count}: {[f'Consignment {boxes[0].type}' for boxes, _ in combo]}")
197
 
198
  if combo_count > combination_limit:
 
271
  return None
272
 
273
 
274
+ # Updated Pydantic Models with Destination Fields
275
  class BoxData(BaseModel):
276
  PieceLength: float
277
  PieceBreadth: float
278
  PieceHeight: float
279
  Priority: int
280
+ Destination: str # Added Destination
281
 
282
 
283
  class ConsignmentData(BaseModel):
284
  ConsignmentNo: str
285
  Priority: int
286
+ Destination: str # Added Destination
287
  boxes: List[BoxData]
288
 
289
 
 
291
  truck_name: str
292
  autoSuggest: bool
293
  consignments_data: List[ConsignmentData]
294
+ destination_mapping: Dict[str, int] # Added destination_mapping
295
 
 
296
 
297
  @app.post("/submit_data/")
298
  async def submit_data(request: SubmitDataRequest):
299
  """
300
  Endpoint to receive data from the client via API.
301
  """
302
+
303
+ frontend_url = os.getenv('FRONTEND_URL')
304
  truck_name = request.truck_name
305
  autoSuggest = request.autoSuggest
306
  consignments_data = request.consignments_data # This is already parsed from JSON
307
+ destination_mapping = request.destination_mapping # Received destination_mapping
308
 
309
+ global stored_combinations, processed_data_store, destination_mapping_global
310
+
311
+ destination_mapping_global = destination_mapping
312
 
313
  # Parse consignments and calculate total quantity based on box dimensions
314
  consignments = []
315
  for consignment_data in consignments_data:
316
  consignment_no = consignment_data.ConsignmentNo # ConsignmentNo is now accessible as a string
317
  is_priority = consignment_data.Priority
318
+ consignment_destination = consignment_data.Destination # Get Destination for Consignment
319
 
320
+ # Aggregate the boxes with same dimensions and destination
321
  box_aggregation = {}
322
  for box_data in consignment_data.boxes:
323
+ dimensions = (box_data.PieceLength, box_data.PieceBreadth, box_data.PieceHeight, box_data.Destination)
324
 
325
  if dimensions in box_aggregation:
326
  box_aggregation[dimensions]['Quantity'] += 1 # Increment the count if dimensions are the same
 
329
  'PieceLength': box_data.PieceLength,
330
  'PieceBreadth': box_data.PieceBreadth,
331
  'PieceHeight': box_data.PieceHeight,
332
+ 'Destination': box_data.Destination, # Include Destination
333
  'Quantity': 1 # Initialize count to 1
334
  }
335
 
 
340
  box['PieceBreadth'] / 2.54,
341
  box['PieceHeight'] / 2.54,
342
  box['Quantity'],
343
+ f"Consignment {consignment_no} ({'Priority' if is_priority else 'Non-Priority'})",
344
+ box['Destination'] # Pass Destination to Box
345
  )
346
  for box in box_aggregation.values()
347
  ]
 
415
  "combination_number": index + 1,
416
  "consignments": [
417
  {
418
+ "consignment_number": f"{boxes[0].type.split(' ')[1]}", # Corrected access
419
+ "priority_status": boxes[0].type.split('(')[1].strip(')'),
420
+ "destination": boxes[0].destination # Include Destination
421
  }
422
  for boxes, _ in combo
423
  ],
 
438
  "height": height
439
  }
440
  },
441
+ "consignments_data": [consignment.dict() for consignment in consignments_data], # **Includes Destination**
442
+ "destination_mapping": destination_mapping # Include destination_mapping
443
  }
444
 
445
  # Redirect to the visualization page
446
+ return RedirectResponse(url=f"{frontend_url}?session_id={session_id}", status_code=302)
447
 
448
  # Prepare box data for visualization
449
  box_data = []
 
458
  'height': box.height,
459
  'type': box.type,
460
  'quantity': 1, # Since we're iterating over each individual box
461
+ 'position': position,
462
+ 'destination': box.destination # Include Destination
463
  })
464
 
465
  # Generate a unique session_id
 
473
  "width": width,
474
  "height": height
475
  }
476
+ },
477
+ "destination_mapping": destination_mapping # Include destination_mapping
478
  }
479
+
480
+ print(" auto suggest data ", processed_data_store[session_id])
481
 
482
  # Redirect to the visualization page
483
+ return RedirectResponse(url=f"{frontend_url}?session_id={session_id}", status_code=302)
 
484
 
485
+ @app.get("/get_visualization/{session_id}/")
486
+ async def get_visualization(session_id: str):
 
 
 
 
 
 
 
487
  """
488
+ Endpoint for the frontend to fetch processed data using session_id.
489
  """
490
+ if session_id not in processed_data_store:
491
+ raise HTTPException(status_code=404, detail="Session ID not found")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
492
 
493
+ return processed_data_store[session_id]
 
494
 
495
+
496
+ class SaveCombinationRequest(BaseModel):
497
+ combination_number: int
498
+ truck: str
499
+ truck_length: float
500
+ truck_width: float
501
+ truck_height: float
502
+ consignments: List[ConsignmentData]
503
+ total_quantity: int
504
+ utilization_percentage: Optional[float]
505
+
506
+ # Endpoint to save combination data
507
+ @app.post("/save_combination/", summary="Save a truck loading combination")
508
+ async def save_combination(data: SaveCombinationRequest):
509
+ try:
510
+
511
+ global destination_mapping_global
512
+ # Establish a connection
513
+ conn = pyodbc.connect(conn_str)
514
+ cursor = conn.cursor()
515
+
516
+ # Prepare data for insertion
517
+ current_date = datetime.now().strftime("%d-%m-%Y")
518
+ truck_name = data.truck
519
+ # Truck dimensions are now captured separately
520
+ truck_length = data.truck_length
521
+ truck_width = data.truck_width
522
+ truck_height = data.truck_height
523
+ total_consignments = len(data.consignments)
524
+ total_boxes = sum(len(consignment.boxes) for consignment in data.consignments)
525
+
526
+ # Prepare box data as payload for storage
527
+ box_data = []
528
+ for consignment in data.consignments:
529
+ for box in consignment.boxes:
530
+ box_entry = {
531
+ 'length': box.PieceLength,
532
+ 'width': box.PieceBreadth,
533
+ 'height': box.PieceHeight,
534
+ 'type': consignment.ConsignmentNo,
535
+ 'quantity': 1, # Assuming the quantity is handled individually in frontend
536
+ 'position': "None",
537
+ 'destination': box.Destination
538
+ }
539
+ box_data.append(box_entry)
540
+
541
+ payload = {
542
+ "boxes": box_data,
543
+ "truck": {
544
+ "name": truck_name,
545
+ "dimensions": {
546
+ "length": truck_length,
547
+ "width": truck_width,
548
+ "height": truck_height
549
+ }
550
+ },
551
+ "destination_mapping": destination_mapping_global,
552
+ "total_boxes": total_boxes,
553
+ "total_consignments": total_consignments
554
+ }
555
+
556
+ # print("payload ", payload)
557
+
558
+ # Insert data into the table
559
+ insert_query = f"""
560
+ INSERT INTO {DB_TABLE_NAME} (Date, truckName, truckDimensions, totalConsignment, totalBoxes, payload)
561
+ VALUES (?, ?, ?, ?, ?, ?)
562
+ """
563
+ cursor.execute(insert_query, (
564
+ current_date,
565
+ truck_name,
566
+ f"{truck_length}L x {truck_width}W x {truck_height}H Feet",
567
+ total_consignments,
568
+ total_boxes, # Total number of boxes calculated
569
+ json.dumps(payload) # Store the payload as a JSON string
570
+ ))
571
+
572
+ # Commit the transaction
573
+ conn.commit()
574
+
575
+ # Close the connection
576
+ cursor.close()
577
+ conn.close()
578
+
579
+ return {"message": "Combination saved successfully.", "Date": current_date}
580
+ except pyodbc.Error as e:
581
+ error_message = str(e)
582
+ raise HTTPException(status_code=500, detail=f"Database error: {error_message}")
583
+ except Exception as e:
584
+ raise HTTPException(status_code=500, detail=str(e))
585
+
586
+ @app.post("/visualize_payload/")
587
+ async def visualize_payload(payload: Dict[str, Any]):
588
+ """
589
+ API to receive a payload and return the processed data to the frontend via session ID.
590
+ """
591
+ global processed_data_store
592
+
593
+ frontend_url = os.getenv('FRONTEND_URL')
594
+ destination_mapping = payload.get('destination_mapping')
595
+
596
+
597
+ # Extract truck details from payload
598
+ truck = payload.get('truck')
599
+ if not truck or 'dimensions' not in truck:
600
+ raise HTTPException(status_code=400, detail="Truck information or dimensions missing.")
601
+
602
+ truck_name = truck.get('name')
603
+ truck_dimensions = truck.get('dimensions')
604
+ if not truck_dimensions:
605
+ raise HTTPException(status_code=400, detail="Truck dimensions missing.")
606
+
607
+ # Extract boxes from payload
608
+ boxes = payload.get('boxes', [])
609
+ if not boxes:
610
+ raise HTTPException(status_code=400, detail="No boxes found in payload.")
611
+
612
+ # Extract and process truck dimensions
613
+ length = truck_dimensions.get('length')
614
+ width = truck_dimensions.get('width')
615
+ height = truck_dimensions.get('height')
616
+ if not length or not width or not height:
617
+ raise HTTPException(status_code=400, detail="Incomplete truck dimensions.")
618
+
619
+ # Initialize Truck object
620
+ truck_obj = Truck(length * 12, width * 12, height * 12) # Convert feet to inches
621
+
622
+ # Process the boxes for FFD packing
623
+ packed_positions = []
624
+ consignments = []
625
+
626
+ for box in boxes:
627
+ # Convert box dimensions to inches (assuming they are given in feet or inches depending on your context)
628
+ box_obj = Box(
629
+ length=box['length'],
630
+ width=box['width'],
631
+ height=box['height'],
632
+ quantity=box.get('quantity', 1),
633
+ box_type=box['type'],
634
+ destination=box['destination']
635
+ )
636
+ # Use pack_boxes_ffd method to find positions for boxes in truck
637
+ position = truck_obj.add_box_ffd(box_obj)
638
+ packed_positions.append((box_obj, position))
639
+
640
+ # Prepare the box data for visualization
641
  box_data = []
642
+ for box_obj, pos in packed_positions:
643
  if pos is not None:
644
  position = {'x': pos[0], 'y': pos[1], 'z': pos[2]}
645
  else:
646
+ position = None # Box could not be packed
647
  box_data.append({
648
+ 'length': box_obj.length / 2.54,
649
+ 'width': box_obj.width / 2.54,
650
+ 'height': box_obj.height / 2.54,
651
+ 'type': box_obj.type,
652
+ 'quantity': box_obj.quantity,
653
+ 'position': position,
654
+ 'destination': box_obj.destination
655
  })
656
 
657
+ # Generate a unique session_id for storing the processed data
658
+ session_id = str(uuid.uuid4())
659
+ processed_data_store[session_id] = {
 
660
  "boxes": box_data,
661
  "truck": {
662
  "name": truck_name,
663
  "dimensions": {
664
+ "length": length,
665
+ "width": width,
666
+ "height": height
667
  }
668
+ },
669
+ "destination_mapping": destination_mapping
670
  }
671
 
672
+ # Return session ID and processed data to the frontend for visualization
673
+ return {"session_id": session_id, "visualization_url": f"{frontend_url}?session_id={session_id}"}