Files
platz/CLAUDE.md
T
Michael Stangl 6e3649ed27 Port codebase from Python 2 to Python 3
Replace Python 2-only constructs throughout libs/: print statements,
ConfigParser -> configparser, sets.Set -> builtin set, string.split ->
str.split, xrange -> range, dict.has_key()/.iteritems() -> in/.items(),
old-style raise Exception, "msg" syntax, and __cmp__ -> __eq__/__lt__
via functools.total_ordering (Python 3 dropped cmp()/__cmp__, which
max()/.sort() rely on). Also cleaned up mixed tab/space indentation
that Python 3's stricter tokenizer rejects.

Fixed two latent bugs that only surfaced once end-to-end runs were
possible under Python 3's stricter error handling:
- HandleBestellung() compared G.Anzahl (a bound method) to an int
  instead of calling G.Anzahl() - silently "worked" under Python 2's
  permissive cross-type ordering, raises TypeError under Python 3.
- LeuteAllein() raised KeyError for VIP-group members, who are never
  added to the regular Gruppen container; now skips people with no
  group entry instead of crashing.

Verified end-to-end: bin/platz.sh --indir work/test1 and --indir
work/test2 (VIP group included) both run to completion and produce
correctly formatted output.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 12:27:27 +02:00

139 lines
8.7 KiB
Markdown

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project overview
`platz` (German: "Sitzplatzverteilung" / seating arrangement) is a genetic-algorithm-based
solver that assigns people to tables ("Tische") for an event, given group memberships, table
sizes/neighbor relationships, and a penalty scheme. It reads an XML order/guest list and INI
table layout, runs a small custom GA framework to search for a good seating, and writes the
result to text/CSV output files.
**This is Python 3 code**, migrated from an original Python 2 implementation (the git
history / old comments may still say "Python 2" — that's now stale). Notable migration
artifacts to be aware of when touching this code:
- `Gruppe`, `Loesung`, and `Sitzplatzverteilung` use `@functools.total_ordering` with
`__eq__`/`__lt__` instead of the old Python 2 `__cmp__` (Python 3 dropped `__cmp__` and
`cmp()` entirely; `max()`/`.sort()` need rich comparisons).
- `sets.Set` → builtin `set`; `ConfigParser``configparser`; `string.split`
`str.split`; `xrange``range`; `dict.has_key(k)``k in dict`; `dict.iteritems()`
`dict.items()`; old-style `raise Exception, "msg"``raise Exception("msg")`.
- `Strukturdaten.LeuteAllein()` was fixed to skip people with no entry in
`self.Gruppen.GvonPid` (e.g. VIP-group members who were never added to the regular
`Gruppen` container) instead of letting `GruppeVonPid` raise `KeyError` — this was a
latent bug that only surfaced once VIP data (`work/test2`) was actually exercised
end-to-end.
- `HandleBestellung()` had a `G.Anzahl` comparison (`G.Anzahl > 0`) that used to silently
compare a bound method to an int under Python 2's permissive cross-type ordering;
Python 3 raises `TypeError` for that, which is what surfaced it. Fixed to call
`G.Anzahl()`.
There is no build system, test suite, or linter configured in this repo. `requirements.txt`
exists but is empty/commented — everything used is standard library — it's there only so
`bin/install_py.*` has something to install into the `.venv`.
## Running the program
Entry point is `libs/platz.py`, invoked via the scripts in `bin/` (each provided as a
`.bat`/`.sh` pair, following the "Standard Programm Template" convention — see
`~/.claude/CLAUDE.md` for the template rules):
- `bin/setenv.bat` / `bin/setenv.sh` — derives `PLATZ` from the script's own location (no
path editing needed), sets `PLATZ_BIN`, `PLATZ_CFG`, `PLATZ_LIBS`, `PLATZ_WORK`,
`PLATZ_IN`, `PLATZ_OUT`, adds `PLATZ_LIBS` to `PYTHONPATH` (idempotently), and creates
missing folders. Sourced by every other script; not normally invoked directly. The `.sh`
variant must be sourced (`source bin/setenv.sh`), not executed.
- `bin/install_py.bat` / `bin/install_py.sh` — calls `setenv`, then creates `.venv` (via
`py -m venv` / `python3 -m venv`) and `pip install -r requirements.txt` if `.venv` doesn't
already exist.
- `bin/activate_venv.bat` / `bin/activate_venv.sh` — calls `setenv`, then activates the
existing `.venv` (errors out if `install_py` hasn't been run yet).
- `bin/get_cmd.bat` / `bin/get_cmd.sh` — calls `setenv`, then opens a new shell with the
`PLATZ_*` environment already set.
- `bin/platz.bat` / `bin/platz.sh` — calls `setenv`, then runs `python
"$PLATZ_LIBS/platz.py"`. This is the actual program entry point (replaces the old
`bin/run` / `bin/run.bat`, which hardcoded absolute paths and have been removed).
`libs/platz.py` requires a command-line switch `--indir <dir>` (parsed with `optparse`)
pointing at a directory that holds the per-run input files and
receives the output files — e.g. `work/test1/`, `work/test2/`, or any new folder following
the same layout:
- `tische.ini` — table definitions: id, `Nummer`, `Hof` (venue/court), `Plaetze` (seats),
`Nachbarliste` (neighbor table ids), `Koordinaten`.
- `bestellung.xml` — the guest order: `<Gruppe>` blocks containing `<Person>` (with
`Vorname`/`Nachname`/optional `Titel`) and an `<Anzahl>` (reservation count per person
template — this expands into that many identical bookings), plus optional `<Tisch>` to
pin a VIP group to a specific table.
- Output (written back into the same `--indir` directory): `TischePersonen.txt` (table →
seated people) and `PersonenTische.csv` (person → table, sorted).
`cfg/platz.cfg` (still located via `$PLATZ_CFG`, independent of `--indir`) only selects the
GA cycle/penalty config:
- `cfg/zyklus.cfg` — defines the GA run schedule ("Zyklus"): a sequence of actions
(`e`=erzeugen/create, `s`=selektieren/select-best, `S`=random-select, `m`=mutieren/mutate,
`j`=behalten/keep parents, `z`=neue Generation/advance generation) each with a count.
- `cfg/strafen.cfg` — the penalty ("Strafpunkte") table: cost of splitting a group across
tables (by group size and number of splits), cost of a lone person, and per-table
under-utilization penalties.
`tische.ini`/`bestellung.xml`/`TischePersonen.txt`/`PersonenTische.csv` are fixed filenames
resolved as `os.path.join(indir, ...)` in `platz.py` — they are no longer configured in
`platz.cfg`.
## Architecture
Two library modules under `libs/`, imported via `PYTHONPATH=$PLATZ_LIBS`:
### `ga.py` — generic genetic-algorithm scaffolding
Domain-agnostic and reusable in principle:
- `Loesung` ("Solution") — base class for anything bred/mutated/selected by the GA. Meant to
be subclassed; `Sitzplatzverteilung` in `Strukturdaten.py` is the concrete solution type
used here.
- `Zyklus` ("Cycle") — parses `Abfolge`/`Anzahl` strings (from `zyklus.cfg`) into the ordered
list of GA actions and their counts for one run.
- `Farm` — owns the population (`Pool`/`PoolNeu`), and executes a `Zyklus` action-by-action
against instances of a given solution class (passed as `Klasse` plus its constructor
args/kwargs). `Bester()` returns the fittest (`max()`) solution found; solutions are ordered
via `__eq__`/`__lt__` (`@total_ordering`) on `self.value`.
### `Strukturdaten.py` — domain model for this specific seating problem
- `Person`, `Gruppe` (group — a `Set` of people, splittable via `teilen()`), `Tisch` (table —
a `Gruppe` subclass with a seat capacity and neighbor-table ids).
- Container/index classes: `Personen`, `Gruppen`, `Tische`, and `Plaetze` ("Seats") which
indexes tables by how many free seats they currently have, to answer "give me a table with
at least N free seats" or "give me two neighboring tables that together fit two groups of
given sizes" efficiently during placement/mutation.
- `Strafliste` ("Penalty list") — loads `strafen.cfg` and computes penalty points for: splitting
a group (`gibPunkte('Trennung', ...)`, recursing to smaller group sizes if no exact rule is
configured), a person sitting with no group-mate at their table (`'allein'`), a person having
no group-mate at a *neighboring* table (`'nichtNachbar'`), and poor seat utilization at a
table (`'Tischbesetzung'`).
- `Sitzplatzverteilung` ("Seating arrangement") — the actual `Loesung` subclass bred by the GA.
Constructor seats VIPs first at pinned tables, then randomly seats groups either
largest-first or smallest-first (coin flip), splitting a group across two neighboring tables
when no single table has enough free seats, recursively splitting further if needed, then
calls `bewerten()` to score the arrangement per `Strafliste`. `mutieren()` picks the
worst-scoring groups (`SchlechteGruppenTopX`), evicts them, and reseats them, hoping for a
better score. `speichern()` writes the two output files.
- `XMLConfig` — parses `bestellung.xml` into `Personen`/`Gruppen`/VIP-group/VIP-seat-map,
expanding each `<Person>` template by its sibling `<Anzahl>` into that many individual
bookings, and treating any `<Gruppe>` containing a `<Tisch>` tag as a VIP group pinned to
that table rather than going through normal GA placement.
### Data flow through `platz.py` (`__main__`)
1. Parse `--indir` (required) from the command line via `optparse`.
2. Read `platz.cfg` to locate `zyklus.cfg`/`strafen.cfg` (env-var expansion via
`os.path.expandvars`).
3. Load `Zyklus` (GA schedule), `Strafliste` (penalties), `Tische` (table layout, from
`<indir>/tische.ini`).
4. Load `<indir>/bestellung.xml` via `XMLConfig` → people, groups, VIP group, VIP seat map.
5. Build a `Farm(Zyklus, Sitzplatzverteilung, Tische=..., Gruppen=..., Strafen=...,
VIPListe=..., VIPs=...)`, which runs the whole GA cycle in its constructor.
6. Take `Farm.Bester()` and call `.speichern(...)` to write `<indir>/TischePersonen.txt` and
`<indir>/PersonenTische.csv`.
Both `libs/platz.py` and `libs/Strukturdaten.py` also have self-test code under
`if __name__ == '__main__':` that exercises the classes directly with hardcoded sample data —
useful as a reference for how the classes are meant to be constructed and used.