81 lines
3.1 KiB
Python
81 lines
3.1 KiB
Python
# policy.py
|
||
# Scenario policy for:
|
||
# techno_lover ≥ 650
|
||
# well_connected ≥ 450
|
||
# creative ≥ 300
|
||
# berlin_local ≥ 750
|
||
#
|
||
# Strategy:
|
||
# 1) If all mins are satisfied -> admit everyone.
|
||
# 2) Otherwise, admit ONLY candidates who contribute to at least one unmet attribute.
|
||
# 3) Before admitting, run a per-attribute feasibility check:
|
||
# After this admission, for every attribute 'a', the remaining slots (S-1)
|
||
# must be ≥ the remaining shortfall for 'a' AFTER counting this candidate.
|
||
# This reserves enough capacity to finish all quotas, avoiding the earlier failure
|
||
# where locals/creatives were starved out.
|
||
# This is stricter than a sum-of-needs check and blocks “useless” admits early.
|
||
#
|
||
# Logging hook:
|
||
# Set DEBUG=True to emit per-decision reasoning to stderr.
|
||
# Works well with: `berghain play --verbose` or `berghain step --verbose`.
|
||
|
||
import sys
|
||
|
||
DEBUG = False # flip to True for reasoning prints
|
||
|
||
|
||
def _log(msg: str) -> None:
|
||
if DEBUG:
|
||
print(msg, file=sys.stderr, flush=True)
|
||
|
||
|
||
def decide(attributes: dict[str, bool],
|
||
tallies: dict[str, int],
|
||
mins: dict[str, int],
|
||
admitted_count: int,
|
||
venue_cap: int) -> bool:
|
||
S = venue_cap - admitted_count
|
||
need = {a: max(0, mins[a] - tallies.get(a, 0)) for a in mins}
|
||
total_need = sum(need.values())
|
||
|
||
# 1) If all constraints are satisfied, admit freely.
|
||
if total_need == 0:
|
||
_log("All mins met -> admit")
|
||
return True
|
||
|
||
# Which unmet attributes would this candidate help?
|
||
helps = [a for a in need if need[a] > 0 and attributes.get(a, False)]
|
||
|
||
# 2) If they don't help any unmet attribute, reject (avoid wasting scarce slots).
|
||
if not helps:
|
||
_log("Helps none of the unmet attributes -> reject")
|
||
return False
|
||
|
||
# 3) Feasibility check per attribute (after admitting this candidate).
|
||
# For every attribute 'a', we must have (S-1) >= max(0, need[a] - (1 if candidate has 'a' else 0))
|
||
# This guarantees we never consume a slot that makes any target impossible.
|
||
S_after = S - 1
|
||
for a in need:
|
||
remaining = need[a]
|
||
if attributes.get(a, False):
|
||
remaining = max(0, remaining - 1)
|
||
if S_after < remaining:
|
||
_log(f"Admitting would break feasibility for '{a}': S-1={S_after} < need'={remaining} -> reject")
|
||
return False
|
||
|
||
# Optional bias: since 'creative' is very rare, note when we’re behind and admit them eagerly.
|
||
if "creative" in need and need["creative"] > 0 and attributes.get("creative", False):
|
||
_log(f"Accept (creative still needed: {need['creative']})")
|
||
return True
|
||
|
||
# Optional bias: locals are heavily required; prefer local if still needed.
|
||
if "berlin_local" in need and need["berlin_local"] > 0 and attributes.get("berlin_local", False):
|
||
_log(f"Accept (berlin_local still needed: {need['berlin_local']})")
|
||
return True
|
||
|
||
# If we reached here, the candidate helps (at least one unmet attribute)
|
||
# and passes feasibility for ALL attributes — safe to admit.
|
||
_log(f"Accept (helps {helps}; all feasibility checks passed)")
|
||
return True
|
||
|