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:
2026-10-20 13:05:51 +02:00
co-authored by Claude Sonnet 5
parent bf15c709ea
commit e2a5149788
2 changed files with 65 additions and 41 deletions
+49 -30
View File
@@ -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;
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,
PROVISION_SHIPMENT and COMEX_PURCHASE_PICKUP (a material+qty+address you must supply - these are the
only ones with a real market buy-fresh cost), PICKUP_SHIPMENT/DELIVERY_SHIPMENT (logistics legs, no
separate cost beyond the material), 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.
Each condition's own `Party` field ("CUSTOMER" or "PROVIDER") says who performs it, checked
against the contract's own top-level `Party` field (the player's role in that contract - usually
"CUSTOMER"). Condition vocabulary, direction confirmed against real contract JSON (2026-10-17):
PAYMENT/REPUTATION (always granted TO the player by the PROVIDER, regardless of which `Party` the
condition lists), PROVISION_SHIPMENT (the player's `Party` supplies this material - a real COST,
buy-fresh-priced), COMEX_PURCHASE_PICKUP (the partner already bought this via Comex - a REWARD
handed to the player, sell-valued), PICKUP_SHIPMENT/DELIVERY_SHIPMENT (a logistics leg performed
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
MATERIAL_TYPES = {"PROVISION_SHIPMENT", "COMEX_PURCHASE_PICKUP"}
def open_contracts():
me = fio.me()
@@ -24,36 +29,48 @@ def open_contracts():
def evaluate(contract: dict, cx: str) -> dict:
"""Payment, priced material requirements (buy-fresh cost at `cx` - assumes none already in free
stock, check storage for materials you may already hold), and any unpriced/contextual conditions
flagged separately for the caller to judge."""
"""Payment, materials the contract hands TO the player (COMEX_PURCHASE_PICKUP, sell-valued),
materials the player must supply (PROVISION_SHIPMENT on the player's own Party, buy-fresh
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
reputation = False
requirements = [] # (ticker, qty, address)
flags = [] # (type, address) for conditions this module won't price
rewards, costs, freight_legs = [], [], []
flags = []
deadline_days = 0.0
for cond in contract["Conditions"]:
t = cond["Type"]
mine = cond.get("Party") == my_party
if t == "PAYMENT" and cond.get("Amount"):
payment += cond["Amount"]
elif t == "REPUTATION":
reputation = True
elif t in MATERIAL_TYPES and cond.get("MaterialTicker"):
requirements.append((cond["MaterialTicker"], cond["MaterialAmount"], cond.get("Address")))
elif t not in MATERIAL_TYPES | {"PAYMENT", "REPUTATION", "PICKUP_SHIPMENT", "DELIVERY_SHIPMENT"}:
elif t == "COMEX_PURCHASE_PICKUP" and cond.get("MaterialTicker"):
rewards.append((cond["MaterialTicker"], cond["MaterialAmount"], cond.get("Address")))
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")))
dl = cond.get("DeadlineDurationMs")
if dl:
deadline_days = max(deadline_days, dl / 86400000)
material_cost = 0.0
short = False
priced = []
for tick, qty, addr in requirements:
r = market.walk(tick, cx, qty, "buy")
priced.append({"ticker": tick, "qty": qty, "address": addr, "cost": r["total"], "short": r["short"]})
material_cost += r["total"]
short = short or r["short"]
def price_all(items, side):
total, priced = 0.0, []
for tick, qty, addr in items:
r = market.walk(tick, cx, qty, side)
priced.append({"ticker": tick, "qty": qty, "address": addr, "value": r["total"], "short": r["short"]})
total += r["total"]
return total, priced
reward_value, priced_rewards = price_all(rewards, "sell")
cost_value, priced_costs = price_all(costs, "buy")
return {
"id": contract["ContractLocalId"],
@@ -61,9 +78,11 @@ def evaluate(contract: dict, cx: str) -> dict:
"payment": payment,
"reputation": reputation,
"deadline_days": deadline_days,
"requirements": priced,
"material_cost": material_cost,
"net": payment - material_cost,
"short": short,
"rewards": priced_rewards,
"reward_value": reward_value,
"costs": priced_costs,
"cost_value": cost_value,
"freight_legs": freight_legs,
"net": payment + reward_value - cost_value,
"flags": flags,
}