# policy.py — “rare-first, German-safe” strategy for Scenario 3 # # Goal: # Hit all minima as early as possible by prioritizing the bottlenecks # (queer_friendly, vinyl_collector) and protecting the big German quota, # while never painting ourselves into a corner. # # Hard guarantees: # • Per-attribute feasibility check on every decision (no dead-ends). # • Reject anyone who doesn’t help an unmet attribute (“non-helper”). # • Once all minima are satisfied, admit everyone. # # Bias (what “does this”): # • While any of {queer_friendly, vinyl_collector, german_speaker} are unmet: # 1) Admit if candidate has queer_friendly or vinyl_collector (rare). # 2) Else admit if candidate has german_speaker (protect 800 target), # BUT stay feasibility-safe for other mins (esp. international). # 3) Otherwise reject even if they help a non-priority min, unless that # non-priority attribute has become *critical* by scarcity. # • Exploit overlaps: queer∩vinyl, queer/vinyl with German are admitted eagerly. # • When rare quotas are safe and German scarcity ≤ 1, admit other helpers. # # Notes: # • This policy may reject some feasible low-priority helpers early to keep # headroom aligned with the binding quotas, as requested. # • DEBUG=True prints reasoning to stderr. import sys from typing import Dict DEBUG = False # set True for verbose reasoning # Scenario 3 stats (iid arrivals) P = { "underground_veteran": 0.6795, "international": 0.5735, "fashion_forward": 0.6910, "queer_friendly": 0.04614, "vinyl_collector": 0.04454, "german_speaker": 0.4565, } # Priority groups RARE = ("queer_friendly", "vinyl_collector") GUARD = "german_speaker" # strongly constrained and conflicts with "international" # Scarcity thresholds CRITICAL_SCARCITY = 1.0 # need exceeds expected supply in remaining slots ALMOST_SAFE_SCARCITY = 0.9 # used to check if rare quotas are basically safe 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 _scarcity(need: Dict[str, int], S: int) -> Dict[str, float]: # Scarcity = remaining shortfall divided by expected remaining supply. # (>1 means we're behind expectation; <1 means we are ahead/on track.) s = {} for a, n in need.items(): exp = max(1e-9, P.get(a, 0.0) * S) s[a] = (n / exp) if n > 0 else 0.0 return s def _feasible_after_admit(attributes: Dict[str, bool], need: Dict[str, int], S: int) -> bool: # After admitting this candidate we have S_after slots; for every attribute a, # the remaining shortfall must fit in those slots. S_after = S - 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 decide(attributes: Dict[str, bool], tallies: Dict[str, int], mins: Dict[str, int], admitted_count: int, venue_cap: int) -> bool: # Remaining slots S = venue_cap - admitted_count # Unmet needs & scarcity need = _need(mins, tallies) total_need = sum(need.values()) # If all minima satisfied, admit everyone. if total_need == 0: _log("All minima met -> admit") return True # Helper set = unmet attributes the candidate has helps = [a for a, n in need.items() if n > 0 and attributes.get(a, False)] if not helps: _log("Non-helper -> reject") return False # Feasibility is a hard gate if not _feasible_after_admit(attributes, need, S): _log("Reject: would break per-attribute feasibility") return False # Scarcity picture sc = _scarcity(need, S) # Are any of the "rare or guard" quotas still unmet? rare_unmet = any(need[a] > 0 for a in RARE) guard_unmet = need.get(GUARD, 0) > 0 in_priority_phase = rare_unmet or guard_unmet # If we're in the priority phase, apply the requested bias: if in_priority_phase: # 1) If candidate helps a rare attribute, admit. if any(attributes.get(a, False) and need.get(a, 0) > 0 for a in RARE): _log(f"Accept: rare helper { [a for a in RARE if attributes.get(a, False)] }") return True # 2) Else if candidate helps German (guard), admit (feasibility already checked). if attributes.get(GUARD, False) and guard_unmet: _log("Accept: german_speaker to protect 800 target") return True # 3) Else candidate helps only non-priority mins. Normally reject here to # keep headroom for rare/German — UNLESS that non-priority attribute # itself is now critical by scarcity. # (This preserves viability for e.g. international/fashion when genuinely at risk.) critical_nonprio = [ a for a in helps if (a not in RARE and a != GUARD and sc.get(a, 0.0) >= CRITICAL_SCARCITY) ] if critical_nonprio: _log(f"Accept: non-priority helper {critical_nonprio} is critical by scarcity") return True # Otherwise reject to conserve headroom for rare/German during priority phase. _log(f"Reject: helper only for non-priority {helps} while rare/guard unmet") return False # If not in priority phase (rare met and German safe), admit any helper that keeps feasibility. # "German safe" defined implicitly by leaving priority phase; still preserve feasibility. _log(f"Accept: post-priority helper {helps}") return True