CoolFace
Apppublic

Jayesh1of1/caravan-layout-optimizer

sourceHugging Faceupdated 6mo agoView on Hugging Face
0likes
App README

🚐 Caravan Layout Optimizer

OpenEnv Hackathon β€” Round 1 Submission A real-world AI environment for optimising caravan interior layouts using iterative LLM inference.

Overview

The Caravan Layout Optimizer is an OpenEnv-compatible environment where an AI agent learns to arrange furniture and fixtures inside a caravan to create the best possible floor plan.

The agent places items one at a time onto a 30 Γ— 15 grid (600 cm Γ— 300 cm, 1 cell = 20 cm) and is evaluated on six real-world criteria:

MetricWeight (Hard task)Description
Feasibility0.22No overlaps, all items within bounds
Weight Balance0.18Left-right and front-back weight distribution
Space Utilisation0.18Target ~70% fill rate
Zone Coherence0.16Kitchen/dining/sofa front; bed/bathroom rear
Aisle Score0.12Columns 13–16 kept clear as walkway
Accessibility0.14High-priority items have free adjacent cells

Why This Environment?

Caravan layout planning is a genuine, hard, real-world optimisation problem:

  • β€”Manufacturers evaluate thousands of layout permutations to find bestselling configurations
  • β€”Weight regulations require balanced axle loads for safe towing (legal requirement in most countries)
  • β€”Buyers need personalised layouts that match their lifestyle (family vs solo traveller)
  • β€”Safety codes mandate clear egress paths β€” modelled here as the central aisle

This makes it a rich benchmark: an agent must simultaneously satisfy hard geometric constraints (no overlap, in-bounds) while optimising five competing soft objectives.


Caravan Grid

x β†’  0         13 16       29
     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”  y=0  (front / hitching end)
     β”‚  FRONT   β”‚    β”‚FRONT β”‚
     β”‚  LEFT    β”‚    β”‚RIGHT β”‚       Kitchen, dining, sofa, fridge
     β”‚          β”‚    β”‚      β”‚
     │───────────ISLE│──────│  y=7
     β”‚          β”‚ A  β”‚      β”‚
     β”‚  REAR    β”‚    β”‚REAR  β”‚       Bed, bathroom, wardrobe, storage
     β”‚  LEFT    β”‚    β”‚RIGHT β”‚
     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”˜  y=14 (rear / sleeping end)

1 cell = 20 cm. Aisle = columns 13–16 (kept free for walking).


Tasks

🟒 Task Easy β€” Basic Placement

Place 3 items (bed, kitchen unit, storage) without overlaps or boundary violations.

Score1.0 Γ— feasibility + 0.1 bonus if all placed
Itemsbed_main, kitchen_unit, storage_a
Max steps20

🟑 Task Medium β€” Balanced Layout

Place 5 items optimising feasibility, weight balance, and space utilisation.

Score0.40 Γ— feasibility + 0.30 Γ— weight_balance + 0.30 Γ— space_utilisation
Itemsbed_main, kitchen_unit, dining_table, bathroom, fridge
Max steps30

πŸ”΄ Task Hard β€” Full Caravan Design

Place all 9 items with full multi-objective scoring including zone coherence, aisle preservation, and accessibility.

Score0.22 Γ— feasibility + 0.18 Γ— weight_balance + 0.18 Γ— space_utilisation + 0.16 Γ— zone_coherence + 0.12 Γ— aisle_score + 0.14 Γ— accessibility
ItemsAll 9 (see catalogue below)
Max steps60

Items Catalogue

Item IDSize (cells)Size (cm)WeightZonePriority
bed_main10 Γ— 5200 Γ— 100 cm40 kgRear1 (high)
kitchen_unit6 Γ— 3120 Γ— 60 cm30 kgFront1 (high)
dining_table5 Γ— 4100 Γ— 80 cm15 kgFront2
storage_a4 Γ— 380 Γ— 60 cm20 kgAny3 (low)
storage_b4 Γ— 380 Γ— 60 cm20 kgAny3 (low)
bathroom5 Γ— 5100 Γ— 100 cm50 kgRear1 (high)
sofa7 Γ— 3140 Γ— 60 cm25 kgFront2
wardrobe4 Γ— 280 Γ— 40 cm18 kgRear3 (low)
fridge2 Γ— 340 Γ— 60 cm22 kgFront1 (high)

Items can be rotated 90Β° (swaps width and height).


API Reference

All endpoints conform to the OpenEnv specification (openenv.yaml).

GET / β€” Health Check

json
{ "status": "ok", "environment": "CaravanLayoutOptimizer", "version": "1.0.0" }

GET /tasks β€” List Tasks

Returns all 3 task objects with id, name, difficulty, description, scoring formula.

GET /items β€” Item Catalogue

Returns all items with dimensions, weight, zone preference.

POST /reset β€” Reset Environment

json
{ "task_id": "task_easy" }

Body is optional β€” defaults to task_easy. Returns initial EnvironmentState.

POST /step β€” Place One Item

json
{
  "item_id": "bed_main",
  "x": 17,
  "y": 9,
  "rotation": 90
}

Returns StepResult: updated state, reward, done flag, info dict.

GET /state β€” Current State

Returns full EnvironmentState without advancing the episode.

GET /grid β€” ASCII Visualisation

Returns the current layout as a human-readable grid + live metrics.


Observation Space

