Web-GUI: fixierte Gruppen anzeigen, Strafprofil waehlen, Farben nutzen
CSVConfig.HandleGaesteliste liefert zusaetzlich FixierteGruppen (Name -> Tisch/Anzahl), damit fest platzierte Gaeste in der Gaesteliste sichtbar (grau hinterlegt) erscheinen. Durchgaengig "VIP" -> "fixiert" umbenannt. Backend: Strafprofile werden gecacht geladen, neuer Endpoint /strafprofile und Query-Parameter strafen an /berechnen, damit sich Profile (default vs. locker) in der GUI vergleichen lassen. SVG-Ausgabe nutzt das Farbschema aus farben.cfg (Rueckfall auf Palette). Frontend: tische.ini und CSV werden bei Auswahl sofort geladen, Auswahl der fixierten Gruppen und des Optimierungsprofils in der Oberflaeche. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+64
-9
@@ -44,6 +44,7 @@ from pydantic import BaseModel, Field
|
||||
|
||||
from ga import Farm, Zyklus
|
||||
from CSVConfig import CSVConfig
|
||||
from Farben import Farbschema
|
||||
from Strukturdaten import Sitzplatzverteilung, Strafliste, Tisch, Tische
|
||||
|
||||
app = FastAPI(
|
||||
@@ -62,11 +63,43 @@ ZYKLUSDATEI = os.path.join( CFGDIR, 'zyklus.cfg' )
|
||||
STRAFENDATEI = os.path.join( CFGDIR, 'strafen.cfg' )
|
||||
ZYKLUSART = os.environ.get( 'PLATZ_ZYKLUSART', 'Adaptiv' )
|
||||
STRAFENART = os.environ.get( 'PLATZ_STRAFENART', 'default' )
|
||||
FARBENDATEI = os.path.join( CFGDIR, 'farben.cfg' )
|
||||
FARBENART = os.environ.get( 'PLATZ_FARBENART', 'grosse_gruppen' )
|
||||
|
||||
GAZyklus = Zyklus()
|
||||
GAZyklus.laden( ZYKLUSDATEI, ZYKLUSART )
|
||||
Strafen = Strafliste()
|
||||
Strafen.laden( STRAFENDATEI, STRAFENART )
|
||||
|
||||
# Strafprofile werden bei Bedarf geladen und gecacht, damit sich in der GUI
|
||||
# verschiedene Profile (z.B. 'default' vs 'locker') auswaehlen und vergleichen
|
||||
# lassen. Das Default-Profil wird schon beim Start geladen (faellt frueh auf,
|
||||
# falls die Datei/Sektion kaputt ist).
|
||||
StrafProfile = {}
|
||||
|
||||
|
||||
def strafprofil(Name):
|
||||
"""liefert (gecacht) die Strafliste einer Sektion aus strafen.cfg;
|
||||
wirft ValueError bei unbekannter Sektion"""
|
||||
if Name not in StrafProfile:
|
||||
SL = Strafliste()
|
||||
SL.laden( STRAFENDATEI, Name )
|
||||
StrafProfile[Name] = SL
|
||||
return StrafProfile[Name]
|
||||
|
||||
|
||||
def strafprofil_namen():
|
||||
"""alle in strafen.cfg vorhandenen Profilnamen (fuer die GUI-Auswahl)"""
|
||||
cfg = configparser.ConfigParser()
|
||||
cfg.read( STRAFENDATEI )
|
||||
return cfg.sections()
|
||||
|
||||
|
||||
strafprofil( STRAFENART ) # Default vorwaermen / frueh scheitern
|
||||
# Farbprofil fuer die SVG-Ausgabe; faellt bei fehlender Datei/Sektion auf die
|
||||
# eingebaute Palette zurueck (Farbschema=None), damit der Server trotzdem laeuft.
|
||||
try:
|
||||
Farben = Farbschema().laden( FARBENDATEI, FARBENART )
|
||||
except (ValueError, OSError):
|
||||
Farben = None
|
||||
|
||||
# ---------------------------------------------------------------- Sessions
|
||||
SESSION_TTL_SEKUNDEN = 24 * 3600
|
||||
@@ -160,7 +193,7 @@ def _VarianteJSON(SV, GruppenNamen, Nr):
|
||||
G = SV.Gruppen.GruppeVonPid( P.Id )
|
||||
return GruppenNamen.get( G.Id, 'Gruppe %s' % G.Id )
|
||||
except KeyError:
|
||||
return 'VIP'
|
||||
return 'fixiert'
|
||||
|
||||
TischListe = []
|
||||
for T in SV.Tische:
|
||||
@@ -220,6 +253,7 @@ def _ErgebnisJSON(S):
|
||||
GruppenNamen = S.get( 'gruppenNamen', {} )
|
||||
return {
|
||||
'lauf': S['laeufe'],
|
||||
'strafen': S.get( 'strafen', STRAFENART ), # verwendetes Strafprofil
|
||||
'varianten': [ _VarianteJSON( SV, GruppenNamen, Nr + 1 )
|
||||
for Nr, SV in enumerate( S['ergebnisse'] ) ],
|
||||
}
|
||||
@@ -389,7 +423,7 @@ def PersonenHochladen(SitzungsId: str, datei: UploadFile = File(...)):
|
||||
Text = Rohdaten.decode( 'cp1252' ) # Excel/Windows-Fallback
|
||||
|
||||
try:
|
||||
[ PN, GN, VIPGruppe, VipHash, GruppenNamen ] = \
|
||||
[ PN, GN, VIPGruppe, VipHash, GruppenNamen, FixierteGruppen ] = \
|
||||
CSVConfig().HandleGaesteliste( Text )
|
||||
except ValueError as Fehler:
|
||||
raise HTTPException( status_code=400, detail=str(Fehler) )
|
||||
@@ -407,12 +441,15 @@ def PersonenHochladen(SitzungsId: str, datei: UploadFile = File(...)):
|
||||
|
||||
return {
|
||||
'personen': GN.NLeute,
|
||||
'vips': VIPGruppe.Anzahl(),
|
||||
'fixiert': VIPGruppe.Anzahl(),
|
||||
'gruppen': [ {
|
||||
'id': G.Id,
|
||||
'name': GruppenNamen.get( G.Id, 'Gruppe %s' % G.Id ),
|
||||
'anzahl': G.Anzahl(),
|
||||
} for G in GN ],
|
||||
# fest platzierte Gruppen (fuer die grau hinterlegte Anzeige)
|
||||
'fixierte': [ { 'name': Name, 'tisch': Info['tisch'], 'anzahl': Info['anzahl'] }
|
||||
for Name, Info in FixierteGruppen.items() ],
|
||||
}
|
||||
|
||||
|
||||
@@ -420,9 +457,14 @@ def PersonenHochladen(SitzungsId: str, datei: UploadFile = File(...)):
|
||||
def Berechnen(SitzungsId: str,
|
||||
varianten: int = Query( default=3, ge=1, le=5,
|
||||
description='wieviele unterschiedliche Varianten der Lauf '
|
||||
'zurueckgeben soll (aus Farm.TopN, dedupliziert)' )):
|
||||
'zurueckgeben soll (aus Farm.TopN, dedupliziert)' ),
|
||||
strafen: str = Query( default=STRAFENART,
|
||||
description='Name des Strafpunkte-Profils (Sektion in strafen.cfg), '
|
||||
'z.B. default oder locker - zum Vergleich waehlbar' )):
|
||||
"""startet die GA-Optimierung; jeder Aufruf ist ein neuer Lauf und
|
||||
liefert bis zu 'varianten' unterschiedliche gute Sitzordnungen"""
|
||||
liefert bis zu 'varianten' unterschiedliche gute Sitzordnungen. Das
|
||||
Strafpunkte-Profil ('strafen') steuert die Bewertung waehrend der
|
||||
Optimierung - so lassen sich z.B. default und locker vergleichen."""
|
||||
S = _GibSession( SitzungsId )
|
||||
if S['tische'] is None:
|
||||
raise HTTPException( status_code=400,
|
||||
@@ -454,9 +496,14 @@ def Berechnen(SitzungsId: str,
|
||||
detail='Tisch %d hat %d Plaetze, aber %d VIPs sind '
|
||||
'dort fest gesetzt' % (Tid, T.Plaetze, AnzahlVIPs) )
|
||||
|
||||
try:
|
||||
SL = strafprofil( strafen )
|
||||
except ValueError as Fehler:
|
||||
raise HTTPException( status_code=400, detail=str(Fehler) )
|
||||
|
||||
try:
|
||||
GAFarm = Farm( GAZyklus, Sitzplatzverteilung,
|
||||
Tische=TE, Gruppen=P['GN'], Strafen=Strafen,
|
||||
Tische=TE, Gruppen=P['GN'], Strafen=SL,
|
||||
VIPListe=P['VipHash'], VIPs=P['VIPGruppe'] )
|
||||
except IndexError as Fehler:
|
||||
raise HTTPException( status_code=400, detail=str(Fehler) )
|
||||
@@ -481,9 +528,17 @@ def Berechnen(SitzungsId: str,
|
||||
|
||||
S['ergebnisse'] = Ergebnisse
|
||||
S['laeufe'] = S['laeufe'] + 1
|
||||
S['strafen'] = strafen # fuer die Anzeige, welches Profil den Lauf ergab
|
||||
return _ErgebnisJSON( S )
|
||||
|
||||
|
||||
@app.get( '/strafprofile' )
|
||||
def Strafprofile():
|
||||
"""verfuegbare Strafpunkte-Profile (Sektionen in strafen.cfg) fuer die
|
||||
Auswahl in der GUI; 'vorgabe' ist das voreingestellte Profil."""
|
||||
return { 'profile': strafprofil_namen(), 'vorgabe': STRAFENART }
|
||||
|
||||
|
||||
@app.get( '/sitzung/{SitzungsId}/ergebnis' )
|
||||
def Ergebnis(SitzungsId: str):
|
||||
"""alle Varianten des letzten Laufs als JSON (beste zuerst)"""
|
||||
@@ -513,7 +568,7 @@ def ErgebnisSVG(SitzungsId: str,
|
||||
os.close( Handle )
|
||||
try:
|
||||
S['ergebnisse'][variante - 1].alsSVG( TempPfad,
|
||||
GruppenNamen=S.get( 'gruppenNamen', {} ) )
|
||||
GruppenNamen=S.get( 'gruppenNamen', {} ), Farbschema=Farben )
|
||||
fsock = open( TempPfad, 'r' )
|
||||
SVG = fsock.read()
|
||||
fsock.close()
|
||||
|
||||
Reference in New Issue
Block a user