feat(dashboard): kanonische lebende Maschinenkarte (Grid + Detail, live) - #52
Conversation
EINE MachineCard-Komponente + EIN Vertrag (MachineCardOut) ersetzt die
Maschinenlisten-Reiter (/machines-Grid, gruppiert nach Synoptik-Stufe) UND
die tote Stammdaten-Box der Detailsicht. Pro Datenpunkt jetzt der aktuelle
Wert + ehrlicher Status statt nur der Einheit. MachineList + MachineSpecs
entfernt (kein Doppel-Code).
Backend (Read-Core, kein Schema-Change):
- latest_values_for_data_points: jüngster Wert je Datenpunkt via readings_1m
(DISTINCT ON, kein Full-Scan).
- derive_datapoint_status (pure): Alarm-Verdikt > Eigenprofil-Korridor
(corridor_at, geteilte Detektor-Band-Quelle, jetzt DRY mit dem Trend-Overlay)
> statisches Normalband > unknown. Kein neu erfundener Schwellwert
(Ehrlichkeitslinie).
- build_fleet_cards/build_machine_card (batched, kein N+1) + DataPointCardOut/
MachineCardOut (additiv).
- GET /api/v1/cards (scope-gefiltert je Rolle, visible_machine_scope) +
GET /api/v1/machines/{id}/card (scope-gated); WS-machine:{id} trägt jetzt die
ganze Karte.
- NOTIFY-Anreicherung (machines_for_data_points): ein Readings-Tick frischt
machine:{id} + overview auf -> die Karte lebt pro Tick.
Frontend:
- MachineCard (compact Grid / full Detail), live über machine:{id} mit
SSR-Erstbild; Stale ehrlich ("Stand vor X"), Hallensprache (kein internes
Vokabular). MachineCardGrid gruppiert nach Synoptik-Stufe.
Tests/Doku: pytest 919 grün (94.45% Coverage), Vitest 679 grün, mypy --strict 0,
ruff clean, tsc/eslint 0, tokens:check synchron, next build ok. GROUND_TRUTH
(§4/§20.1/§21.6/§21.19) + WALKTHROUGH nachgezogen. Test-Isolation: CAGG-Reset
im conftest (TRUNCATE invalidiert readings_1m nicht -> Geister-Werte).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (9)
🚧 Files skipped from review as they are similar to previous changes (8)
📝 WalkthroughWalkthroughDie PR führt eine kanonische lebende Maschinenkarte ein. Backend liefert Kartenlisten, Einzelkarten und WS-Snapshots aus dem Read-Core; Reading-Ticks aktualisieren betroffene Maschinen. Das Frontend rendert die Karte in Übersicht und Detailansicht und ersetzt die alten Listen-/Specs-Komponenten. ChangesKanonische lebende Maschinenkarte
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (4)
src/foreman/realtime/authz.py (1)
100-104: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueOptional: geteilte Linien-Maschinen-Abfrage extrahieren.
Der
shift_lead-Zweig wiederholt exakt die Abfrage ausoverview_scope(Zeilen 80-82):select(Machine.id).where(Machine.line_id.in_(user.assigned_line_ids)). Eine kleine private Hilfsfunktion (z. B._machines_of_user_lines(session, user)) würde die Duplikation entfernen und beide Resolver konsistent halten.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/foreman/realtime/authz.py` around lines 100 - 104, The shift_lead branch duplicates the same machine-by-assigned-lines query already used in overview_scope. Extract the shared select(Machine.id).where(Machine.line_id.in_(user.assigned_line_ids)) logic into a private helper such as _machines_of_user_lines(session, user), then call that helper from both branches to keep authz lookups consistent and remove duplication.frontend/components/machine/machine-card-grid.tsx (1)
27-31: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
keyundaria-labelbesser anmachineClasskoppeln.
group.stageist ein Anzeige-Label. DastageLabelunbekannte Klassen als Rohnamen zurückgibt, kann eine Maschinenklasse mit dem Rohnamen einer bekannten Stufe (z. B."Fördern") dasselbe Label wie eine echte Stufe erzeugen und einen doppelten React-keyverursachen.group.machineClassist der eindeutige Gruppierungsschlüssel.♻️ Vorschlag
- <section key={group.stage} aria-label={group.stage} className="flex flex-col gap-3"> + <section key={group.machineClass ?? "__none__"} aria-label={group.stage} className="flex flex-col gap-3">🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/components/machine/machine-card-grid.tsx` around lines 27 - 31, The grouping section in machine-card-grid should not use the display label from group.stage as the React key or aria-label, because stageLabel can collide with a raw machine class name and create duplicate keys. Update the <section> in MachineCardGrid to use group.machineClass as the stable unique identifier for key and aria-label, while keeping group.stage only for the visible heading text. Ensure the change is applied in the groups.map render path so each group stays uniquely identified even when labels overlap.frontend/app/(app)/machines/page.tsx (1)
20-31: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winFehlerfälle werden still in eine leere Liste verwandelt.
Bei
!response.okund imcatchwird ohne Logging[]zurückgegeben. Downstream rendertMachineCardGriddaraufhin „Keine Maschinen in deinem Zugriff." — ein Backend-/Netzwerkfehler erscheint also als „kein Zugriff". Das widerspricht dem ehrlichen Zustands-Anspruch der PR und erschwert die Diagnose (kein Server-Log). Erwäge zumindest, den Fehler zu loggen, um einen echten leeren Scope von einem Ausfall zu unterscheiden.♻️ Vorschlag
if (!response.ok) { + console.error(`fetchCards: /api/v1/cards antwortete mit ${response.status}`); return []; } return (await response.json()) as MachineCardOut[]; - } catch { + } catch (error) { + console.error("fetchCards: Anfrage an /api/v1/cards fehlgeschlagen", error); return []; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/app/`(app)/machines/page.tsx around lines 20 - 31, The fetch logic in the machines page currently swallows backend and network failures by returning an empty array from both the !response.ok branch and the catch block. Update the data-loading code in the page component that calls fetch for /api/v1/cards to log the failure with enough context before returning [], so MachineCardGrid can still render safely while real outages remain diagnosable. Use the existing backendUrl()/token fetch flow and keep the empty-list fallback, but add error logging in both failure paths.frontend/components/machine/machine-card-grid.test.tsx (1)
17-34: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueOptionale Konsolidierung der
card-Test-Factory.Diese
card-Hilfsfunktion ist nahezu identisch mit der infrontend/lib/machine/grouping.test.ts(Zeilen 14–31) und überschneidet sich mit dem Fixture infrontend/components/machine/machine-detail-view.test.tsx. Eine gemeinsame Test-Factory (z. B.makeMachineCard(overrides)) würde Drift vermeiden, wenn sichMachineCardOuterweitert.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/components/machine/machine-card-grid.test.tsx` around lines 17 - 34, The card test helper in machine-card-grid.test.tsx duplicates the same MachineCardOut fixture logic used in grouping and machine-detail-view tests, so move this into a shared factory like makeMachineCard(overrides). Update the local card helper to delegate to that shared builder and keep the unique symbols card and MachineCardOut aligned so future schema changes only need one fixture update.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@frontend/app/`(app)/machines/page.tsx:
- Around line 20-31: The fetch logic in the machines page currently swallows
backend and network failures by returning an empty array from both the
!response.ok branch and the catch block. Update the data-loading code in the
page component that calls fetch for /api/v1/cards to log the failure with enough
context before returning [], so MachineCardGrid can still render safely while
real outages remain diagnosable. Use the existing backendUrl()/token fetch flow
and keep the empty-list fallback, but add error logging in both failure paths.
In `@frontend/components/machine/machine-card-grid.test.tsx`:
- Around line 17-34: The card test helper in machine-card-grid.test.tsx
duplicates the same MachineCardOut fixture logic used in grouping and
machine-detail-view tests, so move this into a shared factory like
makeMachineCard(overrides). Update the local card helper to delegate to that
shared builder and keep the unique symbols card and MachineCardOut aligned so
future schema changes only need one fixture update.
In `@frontend/components/machine/machine-card-grid.tsx`:
- Around line 27-31: The grouping section in machine-card-grid should not use
the display label from group.stage as the React key or aria-label, because
stageLabel can collide with a raw machine class name and create duplicate keys.
Update the <section> in MachineCardGrid to use group.machineClass as the stable
unique identifier for key and aria-label, while keeping group.stage only for the
visible heading text. Ensure the change is applied in the groups.map render path
so each group stays uniquely identified even when labels overlap.
In `@src/foreman/realtime/authz.py`:
- Around line 100-104: The shift_lead branch duplicates the same
machine-by-assigned-lines query already used in overview_scope. Extract the
shared select(Machine.id).where(Machine.line_id.in_(user.assigned_line_ids))
logic into a private helper such as _machines_of_user_lines(session, user), then
call that helper from both branches to keep authz lookups consistent and remove
duplication.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 99bd51e3-40f7-4338-8b3f-21d9decce69d
📒 Files selected for processing (40)
GROUND_TRUTH.mddocs/WALKTHROUGH.mdfrontend/app/(app)/machines/[id]/page.tsxfrontend/app/(app)/machines/page.tsxfrontend/components/machine/machine-card-grid.test.tsxfrontend/components/machine/machine-card-grid.tsxfrontend/components/machine/machine-card.test.tsxfrontend/components/machine/machine-card.tsxfrontend/components/machine/machine-detail-view.test.tsxfrontend/components/machine/machine-detail-view.tsxfrontend/components/machine/machine-header.tsxfrontend/components/machine/machine-list.test.tsxfrontend/components/machine/machine-list.tsxfrontend/components/machine/machine-specs.test.tsxfrontend/components/machine/machine-specs.tsxfrontend/lib/api/contracts.tsfrontend/lib/machine/card.test.tsfrontend/lib/machine/card.tsfrontend/lib/machine/grouping.test.tsfrontend/lib/machine/grouping.tssrc/foreman/api/routers/dashboard.pysrc/foreman/api/routers/readings.pysrc/foreman/ingestion/service.pysrc/foreman/reads/card.pysrc/foreman/reads/datapoint_status.pysrc/foreman/reads/queries.pysrc/foreman/reads/trend.pysrc/foreman/realtime/authz.pysrc/foreman/realtime/ws.pysrc/foreman/reasoners/drift/baseline.pysrc/foreman/schemas/dashboard.pytests/conftest.pytests/integration/test_dashboard_routes.pytests/integration/test_reads_card.pytests/integration/test_reads_latest_values.pytests/integration/test_realtime_authz.pytests/integration/test_realtime_ws.pytests/unit/test_dashboard_schemas.pytests/unit/test_datapoint_status.pytests/unit/test_drift_corridor.py
💤 Files with no reviewable changes (4)
- frontend/components/machine/machine-list.test.tsx
- frontend/components/machine/machine-list.tsx
- frontend/components/machine/machine-specs.tsx
- frontend/components/machine/machine-specs.test.tsx
Zweiter Reviewer (CodeRabbit) auf PR #52 — 4 Nitpicks (alle Trivial), verifiziert + adressiert: - authz.py: geteilten Linien-Resolver _machines_of_user_lines extrahiert (overview_scope + visible_machine_scope teilen jetzt EINE Linien-Abfrage). - machine-card-grid.tsx: React-key auf group.machineClass (statt Anzeige-Label group.stage) — verhindert Kollision, falls ein roher Klassenname ein Stufen-Label trifft; aria-label bleibt das lesbare Stufen-Label. - machines/page.tsx + machines/[id]/page.tsx: Fehlerfälle (!ok / catch) loggen serverseitig statt still leere Liste/null — ein Ausfall sieht nicht mehr aus wie "kein Zugriff" (Ehrlichkeitslinie); 404/403 im Detail bleiben stiller Normalpfad. - Test-Fixtures: geteilte makeMachineCard-Factory (lib/machine/testing/ card-fixture.ts) ersetzt die duplizierte MachineCardOut-Literal-Logik in grouping/grid/card/detail-view-Tests (kein Drift bei Vertrags-Erweiterung). Gates grün: Vitest 679, tsc/eslint 0, Backend authz 28 (mypy --strict 0, ruff clean). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CodeRabbit-Nitpicks abgearbeitet (Commit 173c735)Alle 4 Nitpicks verifiziert + adressiert (keine waren blockierende Bugs — alle Trivial):
Gates nach den Fixes: Vitest 679 · tsc/eslint 0 · Backend authz 28 (mypy --strict 0, ruff clean). |
Was
Eine kanonische lebende Maschinenkarte als EINE FE-Komponente (
MachineCard) + EIN Vertrag (MachineCardOut) ersetzt sowohl die Maschinenlisten-Reiter unter „Linie & Maschinen" (Grid, gruppiert nach Synoptik-Stufe) als auch die tote Stammdaten-Box der Detailsicht. Pro Datenpunkt jetzt Name · WERT · Einheit + Status-Indikator statt nur der Einheit.MachineList+MachineSpecs(+ Tests) entfernt — kein Doppel-Code. Synoptik-Vorbau (Anlagen_Synoptik.png).Warum
Die Stammdaten-Box war tot (
name · unit, kein Wert). Patrics Auftrag: eine überall identische, lebendige Karte — pro Datenpunkt aktueller Wert + Status, eine Quelle der Wahrheit. Entscheidungen (per AskUserQuestion bestätigt): Detektor-getreuer Status (kein neu erfundener Schwellwert) + Per-Maschinen-WS-Push.Backend (Read-Core, kein Schema-Change / keine Migration)
reads/queries.latest_values_for_data_points— jüngster Wert je Datenpunkt viareadings_1m(DISTINCT ON, kein Full-Scan).reads/datapoint_status.derive_datapoint_status(pure) — Ehrlichkeitslinie: Alarm-Verdikt → Eigenprofil-Korridor (corridor_at, jetzt geteilte Detektor-Band-Quelle mit dem Trend-Overlay, DRY) → statisches Normalband →unknown. Kein erfundener Schwellwert.reads/card.build_fleet_cards/build_machine_card(batched, kein N+1) + additive SchemasDataPointCardOut/MachineCardOut.GET /api/v1/cards(scope-gefiltert je Rolle,visible_machine_scope) +GET /api/v1/machines/{id}/card(scope-gated); WS-machine:{id}trägt jetzt die ganze Karte (Superset des früherenMachineStatusOut).machines_for_data_points): ein Readings-Tick frischtmachine:{id}+overviewauf → die Karte lebt pro Tick (Produzent/Live-Worker-Logik unangetastet, nur die Change-Bündelung). Nebeneffekt: die Stream-„aktiv"-Kachel wird endlich pro Tick ehrlich live.Frontend
MachineCard— EINE Komponente, zwei Dichten (compactGrid /fullDetail-Stammdaten), live überuseTopicState<MachineCardOut>(machine:{id})mit SSR-Erstbild; Stale ehrlich („Stand vor X" bei Stream-Stopp), Hallensprache (kein „Drift"/internes Vokabular).MachineCardGridgruppiert nach Synoptik-Stufe (Fördern/Pressen/Handling/Bestücken/Endkontrolle)./machinesziehtGET /cards,/machines/[id]zieht eine QuelleGET /machines/{id}/card.Getestet
mypy --strict0;ruffclean.tsc --noEmit0;eslint0;tokens:checksynchron (keine neuen Token);next buildok (/machines~1,8 kB / 111 kB,/machines/[id]~8,9 kB / 124 kB — keine Charting-Lib).machine-header-Typ, Test-Lücken: shift_lead-Scope, Multi-Maschinen-NOTIFY, quittierter-offener Alarm).TRUNCATEinvalidiertreadings_1mnicht → Geister-Werte beidata_point_id-Wiederverwendung).Bewusste Entscheidungen / Hinweise
compose_status).machine-trend-panel.tsx/time-series-chart.tsx(bestehende B-Sektion, nicht mein Diff) — wäre ein eigener Wording-Politur-PR.🤖 Generated with Claude Code
Summary by CodeRabbit