""" ShufflerController — mirrors the JS makeShuffler() no-repeat logic. The seen-list for each named pool is kept inside a dict that lives in gr.BrowserState (localStorage). The state dict shape: { "jam": {"seen": ["...", ...]}, "tt_easy": {"seen": [...]}, "tt_medium": {"seen": [...]}, "tt_hard": {"seen": [...]}, "imp_all": {"seen": [...]}, "imp_0": {"seen": [...]}, # per-category index ... "iv_all": {"seen": [...]}, "iv_0": {"seen": [...]}, ... } """ import json import random from pydantic import BaseModel from models.session_models import ShufflerState def _serialise(item) -> str: if isinstance(item, str): return item if isinstance(item, BaseModel): return json.dumps(item.model_dump(), ensure_ascii=False, sort_keys=True) return json.dumps(item, ensure_ascii=False) def next_item(pool: list, key: str, raw_state: dict) -> tuple: """ Pick the next unseen item from pool. Returns (item, updated_raw_state). """ if not pool: return None, raw_state state_dict = raw_state.get(key, {}) if isinstance(raw_state, dict) else {} shuffler = ShufflerState.model_validate(state_dict) seen_set = set(shuffler.seen) unseen = [i for i, item in enumerate(pool) if _serialise(item) not in seen_set] if not unseen: # Full cycle complete — reset shuffler.seen = [] seen_set = set() unseen = list(range(len(pool))) idx = random.choice(unseen) picked = pool[idx] shuffler.seen.append(_serialise(picked)) updated = dict(raw_state) if isinstance(raw_state, dict) else {} updated[key] = shuffler.model_dump() return picked, updated def reset_key(key: str, raw_state: dict) -> dict: """Reset the seen list for a single key.""" updated = dict(raw_state) if isinstance(raw_state, dict) else {} updated[key] = ShufflerState().model_dump() return updated def imp_pool_key(cat: str, categories: list) -> str: if cat == "All": return "imp_all" idx = next((i for i, c in enumerate(categories) if c.category == cat), 0) return f"imp_{idx}" def iv_pool_key(cat: str, categories: list) -> str: if cat == "All": return "iv_all" idx = next((i for i, c in enumerate(categories) if c.category == cat), 0) return f"iv_{idx}"