Rückverfolgbarkeit & Genealogie
Ereignisse statt Überschreiben
Jede Buchung in WERKSPUR ist ein Ereignis: mit Zeit, Ort (Maschine oder Station) und Verursacher. Es gibt kein Update. Ein bestehender Datensatz wird nie verändert oder gelöscht. Der heutige Zustand eines Loses ist die Summe aller Ereignisse, die je an ihm gebucht wurden.
Am Los reihen sich diese Ereignisse aneinander: LotCreated, LotScannedIn, StepCompleted, LotBlocked und LotUnblocked, LotSplit, LotMerged, LotTransformed, LotDrawnDown, LotEmptied. Jedes davon trägt den Namen der Maschine oder Station, an der es entstand, und den Benutzer, der die Buchung ausgelöst hat. Das Kapitel Datenmodell beschreibt die Zustände, die aus diesen Ereignissen entstehen; dieses Kapitel beschreibt, wie WERKSPUR sie dauerhaft und beweisbar macht.
Was Sie in einer Ansicht oder am Terminal sehen, etwa den Zustand in_process oder die Gutmenge eines Loses, ist ein abgeleiteter Stand. WERKSPUR leitet ihn fortlaufend aus den Ereignissen ab. Löschen Sie versehentlich diese abgeleitete Sicht, entsteht kein Datenverlust: WERKSPUR rechnet sie aus dem Ereignis-Strom neu auf. Der Ereignis-Strom selbst bleibt die einzige verbindliche Aufzeichnung, alles andere ist Ableitung.
Der Stammbaum
Draw-down, Split, Merge und Umwandlung erzeugen jeweils eine Kante zwischen einem Eltern-Los und einem Kind-Los. Zieht Fließpressen Material aus einer Kiste in zwei neue Kisten ab, entstehen zwei Kanten von der einen Kiste zu den beiden neuen. Wird aus Draht in Kilogramm ein Teil in Stück, entsteht eine Kante zwischen Rohmaterial und Teil, auch wenn sich die Einheit dabei ändert.
Jede Kante trägt außerdem eine Menge: wie viel vom Eltern-Los in das Kind-Los eingegangen ist. Bei einem Draw-down ist das ein Teil derselben Einheit, bei einer Umwandlung eine Menge in der neuen Einheit des Kind-Loses. Das Feld made_with am Los, aus dem Kapitel Datenmodell, ist die kompakte Sicht auf genau diese Kanten: die Liste der Eltern-Lose, aus denen ein Los entstanden ist.
Diese Kanten bilden zusammen den Stammbaum eines Loses. Die Genealogie-Abfrage läuft rekursiv in beide Richtungen: aufwärts zu den Vorfahren, abwärts zu den Nachkommen, mit begrenzter Tiefe, damit die Abfrage auch bei langen Ketten schnell bleibt. Der Genealogie-Graph in den Ansichten zeichnet aus diesen Kanten einen Baum. Ein Klick führt vom fertigen Teil zurück bis zum Rohmaterial, über beliebig viele Schritte und Umwandlungen hinweg.
Ein einzelnes Los kann mehrere Eltern und mehrere Kinder zugleich haben. Führt ein Schritt zwei Vorprodukte zu einem Los zusammen, laufen zwei Kanten auf dieses eine Los zu. Zieht ein späterer Schritt daraus wieder mehrere Teilmengen ab, laufen von diesem Los mehrere Kanten weg. Der Stammbaum ist damit kein einfacher Baum im strengen Sinn, sondern ein gerichteter Graph; die Genealogie-Abfrage und der Genealogie-Graph behandeln beide Fälle gleich.
Ein gemeinsamer Verarbeitungslauf verknüpft die beteiligten Lose zusätzlich mit diesem Lauf. Diese Verknüpfung ist keine Eltern-Kind-Kante. Ein aus mehreren Losen gebildetes Batch-Los ist dagegen eine Zusammenführung und erzeugt reguläre Genealogie-Kanten.
Das folgende Schema zeigt die möglichen Verzweigungen und Zusammenführungen. Die gestrichelten Kanten kennzeichnen die gemeinsame Verarbeitung, nicht einen Materialfluss von Eltern zu Kindern.
flowchart LR
Parent["Eltern-Los"] -->|"Split"| ChildA["Kind-Los A"]
Parent -->|"Split"| ChildB["Kind-Los B"]
ChildA -->|"Merge"| Merged["Sammel-Los"]
Other["Weiteres Los"] -->|"Merge"| Merged
Merged -->|"Draw-down"| DrawA["Teil-Los A"]
Merged -->|"Draw-down"| DrawB["Teil-Los B"]
DrawA -->|"Umwandlung"| Result["Ergebnis-Los"]
DrawB -->|"Umwandlung"| Result
DrawA -.->|"gemeinsam verarbeitet"| Run["Verarbeitungslauf"]
DrawB -.->|"gemeinsam verarbeitet"| Run
Der Genealogie-Graph zeigt neben den Materialkanten auch den Weg jeder Kiste durch ihre Arbeitsschritte: Für jeden durchlaufenen Schritt erscheint ein eigener Knoten. Dünne Pfeile bedeuten „dieselbe Kiste läuft weiter", kräftige Pfeile mit Mengenangabe stehen für echten Materialfluss. Offene Schritte sind gestrichelt dargestellt, gesperrte Kisten sind entsprechend markiert. Künftige Schritte zeigt der Graph nie: Die Kette endet sichtbar dort, wo die Produktion gerade steht.
Auswirkungsanalyse
Der Finder „Betroffene Aufträge finden“ sitzt auf der Genealogie-Startseite unter /genealogy, direkt unter dem Einstiegsfeld für Boxcode oder Auftragsnummer. Eine eigene Seite dafür gibt es nicht mehr. Die Suche selbst bleibt dabei so streng wie zuvor: Der Server verlangt dafür die Rolle Bediener (operator) oder höher.
Wählen Sie unter „Attribut“ genau einen der vier angebotenen technischen Schlüssel:
toolfür eine Werkzeug-IDmachinefür eine Maschinen-IDoperatorfür die interne Benutzer-ID des Bedienersbatch_runfür die ID eines gemeinsamen Verarbeitungslaufs
Tragen Sie unter „Wert“ die zugehörige ID ein, optional dazu ein Zeitfenster über „Von“ und „Bis“, und wählen Sie „Betroffene finden“. Die Oberfläche entfernt Leerzeichen am Anfang und Ende des Werts. Ein danach leerer Wert löst keine Suche aus. Der Server vergleicht Schlüssel und bereinigten Wert exakt mit den aufgezeichneten Prozessmerkmalen; insbesondere werden weder Teiltreffer noch eine Suche unabhängig von der Groß- und Kleinschreibung durchgeführt. Ein gesetztes Zeitfenster schränkt die Prozessaufzeichnungen entsprechend ein.
Ein Los ist „direkt“ betroffen, wenn es das gesuchte Merkmal selbst in seinen Prozessaufzeichnungen trägt. WERKSPUR folgt von jedem solchen Treffer allen Genealogie-Kanten vorwärts. Ein Los, das nur auf diesem Weg erreicht wird, ist ein Nachfolger. Die Suche reicht über mehrere Genealogie-Ebenen. Trägt ein Nachfolger das Merkmal zusätzlich selbst, zählt er als direkter Treffer.
Der Schlüssel batch_run erfasst alle Kisten, die demselben Verarbeitungslauf zugeordnet wurden. Diese Zuordnung ist keine Eltern-Kind-Kante. Trotzdem zählen die beteiligten Kisten als direkte Treffer, ihre jeweiligen Nachfolger eingeschlossen. Ein Verarbeitungslauf kann Kisten aus mehreren Aufträgen verbinden; das Ergebnis kann deshalb mehrere Aufträge umfassen.
Statt einer flachen Liste einzelner Kisten fasst das Ergebnis nach Auftrag zusammen, ein Eintrag je betroffenem Auftrag: die Anzahl betroffener Boxen insgesamt, wie viele davon direkte Treffer sind, und der ungünstigste Status unter ihnen: gesperrt vor in Bearbeitung vor wartend vor erledigt, alle übrigen Stati gelten dabei als am wenigsten dringlich. Aufträge mit gesperrten Boxen stehen deshalb oben.
Jeder Eintrag verlinkt in den Auftragsgraphen dieses Auftrags: dieselbe Stammbaum-Darstellung aus dem vorigen Abschnitt, jetzt mit den betroffenen Kisten vorab markiert. Direkte Treffer tragen die Markierung „betroffen (direkt)“, ihre Nachfolger „betroffen“; alles andere im Graphen ist abgeblendet. Klicken Sie eine markierte Kiste an, grenzt WERKSPUR die Markierung auf deren eigene Folgekisten ein. So sehen Sie für eine einzelne verdächtige Kiste, wie weit ihr Material tatsächlich gereicht hat, ohne den ganzen Auftrag im Blick behalten zu müssen. Ein zweiter Klick auf dieselbe Kiste oder ein Klick auf die freie Fläche hebt diese Eingrenzung wieder auf. Ein Doppelklick öffnet die Genealogie der angeklickten Kiste direkt.
Bei einer leeren Trefferliste zeigt die Seite „Keine Aufträge sind betroffen.“ Das bedeutet nur, dass kein exakt passender aufgezeichneter Wert und kein daraus folgender Nachfolger gefunden wurde. Schlägt die Abfrage fehl, entfernt die Oberfläche eine vorhandene Ergebnisliste und zeigt „Auswirkungsabfrage konnte nicht ausgeführt werden.“ Wiederholen Sie die Suche erst, nachdem Anmeldung, Verbindung und eingegebene ID geprüft sind.
Manipulationssicher
Jeder Ereignis-Strom, zum Beispiel der eines einzelnen Loses, ist als Kette verkettet: Jedes Ereignis trägt einen HMAC-Hash, der den Hash seines Vorgängers einschließt. Wer ein Ereignis nachträglich verändert, ändert damit auch seinen Hash. Damit passt der Hash nicht mehr zu dem, was das folgende Ereignis als Vorgänger erwartet, und die Kette bricht an dieser Stelle. Ein Angreifer, der ein Ereignis verändert, muss deshalb auch jedes nachfolgende Ereignis in diesem Strom neu verketten.
Regelmäßige Prüfpunkte sichern zusätzlich die jeweils aktuelle Spitze jeder Kette ab. Ein Prüfpunkt hält fest, wo eine Kette zu einem bestimmten Zeitpunkt stand. Selbst wenn jemand sowohl ein Ereignis als auch alle folgenden Hashes in einem Strom neu berechnen könnte, müsste die neu berechnete Kette immer noch zu einem bereits gesetzten Prüfpunkt passen.
Admins prüfen die Kette über die Oberfläche oder direkt über GET /api/audit/verify. Wer Zugriff auf die Kommandozeile hat, prüft mit werkspur verify-audit. Beide Wege liefern dasselbe Ergebnis: entweder ist jede Kette vollständig intakt, oder die Prüfung nennt genau den Strom und die Stelle, an der eine Kette bricht. Wie diese Prüfung im laufenden Betrieb eingebunden ist, etwa als Cron-Job, zeigt das Kapitel Sicherheit im Betrieb.
Was das beim Audit heißt
Ein Rückruf beginnt normalerweise mit einer Frage: Welches Rohmaterial steckt in Lieferung X? Ohne durchgehende Rückverfolgbarkeit bedeutet das Suchen in Lieferscheinen, Chargenzetteln und Excel-Listen aus mehreren Abteilungen, oft über mehrere Tage. Über den Stammbaum beantwortet WERKSPUR dieselbe Frage in Sekunden. Sie folgen der Genealogie vom ausgelieferten Teil zurück bis zum Rohmaterial-Los, aus dem es entstanden ist, und sehen jeden Schritt dazwischen: welche Maschine, welcher Auftrag, welche Menge.
Zu jeder einzelnen Buchung ist außerdem belegbar, wer sie wann an welcher Maschine ausgelöst hat. Für einen Prüfer reicht das Protokoll einer Maschine, um im Streitfall den genauen Ablauf zu zeigen, ohne den vollen Ereignis-Strom lesen zu müssen. Die Kette bestätigt zusätzlich, dass diese Buchungen seither unverändert geblieben sind. Ein Auditor kann das über werkspur verify-audit selbst nachrechnen, statt WERKSPUR dabei zu vertrauen.
Mehrwegbehälter
Behälter mit festem Barcode können nach Abschluss einer Füllung wieder verwendet werden. Jede Füllung bleibt dabei ein eigenes Los mit eigener Rückverfolgung: Der Scan am Terminal öffnet immer die aktive Füllung, während abgeschlossene Füllungen über ihre Aufträge dauerhaft nachvollziehbar bleiben (Anzeige „Füllung 2“, „Füllung 3“, …). Trägt ein Behälter im System noch eine aktive Füllung, lässt er sich nicht neu befüllen: Die Meldung nennt das belegende Los und den zugehörigen Auftrag.
Beispiele
-- lot_genealogy, params: id
WITH RECURSIVE up AS (
SELECT parent_lot, child_lot, quantity, 1 AS depth
FROM genealogy_edges WHERE child_lot = $1
UNION ALL
SELECT e.parent_lot, e.child_lot, e.quantity, u.depth + 1
FROM genealogy_edges e JOIN up u ON e.child_lot = u.parent_lot
WHERE u.depth < 10
), down AS (
SELECT parent_lot, child_lot, quantity, 1 AS depth
FROM genealogy_edges WHERE parent_lot = $1
UNION ALL
SELECT e.parent_lot, e.child_lot, e.quantity, d.depth + 1
FROM genealogy_edges e JOIN down d ON e.parent_lot = d.child_lot
WHERE d.depth < 10
) SELECT 'up' AS direction, depth, parent_lot, child_lot, quantity FROM up UNION ALL SELECT 'down' AS direction, depth, parent_lot, child_lot, quantity FROM down ORDER BY direction, depth LIMIT 500Die Abfrage läuft als WITH RECURSIVE über genealogy_edges, einmal aufwärts (up, von einem Kind-Los zu seinen Eltern) und einmal abwärts (down, von einem Eltern-Los zu seinen Kindern), jeweils ausgehend vom übergebenen Parameter id. Beide Zweige zählen die Tiefe mit und brechen bei zehn Ebenen ab. Das Ergebnis vereinigt beide Richtungen in einer Liste mit direction, depth, parent_lot, child_lot und quantity, genau die Kanten, aus denen der Genealogie-Graph seinen Baum zeichnet.
-- station_log, params: machine_id
SELECT e.ts AS zeit,
CASE e.type WHEN 'LotScannedIn' THEN 'Eingescannt'
WHEN 'StepCompleted' THEN 'Abgeschlossen'
WHEN 'LotDrawnDown' THEN 'Abgezogen'
WHEN 'LotEmptied' THEN 'Geleert'
WHEN 'LotTransformed' THEN 'Umgewandelt' END AS vorgang,
l.order_number AS auftrag,
substring(e.stream from 5) AS kiste,
coalesce(e.data ->> 'output_lot', '') AS ziel,
coalesce(e.data ->> 'qty_good', e.data ->> 'remainder', '') AS menge
FROM events e LEFT JOIN lots_current l ON l.id = substring(e.stream from 5) WHERE e.stream LIKE 'lot:%'
AND e.type IN ('LotScannedIn','StepCompleted','LotDrawnDown','LotEmptied','LotTransformed')
AND e.data ->> 'machine' = $1
ORDER BY e.ts DESC, e.seq DESC LIMIT 20
Die Abfrage liest den rohen Ereignis-Strom aus der events-Tabelle für alle Lose, gefiltert auf eine Maschine und auf fünf Ereignistypen: LotScannedIn, StepCompleted, LotDrawnDown, LotEmptied und LotTransformed. Ein CASE übersetzt jeden Ereignistyp in eine deutsche Vorgangs-Bezeichnung, etwa Eingescannt oder Abgeschlossen. Die Los-ID liest sie aus dem Stream-Namen, den Auftrag über einen Join auf lots_current, und Ziel-Los sowie Menge direkt aus den Ereignisdaten. Das Ergebnis ist ein Protokoll der letzten zwanzig Vorgänge, neueste zuerst.