diff --git a/DEVLOG.md b/DEVLOG.md index 3e9aeba..cc6e541 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -3225,3 +3225,147 @@ Keine Commits in dieser Session. - backend/tests/test_objekte.py | 131 +++++++++++++++++++++++++++ --- +## 2026-09-04 20:54 – 20:55 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** backend + +### Commits +- 6934276 Karte 14 Schritt 2 (Admin-Teil) + Frontend für Anhänger-Kopplung + +### Geänderte Dateien +- DEVLOG.md | 34 ++++ +- backend/DEVLOG.md | 17 ++ +- backend/app/api/v1/api.py | 2 + +- backend/app/api/v1/endpoints/geraet_instanz.py | 91 +++++++++++ +- backend/app/api/v1/endpoints/objekte.py | 4 + +- backend/app/schemas/geraet_instanz.py | 31 ++++ +- backend/app/schemas/objekt.py | 2 + +- backend/app/services/geraet_instanz.py | 130 +++++++++++++++ +- backend/tests/test_geraet_instanz.py | 90 +++++++++++ +- frontend/e2e/mockApi.ts | 3 + +- frontend/src/api/types.ts | 13 ++ +- frontend/src/pages/admin/GeraeteInstanzenListe.tsx | 177 +++++++++++++++++++++ +- frontend/src/pages/admin/ObjektPositionenPanel.tsx | 27 +++- +- frontend/src/pages/admin/ObjektSection.tsx | 71 +++++++++ + +--- +## 2026-09-04 20:55 – 20:55 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 34 ++++ +- backend/DEVLOG.md | 17 ++ +- backend/app/api/v1/api.py | 2 + +- backend/app/api/v1/endpoints/geraet_instanz.py | 91 +++++++++++ +- backend/app/api/v1/endpoints/objekte.py | 4 + +- backend/app/schemas/geraet_instanz.py | 31 ++++ +- backend/app/schemas/objekt.py | 2 + +- backend/app/services/geraet_instanz.py | 130 +++++++++++++++ +- backend/tests/test_geraet_instanz.py | 90 +++++++++++ +- frontend/e2e/mockApi.ts | 3 + +- frontend/src/api/types.ts | 13 ++ +- frontend/src/pages/admin/GeraeteInstanzenListe.tsx | 177 +++++++++++++++++++++ +- frontend/src/pages/admin/ObjektPositionenPanel.tsx | 27 +++- +- frontend/src/pages/admin/ObjektSection.tsx | 71 +++++++++ + +--- +## 2026-09-04 20:59 – 20:59 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 34 +++++++++++++ +- backend/DEVLOG.md | 17 +++++++ +- backend/app/api/v1/api.py | 2 + +- backend/app/api/v1/endpoints/geraet_instanz.py | 91 ++++++++++++++++++++++++++++++++++ +- backend/app/api/v1/endpoints/objekte.py | 4 ++ +- backend/app/schemas/geraet_instanz.py | 31 ++++++++++++ +- backend/app/schemas/objekt.py | 2 + +- backend/app/services/geraet_instanz.py | 130 +++++++++++++++++++++++++++++++++++++++++++++++++ +- backend/tests/test_geraet_instanz.py | 90 ++++++++++++++++++++++++++++++++++ +- frontend/e2e/mockApi.ts | 3 ++ +- frontend/src/api/types.ts | 13 +++++ +- frontend/src/pages/admin/GeraeteInstanzenListe.tsx | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- frontend/src/pages/admin/ObjektPositionenPanel.tsx | 27 ++++++++--- +- frontend/src/pages/admin/ObjektSection.tsx | 71 +++++++++++++++++++++++++++ + +--- +## 2026-09-04 21:00 – 21:00 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 34 +++++++++++++ +- backend/DEVLOG.md | 17 +++++++ +- backend/app/api/v1/api.py | 2 + +- backend/app/api/v1/endpoints/geraet_instanz.py | 91 ++++++++++++++++++++++++++++++++++ +- backend/app/api/v1/endpoints/objekte.py | 4 ++ +- backend/app/schemas/geraet_instanz.py | 31 ++++++++++++ +- backend/app/schemas/objekt.py | 2 + +- backend/app/services/geraet_instanz.py | 130 +++++++++++++++++++++++++++++++++++++++++++++++++ +- backend/tests/test_geraet_instanz.py | 90 ++++++++++++++++++++++++++++++++++ +- frontend/e2e/mockApi.ts | 3 ++ +- frontend/src/api/types.ts | 13 +++++ +- frontend/src/pages/admin/GeraeteInstanzenListe.tsx | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- frontend/src/pages/admin/ObjektPositionenPanel.tsx | 27 ++++++++--- +- frontend/src/pages/admin/ObjektSection.tsx | 71 +++++++++++++++++++++++++++ + +--- +## 2026-09-04 21:00 – 21:09 (8m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 34 +++++++++++++ +- backend/DEVLOG.md | 17 +++++++ +- backend/app/api/v1/api.py | 2 + +- backend/app/api/v1/endpoints/geraet_instanz.py | 91 ++++++++++++++++++++++++++++++++++ +- backend/app/api/v1/endpoints/objekte.py | 4 ++ +- backend/app/schemas/geraet_instanz.py | 31 ++++++++++++ +- backend/app/schemas/objekt.py | 2 + +- backend/app/services/geraet_instanz.py | 130 +++++++++++++++++++++++++++++++++++++++++++++++++ +- backend/tests/test_geraet_instanz.py | 90 ++++++++++++++++++++++++++++++++++ +- frontend/e2e/mockApi.ts | 3 ++ +- frontend/src/api/types.ts | 13 +++++ +- frontend/src/pages/admin/GeraeteInstanzenListe.tsx | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- frontend/src/pages/admin/ObjektPositionenPanel.tsx | 27 ++++++++--- +- frontend/src/pages/admin/ObjektSection.tsx | 71 +++++++++++++++++++++++++++ + +--- +## 2026-09-04 21:10 – 21:22 (12m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 34 +++++++++++++ +- backend/DEVLOG.md | 17 +++++++ +- backend/app/api/v1/api.py | 2 + +- backend/app/api/v1/endpoints/geraet_instanz.py | 91 ++++++++++++++++++++++++++++++++++ +- backend/app/api/v1/endpoints/objekte.py | 4 ++ +- backend/app/schemas/geraet_instanz.py | 31 ++++++++++++ +- backend/app/schemas/objekt.py | 2 + +- backend/app/services/geraet_instanz.py | 130 +++++++++++++++++++++++++++++++++++++++++++++++++ +- backend/tests/test_geraet_instanz.py | 90 ++++++++++++++++++++++++++++++++++ +- frontend/e2e/mockApi.ts | 3 ++ +- frontend/src/api/types.ts | 13 +++++ +- frontend/src/pages/admin/GeraeteInstanzenListe.tsx | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- frontend/src/pages/admin/ObjektPositionenPanel.tsx | 27 ++++++++--- +- frontend/src/pages/admin/ObjektSection.tsx | 71 +++++++++++++++++++++++++++ + +--- diff --git a/backend/scripts/material_bereinigung_2026-09-04.py b/backend/scripts/material_bereinigung_2026-09-04.py new file mode 100644 index 0000000..ea68af3 --- /dev/null +++ b/backend/scripts/material_bereinigung_2026-09-04.py @@ -0,0 +1,110 @@ +"""Einmaliger Datenbereinigungslauf, 2026-09-04: Dubletten im Materialstamm +(Excel-Import-Tippfehler/Leerzeichen-Varianten) zusammengelegt + Datentyp-Fehler +korrigiert. Dokumentation: ergebnisse/material_bereinigung_log.md. + +Deployment-Regel (Prompt 19): dieses Skript wird hier nur als Code/Audit-Trail +abgelegt, wurde bereits interaktiv gegen die Live-DB auf 192.168.1.238 +ausgeführt (nicht erneut laufen lassen - Materialien sind bereits gemerged, +ein zweiter Lauf würde ins Leere laufen bzw. bei neu angelegten Materialien +mit identischen IDs falsche Treffer liefern). + +Aufruf, falls je auf einer anderen/älteren DB nachvollzogen werden muss (im +aktivierten venv, DATABASE_URL passend zur damaligen ID-Vergabe): + + python -m scripts.material_bereinigung_2026-09-04 +""" + +from __future__ import annotations + +import asyncio + +from sqlalchemy import text + +from app.db.session import engine + +# (keep_id, drop_id) - drop wird auf keep umgehängt und danach gelöscht. +DUBLETTEN_PAARE = [ + (36, 100), # "E 153" / "E153" + (47, 95), # "Rettungsdecke" / "Rettungsdecke " (Leerzeichen) + (79, 55), # "Verbandspäckchen klein" (Leerzeichen) + (132, 70), # "Thomasholder Kind" / "Thomas Holder Kind" + (144, 71), # "Samsplint" (Leerzeichen) + (151, 76), # "Rollenpflaster" (Leerzeichen) - NICHT zu verwechseln mit der + # Größenvariante 151 selbst, die bewusst NICHT weiter + # aufgeteilt wurde (siehe Log). + (146, 4), # "Stethoskop" / "Stetoskop" (Tippfehler) + (112, 5), # "Pupillenleuchte" / "Puppillenleuchte" (Tippfehler) + (20, 54), # "Aluderm Strips..." / "...Stripes..." (Tippfehler) + (35, 159), # "Farbkodierte..." / "Farbcodierte..." Blockerspritze + (37, 101), # "Infusionssystem" / "Infussionssystem" (Tippfehler) + (150, 50), # "Verbandspäckchen Groß" / "...gross" + (58, 98), # "Wundschnellverband..." / "Wundschnelölverband..." (Tippfehler) + (168, 90), # "Handschuhe" / "Handschuh lose" + (170, 9), # "Schlingentupfer" / "Schlinggazetupfer" + (62, 81), # "Verbandtuch 60x80" / "Verbandstuch 160x80" (Tippfehler) + (48, 149), # "Elastische Binde" / "Elastische Binden" +] + +# Tabellen mit FK auf material.id (Stand 2026-09-04, siehe alembic-Migrationen). +_EINFACHE_TABELLEN = ["kontrollposition", "fehlbestand", "nachfuellung"] +_UNIQUE_TABELLEN = [ + ("vorlagenposition", "vorlage_id"), + ("objektposition", "objekt_id"), +] + + +async def _merge(conn, keep: int, drop: int) -> None: + for tabelle, gruppen_spalte in _UNIQUE_TABELLEN: + # UNIQUE(gruppen_spalte, material_id): existiert die Zielkombination + # schon, ist die Dublettenzeile ein echter Konflikt -> löschen statt + # umzuhängen (sonst UniqueViolation). + await conn.execute( + text( + f"DELETE FROM {tabelle} t_drop WHERE t_drop.material_id = :drop " + f"AND EXISTS (SELECT 1 FROM {tabelle} t_keep " + f"WHERE t_keep.{gruppen_spalte} = t_drop.{gruppen_spalte} AND t_keep.material_id = :keep)" + ), + {"drop": drop, "keep": keep}, + ) + await conn.execute( + text(f"UPDATE {tabelle} SET material_id = :keep WHERE material_id = :drop"), + {"keep": keep, "drop": drop}, + ) + + for tabelle in _EINFACHE_TABELLEN: + await conn.execute( + text(f"UPDATE {tabelle} SET material_id = :keep WHERE material_id = :drop"), + {"keep": keep, "drop": drop}, + ) + + await conn.execute(text("DELETE FROM material WHERE id = :drop"), {"drop": drop}) + + +async def main() -> None: + async with engine.begin() as conn: + for keep, drop in DUBLETTEN_PAARE: + await _merge(conn, keep, drop) + print(f"gemerged: {drop} -> {keep}") + + # Datentyp-Fehler: SN stand im Namen statt in einem Geräte-Feld. + await conn.execute( + text("UPDATE material SET name = 'Manometer', materialtyp = 'geraet_sn' WHERE id = 148") + ) + r = await conn.execute(text("SELECT id FROM objektposition WHERE material_id = 148")) + for (pos_id,) in r.all(): + await conn.execute( + text( + "INSERT INTO geraet_instanz (objektposition_id, seriennummer) " + "VALUES (:pos_id, '1308409') " + "ON CONFLICT (objektposition_id, seriennummer) DO NOTHING" + ), + {"pos_id": pos_id}, + ) + print("Material 148: 'Manometer SN 1308409' -> 'Manometer' (geraet_sn) + geraet_instanz SN 1308409") + + result = await conn.execute(text("SELECT count(*) FROM material")) + print(f"\nMaterialien danach: {result.scalar_one()}") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/ergebnisse/material_bereinigung_log.md b/ergebnisse/material_bereinigung_log.md new file mode 100644 index 0000000..0c8cafc --- /dev/null +++ b/ergebnisse/material_bereinigung_log.md @@ -0,0 +1,66 @@ +# Materialstamm-Bereinigung – Log + +Laufender Log für Bereinigungsläufe gegen den Materialstamm (Dubletten aus dem +Excel-Import, Datentyp-Fehler). Skripte liegen unter +`backend/scripts/material_bereinigung_.py`, jeweils gegen die Live-DB +auf 192.168.1.238 ausgeführt (nicht Teil der Alembic-Migrationskette, da reine +Datenbereinigung, kein Schema-Wechsel). + +## 2026-09-04 + +Ausgangslage: 172 Materialien, per Fuzzy-Matching (Normalisierung + `difflib`) +auf Dubletten/Tippfehler geprüft. Skript: `material_bereinigung_2026-09-04.py`. + +### Zusammengelegt (17 Paare, 172 → 155 Materialien) + +| Behalten | Entfernt | Grund | +|---|---|---| +| E 153 | E153 | Leerzeichen-Variante | +| Rettungsdecke | Rettungsdecke (Leerzeichen) | Leerzeichen-Variante | +| Verbandspäckchen klein | Verbandspäckchen klein (Leerzeichen) | Leerzeichen-Variante | +| Thomasholder Kind | Thomas Holder Kind | Schreibweise | +| Samsplint | Samsplint (Leerzeichen) | Leerzeichen-Variante | +| Rollenpflaster | Rollenpflaster (Leerzeichen) | Leerzeichen-Variante | +| Stethoskop | Stetoskop | Tippfehler | +| Pupillenleuchte | Puppillenleuchte | Tippfehler | +| Aluderm Strips 1,9x7,2 | Aluderm Stripes 1,9x7,2 | Tippfehler | +| Farbkodierte Blockerspritze | Farbcodierte Blockerspritze | Schreibvariante | +| Infusionssystem | Infussionssystem | Tippfehler | +| Verbandspäckchen Groß | Verbandspäckchen gross | Schreibvariante | +| Wundschnellverband 10x6 a 8 | Wundschnelölverband 10x6 | Tippfehler | +| Handschuhe | Handschuh lose | Nutzer-Entscheidung: gleiches Material | +| Schlingentupfer | Schlinggazetupfer | Nutzer-Entscheidung: gleiches Material | +| Verbandtuch 60x80 | Verbandstuch 160x80 | Nutzer-Entscheidung: Tippfehler ("1" zu viel) | +| Elastische Binde | Elastische Binden | Nutzer-Entscheidung: reiner Plural-Unterschied | + +Ein echter Konflikt trat bei "Aluderm Strips/Stripes" auf: beide Varianten +waren bereits unabhängig in denselben Vorlagen (1, 4) bzw. demselben Objekt +(1) verwendet – die Dublettenzeile wurde dort gelöscht statt umgehängt (siehe +`_merge()`-Logik im Skript, UNIQUE-Constraint-Konfliktbehandlung). + +### Datentyp-Fehler korrigiert + +- **Material 148**: Name war `"Manometer SN 1308409"` (Seriennummer im + Namensfeld statt in einem Geräte-Feld), `materialtyp='ablauf_charge'`. + Korrigiert zu Name `"Manometer"`, `materialtyp='geraet_sn'`, SN `1308409` + als erste `geraet_instanz`-Zeile an der betroffenen Objektposition angelegt + (Karte 14). + +### Bewusst NICHT verändert (Nutzer-Entscheidung) + +- **151 "Rollenpflaster"** (ohne Größenangabe, neben 53 "gross", 106 + "schmal", 107 "breit"): wird in 2 Vorlagen/Objekten (Rucksack PAX SEG, + Rettungsrucksack) genutzt, keine Größe aus dem Kontext ableitbar – + bleibt eigenständig, bis geklärt ist, welcher Größe die bestehenden + Verwendungen entsprechen. +- **102 "Beatmunsmaske"** (Tippfehler, neben 161/162/163 "Beatmungsmasken + S/M/L"): wird in Vorlage/Objekt/Kontroll-Historie genutzt (Rettungsrucksack + Standard), aber unklar welcher Größe die bestehende Verwendung entspricht – + Nutzer-Feedback: "Schreibfehler, müssen wir uns nochmal anschauen". Noch + offen, bei nächster Gelegenheit klären und nachziehen. + +### Bewusst als eigenständig bestätigt (kein Fund, nur Ähnlichkeits-Rauschen) + +Güdeltubus/Laryntubus Gr. 2–5, Flexüle-Farbvarianten, Spritzen-Größen (2er/5er/ +10er/20er), Endotubus-Größen (3,0–8,0), "Größe 1/3/4/5" – alles legitime, +unterschiedliche Produktvarianten, keine Dubletten.