79 lines
2.5 KiB
Python
79 lines
2.5 KiB
Python
# 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 don’t help
|
||
_set_reason("Reject (no help to unmet needs)")
|
||
return False
|
||
|