fix(dashboard): "nie kontrolliert" ging bei zusätzlichen Gründen verloren
CI / backend-tests (push) Failing after 2m3s
CI / frontend-build (push) Successful in 19s

_kategorie_und_gruende() zählte "noch_nie_kontrolliert" bisher nur,
wenn es der EINZIGE Grund war - ein Objekt mit z.B. offenem
Fehlbestand UND nie kontrolliert verlor den Hinweis komplett, landete
nur unter "nicht einsatzbereit" ohne diesen Grund zu nennen. Jetzt
immer als Zusatzgrund angehängt. Neues Feld nie_kontrolliert_gesamt
liefert die tatsächliche Gesamtzahl über alle Kategorien hinweg,
Dashboard nutzt sie statt der (bewusst engeren) "unbekannt"-Kategorie.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
This commit is contained in:
2026-09-06 22:00:01 +02:00
co-authored by Claude Sonnet 5
parent 9a3eb7d810
commit 65203a039c
4 changed files with 40 additions and 7 deletions
+1
View File
@@ -34,6 +34,7 @@ class EinsatzbereitschaftRead(BaseModel):
unbekannt: int
nicht_einsatzbereit: int
gesamt: int
nie_kontrolliert_gesamt: int
details: list[ObjektMangelRead]
+20 -2
View File
@@ -197,6 +197,7 @@ async def einsatzbereitschaft(db: AsyncSession) -> dict:
)
bereit = eingeschraenkt = unbekannt = 0
nie_kontrolliert_gesamt = 0
details: list[dict] = []
for objekt in objekte:
kategorie, gruende = _kategorie_und_gruende(
@@ -210,6 +211,8 @@ async def einsatzbereitschaft(db: AsyncSession) -> dict:
objekt_ids_kritischer_mangel,
objekt_ids_hu_uvv_ueberfaellig,
)
if "noch_nie_kontrolliert" in gruende:
nie_kontrolliert_gesamt += 1
if kategorie == "ready":
bereit += 1
else:
@@ -225,6 +228,10 @@ async def einsatzbereitschaft(db: AsyncSession) -> dict:
"unbekannt": unbekannt,
"nicht_einsatzbereit": len(objekte) - bereit - eingeschraenkt - unbekannt,
"gesamt": len(objekte),
# Bug-Fund 2026-09-06: "unbekannt" zählt nur Objekte OHNE jeden anderen
# Grund - diese Zahl zählt "nie kontrolliert" unabhängig von sonstigen
# Problemen mit, damit sie beim manuellen Nachzählen stimmt.
"nie_kontrolliert_gesamt": nie_kontrolliert_gesamt,
"details": details,
}
@@ -260,12 +267,23 @@ def _kategorie_und_gruende(
if objekt.id in objekt_ids_hu_uvv_ueberfaellig:
gruende.append("hu_uvv_ueberfaellig")
# Bug-Fund 2026-09-06 (Nutzer: "beim genauen Schauen sind es mehr"):
# "noch nie kontrolliert" wurde bisher nur gezählt, wenn es der EINZIGE
# Grund war - ein Objekt mit z.B. offenem Fehlbestand UND nie kontrolliert
# verlor den Hinweis komplett (fiel unter "nicht einsatzbereit" ohne
# diesen Grund zu nennen). Jetzt immer als Zusatzgrund angehängt, wenn
# zutreffend, unabhängig von anderen Gründen.
nie_kontrolliert = letzte_kontrolle is None
if not gruende and fehlbestand_ids and fehlbestand_ids <= fehlbestand_ids_genehmigt:
return "limited", ["mindermenge_genehmigt"]
if not gruende and letzte_kontrolle is None:
zusatz = ["noch_nie_kontrolliert"] if nie_kontrolliert else []
return "limited", ["mindermenge_genehmigt"] + zusatz
if not gruende and nie_kontrolliert:
return "unknown", ["noch_nie_kontrolliert"]
if not gruende:
return "ready", []
if nie_kontrolliert:
gruende = gruende + ["noch_nie_kontrolliert"]
return "not-ready", gruende
+10 -3
View File
@@ -186,6 +186,7 @@ async def test_nie_kontrolliertes_objekt_ist_unbekannt(
assert body["unbekannt"] == 1
assert body["nicht_einsatzbereit"] == 0
assert body["details"][0]["gruende"] == ["noch_nie_kontrolliert"]
assert body["nie_kontrolliert_gesamt"] == 1
@pytest.mark.asyncio
@@ -203,7 +204,11 @@ async def test_offener_fehlbestand_macht_objekt_nicht_einsatzbereit(
body = response.json()
assert body["einsatzbereit"] == 0
assert body["nicht_einsatzbereit"] == 1
assert body["details"][0]["gruende"] == ["fehlbestand_offen"]
# Bug-Fund 2026-09-06: "nie kontrolliert" darf nicht verloren gehen, nur
# weil das Objekt zusätzlich einen anderen Grund hat (objekt_mit_position
# hat nie eine abgeschlossene Kontrolle).
assert body["details"][0]["gruende"] == ["fehlbestand_offen", "noch_nie_kontrolliert"]
assert body["nie_kontrolliert_gesamt"] == 1
@pytest.mark.asyncio
@@ -230,7 +235,8 @@ async def test_genehmigte_mindermenge_macht_objekt_eingeschraenkt_einsatzbereit(
assert body["einsatzbereit"] == 0
assert body["eingeschraenkt_einsatzbereit"] == 1
assert body["nicht_einsatzbereit"] == 0
assert body["details"][0]["gruende"] == ["mindermenge_genehmigt"]
assert body["details"][0]["gruende"] == ["mindermenge_genehmigt", "noch_nie_kontrolliert"]
assert body["nie_kontrolliert_gesamt"] == 1
@pytest.mark.asyncio
@@ -257,7 +263,8 @@ async def test_ueberfaellige_pruefung_macht_objekt_nicht_einsatzbereit(
response = await client.get("/api/v1/dashboard/einsatzbereitschaft", headers=auth_header(token))
body = response.json()
assert body["nicht_einsatzbereit"] == 1
assert body["details"][0]["gruende"] == ["pruefung_ueberfaellig"]
assert body["details"][0]["gruende"] == ["pruefung_ueberfaellig", "noch_nie_kontrolliert"]
assert body["nie_kontrolliert_gesamt"] == 1
prueftermine = await client.get("/api/v1/dashboard/prueftermine", headers=auth_header(token))
prueftermine_body = prueftermine.json()
+9 -2
View File
@@ -42,6 +42,7 @@ interface Einsatzbereitschaft {
unbekannt: number;
nicht_einsatzbereit: number;
gesamt: number;
nie_kontrolliert_gesamt: number;
details: ObjektMangel[];
}
@@ -130,8 +131,8 @@ export function DashboardPage() {
ablaufdatenAbgelaufen > 0
? { text: `${ablaufdatenAbgelaufen} Chargen abgelaufen`, ziel: null }
: null,
bereitschaft && bereitschaft.unbekannt > 0
? { text: `${bereitschaft.unbekannt} Objekte noch nie kontrolliert`, ziel: "/objekte" }
bereitschaft && bereitschaft.nie_kontrolliert_gesamt > 0
? { text: `${bereitschaft.nie_kontrolliert_gesamt} Objekte noch nie kontrolliert`, ziel: "/objekte" }
: null,
].filter((a): a is { text: string; ziel: string | null } => a !== null);
@@ -176,6 +177,12 @@ export function DashboardPage() {
<ReadinessBadge status="not-ready" label={`${bereitschaft.nicht_einsatzbereit} nicht bereit`} />
)}
</div>
{bereitschaft.nie_kontrolliert_gesamt > bereitschaft.unbekannt && (
<p className="text-muted" style={{ fontSize: "0.85rem" }}>
Davon {bereitschaft.nie_kontrolliert_gesamt} insgesamt noch nie kontrolliert (auch bei Objekten mit
weiteren Problemen).
</p>
)}
{bereitschaft.details.length > 0 && (
<>
<button className="btn btn-secondary" onClick={() => setDetailsOffen(!detailsOffen)}>