diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..fe20d37 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,217 @@ +in Agents.md steht alles drin, was du brauchst# AGENTS.md + +## Projektkontext + +LEAG-COALLOG ist ein Proof of Concept zur Optimierung der Braunkohle-Logistik. +Das System verarbeitet eine Excel-Parameterdatei, erzeugt daraus normalisierte +Parquet-Tabellen und loest anschliessend ein Pyomo-Optimierungsmodell fuer +Lieferungen von Tagebauen zu Kraftwerken und Veredlung. + +Ziel ist eine planbare Rohkohleverteilung unter Beruecksichtigung von Nachfrage, +Toleranzen, Mischungsverhaeltnissen, Foerder- und Verladungskapazitaeten, +Verfuegbarkeiten, Schichtmustern, Bunkerlogik und Solver-Limits. Die Webapp dient +als Bedienoberflaeche fuer Upload, Parametrisierung, Laufstatus, Logs, +Visualisierung und Ergebnisdownload. + +## Architektur + +Die Anwendung besteht aus drei Hauptteilen: + +1. **Preprocessing** + - Datei: `src/preprocessing/exploration_preprocess.py` + - Liest die Excel-Datei aus `POC1_INPUT_XLSX`. + - Schreibt vorbereitete Tabellen als Parquet nach `POC1_OUTPUT_DIR`. + - Die Logik stammt aus einem Notebook-Export und ist stark an die Struktur der + Eingabe-Excel gebunden. + +2. **Optimierung** + - Einstieg: `src/optimization/run_optimization.py` + - Modell: `src/optimization/model_builder.py` + - Laedt alle Parquet-Dateien aus einem Datenverzeichnis. + - Baut ein Pyomo `ConcreteModel`. + - Loest mit `highs`, `gurobi` oder `scip`. + - Exportiert `output.xlsx` und optional `warmstart.json`. + +3. **Webapp** + - Backend: `webapp/backend/main.py` + - Frontend: `webapp/frontend/src/App.jsx` + - FastAPI nimmt Uploads entgegen, legt Job-Verzeichnisse unter `var/jobs` an + und startet Preprocessing und Optimierung als Subprozesse. + - React/Vite stellt Upload, Solver-Parameter, Job-Status, Logs, Plots, + Warmstart und Downloads bereit. + +## Datenfluss + +Standardablauf eines Webapp-Laufs: + +1. Browser sendet Excel-Datei, Solver und Parameter an `POST /api/run`. +2. FastAPI erstellt ein Job-Verzeichnis unter `var/jobs//`. +3. Die Upload-Datei wird als `input.xlsx` gespeichert. +4. `exploration_preprocess.py` schreibt `processed/*.parquet`. +5. `run_optimization.py` liest `processed/*.parquet`, baut und loest das Modell. +6. Ergebnisse landen in `output/output.xlsx` und `output/warmstart.json`. +7. Logs liegen unter `logs/preprocess.log` und `logs/optimization.log`. +8. Das Frontend pollt Status, Logs und abgeleitete Visualisierungsdaten. + +Wichtige API-Endpunkte: + +- `GET /api/health` +- `POST /api/run` +- `GET /api/jobs/{job_id}` +- `POST /api/jobs/{job_id}/cancel` +- `GET /api/jobs/{job_id}/output` +- `GET /api/jobs/{job_id}/warmstart` +- `GET /api/jobs/{job_id}/monthly-flows` +- `GET /api/jobs/{job_id}/capacity-timeseries` +- `GET /api/jobs/{job_id}/logs/{log_name}` +- `GET /api/jobs/{job_id}/logs/{log_name}/stream` + +## Optimierungsmodell + +Das Modell bildet Liefermengen als ganzzahlige Schritte ab: + +- Quellen `I`: `Reichwalde`, `Nochten`, `Welzow` +- Ziele `J`: `J`, `SP`, `B3`, `B4`, `V` +- Tage `D`: Bergbauwoche Samstag bis Freitag +- Schichten `S`: `F`, `S`, `N` +- Entscheidung `k[i,j,w,d,s]`: ganzzahlige Anzahl Lieferschritte +- Fluss `x[i,j,w,d,s] = step_size_tonnes * k[i,j,w,d,s]` + +Wichtige Nebenbedingungen und Konzepte: + +- Tages-, Wochen- und Monatstoleranzen fuer Kraftwerke +- separate Veredlungsbedarfe fuer Nochten- und Welzower-Kohle +- harte Mix-Min/Max-Grenzen und weiche Zielabweichungen +- Foerderkapazitaeten pro Monat +- Verladungskapazitaeten pro Schicht und Tag +- Verfuegbarkeiten aus `Verfuegbarkeiten.parquet` +- feste Routenverbote und kombinierte Flussgrenzen +- Schichtglaettung und Schichtmuster +- optionale Bunkerlogik fuer Ziele mit Bunkerparametern +- Warmstart Import/Export fuer Folgelaeufe + +Weitere fachliche Details stehen in: + +- `README.md` +- `docs/app-kommunikation.md` +- `notebooks/constraints_overview.md` +- `latex/modellierung.tex` + +## Wichtige Verzeichnisse + +- `src/preprocessing/`: Excel-zu-Parquet-Datenaufbereitung +- `src/optimization/`: Pyomo-Modell, Solver-Lauf, Ergebnisexport +- `webapp/backend/`: FastAPI-App und Job-Orchestrierung +- `webapp/frontend/`: React/Vite-Frontend +- `data/input/`: lokale Eingabedateien +- `data/processed/`: lokale Preprocessing-Ausgaben +- `data/out/`: lokale Ergebnisartefakte +- `var/jobs/`: persistente Webapp-Jobdaten +- `docs/`: Architektur- und Kommunikationsdokumentation +- `notebooks/`: Analyse- und Modellnotizen +- `results/`: Solver-Debugartefakte wie `iis.ilp` + +## Lokale Kommandos + +Backend starten: + +```bash +uv run python -m uvicorn webapp.backend.main:app --reload +``` + +Frontend starten: + +```bash +cd webapp/frontend +npm install +npm run dev +``` + +Preprocessing direkt ausfuehren: + +```bash +uv run python src/preprocessing/exploration_preprocess.py +``` + +Optimierung direkt ausfuehren: + +```bash +uv run python src/optimization/run_optimization.py --data-dir data/processed --solver highs +``` + +Docker-All-in-One: + +```bash +docker compose up --build +``` + +## Laufzeit und Solver + +Unterstuetzte Solver sind aktuell `highs`, `gurobi` und `scip`, sofern lokal +installiert und fuer Pyomo verfuegbar. Im Docker-Image wird `gurobipy` +installiert. Fuer Gurobi wird entweder eine Lizenzdatei ueber +`GRB_LICENSE_FILE` oder WLS-Umgebungsvariablen verwendet: + +- `GRB_LICENSE_FILE` +- `GRB_WLSACCESSID` +- `GRB_WLSSECRET` +- `GRB_LICENSEID` + +Die Webapp prueft Solver-Verfuegbarkeit ueber `/api/health`. + +## Entwicklungsregeln fuer Agenten + +- Aendere keine Eingabe-, Ergebnis- oder Jobdaten ohne expliziten Grund. +- Behandle `var/jobs`, `data/out`, `data/processed` und `results` als erzeugte + Artefakte, sofern die Aufgabe nicht genau diese Dateien betrifft. +- Respektiere bestehende fachliche Logik im Optimierungsmodell. Viele Grenzen + sind hart kodiert und fachlich motiviert. +- Bei Aenderungen an `model_builder.py` immer pruefen, ob Export, + Visualisierung und Warmstart noch konsistent sind. +- Bei Aenderungen am Excel-Output die Backend-Parser in `webapp/backend/main.py` + mitdenken. Sie lesen konkrete Sheet- und Spaltenstrukturen. +- Bei Aenderungen an API-Antworten das Frontend in `webapp/frontend/src/App.jsx` + synchron halten. +- Die Preprocessing-Datei ist notebookartig und teilweise redundant. Kleine, + lokale Korrekturen sind besser als grosse Refactors ohne Tests. +- Keine Gurobi-Lizenzdateien, Credentials oder lokale Pfade committen. +- UI und Backend muessen auch ohne Gurobi sinnvoll starten koennen, wenn HiGHS + verfuegbar ist. + +## Validierung + +Es gibt derzeit keine klar etablierte automatisierte Testsuite. Je nach Aenderung +sind diese Checks sinnvoll: + +```bash +uv run python src/preprocessing/exploration_preprocess.py +uv run python src/optimization/run_optimization.py --data-dir data/processed --solver highs --time-limit 60 +cd webapp/frontend && npm run build +``` + +Bei Backend- oder Webapp-Aenderungen zusaetzlich: + +```bash +uv run python -m uvicorn webapp.backend.main:app --reload +``` + +Dann im Browser oder per API pruefen: + +- `/api/health` +- Upload ueber Frontend +- Statuspolling +- Logs +- Download von `output.xlsx` +- Download von `warmstart.json` +- Plots fuer Monatsfluesse und Kapazitaetszeitreihen + +## Bekannte Besonderheiten + +- Die Bergbauwoche beginnt fachlich am Samstag. Im Modell wird dafuer ein + Datum-plus-zwei-Tage-Shift zur ISO-Woche verwendet. +- Lieferabweichungen und Mischungsverhaeltnisse beziehen sich auf Lieferungen, + nicht auf Bunkerabfluss. +- `no_three_in_a_row` ist laut README aktuell auf `<= 3` gelockert. +- Bei Infeasibility kann Gurobi ein IIS nach `results/iis.ilp` schreiben. +- Der Projektname in Metadaten und README ist teilweise noch `POC1`; fachlich + entspricht das dem aktuellen LEAG-COALLOG-Prototyp. diff --git a/COALLOG.drawio b/COALLOG.drawio new file mode 100644 index 0000000..8723686 --- /dev/null +++ b/COALLOG.drawio @@ -0,0 +1,252 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/COALLOG_v0.1.png b/COALLOG_v0.1.png new file mode 100644 index 0000000..9f08b9c Binary files /dev/null and b/COALLOG_v0.1.png differ diff --git a/docs/app-kommunikation.md b/docs/app-kommunikation.md new file mode 100644 index 0000000..31afe9d --- /dev/null +++ b/docs/app-kommunikation.md @@ -0,0 +1,102 @@ +# App-Kommunikation + +Dieses Diagramm zeigt die Kommunikation zwischen Browser, FastAPI-Backend, +Job-Ablage und der Python-Optimierungspipeline. + +```mermaid +flowchart LR + user[Benutzer im Browser] + frontend[React Frontend
Vite Build / App.jsx] + backend[FastAPI Backend
webapp/backend/main.py] + jobs[(var/jobs
Job-Status, Uploads, Logs, Outputs)] + preprocess[Preprocessing
src/preprocessing/exploration_preprocess.py] + optimization[Optimierung
src/optimization/run_optimization.py] + model[Pyomo Modell
src/optimization/model_builder.py] + solver[Solver
HiGHS / Gurobi / SCIP] + processed[(processed Parquet
Job-Verzeichnis)] + output[(output.xlsx
warmstart.json)] + + user --> frontend + frontend -->|POST /api/run
Excel + Parameter + optional Warmstart| backend + backend -->|Job-Verzeichnis anlegen| jobs + backend -->|Subprozess starten| preprocess + preprocess -->|liest Excel
schreibt vorbereitete Tabellen| processed + backend -->|Subprozess starten| optimization + optimization -->|lädt Tabellen| processed + optimization --> model + model --> solver + solver -->|Lösung| model + optimization -->|Excel + Warmstart exportieren| output + backend -->|Status completed / failed / cancelled| jobs + + frontend -->|GET /api/jobs/{job_id}| backend + frontend -->|GET /api/jobs/{job_id}/logs/{name}| backend + frontend -->|GET /api/jobs/{job_id}/monthly-flows| backend + frontend -->|GET /api/jobs/{job_id}/capacity-timeseries| backend + frontend -->|GET /api/jobs/{job_id}/output| backend + frontend -->|GET /api/jobs/{job_id}/warmstart| backend + frontend -->|POST /api/jobs/{job_id}/cancel| backend + + backend -->|liest job.json / Logs| jobs + backend -->|liest output.xlsx| output + backend -->|liest processed Parquet| processed +``` + +## Ablauf eines Optimierungslaufs + +```mermaid +sequenceDiagram + participant U as Benutzer + participant F as React Frontend + participant B as FastAPI Backend + participant P as Preprocessing + participant O as Optimierung + participant S as Solver + participant V as var/jobs + + U->>F: Excel-Datei und Solver-Parameter auswaehlen + F->>B: POST /api/run + B->>V: Job-Verzeichnis, input.xlsx, job.json anlegen + B-->>F: job_id zurueckgeben + B->>P: exploration_preprocess.py als Subprozess + P->>V: processed/*.parquet und preprocess.log schreiben + B->>O: run_optimization.py als Subprozess + O->>S: Pyomo-Modell loesen + S-->>O: Loesung / Status + O->>V: output.xlsx, warmstart.json, optimization.log schreiben + B->>V: job.json auf completed/failed/cancelled setzen + + loop Polling waehrend der Laufzeit + F->>B: GET /api/jobs/{job_id} + B-->>F: Status + F->>B: GET /api/jobs/{job_id}/logs/optimization + B-->>F: Logtext + end + + F->>B: GET /api/jobs/{job_id}/monthly-flows + B-->>F: Monatsfluesse fuer Plotly + F->>B: GET /api/jobs/{job_id}/capacity-timeseries + B-->>F: Kapazitaets-Zeitreihen fuer Plotly + F->>B: GET /api/jobs/{job_id}/output + B-->>F: output.xlsx Download +``` + +## Deployment-Sicht + +```mermaid +flowchart TB + subgraph docker[Docker Container leag-coallog] + nginxless[FastAPI / Uvicorn
Port 8080] + static[React Static Build
webapp/frontend/dist] + python[Python Runtime
Preprocessing + Optimierung] + gurobi[Gurobi / HiGHS / SCIP] + end + + browser[Browser] -->|HTTP :8080| nginxless + nginxless -->|liefert statische Dateien| static + nginxless -->|API-Aufrufe /api/*| python + python --> gurobi + python --> volume[(Host Volume ./var:/app/var)] + license[(Host ./licenses:/app/licenses:ro
oder GRB_WLS*)] --> gurobi +``` + diff --git a/src/optimization/model_builder.py b/src/optimization/model_builder.py index 305b19f..7bf9e45 100644 --- a/src/optimization/model_builder.py +++ b/src/optimization/model_builder.py @@ -906,29 +906,6 @@ def build_model( model.W, model.D, rule=lambda m, w, d: m.devV_W_pos[w, d] - m.devV_W_neg[w, d] == m.devV_W[w, d] ) - model.lambda_glatt = pyo.Param(initialize=1e5, mutable=True, within=pyo.NonNegativeReals) - - model.m_ijwd = pyo.Expression( - model.I, - model.J, - model.W, - model.D, - rule=lambda m, i, j, w, d: (1 / len(m.S)) - * sum(m.x[i, j, w, d, s] for s in m.S if (i, j, w, d, s) in m.x), - ) - - model.t_glatt = pyo.Var(model.I, model.J, model.W, model.D, model.S, domain=pyo.NonNegativeReals) - - def glatt_hi(m, i, j, w, d, s): - return m.t_glatt[i, j, w, d, s] >= m.x[i, j, w, d, s] - m.m_ijwd[i, j, w, d] - - def glatt_lo(m, i, j, w, d, s): - return m.t_glatt[i, j, w, d, s] >= -(m.x[i, j, w, d, s] - m.m_ijwd[i, j, w, d]) - - # Constraint: Upper absolute deviation from average shift flow (smoothness). - model.glatt_hi = pyo.Constraint(model.I, model.J, model.W, model.D, model.S, rule=glatt_hi) - # Constraint: Lower absolute deviation from average shift flow (smoothness). - model.glatt_lo = pyo.Constraint(model.I, model.J, model.W, model.D, model.S, rule=glatt_lo) lambda_dev = 100_000 lambda_v = 100_000