chore(scripts): seed_monat.py für simulierte Monatsnutzung (Testdaten, GUI-Review)
CI / backend-tests (push) Successful in 4m9s
CI / frontend-build (push) Successful in 27s

Legt synthetische Benutzer/Standorte/Objekte an und simuliert 30 Tage
Kontrollen/Mängel/Wartung/Verleih/Lagerbewegungen. Nur für die separate
Test-Instanz gedacht (eigene DB, kein Produktionsbezug).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VDMvneg6wKfXdXvTYNd3dN
This commit is contained in:
patrickandClaude Sonnet 5 committed 2026-10-13 12:11:43 +02:00
1 parent a4d4119d55
commit ed88fa585f
1 file changed
+231
+231
View File
@@ -0,0 +1,231 @@
"""Simuliert einen Monat voller Nutzung in einer leeren MABEA-Datenbank -
reine Testdaten (synthetisch, keine echten Personendaten), für GUI-Review
gegen realistische Datenmengen. Läuft NUR gegen die Test-Instanz, nie gegen
Produktion - DATABASE_URL kommt aus der .env des Prozesses, in dem das
Script ausgeführt wird.
Aufruf (auf dem Server, im venv der Test-Instanz):
cd /opt/mabea-test/backend && .venv/bin/python -m scripts.seed_monat
"""
import asyncio
import random
import uuid
from datetime import date, datetime, timedelta, timezone
from decimal import Decimal
import app.models # noqa: F401 (alle ORM-Modelle registrieren, sonst NoReferencedTableError)
from app.core.security import hash_password
from app.db.session import SessionLocal
from app.models.auth import Benutzer, BenutzerRolle, RolleTyp, Systemknoten, KnotenTyp
from app.models.lager import Bestand, Lagerort, LagerortTyp, Lagerplatz, Materialbewegung
from app.models.mangel import Mangel, MangelPrioritaet, MangelStatus
from app.models.objekt import Objekt, ObjektStatus
from app.models.kontrolle import Kontrolle, Kontrollposition, KontrollStatus
from app.models.stammdaten import Bereich, Kategorie, Material, MaterialTyp, Objekttyp, Standort, ZaehlerTyp
from app.models.verleih import ExterneStelle, ExterneStelleKategorie, Verleih
from app.models.wartung import Wartungsauftrag, WartungsauftragStatus
random.seed(42)
START = datetime.now(timezone.utc) - timedelta(days=30)
def tag(offset: int, stunde: int = 9) -> datetime:
return START + timedelta(days=offset, hours=stunde, minutes=random.randint(0, 59))
async def main() -> None:
async with SessionLocal() as db:
# --- Systemknoten (für zustaendiger_server_id) ------------------------
knoten = (await db.execute(Systemknoten.__table__.select())).first()
if knoten is None:
knoten_obj = Systemknoten(name="Hauptserver", typ=KnotenTyp.haupt)
db.add(knoten_obj)
await db.flush()
knoten_id = knoten_obj.id
else:
knoten_id = knoten[0]
# --- Standorte ----------------------------------------------------
standorte = [Standort(name=n) for n in ["Wache Nord", "Wache Süd", "Lager Zentral"]]
db.add_all(standorte)
await db.flush()
# --- Bereich/Kategorie/Objekttypen ---------------------------------
bereich_rd = Bereich(name="Rettungsdienst")
bereich_fzg = Bereich(name="Fahrzeuge")
db.add_all([bereich_rd, bereich_fzg])
await db.flush()
typ_rucksack = Objekttyp(
bereich_id=bereich_rd.id, name="Notfallrucksack",
zaehler_typ=ZaehlerTyp.keiner, wartung_aktiv=False,
)
typ_defi = Objekttyp(
bereich_id=bereich_rd.id, name="Defibrillator",
zaehler_typ=ZaehlerTyp.keiner, wartung_aktiv=True,
)
typ_rtw = Objekttyp(
bereich_id=bereich_fzg.id, name="RTW", ist_zugfahrzeug=True,
zaehler_typ=ZaehlerTyp.kilometer, wartung_aktiv=True,
ressourcenbuchung_aktiv=True,
)
db.add_all([typ_rucksack, typ_defi, typ_rtw])
await db.flush()
# --- Material -------------------------------------------------------
material_liste = [
Material(name="Verbandpäckchen", einheit="Stück", materialtyp=MaterialTyp.standard),
Material(name="Infusionslösung 500ml", einheit="Stück", materialtyp=MaterialTyp.ablauf_charge),
Material(name="Guedel-Tubus Set", einheit="Set", materialtyp=MaterialTyp.standard),
]
db.add_all(material_liste)
await db.flush()
# --- Benutzer ---------------------------------------------------------
benutzer_specs = [
("mmueller", "Max Müller", RolleTyp.mitarbeiter),
("sschmidt", "Sandra Schmidt", RolleTyp.mitarbeiter),
("tweber", "Tobias Weber", RolleTyp.materialverantwortlicher),
("lkoenig", "Laura König", RolleTyp.leitungsverantwortlicher),
("admin1", "Admin", RolleTyp.administration),
]
benutzer = []
for login, name, rolle in benutzer_specs:
b = Benutzer(name=name, login=login, passwort_hash=hash_password("Test1234!"), aktiv=True)
db.add(b)
await db.flush()
db.add(BenutzerRolle(benutzer_id=b.id, rolle=rolle))
benutzer.append(b)
await db.flush()
mitarbeiter = [b for b, (_, _, r) in zip(benutzer, benutzer_specs) if r == RolleTyp.mitarbeiter]
verantwortlicher = benutzer[2]
# --- Objekte ----------------------------------------------------------
objekte = []
for i in range(1, 9):
objekte.append(Objekt(
code=f"RS-{i:03d}", name=f"Rucksack {i}", objekttyp_id=typ_rucksack.id,
standort_id=random.choice(standorte).id, zustaendiger_server_id=knoten_id,
))
for i in range(1, 4):
objekte.append(Objekt(
code=f"DEFI-{i:03d}", name=f"Defibrillator {i}", objekttyp_id=typ_defi.id,
standort_id=random.choice(standorte).id, zustaendiger_server_id=knoten_id,
))
for i in range(1, 3):
objekte.append(Objekt(
code=f"RTW-{i:02d}", name=f"RTW {i}", objekttyp_id=typ_rtw.id,
standort_id=random.choice(standorte).id, zustaendiger_server_id=knoten_id,
))
db.add_all(objekte)
await db.flush()
# --- Externe Stelle (Werkstatt für Wartung/Verleih) --------------------
werkstatt = ExterneStelle(name="KFZ-Werkstatt Meyer", kategorie=ExterneStelleKategorie.werkstatt)
db.add(werkstatt)
await db.flush()
# --- Lager/Bestand ------------------------------------------------------
lager_standort = Lagerort(name="Lager Zentral", typ=LagerortTyp.standort, standort_id=standorte[2].id)
db.add(lager_standort)
await db.flush()
lager_raum = Lagerort(name="Regal A", typ=LagerortTyp.raum, parent_id=lager_standort.id)
db.add(lager_raum)
await db.flush()
lagerplatz = Lagerplatz(lagerort_id=lager_raum.id, code="A-01")
db.add(lagerplatz)
await db.flush()
for m in material_liste:
db.add(Bestand(lagerplatz_id=lagerplatz.id, material_id=m.id, menge=Decimal(100)))
await db.flush()
# --- 30 Tage simulierte Nutzung ----------------------------------------
for tag_offset in range(30):
zeitpunkt = tag(tag_offset)
# Kontrollen: ca. jedes 3. Objekt an einem zufälligen Tag geprüft.
if tag_offset % 3 == 0:
for objekt in random.sample(objekte, k=min(3, len(objekte))):
pruefer = random.choice(mitarbeiter)
k = Kontrolle(
erzeugt_von_server_id=knoten_id, objekt_id=objekt.id, benutzer_id=pruefer.id,
status=KontrollStatus.abgeschlossen, gestartet_am=zeitpunkt,
beendet_am=zeitpunkt + timedelta(minutes=15),
)
db.add(k)
await db.flush()
for m in material_liste:
soll = Decimal(10)
ist = soll if random.random() > 0.15 else soll - Decimal(random.randint(1, 3))
db.add(Kontrollposition(
kontrolle_id=k.id, material_id=m.id,
sollmenge_snapshot=soll, istmenge_erfasst=ist, abweichung=ist != soll,
))
# Mängel: alle ~5 Tage einer, zufällig offen oder erledigt.
if tag_offset % 5 == 0:
objekt = random.choice(objekte)
melder = random.choice(mitarbeiter)
status = random.choice([MangelStatus.neu, MangelStatus.in_bearbeitung, MangelStatus.erledigt])
m = Mangel(
objekt_id=objekt.id, beschreibung=f"Sichtprüfung auffällig an {objekt.name}",
prioritaet=random.choice(list(MangelPrioritaet)), status=status,
gemeldet_von=melder.id, gemeldet_am=zeitpunkt,
erledigt_von=verantwortlicher.id if status == MangelStatus.erledigt else None,
erledigt_am=zeitpunkt + timedelta(days=2) if status == MangelStatus.erledigt else None,
)
db.add(m)
# Wartungsaufträge für Geräte mit wartung_aktiv: alle ~8 Tage.
if tag_offset % 8 == 0:
kandidaten = [o for o in objekte if o.objekttyp_id in (typ_defi.id, typ_rtw.id)]
objekt = random.choice(kandidaten)
erledigt = tag_offset < 20
w = Wartungsauftrag(
objekt_id=objekt.id, wartungsart="Jährliche Prüfung",
status=WartungsauftragStatus.erledigt if erledigt else WartungsauftragStatus.offen,
werkstatt_intern=False, dienstleister_id=werkstatt.id,
erstellt_am=zeitpunkt,
erledigt_am=zeitpunkt + timedelta(days=3) if erledigt else None,
durchgefuehrt_von=verantwortlicher.id if erledigt else None,
kosten=Decimal("145.50") if erledigt else None,
)
db.add(w)
# Verleih: alle ~10 Tage, manche noch offen.
if tag_offset % 10 == 0:
objekt = random.choice(objekte)
rueckgabe_tag = tag_offset + 7
db.add(Verleih(
objekt_id=objekt.id, externe_stelle_id=werkstatt.id,
verliehen_am=zeitpunkt.date(), verliehen_von_benutzer_id=verantwortlicher.id,
erstellt_am=zeitpunkt,
voraussichtliche_rueckgabe_am=zeitpunkt.date() + timedelta(days=7),
tatsaechliche_rueckgabe_am=(
zeitpunkt.date() + timedelta(days=6) if rueckgabe_tag < 28 else None
),
))
# Lagerbewegungen: fast täglich, Entnahme + gelegentlich Zugang.
m = random.choice(material_liste)
menge = Decimal(random.randint(1, 5))
db.add(Materialbewegung(
material_id=m.id, menge=menge, von_lagerplatz_id=lagerplatz.id,
benutzer_id=random.choice(mitarbeiter).id, zeitpunkt=zeitpunkt,
grund="Entnahme für Kontrolle",
))
if tag_offset % 6 == 0:
db.add(Materialbewegung(
material_id=m.id, menge=Decimal(50), nach_lagerplatz_id=lagerplatz.id,
benutzer_id=verantwortlicher.id, zeitpunkt=zeitpunkt, grund="Nachbestellung",
))
await db.commit()
print(f"Seed fertig: {len(objekte)} Objekte, {len(benutzer)} Benutzer, 30 Tage simulierte Nutzung.")
print("Login z.B.: mmueller / Test1234! oder admin1 / Test1234!")
if __name__ == "__main__":
asyncio.run(main())