"""Supplier scoring. Weighted score (0-100): 40% price — cheaper supplier cost vs. the eligible pool 20% inventory — stock sufficiency for the requested qty 15% speed — shipping_time ('3-5 days' -> avg days) 10% fulfillment — supplier_performance.fulfillment_rate 10% returns — inverse of supplier_performance.return_rate 5% history — reliability_score blended with stock_accuracy """ import re def _num(value, default=0.0): try: return float(value or 0) except (TypeError, ValueError): return default def _shipping_days(sp): text = (sp.shipping_time or "").strip() numbers = re.findall(r"\d+", text) if not numbers: return 5.0 return float(sum(int(n) for n in numbers)) / len(numbers) def score_supplier(db, supplier, sp, qty, price_pool=None): perf = supplier.performance # 1) price (40%) — cheaper than pool average is better cost = _num(sp.supplier_cost) if price_pool and price_pool.get("avg_cost"): avg = price_pool["avg_cost"] price_score = max(0.0, 100.0 - (cost / avg - 1.0) * 100.0) else: price_score = 50.0 # 2) inventory (20%) — can we fill the quantity? inventory = int(sp.supplier_inventory or 0) inv_score = 100.0 if inventory >= qty else max(0.0, (inventory / qty) * 100.0) # 3) speed (15%) — fewer shipping days is better days = _shipping_days(sp) speed_score = max(0.0, 100.0 - days * 12.0) # 4) fulfillment (10%) fulfillment = 100.0 if perf is None else _num(perf.fulfillment_rate, 100.0) # 5) returns (10%) — 0% returns = 100 points, 20% returns = 0 points return_rate = 0.0 if perf is None else _num(perf.return_rate, 0.0) returns_score = max(0.0, 100.0 - return_rate * 5.0) # 6) history (5%) — reliability blended with stock accuracy reliability = _num(supplier.reliability_score, 50.0) if perf is None: history_score = reliability else: history_score = 0.5 * reliability + 0.5 * _num(perf.stock_accuracy, 100.0) total = ( 0.40 * price_score + 0.20 * inv_score + 0.15 * speed_score + 0.10 * fulfillment + 0.10 * returns_score + 0.05 * history_score ) return round(min(100.0, max(0.0, total)), 2)