get_currency attributes every trade to numeric currency IDs; all named currencies report zero #217

Closed
opened 2026-08-12 23:12:49 +00:00 by coilyco-ops · 1 comment
Member

Found during a full 22-tool QA sweep of the eco MCP against Eco via Sirens (eco.coilysiren.me:3001), cycle 14 / day 40, Eco 0.13.0.4, on 2026-08-12.

Observed

get_currency returns 185 currencies. Trade attribution splits perfectly the wrong way:

  • 18 currencies whose name is a bare numeric string (2533707, 2967954, 131058, …) carry all 3,668 trades.
  • The other 167 currencies — the ones with real names — carry 0 trades between them.

get_currency(currency="Spectres") resolves (notFound: false) but returns tradeCount: 0, tradeVolume: 0. Meanwhile find_trade(item="CementItem") shows Spectres is actively in use: 1,114 Cement on sale at 0.6 Spectres, plus a second market in Racines at 0.7.

Root cause evidence

get_trades shows currency: "" on every one of the 526 detailed rows, while the same payload sets trade_currency_column_seen: true. Example row:

{"tradeType":"CurrencyTrade","day":40.05,"buyer":"…","item":"FernCampfireSaladItem",
 "quantity":3,"currency":"","currencyAmount":1.2,"unitPrice":0.4,"store":"StoreItem"}

So the ledger parse is dropping the currency name, aggregation falls back to some numeric key (currency object ID?), and the named-currency list is joined from a different source that never matches.

Expected

Trades attributed to named currencies. get_currency(currency="Spectres") should report the trades find_trade can already see.

Impact

The whole currency view is unusable: money supply, per-currency volume, and the narrative line all describe currencies no player recognises. It also makes the currency filter on get_market / find_trade untrustworthy.

Found during a full 22-tool QA sweep of the eco MCP against Eco via Sirens (`eco.coilysiren.me:3001`), cycle 14 / day 40, Eco 0.13.0.4, on 2026-08-12. ## Observed `get_currency` returns 185 currencies. Trade attribution splits perfectly the wrong way: - 18 currencies whose `name` is a bare numeric string (`2533707`, `2967954`, `131058`, …) carry **all 3,668 trades**. - The other 167 currencies — the ones with real names — carry **0 trades between them**. `get_currency(currency="Spectres")` resolves (`notFound: false`) but returns `tradeCount: 0`, `tradeVolume: 0`. Meanwhile `find_trade(item="CementItem")` shows Spectres is actively in use: 1,114 Cement on sale at 0.6 Spectres, plus a second market in Racines at 0.7. ## Root cause evidence `get_trades` shows `currency: ""` on **every one of the 526 detailed rows**, while the same payload sets `trade_currency_column_seen: true`. Example row: ```json {"tradeType":"CurrencyTrade","day":40.05,"buyer":"…","item":"FernCampfireSaladItem", "quantity":3,"currency":"","currencyAmount":1.2,"unitPrice":0.4,"store":"StoreItem"} ``` So the ledger parse is dropping the currency name, aggregation falls back to some numeric key (currency object ID?), and the named-currency list is joined from a different source that never matches. ## Expected Trades attributed to named currencies. `get_currency(currency="Spectres")` should report the trades `find_trade` can already see. ## Impact The whole currency view is unusable: money supply, per-currency volume, and the `narrative` line all describe currencies no player recognises. It also makes the `currency` filter on `get_market` / `find_trade` untrustworthy.
Author
Member

Landed in 69451e9. Recording the diagnosis and the one live step this needs, because the repository half is done but production attribution is not.

Root cause

The exporter keys a CurrencyTrade row by the Currency object's id; CreateCurrency writes its name. Nothing joined them, and both consumers of that column mishandled it in opposite directions:

  • currency.py kept the raw cell and called snapshot.record(<id>), minting a phantom currency named after the id. That is where the 18 numeric-named entries carrying all 3,668 trades came from.
  • trades.py ran the cell through _clean_name, whose _NONSENSE_KEY_RE treats a bare number as a misalignment artifact and blanks it. That is why all 526 detailed rows read currency: "" while the same payload set trade_currency_column_seen: true.

Both symptoms, one column, one missing join key.

