Trade & store logistics: per-item market price intelligence + fair-value cross-reference #49
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), built on the trades-ledger foundation (#6). The fair-price advisor (
src/eco_mcp_app/fair_price.py) today maps Eco items to real-world FRED commodities but openly laments it has no in-game price to compare against (see its "Day 3 of Cycle 13 reality" docstring). TheCurrencyTradeexporter already ships every individual trade, so the in-game price is derivable now, with no server restart.No-reset spine
Data comes entirely from data the live server already exports:
GET /api/v1/exporter/actions?actionName=CurrencyTrade(columns per #6:BankAccount, Currency, CurrencyAmount, NumberOfItems, BoughtOrSold, ShopOwner, Buyer, Seller, WorldObjectItem, ItemUsed, Citizen, ActionLocation, Count, Time). No new C# mod, no server reset. Reuse the streamed-CSV aggregation pattern incrafting.py, the shifted-column corrector (#5), and theBoughtOrSold32/33 decode (#6). If #6's normalized trade model has already landed, consume it rather than re-parsing.Scope
The agent builds a per-item price intelligence layer:
CurrencyAmount / NumberOfItems, per currency, bucketed daily. Median, min, max, and volume (units traded) per bucket.fair_price.pyso the advisor can finally say "in-game median X vs real-world trend Y -> looks over/underpriced," closing the gap its own docstring names. Keep the FRED path working when an item has no in-game trades.get_eco_marketor an extension of the existingfair_pricetool, agent's call) returning text + structured JSON + an_meta.uicard, and a/preview/market.jsonendpoint for the SPA data plane. The/tradeSPA page itself is a sibling issue.DiscordLink parity
DiscordLink shows only a current asking price with no history and no fair-value context. This exceeds it on the price axis: trend, volume, and a real-world benchmark. Live current-shelf price is the one piece deferred to the reset-gated stores exporter (sibling issue).
Acceptance
ward exec smokepasses with it.respx-mocked unit tests for the price math and the fair-value merge.ward exec testandward exec lintgreen.docs/FEATURES.mdupdated for the new surface.Dependencies
Foundation: #6. Parent epic: #37. Sibling logistics engine and
/tradeSPA route consume this.🔒 Reserved by
ward agent --driver claude— containerengineer-claude-eco-app-49on hostKAI-DESKTOP-TOWERis carrying this issue (reserved 2026-07-05T03:26:22Z). 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_market price-intelligence tool + fair_price in-game cross-reference landed on main (closes #49).
The shape of this one was set by #6 doing most of the heavy lifting already. The trades ledger hands back a clean normalized row model, so the market layer is really just a fold: bucket by (item, currency, day), take medians, and diff a short window against a long one.
build_marketcame out as a pure function over row dicts, which made the price math trivial to unit-test with no HTTP at all — that was the satisfying part.What fought back a little: the FRED-vs-in-game "over/underpriced" verdict has no shared unit, so I couldn't just compare numbers. I settled on two honest paths — calibration-anchored when a cycle ratio exists, trend-divergence otherwise (real-world climbing while in-game sits flat reads as underpriced). It closes the gap the module's own docstring kept lamenting without pretending dollars and Credits are the same thing. The other snag was keeping the fair_price merge from breaking the FRED-only tests: gating the exporter fetch on an admin key means keyless hosts (and the existing respx suite) never touch it.
Confidence is high — 333 tests green, lint/mypy/pre-commit clean, smoke lists the tool. Merge with #50 (the sibling stores directory) conflicted in the exact files you'd expect and resolved cleanly. Two honest rough edges worth a follow-up: the market series is built over the ledger's newest-4000-row window (fine for thin early-cycle data, but I'd file an issue before late-cycle), and the trend windows (3d/14d, 5% flat band) are guesses tuned for sparse data, not validated against real volume. The
/tradeSPA page that consumes/preview/market.jsonis the sibling issue, so this is data-plane-only for now.