Trades ledger: pull individual CurrencyTrade/BarterTrade rows, not just counters #6

Closed
opened 2026-06-12 14:06:41 +00:00 by coilysiren · 4 comments
Owner

The economy card only consumes aggregate time-series counters, but the exporter ships every individual trade: /api/v1/exporter/actions?actionName=CurrencyTrade returned 2,730 rows live (cycle 13, day 56) with columns BankAccount, Currency, CurrencyAmount, NumberOfItems, BoughtOrSold, ShopOwner, Buyer, Seller, WorldObjectItem, ItemUsed, Citizen, ActionLocation, Count, Time. BarterTrade exists too (empty this cycle).

Target: a row-level trades surface (likely /trades in the SPA, cross-linked with /economy and /crafting per the wave-0 pattern) - who sold what to whom, for how much, where, when. A price-over-time series per item falls out of this almost for free.

Messy bits to clean en route (filed per the pull-everything rule in AGENTS.md):

  • Numeric party ids - Buyer/Seller/ShopOwner/Citizen are in-game numeric ids; blocked on the id-to-name join (#5). /api/v1/users exposes Name/PlayFabId/SteamId but no numeric id, so the join key is still missing a link.
  • BoughtOrSold is an undecoded enum - live values 32 and 33; needs decoding against Eco's GameActions source (likely Bought/Sold).
  • Time semantics - integer seconds since cycle start (same convention as the species population CSV); convert to in-game day for display.
  • Same misalignment risk as the crafting CSVs (#5) - parse defensively.
The economy card only consumes aggregate time-series counters, but the exporter ships **every individual trade**: `/api/v1/exporter/actions?actionName=CurrencyTrade` returned 2,730 rows live (cycle 13, day 56) with columns `BankAccount, Currency, CurrencyAmount, NumberOfItems, BoughtOrSold, ShopOwner, Buyer, Seller, WorldObjectItem, ItemUsed, Citizen, ActionLocation, Count, Time`. `BarterTrade` exists too (empty this cycle). Target: a row-level trades surface (likely `/trades` in the SPA, cross-linked with /economy and /crafting per the wave-0 pattern) - who sold what to whom, for how much, where, when. A price-over-time series per item falls out of this almost for free. Messy bits to clean en route (filed per the pull-everything rule in AGENTS.md): * **Numeric party ids** - Buyer/Seller/ShopOwner/Citizen are in-game numeric ids; blocked on the id-to-name join (#5). `/api/v1/users` exposes `Name/PlayFabId/SteamId` but no numeric id, so the join key is still missing a link. * **BoughtOrSold is an undecoded enum** - live values 32 and 33; needs decoding against Eco's GameActions source (likely Bought/Sold). * **Time semantics** - integer seconds since cycle start (same convention as the species population CSV); convert to in-game day for display. * **Same misalignment risk** as the crafting CSVs (#5) - parse defensively.
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-eco-app-6 on host KAI-DESKTOP-TOWER is carrying this issue (reserved 2026-07-05T01:40:10Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

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 agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-eco-app-6` on host `KAI-DESKTOP-TOWER` is carrying this issue (reserved 2026-07-05T01:40:10Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. **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). <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Owner

🔓 Reservation released by ward container reap — container eco-app (--driver claude) exited without launching the agent (smoke-test death, ward#222/#264), so the hold it took is retracted. A plain ward agent retry no longer needs --force.

— Claude (she/her), via ward agent

<!-- ward-agent-reservation-released --> 🔓 Reservation released by `ward container reap` — container `eco-app` (`--driver claude`) exited without launching the agent (smoke-test death, ward#222/#264), so the hold it took is retracted. A plain `ward agent` retry no longer needs `--force`. <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-eco-app-6 on host KAI-DESKTOP-TOWER is carrying this issue (reserved 2026-07-05T02:53:52Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

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 agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-eco-app-6` on host `KAI-DESKTOP-TOWER` is carrying this issue (reserved 2026-07-05T02:53:52Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. **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). <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Owner

WARD-OUTCOME: done - shipped a row-level trades ledger (get_eco_trades tool + /trades SPA page) with top buyers/sellers, per-item price-over-time, id-to-name joins, and defensive parsing; landed on main.

This one felt like assembly more than invention, in the best way. The crafting atlas (#5) had already built almost exactly the machinery a row-level surface needs - streamed CSV folding, the extra-column realignment scorer, the /api/v1/citizens id-to-name join - so most of the backend was extracting fetch_citizen_name_map for sharing and folding rows instead of aggregating them. The nice payoff the issue predicted (price-over-time "falls out almost for free") really did: once you have unit price per row, bucketing by in-game day is a few lines.

What fought back was mostly what I couldn't verify offline. The Eco GameActions source isn't in this container, so BoughtOrSold 32/33 stays a heuristic buy/sell decode - I sidestepped the risk by reading who-sold-to-whom straight off the Buyer/Seller columns, so a wrong enum only mislabels a secondary chip. A test fixture also caught me being sloppy: I put the fake extra column between two adjacent name columns, which the realigner genuinely can't disambiguate (that's a real property of the heuristic, not a bug), so I anchored it against a position column like the crafting test does.

Confident in the parsing and aggregation - they're tested against the real cycle-13 column shape, and lint/mypy/pytest (273) + frontend vitest (35) + build all pass. Two things I can't confirm without live data, both filed in docs/trades.md: the buy/sell polarity, and whether each trade logs one row or a buy-leg+sell-leg pair (if two, row counts and gross currency volume are per-leg). Worth a glance once someone's on a live server - and the new mods/stores shelf exporter that landed on main in parallel is a natural complement (shelf-accurate offers vs my history-derived trades).

WARD-OUTCOME: done - shipped a row-level trades ledger (`get_eco_trades` tool + `/trades` SPA page) with top buyers/sellers, per-item price-over-time, id-to-name joins, and defensive parsing; landed on main. This one felt like assembly more than invention, in the best way. The crafting atlas (#5) had already built almost exactly the machinery a row-level surface needs - streamed CSV folding, the extra-column realignment scorer, the `/api/v1/citizens` id-to-name join - so most of the backend was extracting `fetch_citizen_name_map` for sharing and folding rows instead of aggregating them. The nice payoff the issue predicted (price-over-time "falls out almost for free") really did: once you have unit price per row, bucketing by in-game day is a few lines. What fought back was mostly what I *couldn't* verify offline. The Eco `GameActions` source isn't in this container, so `BoughtOrSold` 32/33 stays a heuristic buy/sell decode - I sidestepped the risk by reading who-sold-to-whom straight off the Buyer/Seller columns, so a wrong enum only mislabels a secondary chip. A test fixture also caught me being sloppy: I put the fake extra column between two adjacent name columns, which the realigner genuinely can't disambiguate (that's a real property of the heuristic, not a bug), so I anchored it against a position column like the crafting test does. Confident in the parsing and aggregation - they're tested against the real cycle-13 column shape, and lint/mypy/pytest (273) + frontend vitest (35) + build all pass. Two things I can't confirm without live data, both filed in docs/trades.md: the buy/sell polarity, and whether each trade logs one row or a buy-leg+sell-leg pair (if two, row counts and gross currency volume are per-leg). Worth a glance once someone's on a live server - and the new `mods/stores` shelf exporter that landed on main in parallel is a natural complement (shelf-accurate offers vs my history-derived trades).
Sign in to join this conversation.
No description provided.