Пример #1
0
def price_guide(args_):
    """Scrape pricing information for all wanted parts"""
    # load in wanted parts
    if args_.parts_list.endswith(".bsx"):
        wanted_parts = io.load_bsx(open(args_.parts_list))
    else:
        wanted_parts = io.load_xml(open(args_.parts_list))
    print('Loaded %d different parts' % len(wanted_parts))

    # get prices for available parts
    fmt = "{i:4d} {status:10s} {name:60s} {color:30s} {quantity:5d}"
    print("{i:4s} {status:10s} {name:60s} {color:30s} {quantity:5s}".format(
        i="i", status="status", name="name", color="color", quantity="qty"))
    print((4 + 1 + 10 + 1 + 60 + 1 + 30 + 1 + 5) * "-")

    # load half-complete price guide if available
    if args_.resume:
        old_parts = io.load_price_guide(open(args_.resume))
        old_parts = utils.groupby(old_parts, lambda x: (x['item_id'], x['wanted_color_id']))
    else:
        old_parts = {}

    available_parts = []

    # for each wanted lot
    for (i, item) in enumerate(wanted_parts):
        # skip this item if we already have enough
        matching = old_parts.get((item['ItemID'], item['ColorID']), [])
        quantity_found = sum(e['quantity_available'] for e in matching)

        print(fmt.format(i=i, status="seeking", name=item['ItemName'], color=item['ColorName'], quantity=item['Qty']))

        if quantity_found >= item['Qty']:
            colors = [color.name(c_id) for c_id in set(e['color_id'] for e in matching)]
            print(fmt.format(i=i, status="passing", name=item['ItemName'], color=",".join(colors),
                             quantity=quantity_found))
            available_parts.extend(matching)
        else:
            try:
                # fetch price data for this item in the closest available color
                new = scraper.price_guide(item, max_cost_quantile=args_.max_price_quantile)
                available_parts.extend(new)

                # print out status message
                total_quantity = sum(e['quantity_available'] for e in new)
                colors = [color.name(c_id) for c_id in set(e['color_id'] for e in new)]
                print(fmt.format(i=i, status="found", name=item['ItemName'], color=",".join(colors),
                                 quantity=total_quantity))

                if total_quantity < item['Qty']:
                    print('WARNING! Couldn\'t find enough parts!')

            except Exception as e:
                print('Catastrophic Failure! :(')
                traceback.print_exc()

    # save price data
    io.save_price_guide(open(args_.output, 'w'), available_parts)
Пример #2
0
def price_guide(args):
  """Scrape pricing information for all wanted parts"""
  # load in wanted parts
  if args.parts_list.endswith(".bsx"):
    wanted_parts = io.load_bsx(open(args.parts_list))
  else:
    wanted_parts = io.load_xml(open(args.parts_list))
  print 'Loaded %d different parts' % len(wanted_parts)

  # get prices for available parts
  fmt = "{i:4d} {status:10s} {name:60s} {color:30s} {quantity:5d}"
  print "{i:4s} {status:10s} {name:60s} {color:30s} {quantity:5s}" \
      .format(i="i", status="status", name="name", color="color", quantity="qty")
  print (4 + 1 + 10 + 1 + 60 + 1 + 30 + 1 + 5) * "-"

  # load half-complete price guide if available
  if args.resume:
    old_parts = io.load_price_guide(open(args.resume))
    old_parts = utils.groupby(old_parts, lambda x: (x['item_id'], x['wanted_color_id']))
  else:
    old_parts = {}

  available_parts = []

  # for each wanted lot
  for (i, item) in enumerate(wanted_parts):
    # skip this item if we already have enough
    matching = old_parts.get( (item['ItemID'], item['ColorID']), [])
    quantity_found = sum(e['quantity_available'] for e in matching)

    print fmt.format(i=i, status="seeking", name=item['ItemName'], color=item['ColorName'], quantity=item['Qty'])

    if quantity_found >= item['Qty']:
      colors = [color.name(id) for id in set(e['color_id'] for e in matching)]
      print fmt.format(i=i, status="passing", name=item['ItemName'], color=",".join(colors), quantity=quantity_found)
      available_parts.extend(matching)
    else:
      try:
        # fetch price data for this item in the closest available color
        new = scraper.price_guide(item, max_cost_quantile=args.max_price_quantile)
        available_parts.extend(new)

        # print out status message
        total_quantity = sum(e['quantity_available'] for e in new)
        colors = [color.name(id) for id in set(e['color_id'] for e in new)]
        print fmt.format(i=i, status="found", name=item['ItemName'], color=",".join(colors), quantity=total_quantity)

        if total_quantity < item['Qty']:
          print 'WARNING! Couldn\'t find enough parts!'

      except Exception as e:
        print 'Catastrophic Failure! :('
        traceback.print_exc()

  # save price data
  io.save_price_guide(open(args.output, 'w'), available_parts)
