Skip to content

patatapython/paletizador-optimizer

Repository files navigation

Pallet Optimizer

Automates pallet assignment and label generation for agri-food warehouse logistics

Python License Tests Platform

Leer en Espanol


Context

Every day, a warehouse receives a spreadsheet with orders for 50+ product references across 5 delivery platforms. Each product ships in one of four standardized IFCO reusable crates, all with different dimensions:

 IFCO CRATE TYPES                              Crates per pallet base
 ─────────────────────────────────────────────  ────────────────────────
 LL 6424  │ 600×400×240 mm │ the standard      5 on METRO, 4 on EURO
 LL 6416  │ 600×400×160 mm │ shorter, 2/3 h    5 on METRO, 4 on EURO
 LL 4314  │ 400×300×140 mm │ small footprint   10 on METRO, 8 on EURO
 LL 4310  │ 400×300×100 mm │ smallest, flat    10 on METRO, 8 on EURO

These crates stack onto two types of CHEP pallets, each with different capacity limits:

 CHEP PALLET TYPES
 ──────────────────────────────────────────────────────────────
 METRO  │ 1200 × 1000 mm │ normal height: 40 equiv │ max: 45
 EURO   │ 1200 ×  800 mm │ normal height: 32 equiv │ max: 36

Before this system existed, an operator would sit down with the printed spreadsheet, a pen, and a calculator to figure out the puzzle: how many pallets are needed, which products go on which pallet, how to keep heights even and products together. Then they'd write labels by hand for each pallet — product name, quantity, weight, crate type, destination. For 5 platforms with 50+ products, this took over an hour every single day. It was tedious, error-prone, and the result varied depending on who did it.

What this project does

The operator loads the raw Excel into the application, clicks process, and the system handles the rest. It reads the 5 platforms automatically, calculates the optimal pallet count for each, assigns every product following all the internal business rules, and generates ready-to-print A4 PDF labels — one per product per pallet, with all the data filled in. Three clicks, under three minutes. Done.

Impact

Before (manual) After (automated)
Time 60+ minutes per day ~3 minutes (3 clicks)
Errors Frequent (mental math, copy-paste between sheets) Zero (deterministic algorithm)
Labels Handwritten or none Professional A4 PDF, auto-generated
Consistency Depends on the operator Same optimal result every time
Scalability One platform at a time All 5 platforms processed in a single batch

That's roughly a 95% time reduction. Over a year of 250 working days, that's about 240 hours of labor saved — and fewer mistakes in every shipment.


How the algorithm works

The core engine (asignador_v2.py) doesn't just pack boxes into pallets. It implements the same decision-making process that an experienced operator follows intuitively, but codified into 6 explicit passes that run in strict priority order.

Pass 1 — Sacred perfect pallets. The system scans for products with enough boxes to fill an entire METRO pallet (around 40 equivalent units). When found, it creates a homogeneous pallet with just that one product and locks it — no later pass can touch it. These are the "sacred" pallets. For example, if there are 54 boxes of a product, the system assigns all 54 to one METRO pallet at 40.6 equivalents, slightly over target but keeping the product together.

Pass 2 — Pragmatic sacred pallets. Same idea, but for products that don't quite reach 40 but are close enough (38-40 range) and have no more boxes left in the order. Better to lock them as homogeneous than to mix them later.

Pass 3 — Avoid tiny partitions. If a product has fewer than 5 boxes remaining, the system merges them into an existing mixed pallet instead of creating a ridiculous micro-partition that no human would ever make.

Pass 4 — Preferences. This is where EURO pallets come into play. EURO is treated as auxiliary — it's specifically used for small crates (LL 4314, LL 4310) and microwave products, which are easier to handle on the narrower pallet. A unified scoring system guides placement decisions.

Pass 5 — Forced assignment. Everything left over gets assigned, guaranteed. The system finds the best available pallet for each remaining batch, even if it means exceeding normal height limits (overload mode). One hundred percent of boxes must be assigned — no exceptions.

Pass 6 — Balancing. If the standard deviation of pallet heights exceeds 3.0, the system redistributes small movable products between pallets to even things out. Sacred pallets are never touched.

The result: every box assigned, heights balanced, business rules respected, all in under a second.


The label generation pipeline

Once products are assigned to pallets, the system generates physical labels that warehouse staff stick on each pallet for identification. Each label is an A4 landscape PDF containing the product name, variety, quantity, net weight, crate type, pallet type (METRO/EURO), destination platform, and delivery day.

The labels are built from HTML templates with Jinja2 variables ({{ variedad }}, {{ bultos }}, {{ peso_neto }}...). For each product on each pallet, the engine renders the template with real data and converts it to PDF using WeasyPrint. A platform with 14 products produces a single 14-page PDF, easy to send straight to the printer.

The templates themselves can be customized with a built-in visual WYSIWYG editor — a drag-and-drop tool where users can reposition fields, resize elements with 8-handle grips, upload logos, adjust fonts, and mark fields as dynamic Jinja2 variables, all without touching HTML. The editor exports clean, production-ready templates that plug directly into the generation pipeline.


Screenshots

