Spaces:
Sleeping
Sleeping
| import pandas as pd | |
| from itertools import permutations, combinations | |
| from fastapi import FastAPI, File, UploadFile, Form, HTTPException, Request, Response | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from pydantic import BaseModel, Field | |
| from typing import List, Dict, Any, Optional | |
| import uuid | |
| import requests | |
| import json | |
| from fastapi.responses import RedirectResponse | |
| import pyodbc | |
| import os | |
| from dotenv import load_dotenv | |
| from datetime import datetime | |
| # Load environment variables from .env file | |
| load_dotenv() | |
| # Fetch credentials from environment variables | |
| DB_DRIVER = os.getenv('DB_DRIVER') | |
| DB_SERVER = os.getenv('DB_SERVER') | |
| DB_DATABASE = os.getenv('DB_DATABASE') | |
| DB_UID = os.getenv('DB_UID') | |
| DB_PWD = os.getenv('DB_PWD') | |
| DB_ENCRYPT = os.getenv('DB_ENCRYPT') | |
| DB_NAME = os.getenv('DB_name') | |
| DB_TABLE_NAME = os.getenv('DB_tableName') | |
| frontend_url = os.getenv('FRONTEND_URL_NEW') | |
| # Define connection string | |
| conn_str = ( | |
| f"Driver={DB_DRIVER};" | |
| f"Server={DB_SERVER};" | |
| f"Database={DB_NAME};" # Use the target database | |
| f"UID={DB_UID};" | |
| f"PWD={DB_PWD};" | |
| f"Encrypt={DB_ENCRYPT};" | |
| ) | |
| app = FastAPI() | |
| stored_combinations = {} | |
| destination_mapping_global = {} | |
| processed_data_store: Dict[str, Dict[str, Any]] = {} # Store processed data with session_id | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], # Adjust as needed for security | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| async def preflight_handler(request: Request, rest_of_path: str) -> Response: | |
| return Response(headers={ | |
| "Access-Control-Allow-Origin": "*", # Adjust as needed for security | |
| "Access-Control-Allow-Methods": "POST, GET, OPTIONS, PUT, DELETE", | |
| "Access-Control-Allow-Headers": "*", | |
| }) | |
| # Box and Truck classes | |
| class Box: | |
| def __init__(self, length, width, height, quantity, box_type, destination): | |
| self.length = length | |
| self.width = width | |
| self.height = height | |
| self.quantity = quantity if quantity > 0 else 1 | |
| self.type = box_type | |
| self.destination = destination # Added Destination | |
| def rotated_dimensions(self): | |
| possible_rotations = [ | |
| (self.length, self.width, self.height), | |
| (self.length, self.height, self.width), | |
| (self.width, self.length, self.height), | |
| (self.width, self.height, self.length), | |
| (self.height, self.length, self.width), | |
| (self.height, self.width, self.length) | |
| ] | |
| return sorted(possible_rotations, key=lambda x: (x[0] * x[1], x[2]), reverse=True) | |
| def volume(self): | |
| return self.length * self.width * self.height | |
| class Truck: | |
| def __init__(self, length, width, height): | |
| self.length = length | |
| self.width = width | |
| self.height = height | |
| self.volume = length * width * height | |
| self.placed_boxes = [] | |
| self.available_space = [(0, 0, 0, length, width, height)] | |
| def is_space_supported(self, position, box): | |
| x, y, z = position | |
| bl, bw, bh = box | |
| if z == 0: | |
| return True | |
| for placed_box, placed_position in self.placed_boxes: | |
| px, py, pz = placed_position | |
| pl, pw, ph = placed_box | |
| if (x >= px and x + bl <= px + pl) and (y >= py and y + bw <= py + pw) and (z == pz + ph): | |
| return True | |
| return False | |
| def add_box_ffd(self, box): | |
| best_fit = None | |
| best_fit_space_index = -1 | |
| best_fit_volume_waste = float('inf') | |
| for rotation in box.rotated_dimensions(): | |
| for i, space in enumerate(self.available_space): | |
| x, y, z, l, w, h = space | |
| if rotation[0] <= l and rotation[1] <= w and rotation[2] <= h: | |
| if not self.is_space_supported((x, y, z), rotation): | |
| continue | |
| leftover_volume = (l * w * h) - (rotation[0] * rotation[1] * rotation[2]) | |
| if leftover_volume < best_fit_volume_waste: | |
| best_fit = rotation | |
| best_fit_space_index = i | |
| best_fit_volume_waste = leftover_volume | |
| if best_fit: | |
| x, y, z, l, w, h = self.available_space[best_fit_space_index] | |
| box_position = (x, y, z) | |
| self.placed_boxes.append((best_fit, box_position)) | |
| self.available_space.pop(best_fit_space_index) | |
| self.update_space(best_fit, box_position, l, w, h) | |
| return box_position | |
| else: | |
| return None | |
| def update_space(self, box, position, l, w, h): | |
| x, y, z = position | |
| bl, bw, bh = box | |
| new_spaces = [ | |
| (x + bl, y, z, l - bl, w, h), | |
| (x, y + bw, z, bl, w - bw, h), | |
| (x, y, z + bh, bl, bw, h - bh), | |
| ] | |
| new_spaces = [space for space in new_spaces if space[3] > 0 and space[4] > 0 and space[5] > 0] | |
| self.available_space.extend(new_spaces) | |
| self.available_space.sort(key=lambda space: (space[2], space[1], space[0])) | |
| def pack_boxes_ffd(truck, consignments): | |
| packed_positions = [] | |
| # Sort consignments: priority consignments first, then by total volume (descending) | |
| consignments = sorted(consignments, key=lambda c: (not c[1], -sum(box.volume() * box.quantity for box in c[0]))) | |
| for consignment in consignments: | |
| consignment_boxes = consignment[0] | |
| for box in consignment_boxes: | |
| for _ in range(box.quantity): | |
| position = truck.add_box_ffd(box) | |
| if position is not None: | |
| # Box is packed | |
| packed_positions.append((box, position)) | |
| else: | |
| # Box cannot be packed, include with position as None | |
| packed_positions.append((box, None)) | |
| return packed_positions | |
| def find_max_consignments_combinations(truck, consignments, combination_limit=100): | |
| combinations_that_fit = [] | |
| consignments_sorted = sorted(consignments, key=lambda c: (not c[1], -sum(box.volume() * box.quantity for box in c[0]))) | |
| combo_count = 0 | |
| max_consignments_fitted = 0 | |
| for r in range(1, len(consignments_sorted) + 1): | |
| for combo in combinations(consignments_sorted, r): | |
| combo_count += 1 | |
| # Corrected line: Access the first box's type | |
| if len(combo) == 0 or any(len(consignment[0]) == 0 for consignment in combo): | |
| # Skip if any consignment has no boxes | |
| continue | |
| print(f"Checking combination {combo_count}: {[f'Consignment {boxes[0].type}' for boxes, _ in combo]}") | |
| if combo_count > combination_limit: | |
| print(f"Reached combination limit of {combination_limit}. Stopping further checks.") | |
| break | |
| temp_truck = Truck(truck.length, truck.width, truck.height) | |
| total_volume = sum(sum(box.volume() * box.quantity for box in consignment[0]) for consignment in combo) | |
| if total_volume > temp_truck.volume: | |
| continue | |
| try: | |
| packed_positions = pack_boxes_ffd(temp_truck, combo) | |
| if packed_positions: | |
| print(f"Combination {combo_count} fits with {len(combo)} consignments") | |
| combinations_that_fit.append(combo) | |
| max_consignments_fitted = max(max_consignments_fitted, len(combo)) | |
| if len(combinations_that_fit) >= 5: | |
| break | |
| except HTTPException: | |
| print(f"Combination {combo_count} does not fit") | |
| continue | |
| if combo_count > combination_limit: | |
| break | |
| return combinations_that_fit[-5:] | |
| def suggest_truck(consignments): | |
| trucks = { | |
| "TATA ACE": {"length": 7, "width": 4.8, "height": 4.8}, | |
| "ASHOK LEYLAND DOST": {"length": 7, "width": 4.8, "height": 4.8}, | |
| "MAHINDRA BOLERO PICK UP": {"length": 8, "width": 5, "height": 4.8}, | |
| "ASHOK LEYLAND BADA DOST": {"length": 9.5, "width": 5.5, "height": 5}, | |
| "TATA 407": {"length": 9, "width": 5.5, "height": 5}, | |
| "EICHER 14 FEET": {"length": 14, "width": 6, "height": 6.5}, | |
| "EICHER 17 FEET": {"length": 17, "width": 6, "height": 7}, | |
| "EICHER 19 FEET": {"length": 19, "width": 7, "height": 7}, | |
| "TATA 22 FEET": {"length": 22, "width": 7.5, "height": 7}, | |
| "TATA TRUCK (6 TYRE)": {"length": 17.5, "width": 7, "height": 7}, | |
| "TAURUS 16 T (10 TYRE)": {"length": 21, "width": 7.2, "height": 7}, | |
| "TAURUS 21 T (12 TYRE)": {"length": 24, "width": 7.3, "height": 7}, | |
| "TAURUS 25 T (14 TYRE)": {"length": 28, "width": 7.8, "height": 7}, | |
| "CONTAINER 20 FT": {"length": 20, "width": 8, "height": 8}, | |
| "CONTAINER 32 FT SXL": {"length": 32, "width": 8, "height": 8}, | |
| "CONTAINER 32 FT MXL": {"length": 32, "width": 8, "height": 8}, | |
| "CONTAINER 32 FT SXL / MXL HQ": {"length": 32, "width": 8, "height": 10}, | |
| "20 FEET OPEN ALL SIDE (ODC)": {"length": 20, "width": 8, "height": 8}, | |
| "28-32 FEET OPEN-TRAILOR JCB ODC": {"length": 28, "width": 8, "height": 8}, | |
| "32 FEET OPEN-TRAILOR ODC": {"length": 32, "width": 8, "height": 8}, | |
| "40 FEET OPEN-TRAILOR ODC": {"length": 40, "width": 8, "height": 8}, | |
| "SCV": {"length": 5, "width": 12, "height": 6}, | |
| "LCV": {"length": 11, "width": 5, "height": 5}, | |
| "ICV": {"length": 16, "width": 6.5, "height": 6.5}, | |
| "MCV": {"length": 19, "width": 7, "height": 7} | |
| } | |
| sorted_trucks = sorted(trucks.items(), key=lambda t: t[1]['length'] * t[1]['width'] * t[1]['height']) | |
| for truck_name, dimensions in sorted_trucks: | |
| truck = Truck(dimensions['length'] * 12, dimensions['width'] * 12, dimensions['height'] * 12) | |
| packed_positions = pack_boxes_ffd(truck, consignments) | |
| total_boxes = sum(box.quantity for consignment in consignments for box in consignment[0]) | |
| # Count successfully packed boxes | |
| packed_boxes_count = sum(1 for _, pos in packed_positions if pos is not None) | |
| if packed_boxes_count == total_boxes: | |
| return {"name": truck_name, "dimensions": dimensions} | |
| return None | |
| # Updated Pydantic Models with Destination Fields | |
| class BoxData(BaseModel): | |
| PieceLength: float | |
| PieceBreadth: float | |
| PieceHeight: float | |
| Priority: int | |
| Destination: str # Added Destination | |
| class ConsignmentData(BaseModel): | |
| ConsignmentNo: str | |
| Priority: int | |
| Destination: str # Added Destination | |
| boxes: List[BoxData] | |
| class SubmitDataRequest(BaseModel): | |
| truck_name: str | |
| autoSuggest: bool | |
| consignments_data: List[ConsignmentData] | |
| destination_mapping: Dict[str, int] # Added destination_mapping | |
| async def submit_data(request: SubmitDataRequest): | |
| """ | |
| Endpoint to receive data from the client via API. | |
| """ | |
| load_dotenv() | |
| # frontend_url = os.getenv('FRONTEND_URL_NEW') | |
| frontend_url="https://strong-donut-7eddd8.netlify.app" | |
| truck_name = request.truck_name | |
| autoSuggest = request.autoSuggest | |
| consignments_data = request.consignments_data # This is already parsed from JSON | |
| destination_mapping = request.destination_mapping # Received destination_mapping | |
| global stored_combinations, processed_data_store, destination_mapping_global | |
| destination_mapping_global = destination_mapping | |
| # Parse consignments and calculate total quantity based on box dimensions | |
| consignments = [] | |
| for consignment_data in consignments_data: | |
| consignment_no = consignment_data.ConsignmentNo # ConsignmentNo is now accessible as a string | |
| is_priority = consignment_data.Priority | |
| consignment_destination = consignment_data.Destination # Get Destination for Consignment | |
| # Aggregate the boxes with same dimensions and destination | |
| box_aggregation = {} | |
| for box_data in consignment_data.boxes: | |
| dimensions = (box_data.PieceLength, box_data.PieceBreadth, box_data.PieceHeight, box_data.Destination) | |
| if dimensions in box_aggregation: | |
| box_aggregation[dimensions]['Quantity'] += 1 # Increment the count if dimensions are the same | |
| else: | |
| box_aggregation[dimensions] = { | |
| 'PieceLength': box_data.PieceLength, | |
| 'PieceBreadth': box_data.PieceBreadth, | |
| 'PieceHeight': box_data.PieceHeight, | |
| 'Destination': box_data.Destination, # Include Destination | |
| 'Quantity': 1 # Initialize count to 1 | |
| } | |
| # Convert aggregated boxes back to the expected list format | |
| consignment_boxes = [ | |
| Box( | |
| box['PieceLength'] / 2.54, # Convert cm to inches | |
| box['PieceBreadth'] / 2.54, | |
| box['PieceHeight'] / 2.54, | |
| box['Quantity'], | |
| f"Consignment {consignment_no} ({'Priority' if is_priority else 'Non-Priority'})", | |
| box['Destination'] # Pass Destination to Box | |
| ) | |
| for box in box_aggregation.values() | |
| ] | |
| consignments.append((consignment_boxes, is_priority)) | |
| # Handle autoSuggest logic and other functionality here | |
| if autoSuggest: | |
| suggested_truck = suggest_truck(consignments) | |
| if suggested_truck: | |
| truck_name = suggested_truck["name"] | |
| length = suggested_truck['dimensions']['length'] | |
| width = suggested_truck['dimensions']['width'] | |
| height = suggested_truck['dimensions']['height'] | |
| else: | |
| raise HTTPException(status_code=400, detail="No suitable truck found") | |
| else: | |
| # Assuming truck dimensions are known based on truck_name | |
| trucks = { | |
| "TATA ACE": {"length": 7, "width": 4.8, "height": 4.8}, | |
| "ASHOK LEYLAND DOST": {"length": 7, "width": 4.8, "height": 4.8}, | |
| "MAHINDRA BOLERO PICK UP": {"length": 8, "width": 5, "height": 4.8}, | |
| "ASHOK LEYLAND BADA DOST": {"length": 9.5, "width": 5.5, "height": 5}, | |
| "TATA 407": {"length": 9, "width": 5.5, "height": 5}, | |
| "EICHER 14 FEET": {"length": 14, "width": 6, "height": 6.5}, | |
| "EICHER 17 FEET": {"length": 17, "width": 6, "height": 7}, | |
| "EICHER 19 FEET": {"length": 19, "width": 7, "height": 7}, | |
| "TATA 22 FEET": {"length": 22, "width": 7.5, "height": 7}, | |
| "TATA TRUCK (6 TYRE)": {"length": 17.5, "width": 7, "height": 7}, | |
| "TAURUS 16 T (10 TYRE)": {"length": 21, "width": 7.2, "height": 7}, | |
| "TAURUS 21 T (12 TYRE)": {"length": 24, "width": 7.3, "height": 7}, | |
| "TAURUS 25 T (14 TYRE)": {"length": 28, "width": 7.8, "height": 7}, | |
| "CONTAINER 20 FT": {"length": 20, "width": 8, "height": 8}, | |
| "CONTAINER 32 FT SXL": {"length": 32, "width": 8, "height": 8}, | |
| "CONTAINER 32 FT MXL": {"length": 32, "width": 8, "height": 8}, | |
| "CONTAINER 32 FT SXL / MXL HQ": {"length": 32, "width": 8, "height": 10}, | |
| "20 FEET OPEN ALL SIDE (ODC)": {"length": 20, "width": 8, "height": 8}, | |
| "28-32 FEET OPEN-TRAILOR JCB ODC": {"length": 28, "width": 8, "height": 8}, | |
| "32 FEET OPEN-TRAILOR ODC": {"length": 32, "width": 8, "height": 8}, | |
| "40 FEET OPEN-TRAILOR ODC": {"length": 40, "width": 8, "height": 8}, | |
| "SCV": {"length": 5, "width": 12, "height": 6}, | |
| "LCV": {"length": 11, "width": 5, "height": 5}, | |
| "ICV": {"length": 16, "width": 6.5, "height": 6.5}, | |
| "MCV": {"length": 19, "width": 7, "height": 7} | |
| } | |
| if truck_name not in trucks: | |
| raise HTTPException(status_code=400, detail="Invalid truck name provided") | |
| truck_dimensions = trucks[truck_name] | |
| length = truck_dimensions['length'] | |
| width = truck_dimensions['width'] | |
| height = truck_dimensions['height'] | |
| # Initialize the Truck object and pack boxes | |
| truck = Truck(length * 12, width * 12, height * 12) # Convert feet to inches | |
| packed_positions = pack_boxes_ffd(truck, consignments) | |
| # Count how many boxes were successfully packed | |
| packed_boxes_count = sum(1 for _, pos in packed_positions if pos is not None) | |
| # Total number of boxes | |
| total_boxes = len(packed_positions) # Since we expanded quantities during packing | |
| if packed_boxes_count < total_boxes: | |
| stored_combinations = find_max_consignments_combinations(truck, consignments) | |
| if stored_combinations: | |
| combinations_info = [] | |
| for index, combo in enumerate(stored_combinations): | |
| total_quantity = sum(box.quantity for boxes, _ in combo for box in boxes) | |
| combination_info = { | |
| "combination_number": index + 1, | |
| "consignments": [ | |
| { | |
| "consignment_number": f"{boxes[0].type.split(' ')[1]}", # Corrected access | |
| "priority_status": boxes[0].type.split('(')[1].strip(')'), | |
| "destination": boxes[0].destination # Include Destination | |
| } | |
| for boxes, _ in combo | |
| ], | |
| "total_quantity": total_quantity # Add total quantity for this combination | |
| } | |
| combinations_info.append(combination_info) | |
| # Generate a unique session_id | |
| session_id = str(uuid.uuid4()) | |
| processed_data_store[session_id] = { | |
| "error": "Not all consignments fit.", | |
| "combinations_that_fit": combinations_info, | |
| "truck": { | |
| "name": truck_name, | |
| "dimensions": { | |
| "length": length, | |
| "width": width, | |
| "height": height | |
| } | |
| }, | |
| "consignments_data": [consignment.dict() for consignment in consignments_data], # **Includes Destination** | |
| "destination_mapping": destination_mapping # Include destination_mapping | |
| } | |
| # Redirect to the visualization page | |
| return RedirectResponse(url=f"https://remarkable-frangollo-aed932.netlify.app?session_id={session_id}", status_code=302) | |
| # Prepare box data for visualization | |
| box_data = [] | |
| for box, pos in packed_positions: | |
| if pos is not None: | |
| position = {'x': pos[0], 'y': pos[1], 'z': pos[2]} | |
| else: | |
| position = None # Box could not be packed | |
| box_data.append({ | |
| 'length': box.length, | |
| 'width': box.width, | |
| 'height': box.height, | |
| 'type': box.type, | |
| 'quantity': 1, # Since we're iterating over each individual box | |
| 'position': position, | |
| 'destination': box.destination # Include Destination | |
| }) | |
| # Generate a unique session_id | |
| session_id = str(uuid.uuid4()) | |
| processed_data_store[session_id] = { | |
| "boxes": box_data, | |
| "truck": { | |
| "name": truck_name, | |
| "dimensions": { | |
| "length": length, | |
| "width": width, | |
| "height": height | |
| } | |
| }, | |
| "destination_mapping": destination_mapping # Include destination_mapping | |
| } | |
| print(" auto suggest data ", processed_data_store[session_id]) | |
| # Redirect to the visualization page | |
| return RedirectResponse(url=f"https://remarkable-frangollo-aed932.netlify.app?session_id={session_id}", status_code=302) | |
| async def get_visualization(session_id: str): | |
| """ | |
| Endpoint for the frontend to fetch processed data using session_id. | |
| """ | |
| if session_id not in processed_data_store: | |
| raise HTTPException(status_code=404, detail="Session ID not found") | |
| return processed_data_store[session_id] | |
| class SaveCombinationRequest(BaseModel): | |
| combination_number: int | |
| truck: str | |
| truck_length: float | |
| truck_width: float | |
| truck_height: float | |
| consignments: List[ConsignmentData] | |
| total_quantity: int | |
| utilization_percentage: Optional[float] | |
| # Endpoint to save combination data | |
| async def save_combination(data: SaveCombinationRequest): | |
| try: | |
| global destination_mapping_global | |
| # Establish a connection | |
| conn = pyodbc.connect(conn_str) | |
| cursor = conn.cursor() | |
| # Prepare data for insertion | |
| current_date = datetime.now().strftime("%d-%m-%Y") | |
| truck_name = data.truck | |
| # Truck dimensions are now captured separately | |
| truck_length = data.truck_length | |
| truck_width = data.truck_width | |
| truck_height = data.truck_height | |
| total_consignments = len(data.consignments) | |
| total_boxes = sum(len(consignment.boxes) for consignment in data.consignments) | |
| # Prepare box data as payload for storage | |
| box_data = [] | |
| for consignment in data.consignments: | |
| for box in consignment.boxes: | |
| box_entry = { | |
| 'length': box.PieceLength, | |
| 'width': box.PieceBreadth, | |
| 'height': box.PieceHeight, | |
| 'type': consignment.ConsignmentNo, | |
| 'quantity': 1, # Assuming the quantity is handled individually in frontend | |
| 'position': "None", | |
| 'destination': box.Destination | |
| } | |
| box_data.append(box_entry) | |
| payload = { | |
| "boxes": box_data, | |
| "truck": { | |
| "name": truck_name, | |
| "dimensions": { | |
| "length": truck_length, | |
| "width": truck_width, | |
| "height": truck_height | |
| } | |
| }, | |
| "destination_mapping": destination_mapping_global, | |
| "total_boxes": total_boxes, | |
| "total_consignments": total_consignments | |
| } | |
| # print("payload ", payload) | |
| # Insert data into the table | |
| insert_query = f""" | |
| INSERT INTO {DB_TABLE_NAME} (Date, truckName, truckDimensions, totalConsignment, totalBoxes, payload) | |
| VALUES (?, ?, ?, ?, ?, ?) | |
| """ | |
| cursor.execute(insert_query, ( | |
| current_date, | |
| truck_name, | |
| f"{truck_length}L x {truck_width}W x {truck_height}H Feet", | |
| total_consignments, | |
| total_boxes, # Total number of boxes calculated | |
| json.dumps(payload) # Store the payload as a JSON string | |
| )) | |
| # Commit the transaction | |
| conn.commit() | |
| # Close the connection | |
| cursor.close() | |
| conn.close() | |
| return {"message": "Combination saved successfully.", "Date": current_date} | |
| except pyodbc.Error as e: | |
| error_message = str(e) | |
| raise HTTPException(status_code=500, detail=f"Database error: {error_message}") | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def visualize_payload(payload: Dict[str, Any]): | |
| """ | |
| API to receive a payload and return the processed data to the frontend via session ID. | |
| """ | |
| global processed_data_store | |
| # frontend_url = os.getenv('FRONTEND_URL_NEW') | |
| frontend_url="https://strong-donut-7eddd8.netlify.app" | |
| destination_mapping = payload.get('destination_mapping') | |
| # Extract truck details from payload | |
| truck = payload.get('truck') | |
| if not truck or 'dimensions' not in truck: | |
| raise HTTPException(status_code=400, detail="Truck information or dimensions missing.") | |
| truck_name = truck.get('name') | |
| truck_dimensions = truck.get('dimensions') | |
| if not truck_dimensions: | |
| raise HTTPException(status_code=400, detail="Truck dimensions missing.") | |
| # Extract boxes from payload | |
| boxes = payload.get('boxes', []) | |
| if not boxes: | |
| raise HTTPException(status_code=400, detail="No boxes found in payload.") | |
| # Extract and process truck dimensions | |
| length = truck_dimensions.get('length') | |
| width = truck_dimensions.get('width') | |
| height = truck_dimensions.get('height') | |
| if not length or not width or not height: | |
| raise HTTPException(status_code=400, detail="Incomplete truck dimensions.") | |
| # Initialize Truck object | |
| truck_obj = Truck(length * 12, width * 12, height * 12) # Convert feet to inches | |
| # Process the boxes for FFD packing | |
| packed_positions = [] | |
| consignments = [] | |
| for box in boxes: | |
| # Convert box dimensions to inches (assuming they are given in feet or inches depending on your context) | |
| box_obj = Box( | |
| length=box['length'], | |
| width=box['width'], | |
| height=box['height'], | |
| quantity=box.get('quantity', 1), | |
| box_type=box['type'], | |
| destination=box['destination'] | |
| ) | |
| # Use pack_boxes_ffd method to find positions for boxes in truck | |
| position = truck_obj.add_box_ffd(box_obj) | |
| packed_positions.append((box_obj, position)) | |
| # Prepare the box data for visualization | |
| box_data = [] | |
| for box_obj, pos in packed_positions: | |
| if pos is not None: | |
| position = {'x': pos[0], 'y': pos[1], 'z': pos[2]} | |
| else: | |
| position = None # Box could not be packed | |
| box_data.append({ | |
| 'length': box_obj.length / 2.54, | |
| 'width': box_obj.width / 2.54, | |
| 'height': box_obj.height / 2.54, | |
| 'type': box_obj.type, | |
| 'quantity': box_obj.quantity, | |
| 'position': position, | |
| 'destination': box_obj.destination | |
| }) | |
| # Generate a unique session_id for storing the processed data | |
| session_id = str(uuid.uuid4()) | |
| processed_data_store[session_id] = { | |
| "boxes": box_data, | |
| "truck": { | |
| "name": truck_name, | |
| "dimensions": { | |
| "length": length, | |
| "width": width, | |
| "height": height | |
| } | |
| }, | |
| "destination_mapping": destination_mapping | |
| } | |
| # Return session ID and processed data to the frontend for visualization | |
| return {"session_id": session_id, "visualization_url": f"https://remarkable-frangollo-aed932.netlify.app?session_id={session_id}"} | |