chore(scripts): seed_monat.py für simulierte Monatsnutzung (Testdaten, GUI-Review)
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:
1 parent
a4d4119d55
commit
ed88fa585f
1 file changed
+231
@@ -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())
|
||||
Reference in new issue
Block a user