a1traders / app.py
Waqasjan123's picture
Update app.py
39512f6 verified
import gradio as gr
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import math
import rectpack
import os
# ==============================================================================
# 1. CORE OPTIMIZATION LOGIC (Unchanged)
# ==============================================================================
def find_waste_rectangles(sheet_w, sheet_h, packer):
empty_spaces = [(0, 0, sheet_w, sheet_h)]
if not packer or not packer[0]: return empty_spaces
packed_rects_list = packer[0]
for p_rect in packed_rects_list:
prx, pry, prw, prh = p_rect.x, p_rect.y, p_rect.width, p_rect.height
newly_created_spaces = []
for i in range(len(empty_spaces) - 1, -1, -1):
space = empty_spaces.pop(i)
sx, sy, sw, sh = space
if not (prx + prw <= sx or prx >= sx + sw or pry + prh <= sy or pry >= sy + sh):
if prx > sx: newly_created_spaces.append((sx, sy, prx - sx, sh))
if prx + prw < sx + sw: newly_created_spaces.append((prx + prw, sy, (sx + sw) - (prx + prw), sh))
if pry > sy: newly_created_spaces.append((sx, sy, sw, pry - sy))
if pry + prh < sy + sh: newly_created_spaces.append((sx, pry + prh, sw, (sy + sh) - (pry + prh)))
else:
empty_spaces.append(space)
empty_spaces.extend(newly_created_spaces)
return [s for s in empty_spaces if s[2] > 0.01 and s[3] > 0.01]
def attempt_to_pack(sheet_w, sheet_h, box_w, box_h, num_pieces_to_try, allow_rotation):
if num_pieces_to_try <= 0: return None
rectangles = [(box_w, box_h)] * num_pieces_to_try
packer_algorithms = [rectpack.MaxRectsBaf, rectpack.MaxRectsBlsf, rectpack.MaxRectsBssf, rectpack.SkylineMwf, rectpack.GuillotineBssfLas]
for algo in packer_algorithms:
try:
packer = rectpack.newPacker(rotation=allow_rotation, pack_algo=algo)
packer.add_bin(sheet_w, sheet_h)
for r in rectangles: packer.add_rect(*r)
packer.pack()
if len(packer[0]) == num_pieces_to_try:
return {'packer': packer, 'strategy': algo.__name__}
except Exception:
continue
return None
def find_max_layout(sheet_w, sheet_h, box_w, box_h, allow_rotation):
theoretical_max = math.floor((sheet_w * sheet_h) / (box_w * box_h)) if (box_w * box_h) > 0 else 0
low, high = 0, min(theoretical_max, 1000)
best_confirmed_result = {'pieces': 0, 'packer': None, 'strategy': 'None'}
while low <= high:
mid = (low + high) // 2
if mid == 0:
low = mid + 1
continue
result = attempt_to_pack(sheet_w, sheet_h, box_w, box_h, mid, allow_rotation)
if result:
best_confirmed_result = {'pieces': mid, **result}
low = mid + 1
else:
high = mid - 1
return best_confirmed_result
def analyze_single_sheet(sheet_w, sheet_h, box_w, box_h, gsm, price_per_kg):
result_no_rotation = find_max_layout(sheet_w, sheet_h, box_w, box_h, allow_rotation=False)
result_no_rotation['rotation_used'] = False
result_with_rotation = {'pieces': 0}
if box_w != box_h:
result_with_rotation = find_max_layout(sheet_w, sheet_h, box_w, box_h, allow_rotation=True)
result_with_rotation['rotation_used'] = True
final_winner = result_no_rotation if result_no_rotation['pieces'] >= result_with_rotation['pieces'] else result_with_rotation
if not final_winner.get('packer'):
final_winner['pieces'] = 0
else:
final_winner['pieces'] = len(final_winner['packer'][0])
if final_winner['pieces'] == 0: return None
SQ_IN_TO_SQ_M = 0.00064516
sheet_area_sq_m = (sheet_w * sheet_h) * SQ_IN_TO_SQ_M
sheet_weight_kg = (sheet_area_sq_m * gsm) / 1000
sheet_cost_rs = sheet_weight_kg * price_per_kg
pieces_per_sheet = final_winner['pieces']
piece_area, sheet_area = box_w * box_h, sheet_w * sheet_h
total_piece_area = pieces_per_sheet * piece_area
waste_area = sheet_area - total_piece_area
utilization_percent = (total_piece_area / sheet_area) * 100 if sheet_area > 0 else 0
theoretical_max = math.floor(sheet_area / piece_area) if piece_area > 0 else 0
efficiency_rating = (pieces_per_sheet / theoretical_max) * 100 if theoretical_max > 0 else 0
final_winner.update({
'sheet_w': sheet_w, 'sheet_h': sheet_h, 'box_w': box_w, 'box_h': box_h, 'sheet_cost_rs': sheet_cost_rs,
'sheet_weight_kg': sheet_weight_kg, 'waste_per_sheet_sq_in': waste_area, 'utilization_percent': utilization_percent,
'efficiency_rating': efficiency_rating, 'theoretical_max_pieces': theoretical_max,
'true_cost_per_piece_rs': sheet_cost_rs / pieces_per_sheet if pieces_per_sheet > 0 else 0,
'waste_cost_per_sheet': (waste_area / sheet_area) * sheet_cost_rs if sheet_area > 0 else 0,
})
return final_winner
# ==============================================================================
# 2. GRADIO-SPECIFIC FUNCTIONS (Visualization and Control)
# ==============================================================================
def visualize_pattern_for_gradio(result, rank=1):
"""
FIXED: The plot title now clearly displays the sheet size and rank icon.
"""
if not result or not result.get('packer'):
fig, ax = plt.subplots(1, 1, figsize=(10, 8)); ax.text(0.5, 0.5, 'No layout data available', ha='center', va='center', fontsize=16, color='gray')
ax.set_xlim(0, 1); ax.set_ylim(0, 1); ax.axis('off'); plt.close(fig)
return fig
sheet_w, sheet_h = result['sheet_w'], result['sheet_h']
packer, pieces_per_sheet = result['packer'], result['pieces']
fig_width = min(12, max(8, sheet_w / 3)); fig_height = min(10, max(6, sheet_h / 3)) + 3.5
fig, ax = plt.subplots(1, 1, figsize=(fig_width, fig_height))
rank_icon = "πŸ₯‡" if rank == 1 else "πŸ₯ˆ" if rank == 2 else "πŸ₯‰"
title_color = '#2E8B57' if rank == 1 else '#4682B4' if rank == 2 else '#CD853F'
fig.suptitle(f"{rank_icon} Layout for {sheet_w}β€³ Γ— {sheet_h}β€³ Sheet", fontsize=18, fontweight='bold', color=title_color, y=0.98)
waste_rects = find_waste_rectangles(sheet_w, sheet_h, packer)
for (wx, wy, ww, wh) in waste_rects:
ax.add_patch(patches.Rectangle((wx, wy), ww, wh, facecolor='#FFE4E1', edgecolor='#CD5C5C', linestyle='--', linewidth=1.5, alpha=0.8))
if ww > 1.5 and wh > 1.5: ax.text(wx + ww/2, wy + wh/2, f"{ww:.1f}Γ—{wh:.1f}", ha='center', va='center', fontsize=8, color='#8B0000', fontweight='bold')
colors = ['#4169E1', '#32CD32', '#FF6347', '#FFD700', '#9370DB', '#20B2AA']
for i, rect in enumerate(packer[0]):
x, y, w, h = rect.x, rect.y, rect.width, rect.height
ax.add_patch(patches.Rectangle((x, y), w, h, linewidth=2, edgecolor='black', facecolor=colors[i % len(colors)], alpha=0.8))
ax.text(x + w/2, y + h/2, f'#{i+1}\n{w:.1f}Γ—{h:.1f}', ha='center', va='center', fontsize=9, color='white', fontweight='bold', bbox=dict(boxstyle="round,pad=0.1", facecolor='black', alpha=0.3))
legend_text = (f"**PERFORMANCE**\n- Pieces Fitted: {pieces_per_sheet} / {result.get('theoretical_max_pieces', 'N/A')} (theoretical)\n- Utilization: {result.get('utilization_percent', 0):.1f}% ({result.get('efficiency_rating', 0):.1f}% efficiency)\n\n**COST ANALYSIS**\n- Cost per Piece: Rs. {result['true_cost_per_piece_rs']:.3f}\n- Sheet Cost: Rs. {result['sheet_cost_rs']:.2f}\n\n**CONFIGURATION**\n- Algorithm: {result.get('strategy', 'Unknown')}\n- Rotation: {'Enabled' if result.get('rotation_used', False) else 'Disabled'}")
props = dict(boxstyle='round,pad=0.5', facecolor='#F0F8FF', edgecolor='#4682B4', alpha=0.95, linewidth=2)
fig.text(0.5, 0.01, legend_text, transform=plt.gcf().transFigure, fontsize=10, verticalalignment='bottom', horizontalalignment='center', bbox=props, fontfamily='monospace')
ax.set_xlim(-0.5, sheet_w + 0.5); ax.set_ylim(-0.5, sheet_h + 0.5); ax.set_aspect('equal', adjustable='box'); ax.set_xlabel('Width (inches)', fontsize=12, fontweight='bold'); ax.set_ylabel('Height (inches)', fontsize=12, fontweight='bold'); ax.grid(True, alpha=0.3, linestyle=':'); ax.set_facecolor('#FAFAFA'); ax.add_patch(patches.Rectangle((0, 0), sheet_w, sheet_h, linewidth=3, edgecolor='black', facecolor='none'))
plt.tight_layout(rect=[0, 0.15, 1, 0.95]); plt.close(fig)
return fig
def run_optimizer(gsm, price_per_kg, box_w, box_h, total_pieces_needed, mode, custom_w, custom_h, progress=gr.Progress()):
market_sheets = [(18, 24), (20, 30), (22, 28), (25, 30), (23, 36), (25, 36), (30, 30), (30, 36)]
sheets_to_check = [(custom_w, custom_h)] if mode == "Manual (Custom Size)" else market_sheets
if any(val is None or val <= 0 for val in [gsm, price_per_kg, box_w, box_h, total_pieces_needed]):
return create_error_summary("Please fill in all required fields with values greater than zero."), None, None, None
if mode == "Manual (Custom Size)" and any(val is None or val <= 0 for val in [custom_w, custom_h]):
return create_error_summary("For Manual mode, please provide custom sheet dimensions greater than zero."), None, None, None
all_results = []
progress(0.1, desc="πŸ” Initializing analysis...")
for i, (sw, sh) in enumerate(sheets_to_check):
progress(0.1 + (i / len(sheets_to_check)) * 0.7, desc=f"πŸ“‹ Analyzing {sw}Γ—{sh} sheet...")
if max(box_w, box_h) > max(sw, sh) and min(box_w, box_h) > min(sw,sh):
if not (max(box_w, box_h) <= max(sw, sh) or min(box_w, box_h) <= min(sw,sh)): continue
result = analyze_single_sheet(sw, sh, box_w, box_h, gsm, price_per_kg)
if result: all_results.append(result)
progress(0.85, desc="πŸ“Š Processing results...")
if not all_results:
return create_error_summary("No viable options found. The piece may be too large for the sheets."), None, None, None
all_results.sort(key=lambda x: (x['true_cost_per_piece_rs'], -x['utilization_percent']))
progress(0.95, desc="🎨 Generating visualizations...")
summary = create_data_rich_summary(all_results, total_pieces_needed)
plots = [visualize_pattern_for_gradio(res, rank=i+1) for i, res in enumerate(all_results[:3])]
while len(plots) < 3: plots.append(None)
progress(1.0, desc="βœ… Analysis complete!")
return summary, plots[0], plots[1], plots[2]
def create_error_summary(message):
return f"""# ⚠️ Analysis Error\n**{message}**\n\n## πŸ’‘ Troubleshooting Tips:\n- Ensure all input values are positive numbers.\n- Check that box dimensions can physically fit on the selected sheet sizes."""
def create_data_rich_summary(results, total_pieces_needed):
"""
FIXED: This function now generates a highly detailed, data-rich summary
with all the metrics you requested, formatted beautifully for mobile.
"""
if not results: return create_error_summary("No viable layouts found.")
best_result = results[0]
sheets_needed = math.ceil(total_pieces_needed / best_result['pieces'])
total_cost = sheets_needed * best_result['sheet_cost_rs']
total_weight_kg = sheets_needed * best_result['sheet_weight_kg']
total_waste_sq_in = sheets_needed * best_result['waste_per_sheet_sq_in']
sheet_area = best_result['sheet_w'] * best_result['sheet_h']
total_waste_kg = (total_waste_sq_in / sheet_area) * best_result['sheet_weight_kg'] if sheet_area > 0 else 0
summary = f"""
<div class="report-title">
<h2><img src="https://img.icons8.com/office/40/000000/goal.png" style="vertical-align: middle; height: 32px;"> PRODUCTION OPTIMIZATION REPORT</h2>
<h3><img src="https://img.icons8.com/fluency/48/000000/trophy.png" style="vertical-align: middle; height: 28px;"> RECOMMENDED SOLUTION</h3>
</div>
<div class="summary-card best-choice">
<h3>πŸ₯‡ Best Choice: {best_result['sheet_w']}β€³ Γ— {best_result['sheet_h']}β€³ Sheet</h3>
<ul>
<li><strong>πŸ’° Cost per Box:</strong> Rs. {best_result['true_cost_per_piece_rs']:.3f}</li>
<li><strong>πŸ“¦ Pieces per Sheet:</strong> {best_result['pieces']}</li>
<li><strong>πŸ“ˆ Sheet Utilization:</strong> {best_result['utilization_percent']:.1f}%</li>
<li><strong>βš™οΈ Layout Efficiency:</strong> {best_result['efficiency_rating']:.1f}%</li>
</ul>
<h4>πŸ“‹ Production Plan ({total_pieces_needed:,} pieces)</h4>
<ul>
<li><strong>πŸ“„ Total Sheets Needed:</strong> {sheets_needed:,}</li>
<li><strong>βš–οΈ Total Paper to Buy:</strong> {total_weight_kg:.2f} kg</li>
<li><strong>πŸ’΅ Total Investment:</strong> Rs. {total_cost:,.2f}</li>
</ul>
<h4>πŸ—‘οΈ Waste Analysis (Total)</h4>
<ul>
<li><strong>πŸ“ Total Waste Area:</strong> {total_waste_sq_in:,.1f} sq. inches</li>
<li><strong>βš–οΈ Total Waste Weight:</strong> {total_waste_kg:.2f} kg</li>
</ul>
</div>
---
<h3 class="report-title"><img src="https://img.icons8.com/office/40/000000/bar-chart.png" style="vertical-align: middle; height: 28px;"> ALL VIABLE OPTIONS (RANKED)</h3>
"""
for i, res in enumerate(results):
rank_icon = "πŸ₯‡" if i == 0 else "πŸ₯ˆ" if i == 1 else "πŸ₯‰" if i == 2 else f"<b>#{i+1}</b>"
summary += f"""
<div class="summary-card">
<h4>{rank_icon} Sheet: {res['sheet_w']}β€³ Γ— {res['sheet_h']}β€³</h4>
<ul>
<li><strong>πŸ’° Cost per Box:</strong> Rs. {res['true_cost_per_piece_rs']:.3f}</li>
<li><strong>πŸ“¦ Pieces per Sheet:</strong> {res['pieces']}</li>
<li><strong>πŸ“ˆ Utilization:</strong> {res['utilization_percent']:.1f}%</li>
</ul>
</div>
"""
return summary
def update_ui_visibility(mode):
return gr.Group(visible=(mode == "Manual (Custom Size)"))
# ==============================================================================
# 3. GLOBAL STATE & AUTHENTICATION (Unchanged)
# ==============================================================================
def check_password(password):
correct_password = os.getenv("passwordApp", "defaultpassword123")
if password == correct_password:
return {login_status: gr.update(value="βœ… **Access Granted!** Loading optimizer...", visible=True, elem_classes=["success-message"]), main_interface: gr.update(visible=True), login_interface: gr.update(visible=False)}
return {login_status: gr.update(value="❌ **Incorrect Password!** Please try again.", visible=True, elem_classes=["error-message"]), password_input: gr.update(value="")}
def logout_user():
return {main_interface: gr.update(visible=False), login_interface: gr.update(visible=True), password_input: gr.update(value=""), login_status: gr.update(value="", visible=False)}
# ==============================================================================
# 4. SUPER ENHANCED UI WITH MOBILE-FIRST RESPONSIVE DESIGN
# ==============================================================================
mobile_responsive_css = """
/* --- Global Styles --- */
.login-container { max-width: 450px; margin: 100px auto; padding: 40px; background: rgba(255, 255, 255, 0.95); border-radius: 20px; box-shadow: 0 20px 40px rgba(0,0,0,0.2); }
.login-title { background: linear-gradient(45deg, #667eea, #764ba2); -webkit-background-clip: text; -webkit-text-fill-color: transparent; text-align: center; font-size: 28px; font-weight: bold; margin-bottom: 20px; }
.main-container { background: #FFFFFF; border-radius: 15px; margin: 10px; padding: 20px; box-shadow: 0 10px 30px rgba(0,0,0,0.1); }
.input-section { background: #F9FAFB; border-radius: 12px; padding: 20px; margin: 10px 0; border: 1px solid #E5E7EB; }
.primary-button { background: linear-gradient(45deg, #667eea, #764ba2) !important; color: white !important; border: none !important; border-radius: 10px !important; font-weight: bold !important; }
.success-message { color: #28a745 !important; }
.error-message { color: #dc3545 !important; }
.plot-container { background: white; border-radius: 10px; padding: 15px; margin: 10px 0; box-shadow: 0 5px 15px rgba(0,0,0,0.05); border: 1px solid #E5E7EB; }
.logout-btn { position: fixed !important; top: 15px !important; right: 15px !important; z-index: 1000 !important; }
.report-title h2, .report-title h3 { margin-bottom: 10px; }
/* --- Responsive Summary Cards --- */
.summary-card { border: 1px solid #E5E7EB; border-radius: 12px; padding: 16px; margin-bottom: 16px; background-color: #F9FAFB; }
.summary-card h3, .summary-card h4 { margin-top: 0; margin-bottom: 12px; color: #1F2937; }
.summary-card ul { list-style-type: none; padding-left: 0; margin: 0; }
.summary-card li { margin-bottom: 8px; color: #4B5563; font-size: 1.05em; }
.summary-card li strong { color: #374151; }
.summary-card.best-choice { background-color: #E0F2FE; border-color: #38BDF8; }
/* --- FIXED: Responsive Design for True Full-Width Mobile View --- */
@media (max-width: 768px) {
body, .gradio-container { padding: 0 !important; margin: 0 !important; }
.login-container { margin: 0; padding: 25px; border-radius: 0; }
.main-container { margin: 0; padding: 10px; border-radius: 0; box-shadow: none; }
#main-layout { flex-direction: column !important; gap: 20px !important; }
#main-layout > div { width: 100% !important; }
.logout-btn { top: 10px !important; right: 10px !important; }
}
"""
with gr.Blocks(theme=gr.themes.Soft(primary_hue="indigo", secondary_hue="blue"), title="πŸš€ Paper Box Optimizer Pro", css=mobile_responsive_css) as app:
with gr.Group(visible=True, elem_classes="login-container") as login_interface:
gr.HTML('<div class="login-title">πŸ” SECURE ACCESS PORTAL</div>')
password_input = gr.Textbox(label="Password", type="password", placeholder="Enter your password...")
login_button = gr.Button("πŸ”“ Unlock Optimizer", variant="primary")
login_status = gr.Markdown("", visible=False)
with gr.Group(visible=False, elem_classes="main-container") as main_interface:
logout_button = gr.Button("πŸ”’ Logout", elem_classes="logout-btn")
gr.Markdown("# πŸš€ Paper Box Production Optimizer")
with gr.Row(elem_id="main-layout"):
with gr.Column(scale=1):
with gr.Group(elem_classes="input-section"):
gr.Markdown("### 1. Material & Production Inputs")
gsm_input = gr.Number(label="Paper GSM", value=250)
price_input = gr.Number(label="Price per KG of Paper", value=90)
box_w_input = gr.Number(label="Box Piece WIDTH (in)", value=9.0)
box_h_input = gr.Number(label="Box Piece HEIGHT (in)", value=16.0)
total_pieces_input = gr.Number(label="Total Pieces to Produce", value=1000, precision=0)
with gr.Group(elem_classes="input-section"):
gr.Markdown("### 2. Sheet Size Selection")
mode_select = gr.Radio(["Auto-Optimize (Standard Sizes)", "Manual (Custom Size)"], label="Choose Analysis Mode", value="Auto-Optimize (Standard Sizes)")
with gr.Group(visible=False) as custom_sheet_inputs:
custom_w_input = gr.Number(label="Custom Sheet WIDTH (in)", value=30)
custom_h_input = gr.Number(label="Custom Sheet HEIGHT (in)", value=36)
run_button = gr.Button("Calculate Optimal Plan", elem_classes="primary-button")
with gr.Column(scale=2):
output_summary = gr.Markdown(label="Analysis & Recommendations")
with gr.Accordion("Show/Hide Visual Layouts", open=True):
# FIXED: Labels are now generic as the title is inside the plot
with gr.Group(elem_classes="plot-container"):
output_plot1 = gr.Plot(label="πŸ₯‡ Optimal Layout #1")
with gr.Group(elem_classes="plot-container"):
output_plot2 = gr.Plot(label="πŸ₯ˆ Optimal Layout #2")
with gr.Group(elem_classes="plot-container"):
output_plot3 = gr.Plot(label="πŸ₯‰ Optimal Layout #3")
mode_select.change(fn=update_ui_visibility, inputs=mode_select, outputs=custom_sheet_inputs)
run_button.click(
fn=run_optimizer,
inputs=[gsm_input, price_input, box_w_input, box_h_input, total_pieces_input, mode_select, custom_w_input, custom_h_input],
outputs=[output_summary, output_plot1, output_plot2, output_plot3]
)
auth_outputs = [login_status, main_interface, login_interface, password_input]
login_button.click(fn=check_password, inputs=password_input, outputs=auth_outputs)
password_input.submit(fn=check_password, inputs=password_input, outputs=auth_outputs)
logout_button.click(fn=logout_user, outputs=[main_interface, login_interface, password_input, login_status])
if __name__ == "__main__":
app.launch()