from typing import List, Dict from models import StrategyResult, SelectedVendor, RFQItem, VendorOffer class VendorOptimizer: def __init__(self, rfq_items: List[RFQItem], vendor_offers: List[VendorOffer]): self.rfq_items = {item.id: item for item in rfq_items} self.vendors = vendor_offers def _calculate_item_quality(self, offer: dict) -> float: score = 5.0 trust_bonus = (offer['rating'] / 5.0) * 5.0 score += trust_bonus return min(round(score, 1), 10.0) def _calculate_strategy(self, strategy_name: str) -> StrategyResult: item_offers = {i_id: [] for i_id in self.rfq_items} for vendor in self.vendors: for offer in vendor.offers: if offer.rfq_item_id in item_offers: offer_data = { "vendor_id": vendor.vendor_id, "vendor_name": vendor.vendor_name, "price": offer.unit_price, "delivery": offer.delivery_days, "rating": vendor.rating, "brand": offer.brand, "manufacturer": offer.manufacturer } offer_data['quality_score'] = self._calculate_item_quality(offer_data) item_offers[offer.rfq_item_id].append(offer_data) selected_map = {} total_rfq_cost = 0.0 total_delivery_days = 0 total_quality_accum = 0.0 fulfilled_count = 0 total_market_cost = 0.0 for item_id, item_req in self.rfq_items.items(): offers = item_offers.get(item_id, []) if not offers: continue max_price_for_item = max(o['price'] for o in offers) total_market_cost += (max_price_for_item * item_req.quantity) if strategy_name == "lowest_cost": offers.sort(key=lambda x: (x['price'], -x['quality_score'])) elif strategy_name == "fastest_delivery": offers.sort(key=lambda x: (x['delivery'], x['price'])) elif strategy_name == "best_quality": offers.sort(key=lambda x: (-x['quality_score'], x['price'])) elif strategy_name == "balanced": offers.sort(key=lambda x: ( x['price'] * 0.6 + x['delivery'] * 2.0 + (10.0 - x['quality_score']) * 15.0 )) best = offers[0] vid = best['vendor_id'] if vid not in selected_map: selected_map[vid] = { "vendor_id": vid, "vendor_name": best['vendor_name'], "total_cost": 0.0, "item_ids": [], "quality_sum": 0.0 } line_cost = best['price'] * item_req.quantity selected_map[vid]["total_cost"] += line_cost selected_map[vid]["item_ids"].append(item_id) selected_map[vid]["quality_sum"] += best['quality_score'] total_rfq_cost += line_cost total_delivery_days += best['delivery'] total_quality_accum += best['quality_score'] fulfilled_count += 1 allocations = [] for vid, data in selected_map.items(): v_avg_qual = data["quality_sum"] / len(data["item_ids"]) if data["item_ids"] else 0 allocations.append(SelectedVendor( vendor_id=data['vendor_id'], vendor_name=data['vendor_name'], total_cost=round(data['total_cost'], 2), items_count=len(data['item_ids']), item_ids=data['item_ids'], avg_quality_score=round(v_avg_qual, 1) )) avg_delivery = round(total_delivery_days / fulfilled_count, 1) if fulfilled_count else 0 overall_quality = round(total_quality_accum / fulfilled_count, 1) if fulfilled_count else 0 savings = round(total_market_cost - total_rfq_cost, 2) ui_score = 70.0 ui_score += (overall_quality - 5.0) * 6.0 if total_market_cost > 0: savings_pct = (savings / total_market_cost) ui_score += (savings_pct * 100) return StrategyResult( strategy_name=strategy_name, total_cost=round(total_rfq_cost, 2), avg_delivery_days=avg_delivery, vendor_count=len(allocations), allocations=allocations, savings=savings, score=max(min(round(ui_score, 1), 100), 40), quality_score=overall_quality ) def run_all(self): return { "lowest_cost": self._calculate_strategy("lowest_cost"), "fastest_delivery": self._calculate_strategy("fastest_delivery"), "best_quality": self._calculate_strategy("best_quality"), "balanced": self._calculate_strategy("balanced"), }