Schulportal-Konventionen übernommen: Dark Mode (ThemeToggle), beispieldaten.py + generated.json, kimi.md, mail-server (Postfix-Relay + API), Deploy-Workflow (pscp/plink, npm_default), Screenshots in preview/

This commit is contained in:
2026-10-02 10:46:10 +02:00
parent ca97ea028f
commit b3dfb8ab7c
27 changed files with 7284 additions and 163 deletions
+125
View File
@@ -0,0 +1,125 @@
#!/usr/bin/env python3
"""Hocks Mieterportal – Beispieldaten-Generator.
Erzeugt deterministisch die Demo-Daten des Portals und schreibt sie nach
``src/data/generated.json`` (Units + Transaktionen). Die handschriftlichen
Inhalte (Tickets, Dokumente, Nachrichten, Ankündigungen) liegen weiterhin in
``src/data/seed.ts``.
Aufruf: python beispieldaten.py (Standard-Seed 20261002)
python beispieldaten.py 42 (eigener Seed zum Experimentieren)
Achtung: Der Demo-Mieter „Anna Berger" (Whg. Hauptstraße 12 Nr. 7) und die
Algorithmen sind synchron zu src/data/seed.ts zu halten.
"""
import json
import sys
from pathlib import Path
SEED = int(sys.argv[1]) if len(sys.argv) > 1 else 20261002
BUILDINGS = [
{"name": "Hauptstraße 12", "prefix": "HS", "count": 20, "baseRent": 9.8},
{"name": "Ringweg 3", "prefix": "RW", "count": 15, "baseRent": 8.9},
{"name": "Am Park 5", "prefix": "AP", "count": 15, "baseRent": 10.4},
]
FIRST_NAMES = [
"Anna", "Jonas", "Marta", "Leon", "Sofia", "Felix", "Lena", "Paul",
"Emma", "Noah", "Clara", "Ben", "Nora", "Max", "Julia", "Tim",
"Sarah", "Finn", "Laura", "Jan", "Mia", "Tom", "Ela", "Kim",
]
LAST_NAMES = [
"Berger", "Klein", "Wolf", "Neumann", "Schäfer", "Krüger", "Brandt",
"Vogel", "Otto", "Horn", "Busch", "Thomas", "Pohl", "Sander", "Lindner",
]
DEMO_TENANT = "Anna Berger"
DEMO_UNIT_ID = "hs-7"
MONTHS = [
"Januar", "Februar", "März", "April", "Mai", "Juni",
"Juli", "August", "September", "Oktober", "November", "Dezember",
]
def gen_units() -> list[dict]:
units: list[dict] = []
name_idx = 0
def next_name() -> str:
nonlocal name_idx
n = f"{FIRST_NAMES[name_idx % len(FIRST_NAMES)]} {LAST_NAMES[name_idx % len(LAST_NAMES)]}"
name_idx += 1
return n
for b in BUILDINGS:
for i in range(b["count"]):
num = i + 1
size = 38 + ((num * 7 + len(b["prefix"]) * 3) % 78) # 38–115 m²
cold = round(size * b["baseRent"])
warm = cold + round(2.4 * size) + (35 if num % 3 == 0 else 0)
floor = 0 if num <= 2 else min(4, (num - 1) // 4)
occupied = num % 9 != 0 # ~89 % Vermietung
if b["prefix"] == "HS" and num == 7:
tenant: str | None = DEMO_TENANT
elif occupied:
tenant = next_name()
else:
tenant = None
units.append({
"id": f"{b['prefix'].lower()}-{num}",
"building": b["name"],
"number": str(num),
"floor": floor,
"size": size,
"coldRent": cold,
"warmRent": warm,
"tenant": tenant,
"status": "vermietet" if occupied else "frei",
})
return units
def gen_transactions(units: list[dict]) -> list[dict]:
txs: list[dict] = []
for u in units:
if u["status"] != "vermietet":
continue
for m in range(12):
due = m + 1
if due <= 9:
status = "überfällig" if (ord(u["id"][3]) + m) % 11 == 0 else "bezahlt"
else:
status = "offen"
txs.append({
"id": f"tx-{u['id']}-{m}",
"unitId": u["id"],
"tenant": u["tenant"],
"month": MONTHS[m],
"year": 2026,
"amount": u["warmRent"],
"type": "Miete",
"status": status,
**({"paidAt": f"0{(m % 8) + 1}.0{due}.2026"} if status == "bezahlt" else {}),
})
return txs
def main() -> None:
units = gen_units()
data = {
"meta": {"seed": SEED, "units": len(units),
"rented": sum(1 for u in units if u["status"] == "vermietet")},
"units": units,
"transactions": gen_transactions(units),
}
out = Path(__file__).parent / "src" / "data" / "generated.json"
out.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
print(f"{out} geschrieben: {data['meta']['units']} Wohnungen "
f"({data['meta']['rented']} vermietet), {len(data['transactions'])} Buchungen")
if __name__ == "__main__":
main()