Deployed

Softball Pulp

Auto-generate your softball lineups using linear programming and PuLP.

PythonPuLPLinear ProgrammingOptimizationFlaskPugHTMXuv

Coed softball lineups involve competing constraints. A league mandates a gender ratio in the batting order, players have individual position preferences, and everyone wants fair playing time across innings. This stateless Flask app models the problem as two Mixed Integer Linear Programs, solved with PuLP, and produces a batting order and nine-inning fielding schedule from a list of player names, genders, and playable positions.

Tech Stack

  • Python (Flask, PuLP, PyPugJS, Gunicorn)
  • uv (package manager)
  • Pug server-side templating
  • Pico CSS 2
  • HTMX 2
  • SortableJS
  • Vanilla JavaScript / localStorage
  • Podman
  • NGINX

Architecture

  • Stateless on the server, with no database or user accounts.
  • Three-page flow: input player data → drag-and-drop skill ranking → results.
  • localStorage persists form state across page navigation.
  • Two MILPs are solved per request via PuLP with the CBC solver:
    1. Batting order (maximizes skill weighting toward the top of the order, subject to gender ratio constraints).
    2. Fielding assignments (maximizes skill utilization across all nine innings, subject to position preferences, a minimum of four females per inning, and a constraint preventing any player from sitting out two consecutive innings).
  • A /validate-inputs endpoint dry-runs both solvers before the full submission, surfacing infeasible lineups before the page renders.
  • Containerized with Docker/Podman, served by Gunicorn behind an NGINX reverse proxy on a VPS.

Challenges

  • State is passed between pages via localStorage rather than hidden form fields, which would have become unwieldy across three steps.
  • PuLP cannot express comparisons between binary decision variables without making the model non-linear. Discouraging unnecessary position changes between innings is approximated by injecting a constant bonus at model-construction time, which is a heuristic rather than a true optimization objective.
  • The 3:2 gender ratio constraint is enforced on each group of five batters except the last group, which is always left unconstrained. This matches how the league applies the rule for the trailing batters.

Learnings

  • PuLP with the CBC solver handles 20 players × 9 innings × 10 positions fast enough that the solver is never the bottleneck.
  • Expressing lineup rules as LP constraints is more maintainable than a custom heuristic.
    • Adding a new rule is additive rather than requiring changes to existing logic.
  • HTMX handles the dynamic parts of the input form (adding/removing player cards, pre-submission validation) without pulling in a JavaScript framework.
  • uv is noticeably faster than pip for cold dependency installs in the Docker build.
  • Documenting even a simple web app is time consuming. AI can scaffold the writing, but it still needs manual rework to keep your own voice.
  • Hand-building SVGs in Figma keeps the softball icon looking sharp.

For the Future

  • Move output data into the URL so lineups can be shared directly from the results page.
  • Allow configuration before optimization (e.g., all-male roster, adjusted gender ratios, different league rules).
  • Lower the minimum player count from 10 to 8 to support smaller rosters.
  • Allow a female player to bat multiple times when the roster is short on women.