def listShipsPresent(tdb, cmdenv, station, results): """ Populate results with a list of ships present at the given station """ cur = tdb.query( """ SELECT ship_id FROM ShipVendor WHERE station_id = ? """, [station.ID]) results.summary = ResultRow() results.summary.station = station ships = tdb.shipByID addShip = results.rows.append for (ship_id, ) in cur: ship = ships.get(ship_id, None) if ship: addShip(ResultRow(ship=ship)) if cmdenv.nameSort: results.rows.sort(key=lambda row: row.ship.name()) else: results.rows.sort(key=lambda row: row.ship.cost, reverse=True) return results
def run(results, cmdenv, tdb): from commands.commandenv import ResultRow calc = TradeCalc(tdb, cmdenv) lhs = cmdenv.startStation rhs = cmdenv.stopStation if lhs == rhs: raise CommandLineError("Must specify two different stations.") results.summary = ResultRow() results.summary.fromStation = lhs results.summary.toStation = rhs trades = calc.getTrades(lhs, rhs) if not trades: raise CommandLineError("No profitable trades {} -> {}".format( lhs.name(), rhs.name())) if cmdenv.detail > 1: tdb.getAverageSelling() tdb.getAverageBuying() for item in trades: results.rows.append(item) return results
def run(results, cmdenv, tdb): if cmdenv.lsFromStar and cmdenv.lsFromStar < 0: raise CommandLineError("Invalid (negative) --ls option") system, station = checkSystemAndStation(tdb, cmdenv) systemName = cmdenv.system stationName = cmdenv.station if cmdenv.add: result = addStation(tdb, cmdenv, system, stationName) return checkResultAndExportStations(tdb, cmdenv, result) elif cmdenv.update: result = updateStation(tdb, cmdenv, station) return checkResultAndExportStations(tdb, cmdenv, result) elif cmdenv.remove: result = removeStation(tdb, cmdenv, station) return checkResultAndExportStations(tdb, cmdenv, result) # Otherwise, it's just a query results.summary = ResultRow() results.summary.system = station.system results.summary.station = station avgSell = results.summary.avgSelling = tdb.getAverageSelling() avgBuy = results.summary.avgBuying = tdb.getAverageBuying() class ItemTrade(object): def __init__(self, ID, price, avgAgainst): self.ID, self.item = ID, tdb.itemByID[ID] self.price = int(price) self.avgTrade = avgAgainst.get(ID, 0) # Look up all selling and buying by the station selling, buying = [], [] cur = tdb.query( """ SELECT item_id, demand_price, supply_price FROM StationItem WHERE station_id = ? AND (demand_price > 10 or supply_price > 10) """, [station.ID]) for ID, demand_price, supply_price in cur: if demand_price > 10 and avgSell.get(ID, 0) > 10: buying.append(ItemTrade(ID, demand_price, avgSell)) if supply_price > 10 and avgBuy.get(ID, 0) > 10: selling.append(ItemTrade(ID, supply_price, avgBuy)) selling.sort(key=lambda item: item.price - item.avgTrade, ) results.summary.selling = selling[:5] buying.sort(key=lambda item: item.avgTrade - item.price, ) results.summary.buying = buying[:5] return results
def run(results, cmdenv, tdb): """ Fetch all the data needed to display the results of a "rares" command. Does not actually print anything. Command execution is broken into two steps: 1. cmd.run(results, cmdenv, tdb) Gather all the data required but generate no output, 2. cmd.render(results, cmdenv, tdb) Print output to the user. This separation of concerns allows modularity; you can write a command that calls another command to fetch data for you and knowing it doesn't generate any output. Then you can process the data and return it and let the command parser decide when to turn it into output. It also opens a future door to commands that can present their data in a GUI as well as the command line by having a custom render() function. Parameters: results An object to be populated and returned cmdenv A CommandEnv object populated with the parameters for the command. tdb A TradeDB object to query against. Returns: None End execution without any output results Proceed to "render" with the output. """ # Lookup the system we're currently in. start = cmdenv.nearSystem # Hoist the padSize parameter for convenience padSize = cmdenv.padSize # How far we're want to cast our net. maxLy = float(cmdenv.maxLyPer or 0.0) if cmdenv.illegal: wantIllegality = "Y" elif cmdenv.legal: wantIllegality = "N" else: wantIllegality = "YN?" awaySystems = set() if cmdenv.away or cmdenv.awayFrom: if not cmdenv.away or not cmdenv.awayFrom: raise CommandLineError("Invalid --away/--from usage. See --help") minAwayDist = cmdenv.away for sysName in cmdenv.awayFrom: system = tdb.lookupPlace(sysName).system awaySystems.add(system) # Start to build up the results data. results.summary = ResultRow() results.summary.near = start results.summary.ly = maxLy results.summary.awaySystems = awaySystems distCheckFn = start.distanceTo # Look through the rares list. for rare in tdb.rareItemByID.values(): if not rare.illegal in wantIllegality: continue if padSize: # do we care about pad size? if not rare.station.checkPadSize(padSize): continue rareSys = rare.station.system # Find the un-sqrt'd distance to the system. dist = distCheckFn(rareSys) if 0 < maxLy < dist: continue if awaySystems: awayCheck = rareSys.distanceTo if any(awayCheck(away) < minAwayDist for away in awaySystems): continue # Create a row for this item row = ResultRow() row.rare = rare row.dist = dist results.rows.append(row) # Was anything matched? if not results: print("No matches found.") return None if cmdenv.sortByPrice: results.rows.sort(key=lambda row: row.dist) results.rows.sort(key=lambda row: row.rare.costCr, reverse=True) else: results.rows.sort(key=lambda row: row.rare.costCr, reverse=True) results.rows.sort(key=lambda row: row.dist) if cmdenv.reverse: results.rows.reverse() limit = cmdenv.limit or 0 if limit > 0: results.rows = results.rows[:limit] return results
def run(results, cmdenv, tdb): """ Fetch all the data needed to display the results of a "rares" command. Does not actually print anything. Command execution is broken into two steps: 1. cmd.run(results, cmdenv, tdb) Gather all the data required but generate no output, 2. cmd.render(results, cmdenv, tdb) Print output to the user. This separation of concerns allows modularity; you can write a command that calls another command to fetch data for you and knowing it doesn't generate any output. Then you can process the data and return it and let the command parser decide when to turn it into output. It also opens a future door to commands that can present their data in a GUI as well as the command line by having a custom render() function. Parameters: results An object to be populated and returned cmdenv A CommandEnv object populated with the parameters for the command. tdb A TradeDB object to query against. Returns: None End execution without any output results Proceed to "render" with the output. """ # Lookup the system we're currently in. start = cmdenv.nearSystem # Hoist the padSize, noPlanet and planetary parameter for convenience padSize = cmdenv.padSize noPlanet = cmdenv.noPlanet planetary = cmdenv.planetary # How far we're want to cast our net. maxLy = float(cmdenv.maxLyPer or 0.) if cmdenv.illegal: wantIllegality = 'Y' elif cmdenv.legal: wantIllegality = 'N' else: wantIllegality = 'YN?' awaySystems = set() if cmdenv.away or cmdenv.awayFrom: if not cmdenv.away or not cmdenv.awayFrom: raise CommandLineError("Invalid --away/--from usage. See --help") minAwayDist = cmdenv.away for sysName in cmdenv.awayFrom: system = tdb.lookupPlace(sysName).system awaySystems.add(system) # Start to build up the results data. results.summary = ResultRow() results.summary.near = start results.summary.ly = maxLy results.summary.awaySystems = awaySystems distCheckFn = start.distanceTo # Look through the rares list. for rare in tdb.rareItemByID.values(): if not rare.illegal in wantIllegality: continue if padSize: # do we care about pad size? if not rare.station.checkPadSize(padSize): continue if planetary: # do we care about planetary? if not rare.station.checkPlanetary(planetary): continue if noPlanet and rare.station.planetary != 'N': continue rareSys = rare.station.system # Find the un-sqrt'd distance to the system. dist = distCheckFn(rareSys) if maxLy > 0. and dist > maxLy: continue if awaySystems: awayCheck = rareSys.distanceTo if any(awayCheck(away) < minAwayDist for away in awaySystems): continue # Create a row for this item row = ResultRow() row.rare = rare row.dist = dist results.rows.append(row) # Was anything matched? if not results: print("No matches found.") return None if cmdenv.sortByPrice: results.rows.sort(key=lambda row: row.dist) results.rows.sort(key=lambda row: row.rare.costCr, reverse=True) else: results.rows.sort(key=lambda row: row.rare.costCr, reverse=True) results.rows.sort(key=lambda row: row.dist) if cmdenv.reverse: results.rows.reverse() limit = cmdenv.limit or 0 if limit > 0: results.rows = results.rows[:limit] return results
def run(results, cmdenv, tdb): origin = cmdenv.startStation if not origin.itemCount: raise CommandLineError( "No trade data available for {}".format(origin.name()) ) buying, selling = cmdenv.buying, cmdenv.selling results.summary = ResultRow() results.summary.origin = origin results.summary.buying = cmdenv.buying results.summary.selling = cmdenv.selling tdb.getAverageSelling() tdb.getAverageBuying() cur = tdb.query(""" SELECT item_id, demand_price, demand_units, demand_level, supply_price, supply_units, supply_level, JULIANDAY('now') - JULIANDAY(modified) FROM StationItem WHERE station_id = ? """, [origin.ID]) for row in cur: it = iter(row) item = tdb.itemByID[next(it)] row = ResultRow() row.item = item row.buyCr = int(next(it) or 0) row.avgBuy = tdb.avgBuying.get(item.ID, 0) units, level = int(next(it) or 0), int(next(it) or 0) row.buyUnits = units row.buyLevel = level row.demand = render_units(units, level) if not selling: hasBuy = (row.buyCr or units or level) else: hasBuy = False row.sellCr = int(next(it) or 0) row.avgSell = tdb.avgSelling.get(item.ID, 0) units, level = int(next(it) or 0), int(next(it) or 0) row.sellUnits = units row.sellLevel = level row.supply = render_units(units, level) if not buying: hasSell = (row.sellCr or units or level) else: hasSell = False row.age = float(next(it) or 0) if hasBuy or hasSell: results.rows.append(row) if not results.rows: raise CommandLineError("No items found") results.rows.sort(key=lambda row: row.item.dbname) results.rows.sort(key=lambda row: row.item.category.dbname) return results
def run(results, cmdenv, tdb): from commands.commandenv import ResultRow if cmdenv.lt and cmdenv.gt: if cmdenv.lt <= cmdenv.gt: raise CommandLineError("--gt must be lower than --lt") item = tdb.lookupItem(cmdenv.item) cmdenv.DEBUG0("Looking up item {} (#{})", item.name(), item.ID) avoidSystems = {s for s in cmdenv.avoidPlaces if isinstance(s, System)} avoidStations = {s for s in cmdenv.avoidPlaces if isinstance(s, Station)} results.summary = ResultRow() results.summary.item = item results.summary.avoidSystems = avoidSystems results.summary.avoidStations = avoidStations if cmdenv.detail: avgPrice = tdb.query(""" SELECT AVG(si.demand_price) FROM StationItem AS si WHERE si.item_id = ? AND si.demand_price > 0 """, [item.ID]).fetchone()[0] results.summary.avg = int(avgPrice) # Constraints tables = "StationItem AS si" constraints = [ "(item_id = {} AND demand_price > 0)".format(item.ID), ] columns = [ 'si.station_id', 'si.demand_price', 'si.demand_units', ] bindValues = [] if cmdenv.demand: constraints.append("(demand_units >= ?)") bindValues.append(cmdenv.demand) if cmdenv.lt: constraints.append("(demand_price < ?)") bindValues.append(cmdenv.lt) if cmdenv.gt: constraints.append("(demand_price > ?)") bindValues.append(cmdenv.gt) nearSystem = cmdenv.nearSystem if nearSystem: maxLy = cmdenv.maxLyPer or tdb.maxSystemLinkLy results.summary.near = nearSystem results.summary.ly = maxLy distanceFn = nearSystem.distanceTo else: distanceFn = None whereClause = ' AND '.join(constraints) stmt = """SELECT DISTINCT {columns} FROM {tables} WHERE {where}""".format( columns=','.join(columns), tables=tables, where=whereClause ) cmdenv.DEBUG0('SQL: {}', stmt) cur = tdb.query(stmt, bindValues) stationByID = tdb.stationByID padSize = cmdenv.padSize planetary = cmdenv.planetary wantNoPlanet = cmdenv.noPlanet wantBlackMarket = cmdenv.blackMarket for (stationID, priceCr, demand) in cur: station = stationByID[stationID] if padSize and not station.checkPadSize(padSize): continue if planetary and not station.checkPlanetary(planetary): continue if wantNoPlanet and station.planetary != 'N': continue if wantBlackMarket and station.blackMarket != 'Y': continue if station in avoidStations: continue if station.system in avoidSystems: continue row = ResultRow() row.station = station if distanceFn: distance = distanceFn(row.station.system) if distance > maxLy: continue row.dist = distance row.price = priceCr row.demand = demand row.age = station.itemDataAgeStr results.rows.append(row) if not results.rows: raise NoDataError("No available items found") results.summary.sort = "Price" results.rows.sort(key=lambda result: result.demand, reverse=True) results.rows.sort(key=lambda result: result.price, reverse=True) if nearSystem and not cmdenv.sortByPrice: results.summary.sort = "Dist" results.rows.sort(key=lambda result: result.dist) limit = cmdenv.limit or 0 if limit > 0: results.rows = results.rows[:limit] return results
def run(results, cmdenv, tdb): from commands.commandenv import ResultRow cmdenv = results.cmdenv tdb = cmdenv.tdb srcSystem = cmdenv.nearSystem results.summary = ResultRow() results.limit = cmdenv.limit fields = [ "si.station_id", "JULIANDAY('NOW') - JULIANDAY(MAX(si.modified))", "stn.ls_from_star", ] joins = [] wheres = [] havings = [] if cmdenv.minAge: wheres.append( "(JULIANDAY('NOW') - JULIANDAY(si.modified) >= {})".format( cmdenv.minAge)) nearSys = cmdenv.nearSystem if nearSys: maxLy = cmdenv.maxLyPer or tdb.maxSystemLinkLy maxLy2 = maxLy**2 fields.append("dist2(" "sys.pos_x, sys.pos_y, sys.pos_z," "{}, {}, {}" ") AS d2".format( nearSys.posX, nearSys.posY, nearSys.posZ, )) joins.append("INNER JOIN System sys USING (system_id)") wheres.append("""( sys.pos_x BETWEEN {} and {} AND sys.pos_y BETWEEN {} and {} AND sys.pos_z BETWEEN {} and {} )""".format( nearSys.posX - maxLy, nearSys.posX + maxLy, nearSys.posY - maxLy, nearSys.posY + maxLy, nearSys.posZ - maxLy, nearSys.posZ + maxLy, )) havings.append("d2 <= {}".format(maxLy2)) else: fields.append("0") fieldStr = ','.join(fields) if joins: joinStr = ' '.join(joins) else: joinStr = '' if wheres: whereStr = 'WHERE ' + ' AND '.join(wheres) else: whereStr = '' if havings: haveStr = 'HAVING ' + ' AND '.join(havings) else: haveStr = '' stmt = """ SELECT {fields} FROM StationItem as si INNER JOIN Station stn USING (station_id) {joins} {wheres} GROUP BY 1 {having} ORDER BY 2 DESC """.format( fields=fieldStr, joins=joinStr, wheres=whereStr, having=haveStr, ) cmdenv.DEBUG1(stmt) for (stnID, age, ls, dist2) in tdb.query(stmt): cmdenv.DEBUG2("{}:{}:{}", stnID, age, ls) row = ResultRow() row.station = tdb.stationByID[stnID] row.age = age if ls: row.ls = "{:n}".format(ls) else: row.ls = "?" row.dist = dist2**0.5 results.rows.append(row) if cmdenv.route and len(results.rows) > 1: def walk(startNode, dist): rows = results.rows startNode = rows[startNode] openList = set(rows) path = [startNode] openList.remove(startNode) while len(path) < len(rows): lastNode = path[-1] distFn = lastNode.station.system.distanceTo nearest = min(openList, key=lambda row: distFn(row.station.system)) openList.remove(nearest) path.append(nearest) dist += distFn(nearest.station.system) return (path, dist) if cmdenv.near: bestPath = walk(0, results.rows[0].dist) else: bestPath = (results.rows, float("inf")) for i in range(len(results.rows)): path = walk(i, 0) if path[1] < bestPath[1]: bestPath = path results.rows[:] = bestPath[0] if cmdenv.limit: results.rows[:] = results.rows[:cmdenv.limit] return results
def run(results, cmdenv, tdb): from commands.commandenv import ResultRow cmdenv = results.cmdenv tdb = cmdenv.tdb srcSystem = cmdenv.nearSystem results.summary = ResultRow() results.limit = cmdenv.limit fields = [ "si.station_id", "JULIANDAY('NOW') - JULIANDAY(MAX(si.modified))", "stn.ls_from_star", ] joins = [] wheres = [] havings = [] if cmdenv.minAge: wheres.append( "(JULIANDAY('NOW') - JULIANDAY(si.modified) >= {})" .format(cmdenv.minAge) ) nearSys = cmdenv.nearSystem if nearSys: maxLy = cmdenv.maxLyPer or tdb.maxSystemLinkLy maxLy2 = maxLy ** 2 fields.append( "dist2(" "sys.pos_x, sys.pos_y, sys.pos_z," "{}, {}, {}" ") AS d2".format( nearSys.posX, nearSys.posY, nearSys.posZ, )) joins.append("INNER JOIN System sys USING (system_id)") wheres.append("""( sys.pos_x BETWEEN {} and {} AND sys.pos_y BETWEEN {} and {} AND sys.pos_z BETWEEN {} and {} )""".format( nearSys.posX - maxLy, nearSys.posX + maxLy, nearSys.posY - maxLy, nearSys.posY + maxLy, nearSys.posZ - maxLy, nearSys.posZ + maxLy, )) havings.append("d2 <= {}".format(maxLy2)) else: fields.append("0") fieldStr = ','.join(fields) if joins: joinStr = ' '.join(joins) else: joinStr = '' if wheres: whereStr = 'WHERE ' + ' AND '.join(wheres) else: whereStr = '' if havings: haveStr = 'HAVING ' + ' AND '.join(havings) else: haveStr = '' stmt = """ SELECT {fields} FROM StationItem as si INNER JOIN Station stn USING (station_id) {joins} {wheres} GROUP BY 1 {having} ORDER BY 2 DESC """.format( fields=fieldStr, joins=joinStr, wheres=whereStr, having=haveStr, ) cmdenv.DEBUG1(stmt) for (stnID, age, ls, dist2) in tdb.query(stmt): cmdenv.DEBUG2("{}:{}:{}", stnID, age, ls) row = ResultRow() row.station = tdb.stationByID[stnID] row.age = age if ls: row.ls = "{:n}".format(ls) else: row.ls = "?" row.dist = dist2 ** 0.5 results.rows.append(row) if cmdenv.route and len(results.rows) > 1: def walk(startNode, dist): rows = results.rows startNode = rows[startNode] openList = set(rows) path = [startNode] openList.remove(startNode) while len(path) < len(rows): lastNode = path[-1] distFn = lastNode.station.system.distanceTo nearest = min(openList, key=lambda row: distFn(row.station.system)) openList.remove(nearest) path.append(nearest) dist += distFn(nearest.station.system) return path, dist if cmdenv.near: bestPath = walk(0, results.rows[0].dist) else: bestPath = (results.rows, float("inf")) for i in range(len(results.rows)): path = walk(i, 0) if path[1] < bestPath[1]: bestPath = path results.rows[:] = bestPath[0] if cmdenv.limit: results.rows[:] = results.rows[:cmdenv.limit] return results
def run(results, cmdenv, tdb): cmdenv = results.cmdenv tdb = cmdenv.tdb srcSystem = cmdenv.nearSystem ly = cmdenv.maxLyPer if ly is None: ly = tdb.maxSystemLinkLy results.summary = ResultRow() results.summary.near = srcSystem results.summary.ly = ly results.summary.stations = 0 distances = {srcSystem: 0.0} # Calculate the bounding dimensions for destSys, dist in tdb.genSystemsInRange(srcSystem, ly): distances[destSys] = dist showStations = cmdenv.detail wantStations = cmdenv.stations padSize = cmdenv.padSize planetary = cmdenv.planetary wantNoPlanet = cmdenv.noPlanet wantTrading = cmdenv.trading wantShipYard = cmdenv.shipyard wantBlackMarket = cmdenv.blackMarket wantOutfitting = cmdenv.outfitting wantRearm = cmdenv.rearm wantRefuel = cmdenv.refuel wantRepair = cmdenv.repair def station_filter(stations): for station in stations: if wantNoPlanet and station.planetary != 'N': continue if wantTrading and not station.isTrading: continue if wantBlackMarket and station.blackMarket != 'Y': continue if wantShipYard and station.shipyard != 'Y': continue if padSize and not station.checkPadSize(padSize): continue if planetary and not station.checkPlanetary(planetary): continue if wantOutfitting and station.outfitting != 'Y': continue if wantRearm and station.rearm != 'Y': continue if wantRefuel and station.refuel != 'Y': continue if wantRepair and station.repair != 'Y': continue yield station for (system, dist) in sorted(distances.items(), key=lambda x: x[1]): if showStations or wantStations: stations = [] for (station) in station_filter(system.stations): stations.append( ResultRow( station=station, age=station.itemDataAgeStr, )) if not stations and wantStations: continue row = ResultRow() row.system = system row.dist = dist row.stations = stations if showStations else [] results.rows.append(row) results.summary.stations += len(row.stations) return results
def run(results, cmdenv, tdb): from commands.commandenv import ResultRow srcSystem, dstSystem = cmdenv.origPlace, cmdenv.destPlace if isinstance(srcSystem, Station): srcSystem = srcSystem.system if isinstance(dstSystem, Station): dstSystem = dstSystem.system maxLyPer = cmdenv.maxLyPer or tdb.maxSystemLinkLy cmdenv.DEBUG0("Route from {} to {} with max {}ly per jump.", srcSystem.name(), dstSystem.name(), maxLyPer) # Build a list of src->dst pairs hops = [ [ srcSystem, None ] ] if cmdenv.viaPlaces: for hop in cmdenv.viaPlaces: hops[-1][1] = hop hops.append([hop, None]) hops[-1][1] = dstSystem avoiding = [ avoid for avoid in cmdenv.avoidPlaces if isinstance(avoid, System) ] route = [ ] stationInterval = cmdenv.stationInterval for hop in hops: hopRoute = tdb.getRoute( hop[0], hop[1], maxLyPer, avoiding, stationInterval=stationInterval, ) if not hopRoute: raise NoRouteError( "No route found between {} and {} " "with a max {}ly/jump limit.".format( hop[0].name(), hop[1].name(), maxLyPer, )) route = route[:-1] + hopRoute results.summary = ResultRow( fromSys=srcSystem, toSys=dstSystem, maxLy=maxLyPer, ) lastSys, totalLy, dirLy = srcSystem, 0.00, 0.00 maxPadSize = cmdenv.padSize for (jumpSys, dist) in route: jumpLy = lastSys.distanceTo(jumpSys) totalLy += jumpLy if cmdenv.detail: dirLy = jumpSys.distanceTo(dstSystem) row = ResultRow( action='Via', system=jumpSys, jumpLy=jumpLy, totalLy=totalLy, dirLy=dirLy, ) row.stations = [] if cmdenv.stations: for (station) in jumpSys.stations: if maxPadSize and not station.checkPadSize(maxPadSize): continue rr = ResultRow( station=station, age=station.itemDataAgeStr, ) row.stations.append(rr) results.rows.append(row) lastSys = jumpSys results.rows[0].action='Depart' results.rows[-1].action='Arrive' return results
def run(results, cmdenv, tdb): from commands.commandenv import ResultRow srcSystem, dstSystem = cmdenv.origPlace, cmdenv.destPlace if isinstance(srcSystem, Station): srcSystem = srcSystem.system if isinstance(dstSystem, Station): dstSystem = dstSystem.system maxLyPer = cmdenv.maxLyPer or tdb.maxSystemLinkLy cmdenv.DEBUG0("Route from {} to {} with max {}ly per jump.", srcSystem.name(), dstSystem.name(), maxLyPer) # Build a list of src->dst pairs hops = [[srcSystem, None]] if cmdenv.viaPlaces: for hop in cmdenv.viaPlaces: hops[-1][1] = hop hops.append([hop, None]) hops[-1][1] = dstSystem avoiding = [ avoid for avoid in cmdenv.avoidPlaces if isinstance(avoid, System) ] route = [] stationInterval = cmdenv.stationInterval for hop in hops: hopRoute = tdb.getRoute( hop[0], hop[1], maxLyPer, avoiding, stationInterval=stationInterval, ) if not hopRoute: raise NoRouteError("No route found between {} and {} " "with a max {}ly/jump limit.".format( hop[0].name(), hop[1].name(), maxLyPer, )) route = route[:-1] + hopRoute results.summary = ResultRow( fromSys=srcSystem, toSys=dstSystem, maxLy=maxLyPer, ) lastSys, totalLy, dirLy = srcSystem, 0.00, 0.00 maxPadSize = cmdenv.padSize planetary = cmdenv.planetary noPlanet = cmdenv.noPlanet for (jumpSys, dist) in route: jumpLy = lastSys.distanceTo(jumpSys) totalLy += jumpLy if cmdenv.detail: dirLy = jumpSys.distanceTo(dstSystem) row = ResultRow( action='Via', system=jumpSys, jumpLy=jumpLy, totalLy=totalLy, dirLy=dirLy, ) row.stations = [] if cmdenv.stations: for (station) in jumpSys.stations: if maxPadSize and not station.checkPadSize(maxPadSize): continue if planetary and not station.checkPlanetary(planetary): continue if noPlanet and station.planetary != 'N': continue rr = ResultRow( station=station, age=station.itemDataAgeStr, ) row.stations.append(rr) results.rows.append(row) lastSys = jumpSys results.rows[0].action = 'Depart' results.rows[-1].action = 'Arrive' return results
def run(results, cmdenv, tdb): from commands.commandenv import ResultRow if cmdenv.lt and cmdenv.gt: if cmdenv.lt <= cmdenv.gt: raise CommandLineError("--gt must be lower than --lt") item = tdb.lookupItem(cmdenv.item) cmdenv.DEBUG0("Looking up item {} (#{})", item.name(), item.ID) avoidSystems = {s for s in cmdenv.avoidPlaces if isinstance(s, System)} avoidStations = {s for s in cmdenv.avoidPlaces if isinstance(s, Station)} results.summary = ResultRow() results.summary.item = item results.summary.avoidSystems = avoidSystems results.summary.avoidStations = avoidStations if cmdenv.detail: avgPrice = tdb.query(""" SELECT AVG(si.demand_price) FROM StationItem AS si WHERE si.item_id = ? AND si.demand_price > 0 """, [item.ID]).fetchone()[0] results.summary.avg = int(avgPrice) # Constraints tables = "StationItem AS si" constraints = [ "(item_id = {} AND demand_price > 0)".format(item.ID), ] columns = [ 'si.station_id', 'si.demand_price', 'si.demand_units', ] bindValues = [] if cmdenv.demand: constraints.append("(demand_units >= ?)") bindValues.append(cmdenv.demand) if cmdenv.lt: constraints.append("(demand_price < ?)") bindValues.append(cmdenv.lt) if cmdenv.gt: constraints.append("(demand_price > ?)") bindValues.append(cmdenv.gt) nearSystem = cmdenv.nearSystem if nearSystem: maxLy = cmdenv.maxLyPer or tdb.maxSystemLinkLy results.summary.near = nearSystem results.summary.ly = maxLy distanceFn = nearSystem.distanceTo else: distanceFn = None whereClause = ' AND '.join(constraints) stmt = """SELECT DISTINCT {columns} FROM {tables} WHERE {where}""".format( columns=','.join(columns), tables=tables, where=whereClause ) cmdenv.DEBUG0('SQL: {}', stmt) cur = tdb.query(stmt, bindValues) stationByID = tdb.stationByID padSize = cmdenv.padSize wantBlackMarket = cmdenv.blackMarket for (stationID, priceCr, demand) in cur: station = stationByID[stationID] if padSize and not station.checkPadSize(padSize): continue if wantBlackMarket and station.blackMarket != 'Y': continue if station in avoidStations: continue if station.system in avoidSystems: continue row = ResultRow() row.station = station if distanceFn: distance = distanceFn(row.station.system) if distance > maxLy: continue row.dist = distance row.price = priceCr row.demand = demand row.age = station.itemDataAgeStr results.rows.append(row) if not results.rows: raise NoDataError("No available items found") results.summary.sort = "Price" results.rows.sort(key=lambda result: result.demand, reverse=True) results.rows.sort(key=lambda result: result.price, reverse=True) if nearSystem and not cmdenv.sortByPrice: results.summary.sort = "Dist" results.rows.sort(key=lambda result: result.dist) limit = cmdenv.limit or 0 if limit > 0: results.rows = results.rows[:limit] return results
def run(results, cmdenv, tdb): if cmdenv.lt and cmdenv.gt: if cmdenv.lt <= cmdenv.gt: raise CommandLineError("--gt must be lower than --lt") # Find out what we're looking for. queries, mode = get_lookup_list(cmdenv, tdb) cmdenv.DEBUG0("{} query: {}", mode, queries.values()) avoidSystems = {s for s in cmdenv.avoidPlaces if isinstance(s, System)} avoidStations = {s for s in cmdenv.avoidPlaces if isinstance(s, Station)} # Summarize results.summary = ResultRow() results.summary.mode = mode results.summary.queries = queries results.summary.oneStop = cmdenv.oneStop results.summary.avoidSystems = avoidSystems results.summary.avoidStations = avoidStations # In single mode with detail enabled, add average reports. # Thus if you're looking up "algae" or the "asp", it'll # tell you the average/ship cost. singleMode = len(queries) == 1 if singleMode and cmdenv.detail: first = list(queries.values())[0] if mode is SHIP_MODE: results.summary.avg = first.cost else: avgPrice = tdb.query(""" SELECT AVG(si.supply_price) FROM StationItem AS si WHERE si.item_id = ? AND si.supply_price > 0 """, [first.ID]).fetchone()[0] results.summary.avg = int(avgPrice) # System-based search nearSystem = cmdenv.nearSystem if nearSystem: maxLy = cmdenv.maxLyPer or tdb.maxSystemLinkLy results.summary.near = nearSystem results.summary.ly = maxLy distanceFn = nearSystem.distanceTo else: distanceFn = None oneStopMode = cmdenv.oneStop padSize = cmdenv.padSize wantBlackMarket = cmdenv.blackMarket stations = defaultdict(list) stationByID = tdb.stationByID cur = sql_query(cmdenv, tdb, queries, mode) for (ID, stationID, price, units) in cur: station = stationByID[stationID] if padSize and not station.checkPadSize(padSize): continue if wantBlackMarket and station.blackMarket != 'Y': continue if station in avoidStations: continue if station.system in avoidSystems: continue row = ResultRow() row.station = station if distanceFn: distance = distanceFn(row.station.system) if distance > maxLy: continue row.dist = distance row.item = queries[ID] row.price = price row.units = units row.age = station.itemDataAgeStr if oneStopMode: stationRows = stations[stationID] stationRows.append(row) if len(stationRows) >= len(queries): results.rows.extend(stationRows) else: results.rows.append(row) if not results.rows: if oneStopMode and len(stations): raise NoDataError("No one-stop stations found") raise NoDataError("No available items found") if oneStopMode and not singleMode: results.rows.sort(key=lambda result: result.item.name()) results.rows.sort(key=lambda result: result.station.name()) if cmdenv.sortByUnits: results.summary.sort = "units" results.rows.sort(key=lambda result: result.price) results.rows.sort(key=lambda result: result.units, reverse=True) else: if not oneStopMode: results.summary.sort = "Price" results.rows.sort(key=lambda result: result.units, reverse=True) results.rows.sort(key=lambda result: result.price) if nearSystem and not cmdenv.sortByPrice: results.summary.sort = "Ly" results.rows.sort(key=lambda result: result.dist) limit = cmdenv.limit or 0 if limit > 0: results.rows = results.rows[:limit] return results
def run(results, cmdenv, tdb): cmdenv.DEBUG1("loading trades") if tdb.tradingCount == 0: raise NoDataError("Database does not contain any profitable trades.") # Instantiate the calculator object calc = TradeCalc(tdb, cmdenv) validateRunArguments(tdb, cmdenv, calc) origPlace, viaSet = cmdenv.origPlace, cmdenv.viaSet avoidPlaces = cmdenv.avoidPlaces stopStations = cmdenv.destinations goalSystem = cmdenv.goalSystem maxLs = cmdenv.maxLs # seed the route table with starting places startCr = cmdenv.credits - cmdenv.insurance routes = [ Route( stations=(src,), hops=(), jumps=(), startCr=startCr, gainCr=0, score=0 ) for src in cmdenv.origins ] numHops = cmdenv.hops lastHop = numHops - 1 viaStartPos = 1 if origPlace else 0 cmdenv.DEBUG1("numHops {}, vias {}, adhocHops {}", numHops, len(viaSet), cmdenv.adhocHops) results.summary = ResultRow() results.summary.exception = "" if cmdenv.loop: routePickPred = lambda route: \ route.lastStation is route.firstStation elif cmdenv.shorten: if not cmdenv.destPlace: routePickPred = lambda route: \ route.lastStation is route.firstStation elif isinstance(cmdenv.destPlace, System): routePickPred = lambda route: \ route.lastSystem is cmdenv.destPlace else: routePickPred = lambda route: \ route.lastStation is cmdenv.destPlace else: routePickPred = None pickedRoutes = [] pruneMod = cmdenv.pruneScores / 100 if cmdenv.loop: distancePruning = lambda rt, distLeft: \ rt.lastSystem.distanceTo(rt.firstSystem) <= distLeft elif cmdenv.destPlace and not cmdenv.direct: distancePruning = lambda rt, distLeft: \ any( stop for stop in stopSystems if rt.lastSystem.distanceTo(stop) <= distLeft ) else: distancePruning = False if distancePruning: maxHopDistLy = cmdenv.maxJumpsPer * cmdenv.maxLyPer if not cmdenv.loop: stopSystems = {stop.system for stop in stopStations} for hopNo in range(numHops): restrictTo = None if hopNo == lastHop and stopStations: restrictTo = set(stopStations) manualRestriction = bool(cmdenv.destPlace) elif len(viaSet) > cmdenv.adhocHops: restrictTo = viaSet manualRestriction = True if distancePruning: preCrop = len(routes) distLeft = maxHopDistLy * (numHops - hopNo) routes = [rt for rt in routes if distancePruning(rt, distLeft)] if not routes: if pickedRoutes: break raise NoDataError( "No routes are in-range of any end stations at the end of hop {}" .format(hopNo) ) pruned = preCrop - len(routes) if pruned: cmdenv.NOTE("Pruned {} origins too far from any end stations", pruned) if hopNo >= 1 and (cmdenv.maxRoutes or pruneMod): routes.sort() if pruneMod and hopNo + 1 >= cmdenv.pruneHops and len(routes) > 10: crop = int(len(routes) * pruneMod) routes = routes[:-crop] cmdenv.NOTE("Pruned {} origins", crop) if cmdenv.maxRoutes and len(routes) > cmdenv.maxRoutes: routes = routes[:cmdenv.maxRoutes] if cmdenv.progress: extra = "" if hopNo > 0 and cmdenv.detail > 1: extra = extraRouteProgress(routes) print( "* Hop {:3n}: {:.>10n} origins {}" .format(hopNo+1, len(routes), extra) ) elif cmdenv.debug: cmdenv.DEBUG0("Hop {}...", hopNo+1) try: newRoutes = calc.getBestHops(routes, restrictTo=restrictTo) except NoHopsError: if hopNo == 0 and len(cmdenv.origSystems) == 1: raise NoDataError( "Couldn't find any trading links within {} x {}ly jumps of {}." .format( cmdenv.maxJumpsPer, cmdenv.maxLyPer, cmdenv.origSystems[0].name(), ) ) raise NoDataError( "No routes had reachable trading links at hop #{}".format(hopNo + 1) ) if not newRoutes: if pickedRoutes: break checkReachability(tdb, cmdenv) if hopNo > 0: if restrictTo and manualRestriction: results.summary.exception += routeFailedRestrictions( tdb, cmdenv, restrictTo, maxLs, hopNo ) break results.summary.exception += ( "SORRY: Could not find profitable destinations " "beyond hop #{:n}\n" .format(hopNo + 1) ) break if hopNo == 0: if cmdenv.origPlace and len(routes) == 1: errText = ( "No profitable buyers found for the goods at {}.\n" "\n" "You may want to try:\n" " {} local \"{}\" --ly {} -vv --stations --trading" .format( routes[0].lastStation.name(), sys.argv[0], cmdenv.origPlace.system.name(), cmdenv.maxJumpsPer * cmdenv.maxLyPer, ) ) if isinstance(cmdenv.origPlace, Station): errText += ( "\n" "or:\n" " {} market \"{}\" --sell -vv" .format( sys.argv[0], cmdenv.origPlace.name(), ) ) raise NoDataError(errText) routes = newRoutes if routes and goalSystem: # Promote the winning route to the top of the list # while leaving the remainder of the list intact routes.sort( key=lambda route: 0 if route.lastSystem is goalSystem else 1 ) if routes[0].lastSystem is goalSystem: cmdenv.NOTE("Goal system reached!") routes = routes[:1] break if routePickPred: pickedRoutes.extend( route for route in routes if routePickPred(route) ) if cmdenv.loop or cmdenv.shorten: cmdenv.DEBUG0("Using {} picked routes", len(pickedRoutes)) routes = pickedRoutes # normalise the scores for fairness... for route in routes: cmdenv.DEBUG0( "{} hops, {} score, {} gpt", len(route.hops), route.score, route.gpt ) route.score /= len(route.hops) if not routes: raise NoDataError( "No profitable trades matched your critera, " "or price data along the route is missing." ) if viaSet: routes, caution = filterByVia(routes, viaSet, viaStartPos) if caution: results.summary.exception += caution + "\n" routes.sort() results.data = routes return results
def run(results, cmdenv, tdb): if cmdenv.lt and cmdenv.gt: if cmdenv.lt <= cmdenv.gt: raise CommandLineError("--gt must be lower than --lt") # Find out what we're looking for. queries, mode = get_lookup_list(cmdenv, tdb) cmdenv.DEBUG0("{} query: {}", mode, queries.values()) avoidSystems = {s for s in cmdenv.avoidPlaces if isinstance(s, System)} avoidStations = {s for s in cmdenv.avoidPlaces if isinstance(s, Station)} # Summarize results.summary = ResultRow() results.summary.mode = mode results.summary.queries = queries results.summary.oneStop = cmdenv.oneStop results.summary.avoidSystems = avoidSystems results.summary.avoidStations = avoidStations # In single mode with detail enabled, add average reports. # Thus if you're looking up "algae" or the "asp", it'll # tell you the average/ship cost. singleMode = len(queries) == 1 if singleMode and cmdenv.detail: first = list(queries.values())[0] if mode is SHIP_MODE: results.summary.avg = first.cost else: avgPrice = tdb.query( """ SELECT AVG(si.supply_price) FROM StationItem AS si WHERE si.item_id = ? AND si.supply_price > 0 """, [first.ID]).fetchone()[0] if not avgPrice: avgPrice = 0 results.summary.avg = int(avgPrice) # System-based search nearSystem = cmdenv.nearSystem if nearSystem: maxLy = cmdenv.maxLyPer or tdb.maxSystemLinkLy results.summary.near = nearSystem results.summary.ly = maxLy distanceFn = nearSystem.distanceTo else: distanceFn = None oneStopMode = cmdenv.oneStop padSize = cmdenv.padSize planetary = cmdenv.planetary wantNoPlanet = cmdenv.noPlanet wantBlackMarket = cmdenv.blackMarket stations = defaultdict(list) stationByID = tdb.stationByID cur = sql_query(cmdenv, tdb, queries, mode) for (ID, stationID, price, units) in cur: station = stationByID[stationID] if padSize and not station.checkPadSize(padSize): continue if planetary and not station.checkPlanetary(planetary): continue if wantNoPlanet and station.planetary != 'N': continue if wantBlackMarket and station.blackMarket != 'Y': continue if station in avoidStations: continue if station.system in avoidSystems: continue row = ResultRow() row.station = station if distanceFn: distance = distanceFn(row.station.system) if distance > maxLy: continue row.dist = distance row.item = queries[ID] row.price = price row.units = units row.age = station.itemDataAgeStr if oneStopMode: stationRows = stations[stationID] stationRows.append(row) if len(stationRows) >= len(queries): results.rows.extend(stationRows) else: results.rows.append(row) if not results.rows: if oneStopMode and len(stations): raise NoDataError("No one-stop stations found") raise NoDataError("No available items found") if oneStopMode and not singleMode: results.rows.sort(key=lambda result: result.item.name()) results.rows.sort(key=lambda result: result.station.name()) if cmdenv.sortByUnits: results.summary.sort = "units" results.rows.sort(key=lambda result: result.price) results.rows.sort(key=lambda result: result.units, reverse=True) else: if not oneStopMode: results.summary.sort = "Price" results.rows.sort(key=lambda result: result.units, reverse=True) results.rows.sort(key=lambda result: result.price) if nearSystem and not cmdenv.sortByPrice: results.summary.sort = "Ly" results.rows.sort(key=lambda result: result.dist) limit = cmdenv.limit or 0 if limit > 0: results.rows = results.rows[:limit] return results
def run(results, cmdenv, tdb): cmdenv = results.cmdenv tdb = cmdenv.tdb srcSystem = cmdenv.nearSystem ly = cmdenv.maxLyPer if ly is None: ly = tdb.maxSystemLinkLy results.summary = ResultRow() results.summary.near = srcSystem results.summary.ly = ly results.summary.stations = 0 distances = { srcSystem: 0.0 } # Calculate the bounding dimensions for destSys, dist in tdb.genSystemsInRange(srcSystem, ly): distances[destSys] = dist showStations = cmdenv.detail wantStations = cmdenv.stations padSize = cmdenv.padSize wantTrading = cmdenv.trading wantShipYard = cmdenv.shipyard wantBlackMarket = cmdenv.blackMarket wantOutfitting = cmdenv.outfitting wantRearm = cmdenv.rearm wantRefuel = cmdenv.refuel wantRepair = cmdenv.repair def station_filter(stations): for station in stations: if wantTrading and not station.isTrading: continue if wantBlackMarket and station.blackMarket != 'Y': continue if wantShipYard and station.shipyard != 'Y': continue if padSize and not station.checkPadSize(padSize): continue if wantOutfitting and station.outfitting != 'Y': continue if wantRearm and station.rearm != 'Y': continue if wantRefuel and station.refuel != 'Y': continue if wantRepair and station.repair != 'Y': continue yield station for (system, dist) in sorted(distances.items(), key=lambda x: x[1]): if showStations or wantStations: stations = [] for (station) in station_filter(system.stations): stations.append( ResultRow( station=station, age=station.itemDataAgeStr, ) ) if not stations and wantStations: continue row = ResultRow() row.system = system row.dist = dist row.stations = stations if showStations else [] results.rows.append(row) results.summary.stations += len(row.stations) return results