Why the fix touches the mod

No export carried both halves of the key. CreateCurrency has names only; /api/v1/stores has names only; the ledger has ids only. The stores/economy exporter mod already reads both off CurrencyManager, so CurrencyHoldingsDto now emits id next to currency, and eco-app builds the id→name map from that surface.

The map is applied once, to the shared parsed rows in fetch_parsed_trades, so get_trades, get_stores and get_market all inherit one join. The lookup is skipped entirely when no row carries a numeric currency, so a server whose export already names currencies pays nothing.

Operator action required

The Python side ships with this commit, but attribution stays unresolved on Sirens until the rebuilt mod DLL is on the server:

  1. Build mods/stores (ward exec build-mod-stores — verified locally, 0 warnings) and install the resulting EcoStoreExporter.dll.
  2. Restart the Eco server so the DLL loads.
  3. Verify: curl -H "X-API-Key: …" http://eco.coilysiren.me:3001/api/v1/currency-holdings | head should show an "id" field next to "currency".
  4. Then get_currency(currency="Spectres") should report a non-zero tradeCount, and get_trades rows should carry currency: "Spectres" rather than an id.

Until that happens

The behaviour is honest rather than wrong. Unmapped ids are kept — the volume is real — but never presented as currency names: those records carry unresolvedId: true with the raw value under currencyId, and both get_currency and get_trades emit a warning naming how many trades are affected. No caller will read a bare number as a currency a player recognises.

Sibling issues #236 and #234 depend on this join and are being worked next; they should be re-verified against the live server after step 3.

Landed in `69451e9`. Recording the diagnosis and the one live step this needs, because the repository half is done but production attribution is not. ## Root cause The exporter keys a `CurrencyTrade` row by the **Currency object's id**; `CreateCurrency` writes its **name**. Nothing joined them, and both consumers of that column mishandled it in opposite directions: - `currency.py` kept the raw cell and called `snapshot.record(<id>)`, minting a phantom currency named after the id. That is where the 18 numeric-named entries carrying all 3,668 trades came from. - `trades.py` ran the cell through `_clean_name`, whose `_NONSENSE_KEY_RE` treats a bare number as a misalignment artifact and blanks it. That is why all 526 detailed rows read `currency: ""` while the same payload set `trade_currency_column_seen: true`. Both symptoms, one column, one missing join key. ## Why the fix touches the mod No export carried both halves of the key. `CreateCurrency` has names only; `/api/v1/stores` has names only; the ledger has ids only. The stores/economy exporter mod already reads both off `CurrencyManager`, so `CurrencyHoldingsDto` now emits `id` next to `currency`, and eco-app builds the id→name map from that surface. The map is applied once, to the shared parsed rows in `fetch_parsed_trades`, so `get_trades`, `get_stores` and `get_market` all inherit one join. The lookup is skipped entirely when no row carries a numeric currency, so a server whose export already names currencies pays nothing. ## Operator action required The Python side ships with this commit, but attribution stays unresolved on Sirens until the rebuilt mod DLL is on the server: 1. Build `mods/stores` (`ward exec build-mod-stores` — verified locally, 0 warnings) and install the resulting `EcoStoreExporter.dll`. 2. Restart the Eco server so the DLL loads. 3. Verify: `curl -H "X-API-Key: …" http://eco.coilysiren.me:3001/api/v1/currency-holdings | head` should show an `"id"` field next to `"currency"`. 4. Then `get_currency(currency="Spectres")` should report a non-zero `tradeCount`, and `get_trades` rows should carry `currency: "Spectres"` rather than an id. ## Until that happens The behaviour is honest rather than wrong. Unmapped ids are kept — the volume is real — but never presented as currency names: those records carry `unresolvedId: true` with the raw value under `currencyId`, and both `get_currency` and `get_trades` emit a warning naming how many trades are affected. No caller will read a bare number as a currency a player recognises. Sibling issues https://forgejo.coilysiren.me/coilyco-gaming/eco-app/issues/236 and https://forgejo.coilysiren.me/coilyco-gaming/eco-app/issues/234 depend on this join and are being worked next; they should be re-verified against the live server after step 3.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-gaming/eco-app#217
No description provided.