"""Stage 6 helper: build the user's cold-start state vector from played games. `cold_start_state(played_games)` resolves each (possibly user-typed) title to a canonical catalog `name` via fuzzy matching, then returns the mean of those games' E-vectors — the analog of the running-average state the MDP used — plus the list of names it actually resolved. Empty / unrecognized input yields a zero vector (true cold start). This is the state the policy consumes at inference. """ import numpy as np from thefuzz import fuzz from recommender import artifacts def resolve_name(query: str, min_ratio: int = 80) -> str | None: """Map a user-typed title to the closest canonical catalog name, or None.""" n2r = artifacts.name_to_row() if query in n2r: return query q = query.lower() best, best_score = None, -1 for name in n2r: s = fuzz.ratio(q, name.lower()) if s > best_score: best, best_score = name, s return best if best_score >= min_ratio else None def cold_start_state(played_games) -> tuple[np.ndarray, list[str]]: """Return (state_vector, resolved_names). state = mean of the E-vectors of the resolved games (zero vector if none). """ E = artifacts.embedding_matrix() n2r = artifacts.name_to_row() rows, resolved = [], [] for g in played_games or []: canon = g if g in n2r else resolve_name(g) if canon is not None and canon not in resolved: rows.append(n2r[canon]) resolved.append(canon) if not rows: return np.zeros(E.shape[1], dtype=np.float32), resolved return E[rows].mean(axis=0).astype(np.float32), resolved