python
EnvironmentState:
  task_id          str
  grid_width       int               # 30
  grid_height      int               # 15
  placed_items     List[PlacedItem]  # items already on the grid
  unplaced_items   List[CaravanItem] # items still to place
  step_count       int
  done             bool
  score            float             # [0.0, 1.0]
  metrics:
    feasibility        float
    weight_balance     float
    space_utilisation  float
    zone_coherence     float
    aisle_score        float
    accessibility      float
    items_placed       float
    items_remaining    float
  grid_snapshot    List[List[str]]   # 2D visual grid

Action Space

python
StepAction:
  item_id   str          # must be in unplaced_items
  x         int [0–29]   # column, 0 = left wall
  y         int [0–14]   # row, 0 = front of caravan
  rotation  int {0, 90}  # degrees; 90 swaps width/height

Reward Design

EventReward
Valid placement+0.05
Valid + correct zone+0.07
Invalid placement (overlap / out of bounds)βˆ’0.10
Unknown item IDβˆ’0.05
Episode end+final_score (terminal, grader result)

Dense intermediate rewards guide the agent toward valid, zone-aware placements. The terminal reward from the grader provides the true multi-objective signal.


Inference Architecture β€” Iterative Step-by-Step

Unlike a naive plan-then-execute approach, inference.py implements a true feedback loop:

WHILE items remain:
  1. Read LIVE state from env (after every placement)
  2. Select next item (highest accessibility priority first)
  3. Ask LLM: "place THIS ONE item" with full context:
       - Exact occupied cell ranges of every placed item
       - Free cell count per quadrant
       - Current score and all 6 metrics
       - Items still to place after this one
  4. Client-side pre-validate: bounds + overlap check before calling env
  5. Execute step() in environment
  6. If REJECTED β†’ inject exact error into conversation β†’ LLM retries (up to 3Γ—)
  7. If all retries fail β†’ heuristic scan fallback for that item only
  8. If ACCEPTED β†’ inject success + reward into conversation history
  9. Loop to next item

Key properties:

  • β€”Multi-turn conversation history β€” LLM remembers every prior placement
  • β€”Per-item retry with error injection β€” LLM corrects based on exact rejection reason
  • β€”Client-side pre-validation β€” catches bad coordinates before wasting env steps
  • β€”Per-item heuristic fallback β€” one bad LLM response never breaks the whole layout
  • β€”Priority-first ordering β€” high-access items (bed, kitchen, bathroom) get best spots first

Project Structure

caravan-layout-optimizer/
β”œβ”€β”€ main.py                  # FastAPI server (OpenEnv endpoints)
β”œβ”€β”€ inference.py             # Iterative LLM baseline
β”œβ”€β”€ openenv.yaml             # OpenEnv specification
β”œβ”€β”€ Dockerfile               # HuggingFace Spaces / Docker
β”œβ”€β”€ requirements.txt
β”œβ”€β”€ README.md
└── env/
    β”œβ”€β”€ __init__.py
    β”œβ”€β”€ models.py            # Pydantic typed models
    β”œβ”€β”€ tasks.py             # Task definitions & item catalogue
    β”œβ”€β”€ graders.py           # 6 scoring functions + 3 task graders
    └── caravan_env.py       # Core state machine (reset/step/state)

Setup & Running

Local Development

bash
git clone <your-repo-url>
cd caravan-layout-optimizer
pip install -r requirements.txt

# Start the environment server
uvicorn main:app --host 0.0.0.0 --port 7860

# Verify health
curl http://localhost:7860/

# Run iterative inference
export API_BASE_URL="https://api.openai.com/v1"
export MODEL_NAME="gpt-4o-mini"
export HF_TOKEN="sk-..."
python inference.py

Docker

bash
docker build -t caravan-optimizer .

docker run -p 7860:7860 \
  -e API_BASE_URL="https://api.openai.com/v1" \
  -e MODEL_NAME="gpt-4o-mini" \
  -e HF_TOKEN="sk-..." \
  caravan-optimizer

Hugging Face Spaces

  1. 1.Create a new Space β†’ Docker SDK
  2. 2.Connect your GitHub repo
  3. 3.Add Secrets (Settings β†’ Repository Secrets):
  4. 4.API_BASE_URL
  5. 5.MODEL_NAME
  6. 6.HF_TOKEN
  7. 7.The server starts automatically on port 7860

Environment Variables

VariableRequiredDescription
API_BASE_URLβœ…LLM API base URL (OpenAI-compatible)
MODEL_NAMEβœ…Model identifier, e.g. gpt-4o-mini
HF_TOKENβœ…API key used for LLM calls
ENV_BASE_URL⬜ optionalOverride env server URL (default: http://localhost:7860)

Pre-Submission Checklist

  • β€”[x] GET / returns 200 with status: ok
  • β€”[x] POST /reset works with and without request body
  • β€”[x] POST /step validates action and returns reward + state
  • β€”[x] GET /state returns current state without side effects
  • β€”[x] All 3 tasks defined with graders returning scores in [0.0, 1.0]
  • β€”[x] openenv.yaml spec matches actual API and metrics
  • β€”[x] inference.py named correctly, placed in root directory
  • β€”[x] Inference uses OpenAI client with API_BASE_URL / MODEL_NAME / HF_TOKEN
  • β€”[x] Inference runtime < 20 min (typically ~5 min for all 3 tasks)
  • β€”[x] Dockerfile builds and exposes port 7860
  • β€”[x] Runs within 2 vCPU / 8 GB RAM constraint