Trade & store logistics: store & trader directory (surfaces store owner, a DiscordLink gap) #50
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
Part of the trade & store logistics push under the DiscordLink-replacement epic (#37), on the trades-ledger foundation (#6). DiscordLink's
Trades <name>lookup resolves a search term to a store or player and lists that store's offers, but it does not show the store owner (confirmed gap in itsFormatTrades). eco-app can do the join and exceed it.No-reset spine
Everything here derives from the
CurrencyTradeexporter the server already ships, joined to names via the jobs mod's/api/v1/citizenssurface (the crafting atlas already does this id->name join). No new C# mod, no server reset. TheShopOwner,Buyer,Seller, andActionLocationcolumns give store identity, participants, and place. Consume #6's normalized model if it has landed.Scope
The agent builds store and trader directories from trade history:
ActionLocation+ShopOwner): items traded, buy vs sell mix, price points, total volume and value, unique counterparties, and recency (last trade). ResolveShopOwnerto a citizen name, falling back toCitizen #<id>like the atlas does.Trades <player>.get_eco_stores) returning text + structured JSON + an_meta.uicard, plus/preview/stores.jsonfor the SPA data plane.DiscordLink parity
Meets
Trades <player>/Trades <store>from history and exceeds by surfacing the store owner and per-trader/per-store volume and recency that DiscordLink omits. The one thing history cannot give is the current shelf snapshot (what is listed right now) - that is the reset-gated stores exporter (sibling issue), which this directory upgrades to live once it lands.Acceptance
ward exec smokepasses.Citizen #<id>).respx-mocked unit tests for the aggregation and the citizen join.ward exec test/ward exec lintgreen.docs/FEATURES.mdupdated.Dependencies
Foundation: #6 and the jobs
/api/v1/citizensjoin. Parent: #37. The logistics engine (sibling) consumes these profiles.The live store-offer exporter mod landed in #55 (`mods/stores`, `GET /api/v1/stores`). The DTO this directory upgrades to for shelf-accurate owner/currency/location is documented here: mods/stores/docs/dto.md. A C# shell harness serves the same shape on :5101 (
ward exec run-shell-stores) so the live path can be wired with no server restart.🔒 Reserved by
ward agent --driver claude— containerengineer-claude-eco-app-50on hostKAI-DESKTOP-TOWERis carrying this issue (reserved 2026-07-05T03:27:15Z). 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 - get_eco_stores MCP tool + /preview/stores.json land store & trader directories from trade history, surfacing the store owner DiscordLink omits.
The shape of this one fell out cleanly because #6 did the hard part: the CurrencyTrade parser, the citizen id->name join, and the defensive realignment were all already there. The main design call was resisting the urge to re-fetch — I pulled the ledger's fetch loop up into a shared
trades.fetch_parsed_tradesspine so the ledger and this directory fold the same streamed rows once, rather than hitting the exporter twice. That refactor was the only change to landed code, and the existing trades tests passing unchanged gave me confidence it was behavior-preserving.The one thing I second-guessed: buy-vs-sell mix. The obvious source is the
BoughtOrSoldenum, but #6 flagged it as an undecoded guess. Reading the split structurally instead — owner-as-seller is a sale, owner-as-buyer a purchase, straight off the party columns — is both more robust and needs no decode, so a wrong enum polarity can never corrupt the mix. That felt like the right call and made the tests cleaner too.Confident in the aggregation and the graceful-degradation paths (empty history, unknown ids, partial CSV failure all covered). Rougher edges / follow-ups worth filing: (1) no dedicated SPA page yet — I shipped the
/preview/stores.jsondata plane matching the #53 currency precedent, but a/storesReact route is a natural next step; (2) this is history-only, and the live shelf exporter already landed in #55 (GET /api/v1/stores) — wiring these profiles to that DTO for shelf-accurate current-offer data is the obvious upgrade and the reason I kept the store keying on owner+location so the join is easy.