berghain-challenge/berghain/default_policy.py
2025-09-03 01:51:13 -07:00

79 lines
2.5 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.

# default_policy.py
# Built-in default policy for Berghain Challenge.
#
# Strategy:
# - If all minimums are satisfied, admit freely.
# - Admit creatives until quota met (they are rare).
# - Admit Berlin locals until quota met (they are required in bulk).
# - Otherwise admit if candidate helps at least one unmet need,
# provided there is still enough capacity to meet all others.
# - Reject if candidate does not help with unmet needs
# or would make quotas infeasible.
#
# The policy records a human-readable reason string for each decision,
# retrievable via policy_reason(). Your UI can display this directly.
import sys
DEBUG = False
_last_reason: str | None = None
def _set_reason(msg: str) -> None:
"""Set the last decision reason (and echo to stderr if DEBUG)."""
global _last_reason
_last_reason = msg
if DEBUG:
print(msg, file=sys.stderr, flush=True)
def policy_reason() -> str | None:
"""Return the most recent decision reason string."""
return _last_reason
def decide(attributes: dict[str, bool],
tallies: dict[str, int],
mins: dict[str, int],
admitted_count: int,
venue_cap: int,
**_) -> bool:
"""Return True to admit, False to reject."""
S = venue_cap - admitted_count
need = {a: max(0, mins[a] - tallies.get(a, 0)) for a in mins}
# 1. All quotas already met
if sum(need.values()) == 0:
_set_reason("Admit (all minimums satisfied)")
return True
# 2. Admit creatives until quota met
if need.get("creative", 0) > 0 and attributes.get("creative", False):
_set_reason("Admit creative (rare + still needed)")
return True
# 3. Admit locals until quota met
if need.get("berlin_local", 0) > 0 and attributes.get("berlin_local", False):
_set_reason("Admit berlin_local (large quota still unmet)")
return True
# 4. Admit if candidate helps an unmet need and feasibility is safe
helps = [a for a in need if need[a] > 0 and attributes.get(a, False)]
if helps:
S_after = S - 1
remaining_after = sum(
max(0, need[a] - (1 if attributes.get(a, False) else 0))
for a in need
)
if S_after >= remaining_after:
_set_reason(f"Admit (helps {', '.join(helps)}, feasibility ok)")
return True
else:
_set_reason("Reject (would break feasibility)")
return False
# 5. Reject if they dont help
_set_reason("Reject (no help to unmet needs)")
return False