- _good_parts_map: collapse the per-press 3-query loop (3xN) into 3 grouped
queries across all presses; semantics preserved (Joborder JSON/substring
matching, per-press scoping, whole-map degrade-to-{} on error).
- _press_list + /reasons: served from the kernel cached_select() TTL cache.
Part of the gateway connection-pool-saturation fix
(see fastAPI/agent_docs/db-pool-relief.md).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| api | ||
| dist | ||
| src | ||
| __init__.py | ||
| _db.py | ||
| .gitignore | ||
| .npmrc.example | ||
| env.d.ts | ||
| index.html | ||
| package.json | ||
| README.md | ||
| tailwind.config.cjs | ||
| tsconfig.app.json | ||
| tsconfig.json | ||
| tsconfig.node.json | ||
| vite.config.ts | ||
| yarn.lock | ||
SPA_AfterTemperRejection — Ausschuss-Station
A central, login-free reject-booking terminal for all presses' tempered
carts (Werk Cadolzburg). A Vue 3 SPA that the SPRO Universal Gateway serves as
a static page-module under /module/temper_rejects/. It lists every tempered cart
(one row per ttId, newest first), shows each order's temper flow, and lets a
shop-floor operator book an Ausschuss (count + reason) against a specific cart.
Standalone orientation. Developed on its own (sibling repo, like
FastPress/PressV), mounted into the gateway as a submodule atmodules/temper_rejects/. This README bakes in everything a fresh session needs.
Design is LOCKED (see .claude/memory/ausschuss-station.md in the gateway repo):
per-ttId list · press checkbox filter on the left (default all, greyed when a
press has no carts in the window, hit-count badge) · style-C 5-station temper
belt under each entry (number badges + animated conveyor) · time window
12h/1T/7T/14T/30T. Touch-density controls (56px). Run npm run dev to see it.
Quick start (standalone dev)
Package manager is yarn 1.22.22 (pinned via packageManager; matches PressV).
corepack provides it.
corepack enable # one-time per machine (honours the pinned yarn)
cp .npmrc.example .npmrc # paste a Gitea PAT with "package: Read" scope (yarn v1 reads .npmrc)
yarn install
yarn dev # http://localhost:5173/module/temper_rejects/
With no gateway backend, the store falls back to bundled sample data
(src/data/sample.ts) and the booking flow simulates success — so the whole
terminal (filter, belt, dialog) is demoable offline (a DEMO-DATEN tag shows).
yarn build → dist/ (committed).
How it's wired into the gateway (page-module contract)
Per agent_docs/vue-page-modules.md. The repo root is a Python package
(__init__.py) so the loader can import temper_rejects and call register(app):
| File | Role |
|---|---|
__init__.py |
register(app) — mounts dist/ at MODULE_PREFIX + includes the API router |
_db.py |
kernel DB seam — run_select_query (reads) and transaction() (the reject write) |
api/routes.py |
no-auth API: GET /overview, GET /reasons, POST /reject |
dist/ |
committed build output — the gateway serves this |
URL prefix in two places that MUST match: vite.config.ts →
base: '/module/temper_rejects/' and __init__.py →
MODULE_PREFIX = "/module/temper_rejects". The frontend derives its API base from
import.meta.env.BASE_URL.
# in the gateway repo (fastAPI)
git submodule add https://git.sprodat.eu/Erik/SPA_AfterTemperRejection.git modules/temper_rejects
# MODULES_ENABLED must include temper_rejects (or be empty = load all)
Restart → log shows loaded module: temper_rejects. Smoke:
http://localhost:8800/module/temper_rejects/ and …/api/overview. Deploy:
yarn build, commit dist/, bump the submodule pin. Never build on the host.
Data API contract
Types are authoritative in src/types/station.ts; Python
mirrors them in api/routes.py.
GET /module/temper_rejects/api/overview?window=12h|1T|7T|14T|30T→ tempered carts (perttId, newest first) + the press list for the filter.GET /module/temper_rejects/api/reasons→ active reject reasons (id,reason_de,reason_en).POST /module/temper_rejects/api/reject→ body{ press, orderNo, ttId, count, reason }.
// overview
{ "window":"7T","generatedAt":"…","sample":true,
"presses":["P1","P2","P3","P4","P5","P6","P7","P8","P9"],
"entries":[{ "ttId":10231,"cartId":"00A17226","press":"P5","orderNo":"O-4455",
"article":"Gehäuse 12 mm","partsGood":60,"scrap":2,"doneAt":"…",
"flow":{ "notYetSent":120,"wagen":60,"unterwegs":40,"ofen":72,"fertig":260 } }] }
The flow object is the order-level 5-station distribution shown on the belt.
Belt colour mapping (matches PressV process colours): notYetSent→violet,
wagen→cyan, unterwegs→amber, ofen→orange, fertig→emerald; the reject accent
is Grafana dark-red #C4162A.
Live data wiring
api/routes.py reads live MariaDB and POST /reject writes (details in the gateway
memory .claude/memory/ausschuss-station.md; sample:true only in the standalone dev
fallback). Via from .._db import run_select_query, transaction:
- overview: reads
production.temper_trackingacross all presses (un-scoped), one row perttId, newest-first byttOutOfOvenAt(ttStatus='done'), joined toactive_orders/finished_orders, windowed onttOutOfOvenAt. The order-level 5-station distribution reusesmodules/fastpress/press/temper.py::_temper_aggregates_all. - reasons:
SELECT id, reason_de, reason_en FROM settings.reject_reasons WHERE active=1. - reject: one
INSERTintoproduction.rejectsinside atransaction()(an un-scoped reuse of PressVsend_reject), keyed byrejOrdernumber+rejPressfrom the body — the device-scopedPOST /press/rejects/sendcan't be reused (it derives the press from the device JWT). ⚠/rejectreturns HTTP 200 +{success:false}on failure (not 4xx/5xx — else the frontend store swallows it into a fake success).
Note: a nullable rejTtId column was later added (fastpress migration 009) so rejects
attribute to the correct press cycle — see the gateway memory
temper-cycle-reject-attribution.md.
Open question before go-live
Security of the login-free write: network/reverse-proxy restriction only, or a
static STATION_TOKEN in .env? (No TLS/reverse-proxy exists yet.) Because this
terminal writes, resolve this before exposing it beyond a trusted LAN. (Resolved:
per-ttId booking is desired; belt shows the order-level distribution.)
Tech stack (matches PressV / the fleet)
Vue 3.5 · Vite 8 · TS 5.9 · Pinia 3 · vue-router 4 (hash) · vue-i18n 10 (de default,
?lang=en) · PrimeVue 4.5 (Nora + Industrial preset — the booking dialog uses
Dialog/Select/InputNumber/Button) · Tailwind v4 · @sp-ui-kit/tokens ·
Inter Variable. Dark-first, data-density="touch" (56px controls), flat 2px
corners, reduced-motion disables the conveyor.
Store: src/stores/temperRejects.ts — hydrate() (overview + reasons) →
refresh() → startPolling(20000); press filter state (checkedPresses, default
all) + submitReject() (posts, then refreshes; simulates success with no backend).
Falls back to sample data when a response lacks an entries[] array.
Not used yet but available in the fleet:
@sp-ui-kit/ui-core(e.g.SpNumpadDialogwould be an even more touch-friendly count entry than the PrimeVueInputNumber).
Design tokens
Flow/reject accents (src/style.css): nicht-gesendet #a78bfa, Wagen #22d3ee,
Unterwegs #fbbf24, Ofen #f97316 (hot #ef4444), Fertig #34d399, Ausschuss
#C4162A — :root dark + html:not(.dark) light variants.
Grafana embedding
<iframe src="http://<gateway-host>:8800/module/temper_rejects/#/"
width="100%" height="900" style="border:0"></iframe>
(?lang=en before the # for English.) Gateway sets no frame headers, so it embeds
directly. Because this terminal writes, resolve open question #1 (security)
before exposing it beyond a trusted LAN.