2025-09-03 03:34:30 -07:00

81 lines
3.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 were 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