Trade & store logistics engine: cheapest-source, arbitrage spreads, supply-gap finder #51
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
The core trade & store logistics deliverable under the DiscordLink-replacement epic (#37). This is the "logistics" Kai is focused on: not just showing trades, but telling a player where to buy, where to sell, and what to haul. Builds on the price intelligence and store/trader directory siblings, both on the trades-ledger foundation (#6).
No-reset spine
Consumes the already-exported
CurrencyTradehistory via the sibling price and directory modules. No new C# mod, no server reset. Where a computation wants the current shelf (live asking price), it degrades to the recent-median from history and marks the value as history-derived, so it works today and sharpens automatically when the reset-gated stores exporter (sibling) lands.Scope
The agent builds a logistics engine MCP tool (
find_eco_tradeor similar) answering:_meta.uicard, and/preview/logistics.jsonfor the SPA. Each result names the store, owner, price, and freshness so a player can act on it.DiscordLink parity
This has no DiscordLink equivalent - it only lists trades, it never reasons over them. This is the clearest "exceed" in the whole push: turning the trade ledger into buy/sell/haul decisions.
Acceptance
ward exec smokepasses.respx-mocked unit tests for cheapest-source, spread detection, and supply-gap.ward exec test/ward exec lintgreen.docs/FEATURES.mdupdated.Dependencies
Consumes the price-intelligence and store/trader-directory siblings (both on #6). If they have not landed, build against their documented
/preview/*.jsoncontract and integrate. Parent: #37.The live store-offer exporter mod landed in #55 (`mods/stores`, `GET /api/v1/stores`). The offer-level fields this engine needs for cheapest-source / arbitrage / supply-gap (`item`, `buying`, `price`, `quantity` per store) are documented here: mods/stores/docs/dto.md. The C# shell harness serves the same shape on :5101 (
ward exec run-shell-stores), so the live path is wireable offline ahead of the deploy-time restart.🔒 Reserved by
ward agent --driver claude— containerengineer-claude-eco-app-51on hostKAI-DESKTOP-TOWERis carrying this issue (reserved 2026-07-05T04:02:50Z). Concurrentward agentruns are blocked until it finishes or the reservation goes stale (2h0m0s TTL);--forceoverrides.Do not comment on or edit this issue to steer the run while it is reserved. The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a new issue, dispatched fresh — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494).
— Claude (she/her), via
ward agentWARD-OUTCOME: done - find_eco_trade logistics engine (cheapest-source / best-resale / arbitrage / supply-gap) landed on main with respx tests, /preview/logistics.json, and an MCP card.
Retrospective: this one went smoothly because the two siblings (#49 market, #50 stores) had already carved the pattern deep - a normalized fold + a pure builder + respx wiring + a Jinja card - so most of the work was picking the right data model and letting the existing
fetch_parsed_tradesspine carry the network. The design call that mattered was collapsing both inputs (live/api/v1/storesshelf and the history reconstruction) into oneShelfOfferunit so the four boards are a single pure function over a list - that made the acceptance-critical "honest empty / single-store" cases fall out naturally rather than needing special-casing.What fought back a little: getting the buy/sell perspective straight took care. The DTO's
buyingis the store's point of view, and arbitrage flips it again (you buy from a store's sell offer, sell to a store's buy offer), so I leaned on explicit tests to keep the direction honest. Nothing surprising otherwise - lint's ambiguous-unicode check caught a×and an em-dash I'd have missed.Confidence is high on the engine logic (well-covered, pure, deterministic) and the history path (exercised end-to-end against mocked exporter CSVs). The one genuinely unexercised-in-production surface is the live shelf - I tested it against the documented DTO and the C# shell harness shape, but the real reset-gated mod hasn't deployed, so first contact with live data is the thing to watch. The
#54SPA surface merged cleanly and already consumes this endpoint, so the two halves met in the middle as intended.Rough edges / follow-ups worth filing: (1) the thresholds are seeded, not tuned - once real early-cycle shelves exist they'll want a pass against actual spread noise; (2) the in-game median that powers the "over-priced" gap signal is computed per-fetch rather than reusing
market.py's series, so there's a small dedup opportunity if the two ever want one median source of truth.