Batch results — detailed pallet breakdown

After loading an Excel order file and running the optimizer, the GUI displays results per platform in a tabbed view. Each pallet shows its type (METRO/EURO), whether it's homogeneous or mixed, the products assigned, crate types, equivalent heights, and deviation from target. The operator can scroll through all pallets and switch between the 5 platforms instantly.

Pallet assignment results

PDF report — pallet composition summary

The system exports a printable PDF report that the warehouse operator uses as a reference while building the pallets. It lists every pallet with its contents: product name, quantity, crate type, equivalent height, and running total. This replaces the handwritten notes that operators used before.

PDF palletization report

Generated labels — ready to print

For each product on each pallet, the system generates an A4 label with all the information the client needs: delivery day, pallet type, number of boxes, net weight, product name, variety, destination platform, and both company logos. These are printed and physically attached to the pallets before shipping.

Generated pallet labels

Features

The system has three interfaces. The desktop GUI (CustomTkinter) is the primary tool: it handles CSV loading, order creation, batch Excel processing with expandable platform previews, tabbed result views (executive summary, detailed breakdown, KPI metrics), PDF report export, and label generation with progress tracking. The CLI supports the same core functionality through argparse — single files, batch mode, PDF export, verbose/quiet flags — useful for scripting or quick checks. And the visual template editor provides a standalone design environment for label layouts.


Quick Start

git clone https://github.com/patatapython/paletizador-optimizer.git
cd paletizador-optimizer

python -m venv venv
source venv/bin/activate      # Linux/macOS
# venv\Scripts\activate       # Windows

pip install -r requirements.txt
# GUI
python gui/main_window.py

# CLI
python main.py ejemplos_csv/ejemplo_demo.csv
python main.py ejemplos_csv/ejemplo_demo.csv --pdf report.pdf
python main.py *.csv --batch --quiet
WeasyPrint note (Linux)

Label generation requires GTK3 libraries:

# Debian/Ubuntu
sudo apt install libpango-1.0-0 libharfbuzz0b libpangoft2-1.0-0
# Manjaro/Arch
sudo pacman -S pango harfbuzz

Tech Stack

Layer Technology
Language Python 3.10+
GUI CustomTkinter
Data Pandas, openpyxl, xlrd
PDF Reports ReportLab
PDF Labels WeasyPrint, Jinja2
Images Pillow
Tests pytest (66/68 passing)

Project Structure

paletizador-optimizer/
│
├── asignador_v2.py          # Core engine — 6-pass priority rules
├── calculador.py            # Mode detection and scoring
├── balanceador.py           # Height balancing (pass 6)
├── loader.py                # CSV parsing and validation
├── models.py                # Data models (Palet, Producto...)
├── config.py                # Tunable parameters
├── limits.py                # Height constants and thresholds
├── output.py                # Console report generation
├── main.py                  # CLI entry point (argparse)
├── excel_parser.py          # Batch Excel reader
├── pedido_editor.py         # Order editor widget
│
├── gui/                     # GUI application (MVC pattern)
│   ├── main_window.py       #   Main coordinator
│   ├── sidebar.py           #   Action panel
│   ├── controllers/         #   Business logic
│   ├── views/               #   Summary, detail, metrics
│   ├── components/          #   Reusable UI components
│   ├── dialogs/             #   Modal dialogs
│   └── styles/              #   Color palette
│
├── etiquetas/               # Label generation system
│   ├── generador.py         #   Jinja2 + WeasyPrint + threading
│   ├── config.py            #   Product weights, platforms, days
│   └── templates/           #   HTML A4 landscape templates
│
├── exporters/               # PDF report exporter
├── tests/                   # Test suite (pytest)
├── ejemplos_csv/            # Demo CSV files
└── Proyecto etiquetado/     # Visual WYSIWYG template editor

CSV Format

Producto,Cantidad,Tipo de Caja
PATATA COCER 3KG,54,LL 64x24
CEBOLLA MALLA 1KG,24,LL 64x16
PIMIENTO VERDE,6,LL 43x14
PALETS CHEP METRO,5,
PALETS CHEP EURO,2,

Supported crate types: LL 64x24 (1.0x), LL 64x16 (1.33x), LL 43x14 (3.0x), LL 43x10 (4.0x). Special rows declare pallet counts and optional totals.

Configuration

Key parameters in config.py:

TOLERANCIA_DESVIACION = 3.0        # Acceptable deviation between pallets
UMBRAL_HOMOGENEO_EQUIV = 35.0      # Min equiv to attempt homogeneous pallet
UMBRAL_DESVIACION_BALANCEO = 3.0   # Threshold to trigger balancing
UMBRAL_PARTICION_RIDICULA = 5      # Min boxes before splitting a product
PENALIZACION_ALTURA_MAXIMA = 10.0  # Score penalty for exceeding normal height

Tests

pip install pytest pytest-cov

pytest tests/
pytest tests/ --cov=. --cov-report=term-missing

Covers: models, calculator, assigner (all 6 passes), integration, and label generation.


About

6-pass priority engine for IFCO/CHEP pallet assignment with PDF labels and desktop GUI

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors