fix(windows): Restzeit blieb ewig „wird gemessen" — zwei Docker-Annahmen
Ampel / ampel (push) Successful in 1m32s

Commander: „Über den kompletten vorgang steht dort ‚Restzeit wird gemessen'
aber messung wird nicht abgeschlossen. Das heißt man hat kein ETA"

Zwei Ursachen, beide unabhaengig, beide toedlich fuer sich allein.

## 1. Die Messreihe lag nur in Redis

`eta.py` schrieb sie ausschliesslich in den Cache — mit der Begruendung im
Modul-Kopf: „Der Cache (Redis) ist schon da". Im Container stimmt das. Auf
einem Windows-PC gibt es kein Redis: `cache_get` gab bei JEDEM Aufruf None
zurueck, `beobachtung_hinzufuegen` legte also jedes Mal eine frische Reihe mit
EINEM Punkt an — und `restzeit_sekunden` braucht `MINDEST_PUNKTE = 2`.

Jetzt wird in beide Ablagen geschrieben: in den Cache, wo es einen gibt (er
ueberlebt einen API-Neustart), und in ein Woerterbuch im Prozess. Das ist ein
paar Zahlen gross, gilt nur fuer die Dauer eines Jobs, und im eigenstaendigen
Betrieb gibt es ohnehin nur diesen einen Prozess. Alte Reihen werden nach
einem Tag weggeraeumt.

## 2. Der Ereignisstrom rechnete die Restzeit gar nicht

Die Rechnung stand nur in `/jobs`. Der SSE-Schnappschuss baute seine Jobs mit
dem nackten `_job_row_to_model` — also ohne Restzeit. **Seit der Umstellung
auf den Ereignisstrom (V2-3) liest die Oberflaeche aber genau diesen
Schnappschuss und nicht mehr `/jobs`.** Die Restzeit wurde also brav berechnet
und niemandem gezeigt.

Beides jetzt in `jobs_fuer_ui()` — dieselbe Lehre wie bei
`laufwerke_mit_disc` heute frueh: Eine Auskunft in zwei Fassungen ist eine
Fassung zu viel.

Nebenbei: Unlesbare Einstellungen duerfen die Jobliste nicht umwerfen. Seit
sie auch den Schnappschuss baut, haengt daran die ganze Oberflaeche — zwei
Snapshot-Tests wurden davon prompt rot.

## Beweis

Job in der Datenbank, Fortschritt 10 % -> 25 % ueber 130 s:

    nach 1. Messpunkt : eta_text=''            (richtig, eine Messung reicht nicht)
    nach 2. Messpunkt : 674 s, „noch ca. 11 min"

877 Tests gruen, ruff sauber.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-08-29 15:49:59 +02:00
co-authored by Claude Opus 5
parent aeb3128a85
commit 548742e771
4 changed files with 191 additions and 23 deletions
+51 -18
View File
@@ -519,7 +519,10 @@ async def _snapshot() -> dict:
"""
def sammeln():
return {
"jobs": [_job_row_to_model(z).model_dump() for z in db.list_jobs(limit=50)],
# DIESELBE Funktion wie /jobs — sonst fehlt hier die
# Restzeit, und die Oberflaeche liest seit V2-3 nur noch
# diesen Schnappschuss (Befund 29.08.2026).
"jobs": [m.model_dump() for m in jobs_fuer_ui(limit=50)],
"workers": db.list_workers(),
"logs": [_log_zeile(z) for z in db.list_logs(limit=50)],
}
@@ -947,24 +950,54 @@ async def get_jobs():
Reihe zurück, zwei Tabs zeigten verschiedene Zahlen, und für einen externen
Encoder-Worker gäbe es gar keine (genau dort wollte der Commander sie).
"""
def sammle():
work_dir = os.path.normpath((db.get_settings().get("workDir") or "").strip() or "/")
jetzt = time.monotonic()
modelle = []
for z in db.list_jobs():
modell = _job_row_to_model(z)
modell.can_retry = _kann_neu_komprimieren(z, work_dir)
modell.retry_art = phasen.retry_art(z)
schaetzung = eta.aktualisiere_und_schaetze(
z["id"], z.get("status") or "", z.get("progress") or 0,
jetzt, cache_get, cache_set,
)
modell.eta_sekunden = schaetzung["sekunden"]
modell.eta_text = schaetzung["text"]
modelle.append(modell)
return modelle
return await asyncio.to_thread(jobs_fuer_ui)
return await asyncio.to_thread(sammle)
def jobs_fuer_ui(limit: int = 100) -> list:
"""Die Jobs in UI-Form — MIT Restzeit, Wiederhol-Art und Rohdaten-Frage.
## Warum das eine Funktion ist (Befund 29.08.2026)
> „Über den kompletten vorgang steht dort Restzeit wird gemessen' aber
> messung wird nicht abgeschlossen. Das heißt man hat kein ETA"
Zwei Ursachen, beide davon, dass es die Jobliste ZWEIMAL gab.
**1.** Diese Rechnung stand nur in `/jobs`. Der SSE-Schnappschuss baute
seine Jobs mit dem nackten `_job_row_to_model` — also ohne Restzeit. Seit
der Umstellung auf den Ereignisstrom (V2-3) liest die Oberfläche aber
genau diesen Schnappschuss und nicht mehr `/jobs`. Die Restzeit wurde also
weiterhin brav berechnet und niemandem gezeigt.
**2.** Die Messreihe lag ausschließlich in Redis, das es unter Windows
nicht gibt — Begründung und Rückfall in `eta.py`.
Dieselbe Lehre wie bei `laufwerke_mit_disc`: Eine Auskunft in zwei
Fassungen ist eine Fassung zu viel.
"""
# Unlesbare Einstellungen duerfen die Jobliste NICHT umwerfen — sie
# steuern hier nur, ob „Neu komprimieren" angeboten wird. Seit diese
# Funktion auch den SSE-Schnappschuss baut, haengt daran die ganze
# Oberflaeche (Befund 29.08.2026: zwei Snapshot-Tests wurden dadurch rot).
try:
einstellungen = db.get_settings(bei_fehler_leer=True)
except Exception: # noqa: BLE001
einstellungen = {}
work_dir = os.path.normpath((einstellungen.get("workDir") or "").strip() or "/")
jetzt = time.monotonic()
modelle = []
for z in db.list_jobs(limit=limit):
modell = _job_row_to_model(z)
modell.can_retry = _kann_neu_komprimieren(z, work_dir)
modell.retry_art = phasen.retry_art(z)
schaetzung = eta.aktualisiere_und_schaetze(
z["id"], z.get("status") or "", z.get("progress") or 0,
jetzt, cache_get, cache_set,
)
modell.eta_sekunden = schaetzung["sekunden"]
modell.eta_text = schaetzung["text"]
modelle.append(modell)
return modelle
def _rohdaten_groesse(pfade: list) -> tuple: