Crafting Activity Atlas #17

Closed
opened 2026-06-17 07:29:20 +00:00 by coilysiren · 2 comments
Owner

Originally filed by @coilysiren on 2026-05-02T23:10:05Z

Task 7 — Crafting Activity Atlas

Prereq: read todo/README.md first.

Goal

Build an MCP tool get_eco_crafting_atlas rendering a card of live crafting activity reconstructed from event logs.

Data sources (admin, X-API-Key from SSM /eco-mcp-app/api-admin-token, region us-east-1)

  • GET http://eco.coilysiren.me:3001/api/v1/exporter/actionlist
    • Newline-delimited plain text, NOT JSON. Parse via resp.text.splitlines().
  • GET http://eco.coilysiren.me:3001/api/v1/exporter/actions?actionName=ItemCraftedAction
    • CSV: ActionLocation,WorldObjectItem,Citizen,ItemUsed,OverrideHierarchyActionsToConsumer,Count,Time
    • Size warning: 295 KB on Day 3; will grow to 20+ MB by end-cycle. Must stream-parse (use csv.reader on the response iterator), aggregate in a single pass, and never .text the whole body into memory.
  • Also pull: ChopTree, HarvestOrHunt, DigOrMine, ConstructOrDeconstruct for a full production picture. Same streaming discipline.

Before implementing: check whether /api/v1/exporter/actions accepts a date or time-range query param that would let the server filter. If it does, use it to cap input size. If it doesn't, stream-aggregate on the client.

Analyses to render

  1. Top 20 items produced this cycle — aggregate Count grouped by ItemUsed (the output item).
  2. Crafting station utilization — count of events per WorldObjectItem (e.g. CampfireItem, WorkbenchItem, CarpentryTableItem). Rank hot → cold.
  3. "Flows into what" sankey — edges from WorldObjectItem → ItemUsed, thickness = sum of Count. Use d3-sankeybundle it into the HTML, don't load from CDN at runtime (CSP).
  4. Per-citizen leaderboard — top 10 crafters by total Count across all craft-like action types.

Dead-end (do not attempt)

Static parsing of mod C# source for recipe definitions. Recipes-as-definitions aren't exposed over HTTP. This tool is observed events only — which is strictly better because mod items (BunWulf, Nid, vanilla) all appear naturally where they're actually used.

Implementation notes

  • Consider pre-aggregating hourly and caching in a local SQLite ~/.cache/eco-mcp-app/crafting.sqlite so repeat calls don't re-stream the full CSV. Invalidate on a 5-minute TTL.
  • Add MCP tool to src/eco_mcp_app/server.py.
  • Template under src/eco_mcp_app/templates/partials/.

Acceptance

  • Sankey renders without crossing edges more than 5 times for the current cycle.
  • Leaderboard has ≥ 3 citizens (Day 3 should comfortably support this).
  • Memory use stays under 200 MB even when fed a synthetic 20-MB CSV (stream test; use an in-memory fixture).
  • inv smoke passes.

Migrated from coilyco-flight-deck/eco-mcp-app#9 during the eco repo consolidation (coilysiren/inbox#100).