Пример #3
0
def minimize(args):
  """Minimize the cost of a purchase"""
  ################# Loading ##################################
  # load in wanted parts lists
  if args.parts_list.endswith(".bsx"):
    wanted_parts = io.load_bsx(open(args.parts_list))
  else:
    wanted_parts = io.load_xml(open(args.parts_list))
  print 'Loaded %d different parts' % len(wanted_parts)

  # load in pricing data
  available_parts = io.load_price_guide(open(args.price_guide))
  n_available = len(available_parts)
  n_stores = len(set(e['store_id'] for e in available_parts))
  print 'Loaded %d available lots from %d stores' % (n_available, n_stores)

  # load in store metadata
  if args.store_list is not None:
    store_metadata = io.load_store_metadata(open(args.store_list))
    print 'Loaded metadata for %d stores' % len(store_metadata)

    ################# Filtering Stores #########################
    # select which stores to get parts from
    allowed_stores = list(store_metadata)
    if args.source_country is not None:
      print 'Only allowing stores from %s' % (args.source_country,)
      allowed_stores = filter(lambda x: x['country_name'] == args.source_country, allowed_stores)

    if args.target_country is not None:
      print 'Only allowing stores that ship to %s' % (args.target_country,)
      allowed_stores = [s for s in allowed_stores
                        if args.target_country in s['ships']
                        or (len(s['ships']) == 1 and s['ships'][0] == 'All Countries WorldWide')]

    if args.feedback is not None and args.feedback > 0:
      print 'Only allowing stores with feedback >= %d' % (args.feedback,)
      allowed_stores = filter(lambda x: x['feedback'] >= args.feedback, allowed_stores)

    if args.exclude is not None:
      excludes = set(args.exclude.strip().split(","))
      excludes = map(lambda x: int(x), excludes)
      print 'Forcing exclusion of: %s' % (excludes,)
      allowed_stores = filter(lambda x: not (x['store_id'] in excludes), allowed_stores)

    store_ids = map(lambda x: x['store_id'], allowed_stores)
    store_ids = list(set(store_ids))
    print 'Using %d stores' % len(store_ids)

    available_parts = filter(lambda x: x['store_id'] in store_ids, available_parts)

    solution = minimizer.greedy(wanted_parts, available_parts)[0]
    if not minimizer.is_valid_solution(wanted_parts, solution['allocation']):
      print ("You're too restrictive. There's no way to buy what " +
             "you want with these stores")
      sys.exit(1)

  ################# Minimization #############################
  if args.algorithm in ['ilp', 'greedy']:
    if args.algorithm == 'ilp':
      ### Integer Linear Programming ###
      solution = minimizer.gurobi(
          wanted_parts,
          available_parts,
          allowed_stores,
          shipping_cost=args.shipping_cost
      )[0]
      assert minimizer.is_valid_solution(wanted_parts, solution['allocation'], allowed_stores)
    elif args.algorithm == 'greedy':
      ### Greedy Set Cover ###
      solution = minimizer.greedy(wanted_parts, available_parts)[0]

    # check and save
    io.save_solution(open(args.output + ".json", 'w'), solution)

    # print outs
    stores = set(e['store_id'] for e in solution['allocation'])
    cost = solution['cost']
    unsatisified =  minimizer.unsatisified(wanted_parts, solution['allocation'])
    print 'Total cost: $%.2f | n_stores: %d | remaining lots: %d' % (cost, len(stores), len(unsatisified))


  elif args.algorithm == 'brute-force':
    # for each possible number of stores
    for k in range(1, args.max_n_stores):
      # find all possible solutions using k stores
      solutions = minimizer.brute_force(wanted_parts, available_parts, k)
      solutions = list(sorted(solutions, key=lambda x: x['cost']))
      solutions = solutions[0:10]

      # save output
      output_folder = os.path.join(args.output, str(k))
      try:
        os.makedirs(output_folder)
      except OSError:
        pass

      for (i, solution) in enumerate(solutions):
        output_path = os.path.join(output_folder, "%02d.json" % i)
        with open(output_path, 'w') as f:
          io.save_solution(f, solution)

      # print outs
      if len(solutions) > 0:
        print '%8s %40s' % ('Cost', 'Store IDs')
        for sol in solutions:
          print '$%7.2f %40s' % (sol['cost'], ",".join(str(s) for s in sol['store_ids']))
      else:
        print "No solutions using %d stores" % k