vendor-selector / optimizer.py
MakPr016
Updated dates
0fc22ba
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"),
}