_Originally filed by @coilysiren on 2026-05-02T23:10:05Z_ # Task 7 — Crafting Activity Atlas **Prereq**: read `todo/README.md` first. ## Goal Build an MCP tool `get_eco_crafting_atlas` rendering a card of live crafting activity reconstructed from event logs. ## Data sources (admin, `X-API-Key` from SSM `/eco-mcp-app/api-admin-token`, region `us-east-1`) - `GET http://eco.coilysiren.me:3001/api/v1/exporter/actionlist` - **Newline-delimited plain text, NOT JSON.** Parse via `resp.text.splitlines()`. - `GET http://eco.coilysiren.me:3001/api/v1/exporter/actions?actionName=ItemCraftedAction` - CSV: `ActionLocation,WorldObjectItem,Citizen,ItemUsed,OverrideHierarchyActionsToConsumer,Count,Time` - **Size warning**: 295 KB on Day 3; will grow to 20+ MB by end-cycle. Must **stream-parse** (use `csv.reader` on the response iterator), aggregate in a single pass, and never `.text` the whole body into memory. - Also pull: `ChopTree`, `HarvestOrHunt`, `DigOrMine`, `ConstructOrDeconstruct` for a full production picture. Same streaming discipline. Before implementing: check whether `/api/v1/exporter/actions` accepts a date or time-range query param that would let the server filter. If it does, use it to cap input size. If it doesn't, stream-aggregate on the client. ## Analyses to render 1. **Top 20 items produced this cycle** — aggregate `Count` grouped by `ItemUsed` (the output item). 2. **Crafting station utilization** — count of events per `WorldObjectItem` (e.g. `CampfireItem`, `WorkbenchItem`, `CarpentryTableItem`). Rank hot → cold. 3. **"Flows into what" sankey** — edges from `WorldObjectItem → ItemUsed`, thickness = sum of `Count`. Use `d3-sankey` — **bundle it into the HTML, don't load from CDN at runtime** (CSP). 4. **Per-citizen leaderboard** — top 10 crafters by total `Count` across all craft-like action types. ## Dead-end (do not attempt) Static parsing of mod C# source for recipe definitions. Recipes-as-definitions aren't exposed over HTTP. This tool is **observed events only** — which is strictly better because mod items (BunWulf, Nid, vanilla) all appear naturally where they're actually used. ## Implementation notes - Consider pre-aggregating hourly and caching in a local SQLite `~/.cache/eco-mcp-app/crafting.sqlite` so repeat calls don't re-stream the full CSV. Invalidate on a 5-minute TTL. - Add MCP tool to `src/eco_mcp_app/server.py`. - Template under `src/eco_mcp_app/templates/partials/`. ## Acceptance - Sankey renders without crossing edges more than 5 times for the current cycle. - Leaderboard has ≥ 3 citizens (Day 3 should comfortably support this). - Memory use stays under 200 MB even when fed a synthetic 20-MB CSV (stream test; use an in-memory fixture). - `inv smoke` passes. --- _Migrated from coilyco-flight-deck/eco-mcp-app#9 during the eco repo consolidation (coilysiren/inbox#100)._
coilysiren added
P3
and removed
P2
labels 2026-06-17 08:39:07 +00:00
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-eco-app-17 on host KAI-DESKTOP-TOWER is carrying this issue (reserved 2026-07-05T04:37:05Z). 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-17` on host `KAI-DESKTOP-TOWER` is carrying this issue (reserved 2026-07-05T04:37:05Z). 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 - docs/crafting.md walkthrough + FEATURES.md entry completing the crafting atlas; tool, SPA page, and tests were already green on main.

Honest retrospective: the surprise up front was that the whole atlas was already implemented and merged to main before I arrived - crafting.py, the get_eco_crafting_atlas tool, the /crafting SPA page, the static-SVG sankey card, and 396 green tests including the synthetic ~24 MB stream-memory check. My branch was byte-identical to main. So rather than re-implement (or fabricate a diff), I looked for the genuine gap: the atlas was the one sibling of the trades ledger with no docs/<feature>.md walkthrough and no dedicated FEATURES bullet, which the repo's docs-trifecta convention calls for. That's what I wrote and landed.

What fought back: nothing technical - the ward gate refused repo verbs until the branch had an upstream, which was a one-line fix. The one judgement call worth flagging is that the shipped sankey deviates from this issue's d3-sankey-bundled ask: it renders a static server-side SVG instead. It hits the same acceptance outcome (minimal crossings, CSP-clean, zero JS) and I documented the tradeoff honestly rather than papering over it.

Confidence is high on the doc accuracy (read the source, template, and frontend to write it; lint + full precommit suite green). The rougher edge is that I could only verify against fixtures and the stream test, not a live late-cycle 20 MB CSV - the row-cap valve is defensive but untested against real end-cycle data. Possible follow-up: revisit the top-30 sankey edge cap once a full cycle's production graph exists, since a very wide graph could hide long-tail stations.

WARD-OUTCOME: done - docs/crafting.md walkthrough + FEATURES.md entry completing the crafting atlas; tool, SPA page, and tests were already green on main. Honest retrospective: the surprise up front was that the whole atlas was **already implemented and merged to main** before I arrived - `crafting.py`, the `get_eco_crafting_atlas` tool, the `/crafting` SPA page, the static-SVG sankey card, and 396 green tests including the synthetic ~24 MB stream-memory check. My branch was byte-identical to main. So rather than re-implement (or fabricate a diff), I looked for the genuine gap: the atlas was the one sibling of the trades ledger with **no `docs/<feature>.md` walkthrough and no dedicated FEATURES bullet**, which the repo's docs-trifecta convention calls for. That's what I wrote and landed. What fought back: nothing technical - the ward gate refused repo verbs until the branch had an upstream, which was a one-line fix. The one judgement call worth flagging is that the shipped sankey deviates from this issue's `d3-sankey`-bundled ask: it renders a static server-side SVG instead. It hits the same acceptance outcome (minimal crossings, CSP-clean, zero JS) and I documented the tradeoff honestly rather than papering over it. Confidence is high on the doc accuracy (read the source, template, and frontend to write it; lint + full precommit suite green). The rougher edge is that I could only verify against fixtures and the stream test, not a live late-cycle 20 MB CSV - the row-cap valve is defensive but untested against real end-cycle data. Possible follow-up: revisit the top-30 sankey edge cap once a full cycle's production graph exists, since a very wide graph could hide long-tail stations.
Sign in to join this conversation.
No description provided.