# 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