from __future__ import annotations import re from collections.abc import Iterable from pathlib import Path CPU_VENDOR_CHOICES = ( ("intel", "Intel"), ("amd", "AMD"), ) CPU_FAMILY_CHOICES = ( ("intel_i3", "Intel Core i3"), ("intel_i5", "Intel Core i5"), ("intel_i7", "Intel Core i7"), ("intel_i9", "Intel Core i9"), ("intel_ultra_5", "Intel Core Ultra 5"), ("intel_ultra_7", "Intel Core Ultra 7"), ("intel_ultra_9", "Intel Core Ultra 9"), ("amd_ryzen_3", "AMD Ryzen 3"), ("amd_ryzen_5", "AMD Ryzen 5"), ("amd_ryzen_7", "AMD Ryzen 7"), ("amd_ryzen_9", "AMD Ryzen 9"), ("amd_ryzen_ai", "AMD Ryzen AI"), ("amd_threadripper", "AMD Threadripper"), ("amd_fx", "AMD FX"), ) def _load_catalog() -> tuple[str, ...]: values = [] for line in Path(__file__).with_name("cpu_models.txt").read_text(encoding="utf-8").splitlines(): value = line.strip() if not value: continue if value.startswith("Core Ultra"): value = f"Intel {value}" values.append(value) return tuple(dict.fromkeys(values)) CPU_MODELS = _load_catalog() def _tokens(value: str) -> list[str]: return re.findall(r"[a-z]+\d*|\d+[a-z]*", value.casefold()) def _short_tokens(value: str) -> list[str]: tokens = _tokens(value) while tokens and tokens[0] in {"intel", "amd", "core"}: tokens.pop(0) return tokens def _match_score(model: str, term: str, preferred: bool) -> int: query_tokens = _tokens(term) if not query_tokens: return 100 if preferred else 1 model_tokens = _tokens(model) short_tokens = _short_tokens(model) query_text = " ".join(query_tokens) model_text = " ".join(model_tokens) short_text = " ".join(short_tokens) compact_query = "".join(query_tokens) compact_model = "".join(model_tokens) compact_short = "".join(short_tokens) score = 0 if query_text == model_text: score = 12_000 elif query_text == short_text: score = 11_000 elif compact_query in {compact_model, compact_short}: score = 10_000 elif all(token in model_tokens for token in query_tokens): score = 9_000 elif all(any(candidate.startswith(token) for candidate in model_tokens) for token in query_tokens): score = 8_000 elif compact_query in compact_model: score = 7_000 elif all(token in model_text for token in query_tokens): score = 6_000 else: return 0 if preferred: score += 250 score -= min(len(model), 200) return score def suggest_cpus(term: str, existing_values: Iterable[str] = (), limit: int = 15) -> list[str]: existing = {value.strip() for value in existing_values if value and value.strip()} candidates = list(dict.fromkeys([*existing, *CPU_MODELS])) ranked = [ (_match_score(model, term, model in existing), index, model) for index, model in enumerate(candidates) ] ranked = [item for item in ranked if item[0] > 0] ranked.sort(key=lambda item: (-item[0], item[1], item[2].casefold())) return [model for _, _, model in ranked[:limit]]