"""The time budget — the replacement for "what mood are you in?". `random-gondolatok` 15/D asked for a mood questionnaire before the recommendations. The same file's later BA-interview note (#23) says the mood half is not what makes this work: the friction is deciding at all. A mood→genre mapping would also be invented data — we store no mood signal and never will — whereas "how much time do you have" is a question the library can actually answer, from a column we already have. So the surface asks about TIME, and the answer is deterministic: four bands, no model, no latency. The band also picks the source for free — ten minutes is a YouTube video, an evening is a movie — which is exactly the cross-source behaviour the Home surface exists to provide. The bands overlap on purpose. "I have 30 minutes" should not be answered with a two-minute Short just because it fits; a lower bound is as much a part of the promise as the upper one. """ from typing import Literal Budget = Literal["short", "medium", "long", "any"] DEFAULT_BUDGET: Budget = "any" # budget -> (min_seconds, max_seconds); None = unbounded on that side. BANDS: dict[str, tuple[int | None, int | None]] = { "short": (None, 12 * 60), # "10 perc" — a bit of slack over the round number the chip shows "medium": (8 * 60, 40 * 60), # "30 perc" "long": (40 * 60, None), # "Egy este" "any": (None, None), # "Mindegy" } def band(budget: str) -> tuple[int | None, int | None]: """The (min, max) seconds for a budget. Unknown budgets fall back to the unbounded band rather than raising: the route validates the vocabulary with a Literal, so anything reaching here with a bad value is our own bug, and an unfiltered shelf is a better failure than a 500 on the landing page.""" return BANDS.get(budget, BANDS["any"]) def remaining_seconds(duration_seconds: int | None, position_seconds: int) -> int | None: """What is actually LEFT to watch. The question is "how much time do you have", so a 90-minute film with 8 minutes to go belongs in the ten-minute band, not the evening one. Unknown duration stays unknown — see `fits`.""" if duration_seconds is None: return None return max(0, duration_seconds - max(0, position_seconds)) def fits(seconds: int | None, budget: str) -> bool: """Does a runtime fit the budget? An unknown duration fits only the unbounded band. We cannot promise an item finishes inside ten minutes when we do not know how long it is, and quietly including it would make the one control on this page dishonest — the failure mode the house rules call an un-honest state.""" lo, hi = band(budget) if seconds is None: return lo is None and hi is None if lo is not None and seconds < lo: return False if hi is not None and seconds > hi: return False return True