contd.py: fix condition direction, separate costs/rewards/freight legs
PAYMENT/REPUTATION always go to the player; PROVISION_SHIPMENT (a cost) and PICKUP/DELIVERY_SHIPMENT (a freight leg the player may or may not fly) depend on the condition's own Party field vs the contract's Party, not a flat assumption that every material condition is something the player buys. Net now correctly adds COMEX_PURCHASE_PICKUP rewards instead of only subtracting provisioning costs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+49
-30
@@ -3,19 +3,24 @@ offers shown in APEX's CONTD panel, not the public Local Market hauling ads in p
|
|||||||
Only OPEN contracts are real choices - exactly one of the pool of (usually 3) offers can be accepted;
|
Only OPEN contracts are real choices - exactly one of the pool of (usually 3) offers can be accepted;
|
||||||
CANCELLED/REJECTED/FULFILLED/PARTIALLY_FULFILLED are history or already-committed work.
|
CANCELLED/REJECTED/FULFILLED/PARTIALLY_FULFILLED are history or already-committed work.
|
||||||
|
|
||||||
Condition vocabulary seen in this account's full history (2026-10-16): PAYMENT, REPUTATION,
|
Each condition's own `Party` field ("CUSTOMER" or "PROVIDER") says who performs it, checked
|
||||||
PROVISION_SHIPMENT and COMEX_PURCHASE_PICKUP (a material+qty+address you must supply - these are the
|
against the contract's own top-level `Party` field (the player's role in that contract - usually
|
||||||
only ones with a real market buy-fresh cost), PICKUP_SHIPMENT/DELIVERY_SHIPMENT (logistics legs, no
|
"CUSTOMER"). Condition vocabulary, direction confirmed against real contract JSON (2026-10-17):
|
||||||
separate cost beyond the material), and open-ended ones this module deliberately does NOT price
|
PAYMENT/REPUTATION (always granted TO the player by the PROVIDER, regardless of which `Party` the
|
||||||
because their real cost depends on context this API can't see (a ship already flying that route, a
|
condition lists), PROVISION_SHIPMENT (the player's `Party` supplies this material - a real COST,
|
||||||
planet you were about to survey anyway, a ship already damaged): EXPLORATION (Address = planet to
|
buy-fresh-priced), COMEX_PURCHASE_PICKUP (the partner already bought this via Comex - a REWARD
|
||||||
survey), REPAIR_SHIP, HEADQUARTERS_UPGRADE, BASE_CONSTRUCTION, CONTRIBUTION, WAIT,
|
handed to the player, sell-valued), PICKUP_SHIPMENT/DELIVERY_SHIPMENT (a logistics leg performed
|
||||||
INCREASE_SATISFACTION. Judging whether one of those is actually cheap right now is the caller's job.
|
by whichever `Party` it lists - free to the player when that's the PROVIDER's leg, e.g. their
|
||||||
|
transporter collecting what you provisioned; a real freight cost via `puga.shipping` when it's the
|
||||||
|
player's own leg, not priced by this module, flagged with weight/volume/address instead), and
|
||||||
|
open-ended ones this module deliberately does NOT price because their real cost depends on context
|
||||||
|
this API can't see (a ship already flying that route, a planet you were about to survey anyway, a
|
||||||
|
ship already damaged): EXPLORATION (Address = planet to survey), REPAIR_SHIP,
|
||||||
|
HEADQUARTERS_UPGRADE, BASE_CONSTRUCTION, CONTRIBUTION, WAIT, INCREASE_SATISFACTION. Judging
|
||||||
|
whether one of those is actually cheap right now is the caller's job.
|
||||||
"""
|
"""
|
||||||
from . import fio, market
|
from . import fio, market
|
||||||
|
|
||||||
MATERIAL_TYPES = {"PROVISION_SHIPMENT", "COMEX_PURCHASE_PICKUP"}
|
|
||||||
|
|
||||||
|
|
||||||
def open_contracts():
|
def open_contracts():
|
||||||
me = fio.me()
|
me = fio.me()
|
||||||
@@ -24,36 +29,48 @@ def open_contracts():
|
|||||||
|
|
||||||
|
|
||||||
def evaluate(contract: dict, cx: str) -> dict:
|
def evaluate(contract: dict, cx: str) -> dict:
|
||||||
"""Payment, priced material requirements (buy-fresh cost at `cx` - assumes none already in free
|
"""Payment, materials the contract hands TO the player (COMEX_PURCHASE_PICKUP, sell-valued),
|
||||||
stock, check storage for materials you may already hold), and any unpriced/contextual conditions
|
materials the player must supply (PROVISION_SHIPMENT on the player's own Party, buy-fresh
|
||||||
flagged separately for the caller to judge."""
|
cost), freight legs the player must personally fly (PICKUP/DELIVERY_SHIPMENT on the player's
|
||||||
|
own Party - weight/volume flagged, not priced), and any other unpriced/contextual conditions -
|
||||||
|
all for the caller to weigh. net = payment + reward_value - cost_value (freight excluded,
|
||||||
|
flagged separately)."""
|
||||||
|
my_party = contract.get("Party") # the player's role in THIS contract, e.g. "CUSTOMER"
|
||||||
payment = 0.0
|
payment = 0.0
|
||||||
reputation = False
|
reputation = False
|
||||||
requirements = [] # (ticker, qty, address)
|
rewards, costs, freight_legs = [], [], []
|
||||||
flags = [] # (type, address) for conditions this module won't price
|
flags = []
|
||||||
deadline_days = 0.0
|
deadline_days = 0.0
|
||||||
for cond in contract["Conditions"]:
|
for cond in contract["Conditions"]:
|
||||||
t = cond["Type"]
|
t = cond["Type"]
|
||||||
|
mine = cond.get("Party") == my_party
|
||||||
if t == "PAYMENT" and cond.get("Amount"):
|
if t == "PAYMENT" and cond.get("Amount"):
|
||||||
payment += cond["Amount"]
|
payment += cond["Amount"]
|
||||||
elif t == "REPUTATION":
|
elif t == "REPUTATION":
|
||||||
reputation = True
|
reputation = True
|
||||||
elif t in MATERIAL_TYPES and cond.get("MaterialTicker"):
|
elif t == "COMEX_PURCHASE_PICKUP" and cond.get("MaterialTicker"):
|
||||||
requirements.append((cond["MaterialTicker"], cond["MaterialAmount"], cond.get("Address")))
|
rewards.append((cond["MaterialTicker"], cond["MaterialAmount"], cond.get("Address")))
|
||||||
elif t not in MATERIAL_TYPES | {"PAYMENT", "REPUTATION", "PICKUP_SHIPMENT", "DELIVERY_SHIPMENT"}:
|
elif t == "PROVISION_SHIPMENT" and cond.get("MaterialTicker") and mine:
|
||||||
|
costs.append((cond["MaterialTicker"], cond["MaterialAmount"], cond.get("Address")))
|
||||||
|
elif t in ("PICKUP_SHIPMENT", "DELIVERY_SHIPMENT") and mine:
|
||||||
|
freight_legs.append((t, cond.get("Address"), cond.get("Weight"), cond.get("Volume")))
|
||||||
|
elif t not in ("PAYMENT", "REPUTATION", "COMEX_PURCHASE_PICKUP", "PROVISION_SHIPMENT",
|
||||||
|
"PICKUP_SHIPMENT", "DELIVERY_SHIPMENT"):
|
||||||
flags.append((t, cond.get("Address")))
|
flags.append((t, cond.get("Address")))
|
||||||
dl = cond.get("DeadlineDurationMs")
|
dl = cond.get("DeadlineDurationMs")
|
||||||
if dl:
|
if dl:
|
||||||
deadline_days = max(deadline_days, dl / 86400000)
|
deadline_days = max(deadline_days, dl / 86400000)
|
||||||
|
|
||||||
material_cost = 0.0
|
def price_all(items, side):
|
||||||
short = False
|
total, priced = 0.0, []
|
||||||
priced = []
|
for tick, qty, addr in items:
|
||||||
for tick, qty, addr in requirements:
|
r = market.walk(tick, cx, qty, side)
|
||||||
r = market.walk(tick, cx, qty, "buy")
|
priced.append({"ticker": tick, "qty": qty, "address": addr, "value": r["total"], "short": r["short"]})
|
||||||
priced.append({"ticker": tick, "qty": qty, "address": addr, "cost": r["total"], "short": r["short"]})
|
total += r["total"]
|
||||||
material_cost += r["total"]
|
return total, priced
|
||||||
short = short or r["short"]
|
|
||||||
|
reward_value, priced_rewards = price_all(rewards, "sell")
|
||||||
|
cost_value, priced_costs = price_all(costs, "buy")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"id": contract["ContractLocalId"],
|
"id": contract["ContractLocalId"],
|
||||||
@@ -61,9 +78,11 @@ def evaluate(contract: dict, cx: str) -> dict:
|
|||||||
"payment": payment,
|
"payment": payment,
|
||||||
"reputation": reputation,
|
"reputation": reputation,
|
||||||
"deadline_days": deadline_days,
|
"deadline_days": deadline_days,
|
||||||
"requirements": priced,
|
"rewards": priced_rewards,
|
||||||
"material_cost": material_cost,
|
"reward_value": reward_value,
|
||||||
"net": payment - material_cost,
|
"costs": priced_costs,
|
||||||
"short": short,
|
"cost_value": cost_value,
|
||||||
|
"freight_legs": freight_legs,
|
||||||
|
"net": payment + reward_value - cost_value,
|
||||||
"flags": flags,
|
"flags": flags,
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-11
@@ -1,10 +1,11 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""List this account's OPEN CONTD contract offers (the NPC logistics-officer pool shown in APEX's
|
"""List this account's OPEN CONTD contract offers (the NPC logistics-officer pool shown in APEX's
|
||||||
CONTD panel - usually 3 at a time, only one acceptable) with payment, priced material requirements,
|
CONTD panel - usually 3 at a time, only one acceptable) with payment, materials received/supplied,
|
||||||
and net. Judging the actual best deal is NOT this tool's job for anything it flags as unpriced
|
freight legs the player must personally fly, and net (see puga/contd.py's docstring for exactly
|
||||||
(EXPLORATION, REPAIR_SHIP, etc. - see puga/contd.py's docstring) or for situational cheapness (a ship
|
which condition types are costs vs rewards vs the player's own job). Judging the actual best deal
|
||||||
already flying that route, material already in free/uncommitted storage) - it reports the breakdown,
|
is NOT this tool's job for anything it flags as unpriced (EXPLORATION, REPAIR_SHIP, etc.) or for
|
||||||
the caller reasons about context.
|
a freight leg's real fuel cost (flagged with weight/volume, price via puga.shipping yourself) - it
|
||||||
|
reports the breakdown, the caller reasons about context.
|
||||||
|
|
||||||
puga contd
|
puga contd
|
||||||
puga contd --cx AI1
|
puga contd --cx AI1
|
||||||
@@ -30,18 +31,22 @@ def main():
|
|||||||
print(f"\n{r['id']} partner={r['partner']} deadline {r['deadline_days']:.1f}d"
|
print(f"\n{r['id']} partner={r['partner']} deadline {r['deadline_days']:.1f}d"
|
||||||
+ (" +reputation" if r["reputation"] else ""))
|
+ (" +reputation" if r["reputation"] else ""))
|
||||||
print(f" payment: {r['payment']:,.2f} AIC")
|
print(f" payment: {r['payment']:,.2f} AIC")
|
||||||
for req in r["requirements"]:
|
for rew in r["rewards"]:
|
||||||
flag = " SHORT: book can't fill this qty" if req["short"] else ""
|
print(f" receives: {rew['ticker']} x{rew['qty']:g} @ {rew['address']} market value {rew['value']:,.0f}")
|
||||||
print(f" requires: {req['ticker']} x{req['qty']:g} @ {req['address']} buy-fresh cost {req['cost']:,.0f}{flag}")
|
for cost in r["costs"]:
|
||||||
|
flag = " SHORT: book can't fill this qty" if cost["short"] else ""
|
||||||
|
print(f" must supply: {cost['ticker']} x{cost['qty']:g} @ {cost['address']} buy-fresh cost {cost['value']:,.0f}{flag}")
|
||||||
|
for ltype, addr, wgt, vol in r["freight_legs"]:
|
||||||
|
print(f" YOUR FREIGHT LEG: {ltype} @ {addr} {wgt:g}t / {vol:g}m3 - price via puga.shipping, not included in net")
|
||||||
for ftype, addr in r["flags"]:
|
for ftype, addr in r["flags"]:
|
||||||
print(f" UNPRICED CONDITION: {ftype}" + (f" @ {addr}" if addr else "") + " - context-dependent, judge separately")
|
print(f" UNPRICED CONDITION: {ftype}" + (f" @ {addr}" if addr else "") + " - context-dependent, judge separately")
|
||||||
print(f" material cost: {r['material_cost']:,.0f} -> net (payment - buy-fresh cost): {r['net']:,.0f}")
|
print(f" reward value: {r['reward_value']:,.0f} cost value: {r['cost_value']:,.0f} -> net (payment + reward - cost): {r['net']:,.0f}")
|
||||||
|
|
||||||
priceable = [c for c in contracts if not contd.evaluate(c, a.cx)["flags"]]
|
priceable = [c for c in contracts if not contd.evaluate(c, a.cx)["flags"]]
|
||||||
if len(priceable) == len(contracts):
|
if len(priceable) == len(contracts):
|
||||||
best = max(contracts, key=lambda c: contd.evaluate(c, a.cx)["net"])
|
best = max(contracts, key=lambda c: contd.evaluate(c, a.cx)["net"])
|
||||||
print(f"\nby buy-fresh net alone, best priced: {best['ContractLocalId']}"
|
print(f"\nby payment+reward net alone, best priced: {best['ContractLocalId']}"
|
||||||
" - but check storage for materials you may already hold before trusting this")
|
" - unpriced/flagged conditions (freight legs, exploration, etc.) still need judgment")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user