claude sync: 2026-07-02 10:59
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"autoMemoryDirectory": "~/claude-projects/08-07/memory",
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Edit(~/projects/08-07/**)",
|
||||
"Edit(~/claude-projects/08-07/**)"
|
||||
],
|
||||
"additionalDirectories": [
|
||||
"~/projects/08-07"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(python3 *)",
|
||||
"Bash(cargo *)",
|
||||
"Bash(npm *)",
|
||||
"Bash(npx *)",
|
||||
"WebSearch"
|
||||
]
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+294808
File diff suppressed because one or more lines are too long
@@ -0,0 +1,9 @@
|
||||
- [Würth KI-Demo](project-wuerth-ki-demo.md) — Ziel + zwei Demo-Richtungen (Preis-/Margen-Assistent + Chat; Cross-Sell)
|
||||
- [Quelldaten extracted_xml_data.json](reference-extracted-xml-data.md) — Struktur des Plato-Exports, 191 Aufträge
|
||||
- [Tech-Stack](project-08-07-tech-stack.md) — Tauri (Rust-Backend + Vue) auf SQLite, LLM-Calls im Rust-Kern
|
||||
- [Datenveredelung](project-08-07-datenveredelung.md) — Offline-Anreicherung: Branche, Geo, Dedup, Modul-Klartext; eigener Use Case
|
||||
- [Datenmodell](project-08-07-datenmodell.md) — SQLite-Tabellen (auftraege/positionen/module/kunden) als Arbeitsschicht
|
||||
- [Datenbasis](project-08-07-datenbasis.md) — 191 jetzt, Ziel ~2000; reicht für Demo, nicht für statistisches Cross-Sell
|
||||
- [Fahrzeug-Veredelung](project-08-07-fahrzeug-veredelung.md) — Fzg-Typ nur in .plax-Rohdaten (PDF+Freitext), per LLM → auftrag_fzg; 134/191, fast alles Vans
|
||||
- [Stückliste-Pipeline](project-08-07-stueckliste-pipeline.md) — Bauteil-Klartext+Artikelnr. aus den PDFs in den .plax; wiederholbarer Extract → module.klartext, Popup je Auftrag
|
||||
- [AI-Assistent](project-08-07-ai-assistent.md) — Tab „Fragen": Text→SQL→Auto-Visualisierung via Claude (Sonnet 5); API-Key in ~/.ssh/anthropic_api_key
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
name: project-08-07-ai-assistent
|
||||
description: Natursprache-Assistent (frage_ai) — Text→SQL→Auto-Visualisierung über Claude; API-Key liegt in ~/.ssh
|
||||
metadata:
|
||||
node_type: memory
|
||||
type: project
|
||||
originSessionId: 9605d7dd-3c40-4f92-b05f-540606518879
|
||||
---
|
||||
|
||||
Tab **„Fragen"** in der App: Freitext-Frage → Rust-Command `frage_ai` schickt Frage + DB-Schema (aus `sqlite_master`) an die **Claude-API** (Modell `claude-sonnet-5`, `thinking:disabled`, max_tokens 1024) und lässt ein JSON zurückgeben: `{sql, chart(kpi|bar|table), label, value, titel, erklaerung}`. Rust prüft **read-only** (nur `SELECT`/`WITH`, kein `;`, keine DDL/DML) via `is_read_only`, führt die SQL auf `wuerth.db` aus und gibt Spalten+Zeilen zurück. Frontend rendert kpi/bar/table + zeigt die erzeugte SQL. Siehe [[project-08-07-tech-stack]].
|
||||
|
||||
**API-Key:** liegt **nicht** im Projekt/`.env`, sondern in **`~/.ssh/anthropic_api_key`** (chmod 600, eine Zeile `sk-ant-…`). `read_api_key()` liest den Pfad, nimmt die erste `sk-ant-`-Zeile. Getestet: gültig, API antwortet HTTP 200.
|
||||
|
||||
**Kosten** (Anthropic-API, separat vom Claude-Abo): Sonnet 5 ~0,01 $/Frage (Intro-Preis), Haiku 4.5 ~0,004 $/Frage → 5 $ reichen für hunderte Fragen. Das Abo ist dafür nicht nutzbar; eigener Console-Key nötig.
|
||||
|
||||
**Deps:** `reqwest` (rustls-tls, blocking) in `src-tauri/Cargo.toml`; Command läuft über `spawn_blocking`.
|
||||
|
||||
**End-to-End verifiziert:** „Umsatz je Gewerk" → bar auf `v_kunde_360`; „Gesamtumsatz in Euro" → kpi 609.861 €. Prompt-Hinweis: Kunden-Anzeigename = `name_roh` (nicht `name_1`).
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
name: project-08-07-datenbasis
|
||||
description: Datenbasis-Bewertung — aktuell 191 Aufträge, Ziel ~2000; reicht für Demo, nicht für statistisches Cross-Sell
|
||||
metadata:
|
||||
type: project
|
||||
---
|
||||
|
||||
Aktueller Export: **191 Aufträge / 152 Firmen**. Marcus versucht, **~2000** zu beschaffen.
|
||||
|
||||
Bewertung nach Zweck:
|
||||
- **Demo** (Chat, Preis-/Margen-Assistent als Narrativ, Veredelung): 191 reichen locker — es zählen überzeugende Einzelfälle, keine Statistik.
|
||||
- **Cross-Sell belastbar:** grenzwertig bei 191 (viele seltene/unikale Module, dünnes Ko-Vorkommen, Long Tail). Robuste „X→Y"-Regeln eher ab 1.000–5.000 Aufträgen → daher das 2000-Ziel.
|
||||
- **Klassisches ML-Margenmodell:** 191 zu wenig (Überanpassung bei ~25 Merkmalen). Deshalb LLM-basiertes Reasoning über ähnliche Fälle statt Training — funktioniert auch mit wenig Daten und ist für die Demo ohnehin der bessere Weg.
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
name: project-08-07-datenmodell
|
||||
description: Strukturierte SQLite-Ablage als abgeleitete Arbeitsschicht; roher JSON bleibt Quelle
|
||||
metadata:
|
||||
type: project
|
||||
---
|
||||
|
||||
Daten werden zusätzlich **strukturiert in SQLite** abgelegt (eine Datei, kein Setup). Der rohe JSON bleibt Single Source of Truth; SQLite ist die abgeleitete Arbeitsschicht.
|
||||
|
||||
**Why:** Cross-Sell braucht Aggregation über Module, Ko-Vorkommen und Joins mit der Veredelung — im verschachtelten JSON-Baum mühsam, als SQL trivial (group by). Anreicherung liegt pro Kunde einmal statt je Auftrag redundant. Demo-Abfragen werden deterministisch und schnell.
|
||||
|
||||
**Tabellen (How to apply):**
|
||||
- `auftraege` — ein Satz pro Export: Kopf, Datum, Status, Margen, Kunden-Bezug.
|
||||
- `positionen` — eine Zeile je Modul: auftrag_id, modul_id, Mengen, Preise (Basis der Warenkorbanalyse).
|
||||
- `module` — Stammdaten: modul_id → Klartext, Kategorie (einmal, geteilt).
|
||||
- `kunden` — dedupliziert, mit Branche/Größe/Geo aus der [[project-08-07-datenveredelung]].
|
||||
|
||||
Siehe [[project-08-07-datenbasis]] zur Mengenbewertung und [[reference-extracted-xml-data]] zur Quellstruktur.
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: project-08-07-datenveredelung
|
||||
description: Offline-Batch-Anreicherung der Würth-Daten (Branche, Geo, Dedup, Modul-Klartext) — eigener verkäuflicher Use Case
|
||||
metadata:
|
||||
type: project
|
||||
---
|
||||
|
||||
Die Daten werden **offline einmalig veredelt** (nicht live in der Demo), und die Demo läuft auf dem fertigen, sauberen Datensatz. Die Anreicherung ist selbst ein verkäuflicher Use Case: **Stammdaten-Veredelung / Data Quality** — vorhandene Kundendaten klassifizieren, vervollständigen, vereinheitlichen; anschlussfähig fürs CRM (Segmentierung, Zielgruppen, Cross-Sell).
|
||||
|
||||
**Branche zuordnen (dreistufig):**
|
||||
1. LLM auf Firmenname — ~70 % eindeutig aus dem Namen (Elektro, Heizung/Sanitär/Klima, Holz/Zimmerei, Bau/Dach, Kfz/Autohaus, öffentlich/Stadtwerke, Fördertechnik).
|
||||
2. Modulprofil aus `artikel_data_json` als Korrektur/Fallback für ~30 % generische Namen (Eigennamen, „Gloria", „SOS", „lean GmbH").
|
||||
3. Web-Anreicherung mit **Name + PLZ** (offline, gecacht) — hebt Trefferquote auf ~100 %, liefert echte Branche, Größe, Gewerk; bestätigt Treffer über Firmensitz. Unsichere Matches als „unbestätigt" markieren, nicht raten.
|
||||
|
||||
**Weitere Veredelung:**
|
||||
- Kunde: Dublettenauflösung + Kunden-ID (gleiche Firma mehrfach), Geo aus PLZ (Ort/Region/Koordinaten), Rechtsform normieren, Land vereinheitlichen, Segment (Handwerk/Industrie/Flotte/öffentlich).
|
||||
- Fahrzeug: leere `fzg_*`-Felder aus Modulen/Radstand-Codes inferieren (Fahrzeugklasse, Aufbautyp).
|
||||
- Artikel/Konfiguration (größter Hebel): `modul_id` → lesbarer Klartext + Kategorie (Boden, Regal, Befestigung, Montageset); Konfigurationsprofil-Label („Elektriker-Setup"); Komplexitätsmaß (Anzahl Komponenten).
|
||||
- Preise/Kennzahlen: Datum aus Schlüssel als echtes Feld; Auftragswert-, Rabatt-, Margenklasse; Conversion-Label aus `quotation_state`.
|
||||
- Bereinigung zuerst: HTML-Entities/Encoding, Whitespace; Konsistenz-Flags (Struktur ↔ Stückliste, Summen nachrechnen).
|
||||
|
||||
**Reihenfolge:** Bereinigung + Modul-Klartext → Kunde/Geo/Branche → abgeleitete Kennzahlen.
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
name: project-08-07-fahrzeug-veredelung
|
||||
description: "Fahrzeugtyp steckt nur in den .plax-Rohdaten (PDF-Block + Freitext), nicht im extrahierten JSON; per LLM extrahiert nach auftrag_fzg"
|
||||
metadata:
|
||||
node_type: memory
|
||||
type: project
|
||||
originSessionId: a88dd1e6-722f-4188-bb2e-98574c0cf8d1
|
||||
---
|
||||
|
||||
Die `fzg_*`-Felder im extrahierten JSON sind durchgängig leer; auch im PDF und XML sind die **strukturierten** Fahrzeugfelder (`FZG_MART_NR` etc.) leer. Der Fahrzeugtyp steckt aber in den `.plax`-Rohdaten unter `data/Auftragsplanungen/*.plax` (ZIP je Auftrag, Dateiname = `<auftrag_id>_<quotation_id>.plax`):
|
||||
|
||||
- **PDF-Block** „Fahrzeugbeschreibung" / „Kjøretøy beskrivelse" (155/191 vorhanden): bei DE-Aufträgen oft gefüllt mit Hersteller/Typ, Radstand+Länge (L1/L2/L3), Höhe (H1–H3), Baujahr (z. B. „Ford Transit Custom, L1 H1").
|
||||
- **Freitext** `subject` (storage.dict) + `INTERNER_TEXT` (großes XML im storage_dump): trägt den Typ bei NO-Aufträgen als Klartext (z. B. „Landcruiser"). Chaotisch: Tippfehler („Tranist", „Spritner"), gemischt DE/NO/FR/IT.
|
||||
|
||||
**Pipeline** (alle idempotent, nach [[project-08-07-datenmodell]]):
|
||||
`collect_fzg_text.py` (braucht `pdftotext`/poppler) sammelt je Auftrag subject+interner Text+PDF-Block+fzg-Modulcodes → `fzg_texts.json` → 8 LLM-Agents extrahieren → `fzg_enrichment/result_*.json` → `enrich_fzg_load.py` füllt Tabelle `auftrag_fzg` (fzg_klasse, hersteller, modell, groesse, baujahr, konfidenz, quelle).
|
||||
|
||||
**Ergebnis:** 134/191 erkannt, 57 „unbekannt" (nicht geraten). Aufbau-Cluster `van|pickup|pkw|lkw|unbekannt`: fast alles **van** (133), 1 pickup, 0 pkw/lkw — der Aufbau-Cluster differenziert kaum; die **Größenklasse L1/L2/L3** trägt die eigentliche Unterscheidung. Frontend zeigt je Auftrag ein Cluster-Icon + Modell·Größe.
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: project-08-07-stueckliste-pipeline
|
||||
description: Klartext-Stücklisten je Auftrag aus den eingebetteten PDFs der .plax ziehen — wiederholbare Pipeline für die kommenden ~2000 Aufträge
|
||||
metadata:
|
||||
node_type: memory
|
||||
type: project
|
||||
originSessionId: 9605d7dd-3c40-4f92-b05f-540606518879
|
||||
---
|
||||
|
||||
Die Bauteil-Klartexte (Bezeichnung + Würth-Artikelnummer) stehen **nur in der eingebetteten PDF** jeder `.plax` (Auftragsbestätigung/Angebot), nicht in der XML/JSON — dort nur `modul_id` + Preise. Siehe [[reference-extracted-xml-data]], [[project-08-07-datenmodell]].
|
||||
|
||||
**Fundort in der .plax (ZIP):** `auftragsbestaetigung_*.pdf`. Stücklisten-Zeile: `Pos | Artikelnummer | Abmessungen | Menge | Nettowert`, darunter die Bezeichnungszeile, die mit dem Kurzzeichen (= `modul_id`) beginnt. Bei mehrsprachig-beschreibenden `modul_id` (z. B. `KlesknaggWürth4kroker`) ist die Zeile selbst die Bezeichnung mit Trennzeichen (`Klesknagg, Würth, 4 kroker`).
|
||||
|
||||
**Wiederholbare Pipeline (bei neuen .plax / für die ~2000 Aufträge):** `import.py` löscht `wuerth.db` und baut aus `schema.sql` **komplett neu** — danach müssen ALLE Folge-Loader wieder laufen, sonst sind Stückliste UND Veredelung leer. Reihenfolge:
|
||||
1. Neue `.plax` nach `data/Auftragsplanungen/` legen; aktualisierten `data/extracted_xml_data.json` nach `data/`.
|
||||
2. `python3 pipeline/import.py` — kunden/auftraege/module/positionen.
|
||||
3. `python3 pipeline/extract_stueckliste.py` — PDFs → `module.klartext` + `module.artikelnummer`, Beleg `pipeline/stueckliste.json`.
|
||||
4. `python3 pipeline/enrich_currency.py` — echte Währung + Wechselkurs (siehe unten) → `auftraege.waehrung/wechselkurs/kurs_datum`, Cache `pipeline/fx_cache.json`.
|
||||
5. `python3 pipeline/enrich_load.py` — Firmendaten (`kunde_firmendaten`, `web_cache`) aus `pipeline/enrichment/*.json` (offline-Cache).
|
||||
6. `python3 pipeline/enrich_fzg_load.py` — Fahrzeug (`auftrag_fzg`) aus `pipeline/fzg_enrichment/*.json`.
|
||||
7. App gegen die DB; Popup je Auftrag rendert die Stückliste (`list_positionen`) inkl. EUR-Spalte + Kurs-Fußzeile.
|
||||
|
||||
Schritte 5/6 sind idempotent (leeren ihre Tabellen vorab) und laufen offline aus dem JSON-Cache — kein Web-Call.
|
||||
|
||||
**Währung/Wechselkurs (Schritt 4):** `extracted_xml_data.json`-Feld `currency` ist unbrauchbar (konstant `EUR` für alle). Echte Landeswährung nur im .plax-XML: `VARNAME:CURRENCY:USER:EINSTELLUNGEN_LAND` (EUR/NOK/CHF/PLN), bestätigt durch Label-Übersetzung `EUR→<Währung>`. Verteilung Stand 191: EUR 170, NOK 15, CHF 5, PLN 1. Kurs = EZB-Referenz zum Auftragsdatum (Abschlusstag aus dem Export-Key) via `api.frankfurter.dev` (User-Agent-Header nötig, sonst 403; wählt bei Wochenende/Feiertag den letzten Handelstag). `wechselkurs` = Einheiten je EUR (EUR=1), EUR-Wert = Betrag ÷ Kurs. `v_kunde_360.umsatz_netto` und `list_auftraege.umsatz_eur` sind darüber EUR-normiert (sonst NOK+EUR gemischt).
|
||||
|
||||
**Matching-Logik (extract_stueckliste.py):** Positionszeile per Split an ≥2 Leerzeichen (Spalten sauber trennen, sonst laufen Artikelnummer/Abmessung zusammen). Bezeichnung→`modul_id` über (a) erstes Token == modul_id, (b) normalisierter Präfix (Sonderzeichen/Leerzeichen strippen, lower) — fängt die ~10 % beschreibenden IDs, Abdeckung nahe 100 %. Kanonische Bezeichnung je Modul = häufigste, Deutsch bevorzugt (dieselbe modul_id hat je Auftrag DE/NO/IT-Varianten). Fallback `klartext = modul_id`, wenn ein Modul in keiner PDF vorkommt.
|
||||
|
||||
**Gemessen (Stand 213 .plax / 804 module):** 7.138 Positionszeilen, 90 % direkt per Kurzzeichen gemappt; Rest über Normalisierung.
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
name: project-08-07-tech-stack
|
||||
description: Tech-Stack der Würth-KI-Demo — Tauri-Desktop-App (Rust-Backend + Vue-Frontend) auf SQLite
|
||||
metadata:
|
||||
type: project
|
||||
---
|
||||
|
||||
Entscheidung: **Tauri-Desktop-App** statt separatem Web-Backend. Der Rust-Kern ist das Backend.
|
||||
|
||||
**Why:** Die LLM-Aufrufe brauchen den Claude-API-Key, der nicht in den Browser darf. Ohne Backend ginge nur SQLite-per-WASM (sql.js) + Vorberechnung — dann fällt der überzeugende Live-LLM-Teil weg oder der Key liegt offen. Tauri hält Key, SQLite und LLM-Calls im Rust-Kern, liefert eine Binary ohne Deploy — ideal für eine Vorführung.
|
||||
|
||||
**How to apply:**
|
||||
- Frontend: Vue im Tauri-Webview (Vite + Vue, `create-tauri-app`).
|
||||
- Rust-Backend als Tauri-Commands (über `invoke()` vom Frontend):
|
||||
- `chat(frage)` → Kontext aus SQLite bauen, Claude-API via `reqwest` rufen, Antwort zurück; Key bleibt in Rust (.env/Tauri-Config).
|
||||
- `query_orders`, `cross_sell(modul_id)`, `customer_segments` → feste SQL-Abfragen via `rusqlite`.
|
||||
- Rust-Deps: `rusqlite`, `reqwest`, `serde`, `tokio`.
|
||||
- Daten: veredelte SQLite-Datei als Tauri-Resource gebündelt.
|
||||
- Veredelungs-Pipeline bleibt getrennt (Python), erzeugt nur die `.db`.
|
||||
- Voraussetzung lokal: Rust-Toolchain + Node.
|
||||
- App-Code soll in eigenes Projekt unter `~/projects/08-07` (analog Limbach: `~/claude-projects/08-07` = Memory/Steuerung, `~/projects/08-07` = Code).
|
||||
|
||||
Claude-Modelle (Stand 2026): Opus 4.8 `claude-opus-4-8`, Sonnet 4.6 `claude-sonnet-4-6`, Haiku 4.5 `claude-haiku-4-5-20251001`, Fable 5 `claude-fable-5`.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: project-wuerth-ki-demo
|
||||
description: KI-Demo für Würth auf Basis realer Angebots-/Auftragsdaten der Fahrzeugeinrichtung; zwei Demo-Richtungen
|
||||
metadata:
|
||||
type: project
|
||||
---
|
||||
|
||||
Ziel: einem Unternehmen (Würth-Kontext) zeigen, was KI mit den vorhandenen Angebots-/Auftragsdaten aus dem Plato-Konfigurator leisten kann. Projektordner `~/claude-projects/08-07`, Quelldaten unter `data/` (siehe [[reference-extracted-xml-data]]).
|
||||
|
||||
Zwei Demo-Richtungen:
|
||||
1. **Preis-/Margen-Assistent + Chat** — aus historischen Aufträgen (Einkaufspreis, Listenpreis, gewährter Rabatt, Marge RE/KA) ableiten, welcher Preis/Rabatt bei vergleichbarer Konfiguration üblich war; Margenwarnung; Ausreißererkennung. Kombiniert mit natürlichsprachiger Abfrage über den Bestand. LLM-basiert (Reasoning über ähnliche Fälle), kein klassisches ML-Training.
|
||||
2. **Empfehlung / Cross-Sell** — Warenkorbanalyse über die Module (Ko-Vorkommen), Empfehlung fehlender Positionen; verstärkt durch Branche aus der Veredelung („Betriebe dieser Branche kaufen typischerweise auch …").
|
||||
|
||||
Voraussetzung für Richtung 2 ist die strukturierte Ablage ([[project-08-07-datenmodell]]) und die [[project-08-07-datenveredelung]]. Technik: [[project-08-07-tech-stack]]. Datenmengen-Bewertung: [[project-08-07-datenbasis]].
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: reference-extracted-xml-data
|
||||
description: Struktur des Quell-Datensatzes data/extracted_xml_data.json — Plato-Export, 191 Aufträge
|
||||
metadata:
|
||||
type: reference
|
||||
---
|
||||
|
||||
`data/extracted_xml_data.json` (≈18,6 MB). Top-Level-Dict, 191 Datensätze; Schlüssel `export_<Kürzel>_<JJJJMMTT>_<Nr>-1` (Kürzel = Bearbeiter/Kunde, Datum im Schlüssel). 152 eindeutige Firmen, einige Kunden mehrfach (z. B. Die Autobahn ×12, Elektrotechnik Schäfer ×7). Daten von 2024 bis 2026-06-24.
|
||||
|
||||
Pro Datensatz ~36 Felder, drei Blöcke + zwei verschachtelte JSON-Strukturen:
|
||||
- **Kopf/Preise** `kopf_*`: einkaufspreis, summe_positionen_liste, summe_positionen, montage_zeit/-wert, fracht, rabatt_wert/-prozent, aufschlag_wert/-prozent, kundenpreis_netto, aw_wert, re, ka.
|
||||
- **Fahrzeug** `fzg_*`: hersteller_id, typ, baujahr, dach, antrieb, leasing, rad_stand, seitentuer, wand — durchgängig leer, Kandidat zum Nachfüllen.
|
||||
- **Kunde** `kde_*` + Meta: name, name_1/2, landeskenner, plz, strasse; currency, montage_art, quotation_state (z. B. `order`), wuerth_innen_landeskenner.
|
||||
- **struktur_data_json**: Aufbau-/Geometriebaum (`root` mit Wänden rechts/links/stirn/heck/boden/dach, Bereich `ungeplant` mit Komponenten `COMP_xxxx`: pos/rot/skalierung, modul_id, platzierung, menge).
|
||||
- **artikel_data_json**: Stückliste je `modul_id` mit einkaufspreis, preis_liste, preis, rabatt_wert/-modus/-max/-fix, aufschlag_wert, aw_wert, typ.
|
||||
|
||||
Datenhygiene: HTML-Entities (`&`) und Unicode-Escapes auflösen; `landeskenner` uneinheitlich (`NO`/`Norway`, `DE`/`Deutschland`, teils leer); PLZ teils nur Ziffer ohne Ort; `modul_id` kryptisch und gemischtsprachig (de/no), z. B. `SMU73`, `PRGR2GULVMELLOMSTORVAREBILL1/L2/L3`, `KlesknaggWürth4kroker`.
|
||||
@@ -14,9 +14,6 @@
|
||||
"Bash(~/claude-projects/global/bin/git-sync:*)"
|
||||
]
|
||||
},
|
||||
"env": {
|
||||
"PATH": "/Users/marcus.hinz/.local/share/fnm/aliases/default/bin:/Users/marcus.hinz/.local/bin:${PATH}"
|
||||
},
|
||||
"enabledPlugins": {
|
||||
"frontend-design@claude-plugins-official": true,
|
||||
"swift-lsp@claude-plugins-official": true
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 3.9 KiB |
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 435 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 110 KiB |
@@ -6,12 +6,14 @@
|
||||
"Edit(~/projects/adesso-cpq/**)",
|
||||
"Edit(~/projects/geko-infra/**)",
|
||||
"Edit(~/projects/fire-visual/**)",
|
||||
"Edit(~/projects/geko-fire/**)",
|
||||
"Edit(~/claude-projects/robotunits/**)"
|
||||
],
|
||||
"additionalDirectories": [
|
||||
"~/projects/geko-core",
|
||||
"~/projects/adesso-cpq",
|
||||
"~/projects/geko-infra"
|
||||
"~/projects/geko-infra",
|
||||
"~/projects/geko-fire"
|
||||
]
|
||||
},
|
||||
"hooks": {
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
<!--
|
||||
RSS generated by JIRA (1001.0.0-SNAPSHOT#100292-rev:5fda9e0fdd2f2fe2b6bc3376de24f51bf166a518) at Wed Jul 01 12:29:44 UTC 2026
|
||||
|
||||
It is possible to restrict the fields that are returned in this document by specifying the 'field' parameter in your request.
|
||||
For example, to request only the issue key and summary add field=key&field=summary to the URL of your request.
|
||||
-->
|
||||
<rss version="0.92" >
|
||||
<channel>
|
||||
<title>Jira adesso Group extern</title>
|
||||
<link>https://adesso-group-extern.atlassian.net</link>
|
||||
<description>This file is an XML representation of an issue</description>
|
||||
<language>en-us</language> <build-info>
|
||||
<version>1001.0.0-SNAPSHOT</version>
|
||||
<build-number>100292</build-number>
|
||||
<build-date>30-06-2026</build-date>
|
||||
</build-info>
|
||||
|
||||
<item>
|
||||
<title>[GEKO-76] Allround-Schutzzaun: Intelligente Steher-Logik</title>
|
||||
<link>https://adesso-group-extern.atlassian.net/browse/GEKO-76</link>
|
||||
<project id="12341" key="GEKO">GEKO</project>
|
||||
<description><p>Dieses Ticket implementiert die selbstheilende Logik für die Steher (Pfosten) des Allround-Schutzzauns. Ein platziertes Allround-Element wird automatisch in drei funktionsfähige 3D-Komponenten zerlegt. Die Steher passen ihre Bauform (A–G) nach dem Platzieren via lokalem Event-Listener dynamisch an belegte PMIs an und steuern ihre Höhe synchron zu den angeschlossenen Zäunen. Nutzer können Bauform und Höhe über das Detailmenü manuell überschreiben, wobei das UI die wählbaren Bauformen streng auf die laut Matrix gültigen Optionen einschränkt.</p>
|
||||
|
||||
<h3><a name="%F0%9F%9F%A6DefinitionofReady%C2%A0"></a><b>�� Definition of Ready</b> </h3>
|
||||
|
||||
<h4><a name="Wer%3F%C2%A0"></a><b>Wer?</b> </h4>
|
||||
|
||||
<p>Anwender des 3D-Viewings</p>
|
||||
|
||||
<h4><a name="Was%3F%C2%A0"></a><b>Was?</b> </h4>
|
||||
|
||||
<ul>
|
||||
<li>Automatische Aufspaltung eines Allround-Schutzzaun-Objekts in separate Steher- und Zaunknoten.</li>
|
||||
<li>Dynamischer In-Place-Austausch der Steher-Bauform (A–G) basierend auf belegten PMI-Winkeln.</li>
|
||||
<li>Automatische Höhensteuerung des Stehers basierend auf dem Maximum der angeschlossenen Zäune samt Fußfreiheit.</li>
|
||||
<li>Implementierung einer fachlichen Ausnahme für die Lösch-Kaskadierung bei Schutzzäunen.</li>
|
||||
<li>Manuelle Override-Möglichkeit für Bauform (dynamisch eingeschränkt) und Höhe (nur Vergrößerung) im Detailmenü.</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<p><b>Warum?</b> </p>
|
||||
|
||||
<p>Steher müssen im 3D-Viewer als eigenständige Komponenten agieren, sich aber topologisch korrekt "selbst heilen" (Ecken/Kreuzungen bilden), um dem Nutzer manuelle Arbeit zu ersparen und fehlerfreie Stücklisten zu garantieren.</p>
|
||||
|
||||
<h4><a name="Wozu%3F%C2%A0"></a><b>Wozu?</b> </h4>
|
||||
|
||||
<p>Gewährleistung einer konsistenten Schutzzaun-Topologie im Szenegraphen.</p></description>
|
||||
<environment></environment>
|
||||
<key id="139414">GEKO-76</key>
|
||||
<summary>Allround-Schutzzaun: Intelligente Steher-Logik</summary>
|
||||
<type id="12871" iconUrl="https://adesso-group-extern.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10318?size=medium">Task</type>
|
||||
<parent id="119259">GEKO-6</parent>
|
||||
<priority id="10000" iconUrl="https://adesso-group-extern.atlassian.net/images/icons/priorities/critical.svg">Critical</priority>
|
||||
<status id="14794" iconUrl="https://adesso-group-extern.atlassian.net/" description="">To Do</status>
|
||||
<statusCategory id="2" key="new" colorName="blue-gray"/>
|
||||
<resolution id="-1">Unresolved</resolution>
|
||||
<assignee accountid="712020:1a31ac71-0e6f-49af-83f9-0d630a810204">Marcus Hinz</assignee>
|
||||
<reporter accountid="712020:0a2bb4c7-7da6-48f0-8264-d021465e5f0f">Lukas Hörger</reporter>
|
||||
<labels>
|
||||
</labels>
|
||||
<created>Wed, 8 Apr 2026 16:57:23 +0200</created>
|
||||
<updated>Wed, 1 Jul 2026 14:27:06 +0200</updated>
|
||||
<due></due>
|
||||
<votes>0</votes>
|
||||
<watches>0</watches>
|
||||
<attachments>
|
||||
</attachments>
|
||||
<subtasks>
|
||||
</subtasks>
|
||||
<customfields>
|
||||
<customfield id="customfield_13434" key="com.atlassian.jira.plugin.system.customfieldtypes:textfield">
|
||||
<customfieldname>Ansprechpartner</customfieldname>
|
||||
<customfieldvalues>
|
||||
<customfieldvalue>Lukas</customfieldvalue>
|
||||
|
||||
</customfieldvalues>
|
||||
</customfield>
|
||||
<customfield id="customfield_10000" key="com.atlassian.jira.plugins.jira-development-integration-plugin:devsummarycf">
|
||||
<customfieldname>Development</customfieldname>
|
||||
<customfieldvalues>
|
||||
|
||||
</customfieldvalues>
|
||||
</customfield>
|
||||
<customfield id="customfield_10019" key="com.pyxis.greenhopper.jira:gh-lexo-rank">
|
||||
<customfieldname>Rank</customfieldname>
|
||||
<customfieldvalues>
|
||||
<customfieldvalue>0|i5ji9e:</customfieldvalue>
|
||||
|
||||
</customfieldvalues>
|
||||
</customfield>
|
||||
<customfield id="customfield_13432" key="com.atlassian.jira.plugin.system.customfieldtypes:select">
|
||||
<customfieldname>T-Shirt Size - Storypoint</customfieldname>
|
||||
<customfieldvalues>
|
||||
<customfieldvalue key="20364"><![CDATA[M]]></customfieldvalue>
|
||||
|
||||
</customfieldvalues>
|
||||
</customfield>
|
||||
<customfield id="customfield_13430" key="com.atlassian.jira.plugin.system.customfieldtypes:textarea">
|
||||
<customfieldname>�� Definition of Ready - Formale Kriterien</customfieldname>
|
||||
<customfieldvalues>
|
||||
<customfieldvalue><h3><a name="%F0%9F%93%98Funktionsbeschreibung%3A"></a><b>�� Funktionsbeschreibung:</b></h3>
|
||||
|
||||
<ul>
|
||||
<li><b>Input:</b> Platzierungs-, Änderungs- oder Lösch-Events von Allround-Schutzzaunkomponenten im Viewport, sowie manuelle Eingaben im Detailmenü.</li>
|
||||
<li><b>Verhalten / Ablauf:</b>
|
||||
<ol>
|
||||
<li><b>Aufspaltung bei Erst-Einbau (Typ 1):</b> Wird ein Allround-Element frei platziert, splittet die Engine dieses in drei 3D-Komponenten: <tt>Vorderer Steher</tt> -&gt; <tt>Zaunelement</tt> -&gt; <tt>Hinterer Steher</tt>. Der vordere Steher ist die Wurzel (Weltkoordinaten/Typ 1). Zaun und hinterer Steher hängen kaskadierend via PMI (Typ 2) daran.</li>
|
||||
<li><b>Anbau von Folge-Elementen (Typ 2):</b> Snappt ein neues Element an das Ende eines bestehenden Zauns, wird der hintere Steher des Vorgängers zum gemeinsamen <em>Verbindungssteher</em>. Das neue Zaunelement hängt an dessen PMI.</li>
|
||||
<li><b>Selbstheilende Bauform-Aktualisierung:</b> Nach dem Mouse-Up-Event prüft ein lokales Callback die belegten PMI-Seiten des Stehers. Anhand einer exakten Matrix <em>(siehe separater Algorithmus-Abschnitt unten)</em> wird das 3D-Modell in-place gegen die passende Bauform (A-G) ausgetauscht.</li>
|
||||
<li><b>Höhen-Synchronisation &amp; Metadaten:</b> Die Standardhöhe ist 2200 mm. Die Steherhöhe folgt automatisch dem Maximum aller verbundenen Zäune: <tt>max(Zaunhöhe 1 + Fußfreiheit 1, Zaunhöhe 2 + Fußfreiheit 2, ...)</tt>. Jeder Steher führt pro Zaun den unteren Startpunkt (Fußfreiheit) und das obere Ende als Metadatum mit. Weicht die Höhe von 2200 mm ab, generiert GEKO einen <em>Sondersteher</em>.</li>
|
||||
<li><b>Manuelles Override (Detailmenü):</b>
|
||||
<ol>
|
||||
<li><em>Höhe:</em> Der Nutzer kann die Höhe manuell vergrößern. Ein Verkleinern unter das berechnete Minimum wird blockiert.</li>
|
||||
<li><em>Bauform:</em> Der Nutzer kann die automatisch berechnete Bauform im Detailmenü manuell überschreiben. <b>Wichtig für die UI-Logik:</b> Das Auswahl-Dropdown darf dem Nutzer dynamisch nur die Bauformen (A-G) anbieten, die für die aktuelle "Eigene Anbau-Seite" (STARTPUNKT, L oder R) laut der Ermittlungs-Matrix überhaupt zulässig sind. Ist ein Steher beispielsweise auf Anbau-Seite <tt>L</tt> eingebaut, müssen die Optionen <tt>C</tt> und <tt>E</tt> im Menü ausgeblendet oder deaktiviert werden.</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li><b>Kaskadierendes Löschverhalten (Sonderregel):</b>
|
||||
<ul>
|
||||
<li><em>Steher löschen:</em> Alle direkt verbundenen Zäune werden mitgelöscht.</li>
|
||||
<li><em>Zaun löschen:</em> Angrenzende Steher ohne weitere Zäune werden mitgelöscht. Angrenzende Steher mit weiteren Zäunen bleiben stehen und aktualisieren ihre Bauform.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
|
||||
<h4><a name="Algorithmus%3ASteherBauformermitteln%28%7B%7Blayouttauschensteher%7D%7D%29"></a>Algorithmus: Steher-Bauform ermitteln (<tt>layout_tauschen_steher</tt>)</h4>
|
||||
|
||||
<p>Die Engine ermittelt für jeden Steher die Seite des Ankers (ist er selbst am Startpunkt/Typ 1 Platzierung, an der linken (L) oder rechten (R) Seite eines Elements angebaut?) und liest die Winkel der aktuell an ihm belegten PMIs (0°, 90°, 180°). Daraus ergibt sich folgende Matrix zur Bestimmung der Bauform-Artikelnummer (A-G):</p>
|
||||
|
||||
<div class='table-wrap'>
|
||||
<table class='confluenceTable'><tbody>
|
||||
<tr>
|
||||
<td class='confluenceTd'><b>Eigene Anbau-Seite</b></td>
|
||||
<td class='confluenceTd'><b>Belegte PMIs am Steher</b></td>
|
||||
<td class='confluenceTd'><b>Resultierende Bauform</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class='confluenceTd'><b>STARTPUNKT</b></td>
|
||||
<td class='confluenceTd'><tt>0</tt> und <tt>180</tt></td>
|
||||
<td class='confluenceTd'><b>A</b> (ana)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class='confluenceTd'><b>STARTPUNKT</b></td>
|
||||
<td class='confluenceTd'><tt>180</tt></td>
|
||||
<td class='confluenceTd'><b>B</b> (bna)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class='confluenceTd'><b>STARTPUNKT</b></td>
|
||||
<td class='confluenceTd'><tt>0</tt></td>
|
||||
<td class='confluenceTd'><b>C</b> (cna)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class='confluenceTd'><b>STARTPUNKT</b></td>
|
||||
<td class='confluenceTd'><tt>90</tt> und <tt>180</tt></td>
|
||||
<td class='confluenceTd'><b>D</b> (dna)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class='confluenceTd'><b>STARTPUNKT</b></td>
|
||||
<td class='confluenceTd'><tt>0</tt> und <tt>90</tt></td>
|
||||
<td class='confluenceTd'><b>E</b> (ena)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class='confluenceTd'><b>STARTPUNKT</b></td>
|
||||
<td class='confluenceTd'><tt>0</tt>, <tt>90</tt> und <tt>180</tt></td>
|
||||
<td class='confluenceTd'><b>F</b> (fna)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class='confluenceTd'><b>STARTPUNKT</b></td>
|
||||
<td class='confluenceTd'><em>Sonstige</em></td>
|
||||
<td class='confluenceTd'><b>G</b> (gna)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class='confluenceTd'><b>L</b> (Links)</td>
|
||||
<td class='confluenceTd'><em>(keine)</em></td>
|
||||
<td class='confluenceTd'><b>B</b> (bna)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class='confluenceTd'><b>L</b> (Links)</td>
|
||||
<td class='confluenceTd'><tt>0</tt></td>
|
||||
<td class='confluenceTd'><b>A</b> (ana)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class='confluenceTd'><b>L</b> (Links)</td>
|
||||
<td class='confluenceTd'><tt>90</tt></td>
|
||||
<td class='confluenceTd'><b>D</b> (dna)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class='confluenceTd'><b>L</b> (Links)</td>
|
||||
<td class='confluenceTd'><tt>0</tt> und <tt>90</tt></td>
|
||||
<td class='confluenceTd'><b>F</b> (fna)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class='confluenceTd'><b>L</b> (Links)</td>
|
||||
<td class='confluenceTd'><em>Sonstige</em></td>
|
||||
<td class='confluenceTd'><b>G</b> (gna)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class='confluenceTd'><b>R</b> (Rechts)</td>
|
||||
<td class='confluenceTd'><em>(keine)</em></td>
|
||||
<td class='confluenceTd'><b>C</b> (cna)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class='confluenceTd'><b>R</b> (Rechts)</td>
|
||||
<td class='confluenceTd'><tt>180</tt></td>
|
||||
<td class='confluenceTd'><b>A</b> (ana)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class='confluenceTd'><b>R</b> (Rechts)</td>
|
||||
<td class='confluenceTd'><tt>90</tt> und <tt>180</tt></td>
|
||||
<td class='confluenceTd'><b>F</b> (fna)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class='confluenceTd'><b>R</b> (Rechts)</td>
|
||||
<td class='confluenceTd'><em>Sonstige</em></td>
|
||||
<td class='confluenceTd'><b>G</b> (gna)</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
|
||||
|
||||
<h4><a name="NichtZiele%28OutofScope%29"></a>Nicht-Ziele (Out of Scope)</h4>
|
||||
|
||||
<ul>
|
||||
<li><b>Echtzeit-Austausch während des Ziehens:</b> Die Bauform- und Höhenberechnung triggert niemals während der Mausbewegung, sondern exakt nach dem Mouse-Up-Event oder nach Bestätigung im Detailmenü.</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<hr />
|
||||
|
||||
<h3><a name="%F0%9F%93%91Akzeptanzkriterien%3A"></a><b>�� Akzeptanzkriterien:</b></h3>
|
||||
|
||||
<ul>
|
||||
<li>Jedes Allround-Zaunelement wird beim Platzieren in separat selektierbare Steher- und Zaunkomponenten aufgeteilt.</li>
|
||||
<li>Beim sequentiellen Anbau teilen sich zwei Zaunelemente exakt einen gemeinsamen Verbindungssteher.</li>
|
||||
<li>Die Steher-Bauform heilt sich nach dem Mouse-Up gemäß der Matrix (A-G) selbst.</li>
|
||||
<li>Im Detailmenü kann der Nutzer die Bauform manuell überschreiben. <b>Das UI schränkt die Auswahlmöglichkeiten hierbei strikt und dynamisch auf die für diese Anbau-Seite gültigen Zeilen der Matrix ein.</b></li>
|
||||
<li>Die Steherhöhe entspricht mindestens dem Maximum aus <tt>Zaunhöhe + Fußfreiheit</tt>.</li>
|
||||
<li>Manuelle Höhen-Eingaben unter das Minimum werden im UI blockiert.</li>
|
||||
<li>Sonderregel Löschen (Steher): Löschen eines Stehers löscht angebundene Zäune.</li>
|
||||
<li>Sonderregel Löschen (Zaun): Löschen eines Zauns löscht alleinstehende Steher; verbundene Steher bleiben stehen und passen Bauform an.</li>
|
||||
<li>Sonderregel Löschen (Kaskade): Löschen eines Stehers führt zur Löschung der angebundenen Zäune. Löschung eines angebundenen Zauns führt, falls vorhanden, zur Löschung alleinstehender Steher</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<hr />
|
||||
|
||||
<h3><a name="%F0%9F%A7%AATestCases%3A"></a><b>�� Test Cases:</b></h3>
|
||||
|
||||
<ul>
|
||||
<li><b><span class="error">&#91;Lösch-Kettenabbruch&#93;</span></b>: Strang <tt>Steher A -&gt; Zaun 1 -&gt; Steher B -&gt; Zaun 2 -&gt; Steher C</tt> aufbauen -&gt; <tt>Steher A</tt> löschen -&gt; <em>Ergebnis:</em> <tt>Zaun 1</tt> wird gelöscht. <tt>Steher B</tt> bleibt stehen (da <tt>Zaun 2</tt> angebunden) und wechselt Bauform. <tt>Zaun 2</tt> und <tt>Steher C</tt> bleiben unberührt.</li>
|
||||
<li><b><span class="error">&#91;Bauform-Selbstheilung bei Eck-Anbau&#93;</span></b>: Geraden Zaun platzieren (Verbindungssteher ist Bauform A) -&gt; Neues Zaunelement im 90°-Winkel anbauen -&gt; <em>Ergebnis:</em> Nach Mouse-Up wechselt der Verbindungssteher sofort in die passende Eck-Bauform laut Matrix.</li>
|
||||
<li><b><span class="error">&#91;Manuelle Override-Tests &amp; UI-Restriktion&#93;</span></b>: Folge-Zaunelement anbauen (Verbindungssteher befindet sich auf Anbau-Seite <tt>L</tt>). Verbindungssteher selektieren und Detailmenü öffnen -&gt; <em>Ergebnis:</em> Das Dropdown für die Bauform bietet nur A, B, D, F und G an. Die Optionen C und E sind nicht wählbar oder ausgeblendet. Der Wechsel auf eine zulässige Bauform (z. B. F) aktualisiert das 3D-Modell sofort.</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<hr />
|
||||
|
||||
<h3><a name="%E2%9A%A0%EF%B8%8FEdgeCases%3A%C2%A0"></a><b>⚠️ Edge Cases:</b> </h3>
|
||||
|
||||
<ul>
|
||||
<li><b><span class="error">&#91;Misch-Höhen an einem Steher&#93;</span></b>: Zäune mit Höhen 2500 mm und 2200 mm treffen sich -&gt; <em>Verhalten:</em> Steher übernimmt die maximale Höhe und wird zum Sondersteher.</li>
|
||||
</ul>
|
||||
</customfieldvalue>
|
||||
|
||||
</customfieldvalues>
|
||||
</customfield>
|
||||
<customfield id="customfield_13429" key="com.atlassian.jira.plugin.system.customfieldtypes:multicheckboxes">
|
||||
<customfieldname>�� DoR - Checkliste</customfieldname>
|
||||
<customfieldvalues>
|
||||
<customfieldvalue key="20347"><![CDATA[Ziel eindeutig formuliert (W-Fragen vollständig?)]]></customfieldvalue>
|
||||
<customfieldvalue key="20348"><![CDATA[Ansprechpartner benannt]]></customfieldvalue>
|
||||
<customfieldvalue key="20349"><![CDATA[Akzeptanzkriterien definiert]]></customfieldvalue>
|
||||
<customfieldvalue key="20350"><![CDATA[mind. ein Testfall dokumentiert]]></customfieldvalue>
|
||||
<customfieldvalue key="20351"><![CDATA[Priorität gesetzt]]></customfieldvalue>
|
||||
<customfieldvalue key="20352"><![CDATA[Edge Cases beschrieben]]></customfieldvalue>
|
||||
|
||||
</customfieldvalues>
|
||||
</customfield>
|
||||
</customfields>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
@@ -1,81 +1,3 @@
|
||||
# Offene Punkte — bei jedem Start prüfen und abhaken
|
||||
|
||||
Reihenfolge: Paul · Marcel (fire-visual / Bundle) · cpq.
|
||||
|
||||
---
|
||||
|
||||
## Paul
|
||||
|
||||
### [ ] Schutzzaun „Element" kaputt
|
||||
- **Wo:** `adesso-cpq/packages/frontend/src/components/building-blocks/ProductBrowser.vue`, Filterzeile (TODO (Paul)).
|
||||
- **Stand:** Kataloggruppe `Element` im `schutzzaun_basic` per Filter rausgenommen.
|
||||
- **Nötig:** Element richtig fixen statt ausfiltern.
|
||||
|
||||
---
|
||||
|
||||
## Marcel — cpq-Selection
|
||||
|
||||
### [ ] Selection-Logik
|
||||
- **Wo:** `adesso-cpq/packages/frontend/src/components/visual/DefaultCommandProzessor.ts`, Events `selection` / `selection-click` (FIXME (Marcel)).
|
||||
- **Nötig:** Selection-Logik sauber in den Griff bekommen.
|
||||
|
||||
---
|
||||
|
||||
## Marcel — Scale
|
||||
|
||||
### [ ] Scale-Hack vs. PlacementMode — wie lösen?
|
||||
- **Hack:** `Fire3D.vue` skaliert per `scaleScene` jedes Root-Handle zur Laufzeit mit `0.002`.
|
||||
- **Konflikt:** PlacementMode überschreibt die Subject-Matrix mit `Vector3.one` (`subject.matrix = bestMatrix · dragPointTransform⁻¹`). DragPoint-Transform trägt `S`, sein Inverses `1/S`, Subject bekommt `1/S` → Geometrie netto `S·1/S = 1` → ~500×.
|
||||
- **Folge:** Jeder einheitliche Laufzeit-Scale (Root, Wrapper, „kleine" Selection) überlebt den Snap nicht. Hack und PlacementMode sind auf demselben Subject unvereinbar.
|
||||
- **Einzige überlebende Lösung:** `S=1`, also reale kleine Koordinaten in den Daten selbst — Geometrie **und** PMIs konsistent, kein Laufzeit-Scale — plus Kamera-Config (`camera-controller` `defaultPosition`/`fixPoint`, Ortho-Frustum auf mm).
|
||||
- **Offen/ungeprüft:** Ob fire-visual einen vorgesehenen Mechanismus hat (Unit-System `unitConverter`/`environment.units`; FBX `UnitScaleFactor` greift bisher nur beim Import, nicht auf fertige JSON-SceneGraphen).
|
||||
- **Entscheidung nötig:** Daten in realer Einheit liefern vs. Lib-Eingriff (PlacementMode behält Subject-Scale).
|
||||
|
||||
---
|
||||
|
||||
## Marcel (Montag) — fire-visual
|
||||
|
||||
### [ ] Lib-Bug movementMode — an fire-visual melden
|
||||
- **Wo:** `fire-visual` `threed/interaction/module/src/interactionMode/movementMode.ts`, `updateArrowHighlight`.
|
||||
- **Bug:** Z. 295–299 greift `(this.interactionHelperHandle.find('.xy-plane') as Handle).renderer…` **ohne** null-Prüfung — die Achsen (Z. 305–318) sind dagegen abgesichert (`if (!arrowElement) return; if (arrowHandle?.renderer)`).
|
||||
- **Trigger:** Plane-Kind fehlt (Gizmo-Helper existiert, Planes weg — z. B. wenn ein voller `setScene` das Gizmo bei aktivem Move abräumt) → `Cannot read properties of null (reading 'renderer')` in **jedem rAF-Frame** → kompletter Freeze.
|
||||
- **Fix:** Upstream — Z. 295–299 konsistent zu Z. 305–318 absichern.
|
||||
- **Stand:** Cpq-seitig vorerst umschifft, Upstream-Fix trotzdem nötig.
|
||||
|
||||
### [ ] Funktionierendes Bundle (`@ad/fire` mit Relations-Resolver)
|
||||
- **Ist:** cpq lädt `@ad/fire@1.0.5` (gebündelt in `packages/runtime/visual` via `tsup`, gelinkt nach `packages/frontend/node_modules/@ad-cpq/visual-runtime`).
|
||||
- **Lücke:** 1.0.5 hat `RelationsFeature` + `createRelation`, aber **nicht** `resolveRelations` / Command `resolve_relations` (im Bundle 0 Vorkommen) — der Resolver kam in der fire-visual-Quelle (`~/projects/fire-visual`) erst nach 1.0.5 dazu.
|
||||
- **Folge:** `attachment_relations` (mit `constraint_position`) wird beim Placement zwar geschrieben und die Relation instanziiert, bleibt aber **inert** (kein Resolver, kein Trigger) → verbundene Teile ziehen beim Move nicht mit.
|
||||
- **Nötig:** `@ad/fire` auf Stand mit Resolver bringen — fire-visual lokal bauen (`build:local`, yarn-workspaces-Monorepo) → neues `@ad/fire`, cpq's `@ad/fire@1.0.5` darauf umbiegen (pnpm-override / `file:`-Link), `packages/runtime/visual` neu bauen. Achtung API-Drift neuere fire-visual vs. cpq/Runtime.
|
||||
- **Zusätzlich offen, sobald Resolver da:**
|
||||
1. **Auto-Trigger fehlt auch in der Quelle** — `resolveRelations` nur via Command, kein Update-Hook/Transform-Abo; sauber wäre der Trigger im Move-/Placement-Mode der Engine (mit gezogenem Teil gepinnt), sonst muss cpq es von außen treiben.
|
||||
2. **Persistenz** — aufgelöste Nachbar-Transforms leben in der Engine, nicht im cpq-eventStore → Readback (`get_transformation`) nötig, sonst Rücksprung beim nächsten `setScene`.
|
||||
|
||||
### [ ] Sichtbarer Gap beim Verbau-Ziehen
|
||||
- **Symptom:** Beim Ziehen eines verbundenen Teils hängen die Follower sichtbar hinterher.
|
||||
- **Ursache:** `resolutionScheduler` löst **deferred** auf — das gegriffene Teil bewegt der Gizmo in der Input/Update-Phase desselben Frames, die Relationen werden nur **markiert** (`_onTransformChange`) und erst in der **Process-Phase** (`_resolveNow`) aufgelöst, also einen Schritt später.
|
||||
- **Effekt:** Follower trailen konstant um eine Frame-Bewegung (Gap skaliert mit Ziehgeschwindigkeit; bei Stillstand holen sie im nächsten Frame auf → „im Block"). Kein Timer/Throttle, `MAX_ITERATIONS=10`.
|
||||
- **Erwartung:** fire-visual nimmt den Gap raus (Auflösung synchron zum Move) **oder** animiert das Nachziehen sauber.
|
||||
- **Stand:** Cpq-seitig nicht lösbar ohne den Resolver gegen die Lib synchron zu treiben.
|
||||
|
||||
### [ ] Null-Matrix-Crash beim `setScene`-Teardown (Relationen + Rotation)
|
||||
- **Symptom:** Nach mehreren Rotationen verbundener Teile (jede über cpq-`setScene` → Rebuild): `Uncaught Error: Matrix4_4(...zeros...) - inverse: can't be calculated` in `VolumeContext.updateWTL` ← `ColliderComponent.syncTransformation` ← `SynchronizeQueue.process` ← rAF, **jeden Frame** → Freeze. Davor flutet der Log mit `FeatureComponent - deregister` (Teardown der alten Szene).
|
||||
- **Ursache:** Ein Collider synchronisiert während/nach dem `setScene`-Teardown gegen eine bereits abgeräumte/**genullte** Transform → Null-Matrix → Inverse unmöglich.
|
||||
- **Repro:** Aktive `attachment_relations` (Position+Rotation) + wiederholtes `setScene`; kumulativ (nach einigen Rebuilds, gleiche Transform-Paarung).
|
||||
- **Ausgeschlossen:** **Nicht** das Quaternion-Format (`values` = `[w,x,y,z]`, verifiziert), **nicht** Resolver/`localA`/`constraint_rotation` (mit vollständigem Constraint-Satz unverändert). Während des Ziehens (Engine-Live-Resolve) alles korrekt — nur der cpq-`setScene`-Rebuild beim Loslassen triggert es.
|
||||
- **Einordnung:** Gleiche Klasse wie der movementMode-null-renderer-Freeze — Lib räumt beim Rebuild nicht sauber ab bzw. Collider-Sync läuft gegen tote Transform. Upstream-Fix nötig.
|
||||
- **Stand:** Cpq-seitig umschifft — `handleRotateEnd` macht **kein** `setScene` mehr (Engine hat den Rotations-Endzustand live korrekt; Event wird nur recorded).
|
||||
- **Offen:** Ein **nachfolgendes** `setScene` (z. B. Placement nach Rotation) replayt die Rotation und könnte denselben Crash auslösen. Eigentlicher Fix: upstream oder **inkrementelles** Transform-Update (`set_transformation`) statt Full-`setScene` für Move/Rotate.
|
||||
|
||||
---
|
||||
|
||||
## cpq
|
||||
|
||||
### [ ] Verbau-Code refactoren (nach den Klärungen)
|
||||
- **Wo:** `DefaultCommandProzessor` / `SceneReplayProcessor` / `EventStore` / `eventDataUtil`.
|
||||
- **Gewachsen über die Klärungsrunden (A-Modell):**
|
||||
- Move-Ende recorded bewegtes Teil + transitive Follower als eine `pushGroup`-Gruppe.
|
||||
- Placement schreibt EIN gemeinsames `attachment_relations` (wegen Clobber im `relationsProcessor`).
|
||||
- Position-Korrektur `pos = event.position − dragPMI-Offset`.
|
||||
- Subject-Ausschluss in DropPoints via `:not`.
|
||||
- **Stand:** Funktioniert (Kette ≥4, Undo/Redo gruppiert), aber aufräumen.
|
||||
Keine offenen Punkte.
|
||||
|
||||
Binary file not shown.
@@ -8,3 +8,4 @@
|
||||
- [cpq Storybook starten](cpq-storybook-start.md) — pnpm --filter @ad-cpq/common-frontend storybook (Port 6006)
|
||||
- [Git nur Fast-Forward](feedback-git-nur-fast-forward.md) — kein rebase/merge/force; bei Divergenz melden statt auflösen
|
||||
- [fire-visual Placement-Mode](fire-visual-placement-mode.md) — snap-to-point: DragPoints/DropPoints, processInput-Algorithmus, start_placement_mode-Command, PMI-DropPoints
|
||||
- [cpq Verbau 90° am Steher](cpq-verbau-90grad-steher.md) — Engine kann's, cpq-GroupProcessor (translation-only) plättet die Rotation beim Replay; Punktdaten tragen die Orientierung schon
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: cpq-verbau-90grad-steher
|
||||
description: "Ziel 90°-Anbau am Steher wie jetzt Links/Rechts — Engine kann es, cpq-GroupProcessor (translation-only) ist der Blocker"
|
||||
metadata:
|
||||
node_type: memory
|
||||
type: project
|
||||
originSessionId: e3d54daa-eb88-474f-9e99-62bfe83821b2
|
||||
---
|
||||
|
||||
Ziel: an den Stehern im 90°-Winkel anbauen können, genau wie der jetzige Links/Rechts-Verbau.
|
||||
|
||||
**Wer ist der Blocker: wir (cpq), nicht fire-visual.**
|
||||
|
||||
- **fire-visual kann es.** Das Live-Snapping im PlacementMode setzt die Subject-Matrix als `bestMatrix · dragPointTransform⁻¹` — die Rotation des Drag-Punkts ist drin, die Engine richtet also den vollen Frame aus und dreht das Teil beim Snap schon korrekt um 90°. (Vorbehalt: aus der Placement-Matrix-Notiz abgeleitet, nicht aus frischem Quell-Read; bei Bedarf `PlacementMode`-Quelle prüfen, ob DropPoints den Rotations-Frame durchreichen.)
|
||||
- **cpq verliert es beim Replay.** Nach dem Snap übernehmen wir das Engine-Ergebnis nicht, sondern merken nur „Punkt A trifft Punkt B" (`group-merge` mit zwei PMI-IDs) und rechnen die Anordnung im `GroupProcessor` selbst neu — rein translatorisch. Beim nächsten `setScene` wird die 90°-Drehung auf achsparallel zurückgeplättet.
|
||||
|
||||
**Warum schwer — das Dock-Modell ist translation-only, einachsig, gemeinsame Orientierung. Links/Rechts geht nur, weil nie rotiert wird.** Zu ändernde Schichten:
|
||||
1. `Edge` = `{from,to,fromPmi,toPmi}` kennt keine Rotation → relative Rotation pro Kante nötig.
|
||||
2. `computePositions` dockt mit `pos(to)=pos(from)+pmi(from)−pmi(to)` (nur Position) → Rotation entlang der Kanten komponieren.
|
||||
3. `pmiLocal` liest nur `position.value`. **Die Orientierung steckt schon in den Daten** (`properties.rotation`), wird aber ignoriert — und Format passt nicht: 3 Euler-Grad (z. B. `CS_EIN_90_0_0` → `[0,-0,180]`), deklariert als „quaternion", während `transformMath` `[w,x,y,z]` erwartet.
|
||||
4. Placement-Mode hartverdrahtet auf `.name-CS_LINKS`↔`.name-CS_RECHTS`; Steher-Anschlüsse leben in der `CS_EIN_*`-Familie → neue Drag-/Drop-Selektoren + Open-End-Logik (`occupiedPmis`).
|
||||
5. `buildDetachEvent`/`areConnected` nehmen einen 1-achsigen Lauf an (dominante Achse, Links→Rechts-Leseordnung) → 90°-Anbau macht den Verbau zum 2D-Baum, Annahmen brechen.
|
||||
|
||||
**Datenlage:** kein plain `CS_VORNE`/`CS_HINTEN` (0 Teile); `CS_LINKS`/`CS_RECHTS` nur auf 12 Teilen; Vorne/Hinten nur in `CS_EIN_*` (rotationscodiert: `CS_EIN_VORNE`, `CS_EIN_HINTEN`, `CS_EIN_STEHER_VORNE/HINTEN`, `CS_EIN_90_0_0` …).
|
||||
|
||||
**Machbar:** Erweiterung unseres Modells (Rotation konsumieren + entlang Kanten propagieren + Format-Fix), keine Nachgenerierung der Teile — die nötige Orientierung liefern die Punktdaten bereits.
|
||||
|
||||
Siehe [[cpq-verbau-architektur]], [[fire-visual-placement-mode]].
|
||||
@@ -28,3 +28,4 @@
|
||||
- [Projekt: Bestätigen-Bug (FSA-Loop)](project_bestaetigen_bug.md) — Edge/Windows beim Kollegen; morgen Policy + Konsolen-reason prüfen
|
||||
- [Projekt: Prod-Ist-Stand](project_prod_ist_stand.md) — Prod (ocp-01) LIVE: backend 0.38.4 Running, Secrets inkl. SAP gemountet; nur PR→main mergen offen; Quota 3CPU knapp
|
||||
- [SAP-Config](sap-config.md) — echter Zugriff via Basic Auth (sy08804394), Key-Predicate-URL über Proxy; Env→SealedSecret/Overlay-Mapping im ops-Repo; Adapter-Patch offen
|
||||
- [Projekt: Optimistische Sperre](project_optimistic_locking.md) — VERSION_CONFLICT (409) in persistCustomer gegen Last-Write-Wins; greift nur bei version!=null (Re-Send bleibt idempotent); lastUpdateTs ist server-autoritativ
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
name: project-optimistic-locking
|
||||
description: "Server-seitige optimistische Sperre für Customer-Writes (VERSION_CONFLICT) — Fix gegen Last-Write-Wins bei zwei Sales am selben Kunden, eingebaut 2026-06-29."
|
||||
metadata:
|
||||
node_type: memory
|
||||
type: project
|
||||
originSessionId: 31351046-6fed-46e2-b117-bcc9d8064774
|
||||
---
|
||||
|
||||
Vor Go-live (Donnerstag 2026-07-02) das Concurrency-Verhalten der Customer-Writes gefixt.
|
||||
|
||||
**Ausgangslage (charakterisiert):** `@VersionColumn` war im Schreibpfad wirkungslos — `persistCustomer` machte `repo.merge(existing, scalars)`, das die `version` aus dem Client-Payload übernahm. Folge: Last-Write-Wins, `version` lief sogar rückwärts (2→1), stiller Datenverlust bei parallelen Editoren. Jeder Sales hat prinzipiell alle Kunden des Landes → der Fall ist real.
|
||||
|
||||
**Fix (rein BE, kein FE-Eingriff):**
|
||||
- `libs/shared/src/lib/errors/error-catalog.ts`: neuer Code `VERSION_CONFLICT` (HTTP 409).
|
||||
- `libs/customer/customer-be/src/lib/customer-core.service.ts`: `enforceVersion()` in `persistCustomer` vor dem Merge — `incoming.version !== existing.version` → `DomainException('VERSION_CONFLICT', …, forcedState: existing)`. Danach zählt `@VersionColumn` wieder hoch.
|
||||
- **Kernregel:** Sperre greift nur, wenn `incoming.version != null`. Ein nie synchronisierter Neuanlage-Stand (version undefined), der nach Crash erneut gesendet wird, ist ein idempotenter Re-Send — sonst wäre at-least-once / Crash-Recovery (S8b) kaputtgegangen. Ein bereits synchronisierter Datensatz trägt clientseitig immer eine version (reconcile schreibt den Server-Stand lokal zurück).
|
||||
|
||||
**Verhalten:** stale Edit → 409 → FE rollt über den vorhandenen `forcedState`-Mechanismus (wie SAP-Readonly) auf den Server-Stand zurück, Fehler sichtbar. Verliererseite verliert ihre Änderung (mit Meldung); ein Merge-Dialog wäre eine spätere fachliche Erweiterung.
|
||||
|
||||
**Wichtig für künftige Fixes:** `version` ist als „wer ist neuer"-Vergleichsbasis untauglich (client-steuerbar). `lastUpdateTs` ist server-autoritativ (`@BeforeUpdate` → `Date.now()`) — das ist die verlässliche Basis. Belegt in den Tests.
|
||||
|
||||
**Tests:** `customer-core.concurrency.spec.ts` (5, BE-direkt gegen In-Memory-SQLite) + `sync-loop.integration.spec.ts` S10 (End-to-End Multi-Sales). Gesamt 8 Projekte grün. Siehe [[project-sync-testing]] für den Test-Harness.
|
||||
|
||||
**Lokaler Testlauf:** node 22 via fnm nötig (`eval "$(/opt/homebrew/bin/fnm env)" && fnm use 22`), better-sqlite3 baut nicht gegen node 26. node_modules waren nicht installiert → `npm ci`. Repo: `~/projects/fahrzeugeinrichtung-plato-backend`.
|
||||
Reference in New Issue
Block a user