|
|
import gradio as gr
|
|
|
import pandas as pd
|
|
|
import numpy as np
|
|
|
import os
|
|
|
from datetime import datetime
|
|
|
import tempfile
|
|
|
from collections import defaultdict
|
|
|
|
|
|
|
|
|
REQUIRED_COLS = [
|
|
|
"Account",
|
|
|
"Order #",
|
|
|
"DESIGN",
|
|
|
"Labels",
|
|
|
"Colours",
|
|
|
"Kgs",
|
|
|
"Pending"
|
|
|
]
|
|
|
|
|
|
|
|
|
OPTIONAL_COLS = ["Sqm", "Unnamed: 0"]
|
|
|
|
|
|
def _normalize_columns(df: pd.DataFrame) -> pd.DataFrame:
|
|
|
"""Normalize column names by stripping whitespace"""
|
|
|
df = df.copy()
|
|
|
df.columns = [str(c).strip() for c in df.columns]
|
|
|
return df
|
|
|
|
|
|
def _parse_colours(colour_str):
|
|
|
"""Parse colour string into list of individual colours"""
|
|
|
if pd.isna(colour_str):
|
|
|
return []
|
|
|
|
|
|
|
|
|
colour_str = str(colour_str).strip()
|
|
|
|
|
|
|
|
|
for sep in [',', ';', '|', '/', '+', '&']:
|
|
|
if sep in colour_str:
|
|
|
colours = [c.strip().upper() for c in colour_str.split(sep) if c.strip()]
|
|
|
return colours
|
|
|
|
|
|
|
|
|
return [colour_str.upper()] if colour_str else []
|
|
|
|
|
|
def calculate_colour_totals(df: pd.DataFrame) -> pd.DataFrame:
|
|
|
"""Calculate total quantity required for each colour across all designs"""
|
|
|
colour_totals = defaultdict(float)
|
|
|
colour_details = defaultdict(list)
|
|
|
|
|
|
for _, row in df.iterrows():
|
|
|
colours = _parse_colours(row['Colours'])
|
|
|
kgs = pd.to_numeric(row['Kgs'], errors='coerce') or 0
|
|
|
design = str(row.get('DESIGN', 'Unknown'))
|
|
|
order_num = str(row.get('Order #', 'Unknown'))
|
|
|
|
|
|
if colours and kgs > 0:
|
|
|
|
|
|
kgs_per_colour = kgs / len(colours)
|
|
|
for colour in colours:
|
|
|
colour_totals[colour] += kgs_per_colour
|
|
|
colour_details[colour].append({
|
|
|
'Design': design,
|
|
|
'Order': order_num,
|
|
|
'Kgs_Contribution': kgs_per_colour,
|
|
|
'Total_Order_Kgs': kgs
|
|
|
})
|
|
|
|
|
|
|
|
|
colour_rows = []
|
|
|
for colour, total_kgs in sorted(colour_totals.items(), key=lambda x: x[1], reverse=True):
|
|
|
designs_using = list(set([detail['Design'] for detail in colour_details[colour]]))
|
|
|
orders_count = len(colour_details[colour])
|
|
|
|
|
|
colour_rows.append({
|
|
|
'Colour': colour,
|
|
|
'Total_Kgs_Required': round(total_kgs, 2),
|
|
|
'Designs_Using_This_Colour': ', '.join(sorted(designs_using)),
|
|
|
'Number_of_Orders': orders_count,
|
|
|
'Priority_Rank': len(colour_rows) + 1
|
|
|
})
|
|
|
|
|
|
colour_df = pd.DataFrame(colour_rows)
|
|
|
return colour_df, colour_details
|
|
|
|
|
|
def create_detailed_colour_breakdown(colour_details: dict) -> pd.DataFrame:
|
|
|
"""Create detailed breakdown showing which orders contribute to each colour"""
|
|
|
breakdown_rows = []
|
|
|
|
|
|
for colour, details in colour_details.items():
|
|
|
for detail in details:
|
|
|
breakdown_rows.append({
|
|
|
'Colour': colour,
|
|
|
'Design': detail['Design'],
|
|
|
'Order_Number': detail['Order'],
|
|
|
'Kgs_for_This_Colour': round(detail['Kgs_Contribution'], 2),
|
|
|
'Total_Order_Kgs': detail['Total_Order_Kgs']
|
|
|
})
|
|
|
|
|
|
breakdown_df = pd.DataFrame(breakdown_rows)
|
|
|
|
|
|
breakdown_df = breakdown_df.sort_values(['Colour', 'Kgs_for_This_Colour'], ascending=[True, False])
|
|
|
|
|
|
return breakdown_df
|
|
|
|
|
|
def detect_date_columns(df: pd.DataFrame) -> list:
|
|
|
"""Detect date columns in the dataframe"""
|
|
|
date_columns = []
|
|
|
|
|
|
for col in df.columns:
|
|
|
col_str = str(col).strip()
|
|
|
|
|
|
|
|
|
try:
|
|
|
pd.to_datetime(col_str)
|
|
|
date_columns.append(col)
|
|
|
except:
|
|
|
|
|
|
if '/' in col_str and len(col_str.split('/')) == 2:
|
|
|
try:
|
|
|
parts = col_str.split('/')
|
|
|
if all(part.isdigit() for part in parts):
|
|
|
date_columns.append(col)
|
|
|
except:
|
|
|
pass
|
|
|
|
|
|
return date_columns
|
|
|
|
|
|
def find_earliest_order_date(df: pd.DataFrame) -> pd.Series:
|
|
|
"""Find the earliest date for each order from date columns"""
|
|
|
date_columns = detect_date_columns(df)
|
|
|
|
|
|
if not date_columns:
|
|
|
|
|
|
return pd.Series([365] * len(df), index=df.index)
|
|
|
|
|
|
earliest_dates = []
|
|
|
|
|
|
for idx, row in df.iterrows():
|
|
|
order_dates = []
|
|
|
|
|
|
for date_col in date_columns:
|
|
|
cell_value = row[date_col]
|
|
|
|
|
|
|
|
|
if pd.isna(cell_value) or cell_value == 0 or cell_value == "":
|
|
|
continue
|
|
|
|
|
|
|
|
|
try:
|
|
|
if '/' in str(date_col):
|
|
|
|
|
|
day, month = str(date_col).split('/')
|
|
|
|
|
|
date_obj = pd.to_datetime(f"2025-{month.zfill(2)}-{day.zfill(2)}")
|
|
|
else:
|
|
|
|
|
|
date_obj = pd.to_datetime(str(date_col))
|
|
|
|
|
|
|
|
|
if not pd.isna(cell_value) and str(cell_value).strip() != "" and str(cell_value) != "0":
|
|
|
order_dates.append(date_obj)
|
|
|
|
|
|
except:
|
|
|
continue
|
|
|
|
|
|
|
|
|
if order_dates:
|
|
|
earliest_date = min(order_dates)
|
|
|
else:
|
|
|
|
|
|
earliest_date = pd.to_datetime("2024-01-01")
|
|
|
|
|
|
earliest_dates.append(earliest_date)
|
|
|
|
|
|
return pd.Series(earliest_dates, index=df.index)
|
|
|
|
|
|
def compute_dyeing_priority(df: pd.DataFrame, min_kgs: int = 100, weights: dict = None) -> tuple:
|
|
|
"""
|
|
|
Compute dyeing priority based on:
|
|
|
1. Oldest orders with minimum kgs per design
|
|
|
2. Designs with fewest colours
|
|
|
3. Order age
|
|
|
"""
|
|
|
|
|
|
|
|
|
if weights is None:
|
|
|
weights = {"AGE_WEIGHT": 50, "COLOUR_SIMPLICITY_WEIGHT": 30, "DESIGN_WEIGHT": 20}
|
|
|
|
|
|
df = _normalize_columns(df)
|
|
|
|
|
|
|
|
|
missing = [c for c in REQUIRED_COLS if c not in df.columns]
|
|
|
if missing:
|
|
|
raise ValueError(f"Missing required columns: {missing}. Found columns: {list(df.columns)}")
|
|
|
|
|
|
|
|
|
out = df.copy()
|
|
|
|
|
|
|
|
|
out["OrderDate"] = find_earliest_order_date(out)
|
|
|
|
|
|
|
|
|
today = pd.Timestamp.now().normalize()
|
|
|
out["OrderAgeDays"] = (today - out["OrderDate"]).dt.days
|
|
|
out["OrderAgeDays"] = out["OrderAgeDays"].fillna(0).clip(lower=0)
|
|
|
|
|
|
|
|
|
out["Kgs"] = pd.to_numeric(out["Kgs"], errors="coerce").fillna(0)
|
|
|
|
|
|
|
|
|
out["ColourList"] = out["Colours"].apply(_parse_colours)
|
|
|
out["ColourCount"] = out["ColourList"].apply(len)
|
|
|
|
|
|
|
|
|
design_groups = out.groupby("DESIGN").agg({
|
|
|
"Kgs": "sum",
|
|
|
"OrderDate": "min",
|
|
|
"OrderAgeDays": "max",
|
|
|
"ColourCount": "first",
|
|
|
"Order #": "count"
|
|
|
}).reset_index()
|
|
|
|
|
|
design_groups.columns = ["DESIGN", "Total_Kgs", "Oldest_Date", "Max_Age_Days", "ColourCount", "Order_Count"]
|
|
|
|
|
|
|
|
|
design_groups["MeetsMinKgs"] = design_groups["Total_Kgs"] >= min_kgs
|
|
|
|
|
|
|
|
|
eligible_designs = design_groups[design_groups["MeetsMinKgs"]].copy()
|
|
|
|
|
|
if len(eligible_designs) == 0:
|
|
|
|
|
|
eligible_designs = design_groups.copy()
|
|
|
eligible_designs["MeetsMinKgs"] = False
|
|
|
|
|
|
|
|
|
if eligible_designs["Max_Age_Days"].max() > 0:
|
|
|
eligible_designs["AgeScore_01"] = eligible_designs["Max_Age_Days"] / eligible_designs["Max_Age_Days"].max()
|
|
|
else:
|
|
|
eligible_designs["AgeScore_01"] = 0
|
|
|
|
|
|
|
|
|
if eligible_designs["ColourCount"].max() > 0:
|
|
|
eligible_designs["ColourSimplicityScore_01"] = 1 - (eligible_designs["ColourCount"] / eligible_designs["ColourCount"].max())
|
|
|
else:
|
|
|
eligible_designs["ColourSimplicityScore_01"] = 0
|
|
|
|
|
|
|
|
|
if eligible_designs["Total_Kgs"].max() > 0:
|
|
|
eligible_designs["VolumeScore_01"] = eligible_designs["Total_Kgs"] / eligible_designs["Total_Kgs"].max()
|
|
|
else:
|
|
|
eligible_designs["VolumeScore_01"] = 0
|
|
|
|
|
|
|
|
|
w_age = weights["AGE_WEIGHT"] / 100.0
|
|
|
w_colour = weights["COLOUR_SIMPLICITY_WEIGHT"] / 100.0
|
|
|
w_design = weights["DESIGN_WEIGHT"] / 100.0
|
|
|
|
|
|
eligible_designs["AgeScore"] = eligible_designs["AgeScore_01"] * w_age
|
|
|
eligible_designs["ColourSimplicityScore"] = eligible_designs["ColourSimplicityScore_01"] * w_colour
|
|
|
eligible_designs["VolumeScore"] = eligible_designs["VolumeScore_01"] * w_design
|
|
|
|
|
|
eligible_designs["PriorityScore"] = (
|
|
|
eligible_designs["AgeScore"] +
|
|
|
eligible_designs["ColourSimplicityScore"] +
|
|
|
eligible_designs["VolumeScore"]
|
|
|
)
|
|
|
|
|
|
|
|
|
eligible_designs = eligible_designs.sort_values(
|
|
|
["MeetsMinKgs", "PriorityScore", "Max_Age_Days"],
|
|
|
ascending=[False, False, False]
|
|
|
)
|
|
|
|
|
|
|
|
|
detailed_results = out.merge(
|
|
|
eligible_designs[["DESIGN", "Total_Kgs", "Max_Age_Days", "MeetsMinKgs",
|
|
|
"AgeScore", "ColourSimplicityScore", "VolumeScore", "PriorityScore"]],
|
|
|
on="DESIGN",
|
|
|
how="left"
|
|
|
)
|
|
|
|
|
|
|
|
|
detailed_results = detailed_results.sort_values(
|
|
|
["MeetsMinKgs", "PriorityScore", "OrderAgeDays"],
|
|
|
ascending=[False, False, False]
|
|
|
)
|
|
|
|
|
|
|
|
|
colour_totals, colour_details = calculate_colour_totals(out)
|
|
|
colour_breakdown = create_detailed_colour_breakdown(colour_details)
|
|
|
|
|
|
return detailed_results, eligible_designs, colour_totals, colour_breakdown
|
|
|
|
|
|
def save_dyeing_results(detailed_df, design_summary, colour_totals, colour_breakdown, output_path, min_kgs, weights):
|
|
|
"""Save all results with multiple sheets"""
|
|
|
|
|
|
with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
|
|
|
|
|
|
|
|
|
colour_totals.to_excel(writer, sheet_name='COLOUR_REQUIREMENTS', index=False)
|
|
|
|
|
|
|
|
|
colour_breakdown.to_excel(writer, sheet_name='Colour_Order_Breakdown', index=False)
|
|
|
|
|
|
|
|
|
design_summary.to_excel(writer, sheet_name='Design_Priority_Summary', index=False)
|
|
|
|
|
|
|
|
|
detailed_df.to_excel(writer, sheet_name='Order_Priority_Detail', index=False)
|
|
|
|
|
|
|
|
|
instructions_data = [
|
|
|
['π¨ DYEING PRIORITY & COLOUR REQUIREMENTS ANALYSIS'],
|
|
|
[''],
|
|
|
['π SHEET EXPLANATIONS:'],
|
|
|
[''],
|
|
|
['1. COLOUR_REQUIREMENTS - π― MAIN OUTPUT YOU NEED'],
|
|
|
[' β’ Total kgs needed for each colour (consolidated across all designs)'],
|
|
|
[' β’ No colour repetition - each colour listed once with total quantity'],
|
|
|
[' β’ Sorted by quantity (highest first) for production planning'],
|
|
|
[' β’ Shows which designs use each colour and order count'],
|
|
|
[''],
|
|
|
['2. Colour_Order_Breakdown - Detailed breakdown'],
|
|
|
[' β’ Shows exactly which orders contribute to each colour total'],
|
|
|
[' β’ Useful for tracking and verification'],
|
|
|
[''],
|
|
|
['3. Design_Priority_Summary - Design-level priorities'],
|
|
|
[' β’ Ranked by priority score for production sequence'],
|
|
|
[''],
|
|
|
['4. Order_Priority_Detail - Individual order details'],
|
|
|
[' β’ All orders with calculated priority scores'],
|
|
|
[''],
|
|
|
['π― PRIORITY METHODOLOGY:'],
|
|
|
[f'β’ Age Weight: {weights["AGE_WEIGHT"]}% - Prioritizes older orders'],
|
|
|
[f'β’ Colour Simplicity Weight: {weights["COLOUR_SIMPLICITY_WEIGHT"]}% - Fewer colours = higher priority'],
|
|
|
[f'β’ Design Volume Weight: {weights["DESIGN_WEIGHT"]}% - Larger quantities get priority'],
|
|
|
[f'β’ Minimum Kgs Threshold: {min_kgs} - Only designs with total kgs >= this value are prioritized'],
|
|
|
[''],
|
|
|
['π¨ COLOUR CONSOLIDATION LOGIC:'],
|
|
|
['β’ If RED is used in Design-A (100kg) and Design-B (50kg)'],
|
|
|
['β’ Output shows: RED = 150kg total (no repetition)'],
|
|
|
['β’ Helps plan exact dye batch quantities needed'],
|
|
|
['β’ Multi-colour orders split proportionally (e.g., "Red,Blue" 100kg = 50kg each)'],
|
|
|
[''],
|
|
|
['π USAGE RECOMMENDATIONS:'],
|
|
|
['β’ Use COLOUR_REQUIREMENTS sheet for dye purchasing/batching'],
|
|
|
['β’ Use Design_Priority_Summary for production sequence planning'],
|
|
|
['β’ Check Colour_Order_Breakdown for detailed verification'],
|
|
|
[''],
|
|
|
[f'Generated on: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}']
|
|
|
]
|
|
|
|
|
|
instructions_df = pd.DataFrame(instructions_data, columns=['Instructions'])
|
|
|
instructions_df.to_excel(writer, sheet_name='Instructions', index=False)
|
|
|
|
|
|
|
|
|
def load_excel(file):
|
|
|
"""Load Excel file and return available sheet names"""
|
|
|
if file is None:
|
|
|
return gr.Dropdown(choices=[]), "Please upload a file first."
|
|
|
|
|
|
try:
|
|
|
xls = pd.ExcelFile(file.name)
|
|
|
return gr.Dropdown(choices=xls.sheet_names, value=xls.sheet_names[0]), "β
File loaded successfully!"
|
|
|
except Exception as e:
|
|
|
return gr.Dropdown(choices=[]), f"β Error loading file: {str(e)}"
|
|
|
|
|
|
def validate_weights(age_weight, colour_weight, design_weight):
|
|
|
"""Validate that weights sum to 100%"""
|
|
|
total = age_weight + colour_weight + design_weight
|
|
|
if total == 100:
|
|
|
return "β
Weights are valid (sum = 100%)"
|
|
|
else:
|
|
|
return f"β οΈ Weights sum to {total}%. Please adjust to equal 100%."
|
|
|
|
|
|
def preview_dyeing_data(file, sheet_name):
|
|
|
"""Preview the selected sheet data for dyeing analysis"""
|
|
|
if file is None or not sheet_name:
|
|
|
return "Please upload a file and select a sheet first.", pd.DataFrame()
|
|
|
|
|
|
try:
|
|
|
df = pd.read_excel(file.name, sheet_name=sheet_name)
|
|
|
|
|
|
|
|
|
preview_info = f"π **Sheet: {sheet_name}**\n"
|
|
|
preview_info += f"- Rows: {len(df)}\n"
|
|
|
preview_info += f"- Columns: {len(df.columns)}\n\n"
|
|
|
|
|
|
|
|
|
df_norm = df.copy()
|
|
|
df_norm.columns = [str(c).strip() for c in df_norm.columns]
|
|
|
missing = [c for c in REQUIRED_COLS if c not in df_norm.columns]
|
|
|
|
|
|
if missing:
|
|
|
preview_info += f"β **Missing required columns:** {missing}\n\n"
|
|
|
else:
|
|
|
preview_info += "β
**All required columns found!**\n\n"
|
|
|
|
|
|
|
|
|
date_columns = detect_date_columns(df_norm)
|
|
|
if date_columns:
|
|
|
preview_info += f"π
**Date columns detected:** {len(date_columns)} columns\n"
|
|
|
preview_info += f" Sample dates: {date_columns[:5]}\n\n"
|
|
|
else:
|
|
|
preview_info += "β οΈ **No date columns detected** - will use default prioritization\n\n"
|
|
|
|
|
|
|
|
|
if 'Kgs' in df_norm.columns:
|
|
|
total_kgs = pd.to_numeric(df_norm['Kgs'], errors='coerce').sum()
|
|
|
preview_info += f"**Total Kgs:** {total_kgs:,.1f}\n"
|
|
|
|
|
|
if 'DESIGN' in df_norm.columns:
|
|
|
unique_designs = df_norm['DESIGN'].nunique()
|
|
|
preview_info += f"**Unique Designs:** {unique_designs}\n"
|
|
|
|
|
|
preview_info += f"\n**Available columns:**\n"
|
|
|
for i, col in enumerate(df.columns, 1):
|
|
|
marker = "π
" if col in date_columns else ""
|
|
|
preview_info += f"{i}. {col} {marker}\n"
|
|
|
|
|
|
|
|
|
preview_df = df.head(5)
|
|
|
|
|
|
return preview_info, preview_df
|
|
|
|
|
|
except Exception as e:
|
|
|
return f"β Error previewing data: {str(e)}", pd.DataFrame()
|
|
|
|
|
|
def process_dyeing_priority(file, sheet_name, age_weight, colour_weight, design_weight, min_kgs):
|
|
|
"""Main processing function for dyeing priorities"""
|
|
|
|
|
|
if file is None:
|
|
|
return None, None, None, "β Please upload a file first."
|
|
|
|
|
|
if not sheet_name:
|
|
|
return None, None, None, "β Please select a sheet."
|
|
|
|
|
|
|
|
|
total_weight = age_weight + colour_weight + design_weight
|
|
|
if total_weight != 100:
|
|
|
return None, None, None, f"β Error: Total weight must equal 100% (currently {total_weight}%)"
|
|
|
|
|
|
try:
|
|
|
|
|
|
df = pd.read_excel(file.name, sheet_name=sheet_name)
|
|
|
|
|
|
if df.empty:
|
|
|
return None, None, None, "β The selected sheet is empty."
|
|
|
|
|
|
|
|
|
weights = {
|
|
|
"AGE_WEIGHT": age_weight,
|
|
|
"COLOUR_SIMPLICITY_WEIGHT": colour_weight,
|
|
|
"DESIGN_WEIGHT": design_weight
|
|
|
}
|
|
|
|
|
|
|
|
|
detailed_results, design_summary, colour_totals, colour_breakdown = compute_dyeing_priority(
|
|
|
df, min_kgs=min_kgs, weights=weights
|
|
|
)
|
|
|
|
|
|
|
|
|
output_path = tempfile.NamedTemporaryFile(delete=False, suffix='.xlsx').name
|
|
|
save_dyeing_results(detailed_results, design_summary, colour_totals, colour_breakdown, output_path, min_kgs, weights)
|
|
|
|
|
|
|
|
|
total_designs = len(design_summary)
|
|
|
eligible_designs = sum(design_summary['MeetsMinKgs'])
|
|
|
total_colours = len(colour_totals)
|
|
|
top_colours = colour_totals.head(3)['Colour'].tolist() if len(colour_totals) > 0 else []
|
|
|
|
|
|
success_msg = f"β
Dyeing Priority Analysis Complete!\n"
|
|
|
success_msg += f"π SUMMARY:\n"
|
|
|
success_msg += f"- Total Designs Analyzed: {total_designs}\n"
|
|
|
success_msg += f"- Designs Meeting {min_kgs}kg Threshold: {eligible_designs}\n"
|
|
|
success_msg += f"- Unique Colours Required: {total_colours}\n"
|
|
|
if top_colours:
|
|
|
success_msg += f"- Top 3 Colours by Volume: {', '.join(top_colours)}\n"
|
|
|
success_msg += f"- Highest Priority Score: {design_summary['PriorityScore'].max():.3f}\n\n"
|
|
|
success_msg += f"π¨ COLOUR REQUIREMENTS sheet contains consolidated totals!\n"
|
|
|
success_msg += f"π₯ Download complete analysis below"
|
|
|
|
|
|
return output_path, design_summary.head(10), colour_totals.head(15), success_msg
|
|
|
|
|
|
except Exception as e:
|
|
|
return None, None, None, f"β Error processing data: {str(e)}"
|
|
|
|
|
|
|
|
|
def create_dyeing_interface():
|
|
|
with gr.Blocks(title="Dyeing Urgency Priority Calculator", theme=gr.themes.Soft()) as demo:
|
|
|
|
|
|
gr.Markdown("""
|
|
|
# π¨ Dyeing Urgency Priority Calculator
|
|
|
|
|
|
Upload your Excel file with dyeing/textile manufacturing data to calculate production priorities based on:
|
|
|
- **Order Age**: Prioritize older orders first (detects dates from column headers)
|
|
|
- **Colour Simplicity**: Fewer colours = easier production
|
|
|
- **Design Volume**: Larger quantities for efficiency
|
|
|
|
|
|
**Expected Columns**: Account, Order #, DESIGN, Labels, Colours, Kgs, Pending
|
|
|
**Date Detection**: Automatically detects date columns (like 2025-01-08, 13/8, etc.)
|
|
|
""")
|
|
|
|
|
|
with gr.Row():
|
|
|
with gr.Column(scale=1):
|
|
|
gr.Markdown("## π File Upload & Selection")
|
|
|
|
|
|
file_input = gr.File(
|
|
|
label="Upload Excel File",
|
|
|
file_types=[".xlsx", ".xls"],
|
|
|
type="filepath"
|
|
|
)
|
|
|
|
|
|
sheet_dropdown = gr.Dropdown(
|
|
|
label="Select Sheet",
|
|
|
choices=[],
|
|
|
interactive=True
|
|
|
)
|
|
|
|
|
|
file_status = gr.Textbox(label="File Status", interactive=False)
|
|
|
|
|
|
with gr.Column(scale=1):
|
|
|
gr.Markdown("## βοΈ Priority Weights (must sum to 100%)")
|
|
|
|
|
|
age_weight = gr.Slider(
|
|
|
minimum=0, maximum=100, value=50, step=1,
|
|
|
label="Age Weight (%)",
|
|
|
info="Higher = prioritize older orders more"
|
|
|
)
|
|
|
|
|
|
colour_weight = gr.Slider(
|
|
|
minimum=0, maximum=100, value=30, step=1,
|
|
|
label="Colour Simplicity Weight (%)",
|
|
|
info="Higher = prioritize designs with fewer colours"
|
|
|
)
|
|
|
|
|
|
design_weight = gr.Slider(
|
|
|
minimum=0, maximum=100, value=20, step=1,
|
|
|
label="Design Volume Weight (%)",
|
|
|
info="Higher = prioritize larger quantity designs"
|
|
|
)
|
|
|
|
|
|
weight_status = gr.Textbox(label="Weight Validation", interactive=False)
|
|
|
|
|
|
min_kgs = gr.Number(
|
|
|
label="Minimum Kgs Threshold per Design",
|
|
|
value=100,
|
|
|
info="Only designs with total kgs >= this value get priority"
|
|
|
)
|
|
|
|
|
|
with gr.Row():
|
|
|
preview_btn = gr.Button("ποΈ Preview Data", variant="secondary")
|
|
|
process_btn = gr.Button("π¨ Calculate Dyeing Priorities", variant="primary", size="lg")
|
|
|
|
|
|
with gr.Row():
|
|
|
with gr.Column():
|
|
|
gr.Markdown("## π Data Preview")
|
|
|
preview_info = gr.Textbox(label="Data Information", lines=10, interactive=False)
|
|
|
preview_table = gr.Dataframe(label="Sample Data")
|
|
|
|
|
|
with gr.Row():
|
|
|
with gr.Column():
|
|
|
gr.Markdown("## π Priority Results")
|
|
|
results_info = gr.Textbox(label="Processing Status", interactive=False)
|
|
|
|
|
|
with gr.Column():
|
|
|
download_file = gr.File(label="π₯ Download Complete Analysis")
|
|
|
|
|
|
with gr.Row():
|
|
|
with gr.Column():
|
|
|
gr.Markdown("## π Top Design Priorities")
|
|
|
design_results = gr.Dataframe(label="Design Priority Summary")
|
|
|
|
|
|
with gr.Column():
|
|
|
gr.Markdown("## π¨ Colour Requirements (Consolidated)")
|
|
|
colour_results = gr.Dataframe(
|
|
|
label="Total Kgs Required Per Colour",
|
|
|
headers=["Colour", "Total Kgs", "Used in Designs", "Orders Count"],
|
|
|
interactive=False
|
|
|
)
|
|
|
|
|
|
|
|
|
file_input.change(
|
|
|
fn=load_excel,
|
|
|
inputs=[file_input],
|
|
|
outputs=[sheet_dropdown, file_status]
|
|
|
)
|
|
|
|
|
|
for weight_input in [age_weight, colour_weight, design_weight]:
|
|
|
weight_input.change(
|
|
|
fn=validate_weights,
|
|
|
inputs=[age_weight, colour_weight, design_weight],
|
|
|
outputs=[weight_status]
|
|
|
)
|
|
|
|
|
|
preview_btn.click(
|
|
|
fn=preview_dyeing_data,
|
|
|
inputs=[file_input, sheet_dropdown],
|
|
|
outputs=[preview_info, preview_table]
|
|
|
)
|
|
|
|
|
|
process_btn.click(
|
|
|
fn=process_dyeing_priority,
|
|
|
inputs=[file_input, sheet_dropdown, age_weight, colour_weight, design_weight, min_kgs],
|
|
|
outputs=[download_file, design_results, colour_results, results_info]
|
|
|
)
|
|
|
|
|
|
|
|
|
demo.load(
|
|
|
fn=validate_weights,
|
|
|
inputs=[age_weight, colour_weight, design_weight],
|
|
|
outputs=[weight_status]
|
|
|
)
|
|
|
|
|
|
return demo
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
demo = create_dyeing_interface()
|
|
|
demo.launch(
|
|
|
|
|
|
|
|
|
share=True,
|
|
|
debug=True
|
|
|
) |