"""Ranking rules for a product line. Kept out of ``app.py`` so it can be imported and tested without executing the Streamlit page. Deciding which 10 of 99 products to spend an hour analyzing is real logic, not presentation. """ from __future__ import annotations # Label shown in the picker → (field to sort on, descending?). # Ordered by how often each is the right question to ask of a line. LINE_SORTS: dict[str, tuple[str, bool]] = { "Sales velocity (30d)": ("avg_30d", True), "Inventory on hand": ("inventory", True), "Cover days — lowest first": ("cover_days", False), "Reorder size (min PO)": ("min_po_quantity", True), "A–Z": ("sku", False), } def line_sort_key(product: dict, field: str): """Sort key tolerating both text and numeric fields, and missing values. Two things it has to get right: * **Mixed types.** A single numeric key raises ``TypeError`` the moment the user picks A–Z, because it compares ``str`` against ``0``. * **Missing values sort last, in either direction.** A SKU with no cover-days reading is not the most urgent item in the line — but a plain ``or 0`` makes it exactly that when sorting ascending. """ value = product.get(field) if isinstance(value, str): return (False, value.upper()) return (value is None, value if value is not None else 0) def rank(products: list[dict], sort_label: str) -> list[dict]: """Order a line by one of `LINE_SORTS`, falling back to the first rule.""" field, reverse = LINE_SORTS.get(sort_label, next(iter(LINE_SORTS.values()))) return sorted(products, key=lambda p: line_sort_key(p, field), reverse=reverse)