# policy.py — ultra-selective front-loading with a hard “GI-both” (German∩International) reserve # # Problem you hit: # Early admits were too loose, starving the truly binding quotas later # (queer_friendly, vinyl_collector, and the necessary overlap of # german_speaker ∩ international). # # What this does: # 1) Helper-only + strict feasibility (can’t paint into a corner). # 2) A **virtual required-overlap** constraint for (german_speaker, international): # GI_BOTH_MIN = max(0, minG + minI − CAP) = 450 in Scenario 3. # We maintain a live counter of how many “GI-both” admits we’ve made and # run a feasibility check against the remaining seats (hard gate). # 3) **Front-load gating**: # • While GI-both is behind its *own* pace, admit ONLY: # – GI-both candidates, OR # – Rare helpers: queer_friendly or vinyl_collector. # (No german-only, no international-only, no “easy” helpers.) # • If GI-both is on pace but (queer_friendly or vinyl_collector) is behind, # admit ONLY rare helpers (or GI-both). # 4) After the front-load phase (GI-both pace OK and rare OK), relax to helper-only # (still blocking anything that would break either base feasibility or GI-both reserve). # # Result: # Early acceptance rate is intentionally low (lots of rejections) until the # binding quotas are on track; this keeps headroom for scarce attributes and # the GI overlap you *must* accumulate to hit both 800 Germans and 650 # Internationals in only 1000 seats. # # Toggle DEBUG=True for per-decision logs. import sys from math import ceil from typing import Dict, List DEBUG = False # set True for verbose reasoning # ---- Scenario 3 attribute names ---- U = "underground_veteran" I = "international" F = "fashion_forward" Q = "queer_friendly" V = "vinyl_collector" G = "german_speaker" PRIORITY_RARE = (Q, V) # truly scarce attributes to front-load PRIORITY_GI_PAIR = (G, I) # the negative-correlated pair requiring a big overlap # Small pacing slack so we don't thrash on single-count fluctuations. PACE_SLACK = 0 # Module-level state to count admitted intersections we care about. _GI_BOTH_ADMITTED = 0 # count of admits who were BOTH german_speaker AND international def _log(msg: str) -> None: if DEBUG: print(msg, file=sys.stderr, flush=True) def _need(mins: Dict[str, int], tallies: Dict[str, int]) -> Dict[str, int]: return {a: max(0, mins[a] - tallies.get(a, 0)) for a in mins} def _helpers(attributes: Dict[str, bool], need: Dict[str, int]) -> List[str]: # Attributes this candidate has that are still unmet return [a for a, n in need.items() if n > 0 and attributes.get(a, False)] def _pace_required(total_target: int, admitted_count: int, cap: int) -> int: # On-track count by now for a quota of 'total_target' return ceil(total_target * admitted_count / max(1, cap)) def _feasible_base(attributes: Dict[str, bool], need: Dict[str, int], remaining_slots: int) -> bool: # Classic per-attribute feasibility: after admitting, each remaining shortfall fits in S_after. S_after = remaining_slots - 1 for a, n in need.items(): rem = n - (1 if attributes.get(a, False) else 0) if rem < 0: rem = 0 if S_after < rem: return False return True def _feasible_gi_both(is_gi_both: bool, gi_both_min: int, remaining_slots: int) -> bool: # Virtual feasibility for required overlap: after admitting, there must be # enough seats to still acquire the remaining GI-both admits. global _GI_BOTH_ADMITTED have_after = _GI_BOTH_ADMITTED + (1 if is_gi_both else 0) need_after = max(0, gi_both_min - have_after) S_after = remaining_slots - 1 return S_after >= need_after def decide(attributes: Dict[str, bool], tallies: Dict[str, int], mins: Dict[str, int], admitted_count: int, venue_cap: int) -> bool: global _GI_BOTH_ADMITTED S = venue_cap - admitted_count # remaining seats need = _need(mins, tallies) total_need = sum(need.values()) # Admit freely once all minima are met. if total_need == 0: _log("All minima satisfied -> admit") return True # Candidate properties hasG = bool(attributes.get(G, False)) hasI = bool(attributes.get(I, False)) hasQ = bool(attributes.get(Q, False)) hasV = bool(attributes.get(V, False)) is_GI_both = hasG and hasI # Must help at least one STILL-UNMET base attribute. helps = _helpers(attributes, need) if not helps: _log("Reject: non-helper") return False # --- Hard feasibility gates --- # 1) Base (per-attribute) feasibility if not _feasible_base(attributes, need, S): _log("Reject: would break per-attribute feasibility") return False # 2) Virtual GI-both feasibility gi_both_min_total = max(0, mins.get(G, 0) + mins.get(I, 0) - venue_cap) # = 450 here if gi_both_min_total > 0 and not _feasible_gi_both(is_GI_both, gi_both_min_total, S): _log("Reject: would break GI-both overlap feasibility") return False # --- Pacing (front-load) guards --- # On-track counts by now for the truly binding quotas and the GI-both overlap. q_pace = _pace_required(mins.get(Q, 0), admitted_count, venue_cap) v_pace = _pace_required(mins.get(V, 0), admitted_count, venue_cap) g_pace = _pace_required(mins.get(G, 0), admitted_count, venue_cap) gi_both_pace = _pace_required(gi_both_min_total, admitted_count, venue_cap) q_have = tallies.get(Q, 0) v_have = tallies.get(V, 0) g_have = tallies.get(G, 0) gi_both_have = _GI_BOTH_ADMITTED # exact, tracked by us behind_Q = (q_have + PACE_SLACK) < q_pace behind_V = (v_have + PACE_SLACK) < v_pace behind_GI_both = (gi_both_have + PACE_SLACK) < gi_both_pace behind_G = (g_have + PACE_SLACK) < g_pace # Priority front-load order: # 1) GI-both pace # 2) Rare (Q, V) pace # 3) German pace (ONLY when GI-both is on pace) if behind_GI_both: # While GI-both is behind, ONLY admit GI-both or rare helpers. if is_GI_both: _log("Accept: GI-both behind -> take GI-both") _GI_BOTH_ADMITTED += 1 return True if (hasQ and need.get(Q, 0) > 0) or (hasV and need.get(V, 0) > 0): _log("Accept: GI-both behind -> take rare (Q/V)") return True _log("Reject: GI-both behind -> conserve seat (no GI-both, no rare)") return False # GI-both is on pace now. # If rare quotas are behind, only admit rare (or GI-both which is always welcome). if behind_Q or behind_V: if is_GI_both: _log("Accept: rare behind, GI-both bonus") _GI_BOTH_ADMITTED += 1 return True if (behind_Q and hasQ and need.get(Q, 0) > 0) or (behind_V and hasV and need.get(V, 0) > 0): _log("Accept: rare behind -> take needed rare") return True _log("Reject: rare behind -> conserve seat (candidate not needed rare)") return False # If German is behind (but GI-both is fine), allow German-only as a third-tier priority. if behind_G: if is_GI_both: _log("Accept: German behind, GI-both present") _GI_BOTH_ADMITTED += 1 return True if hasG and need.get(G, 0) > 0 and not hasI: _log("Accept: German behind -> take German-only") return True # Don’t pull in International-only here; keep GI balance healthy. if hasI and not hasG: _log("Reject: German behind -> skip International-only") return False # --- Post front-load: Helper-only, with GI-both feasibility guard already enforced --- # Still avoid pure International-only unless we either (a) need International # and (b) GI-both target is already fully satisfied. gi_both_remaining = max(0, gi_both_min_total - _GI_BOTH_ADMITTED) if hasI and not hasG and gi_both_remaining > 0: # We still owe GI-both; don't consume seats with International-only yet. _log("Reject: GI-both remaining -> defer International-only") return False # Safe to admit any feasible helper now. if is_GI_both: _GI_BOTH_ADMITTED += 1 _log("Accept: on-track helper (GI-both)") return True _log(f"Accept: on-track helper {helps}") return True