Add an appraisal compare-by-location page #17

Closed
opened 2026-07-08 22:29:48 +02:00 by Sirttas · 0 comments
Owner

Summary

Add a page that appraises the same items across multiple market locations side by side, so you can compare what a pile is worth at several stations at once. Locations are chosen by the user via a search-backed select (mammon's universe location search), not a fixed trade-hub list.

Flow: parse once, then appraise per location

Rather than posting the raw text to a paste-appraisal endpoint once per location, gemory should:

  1. Parse the pasted block once via POST /market/types/paste (parseStacks, mammon #40) → a list of marketTypeId + quantity stacks.
  2. Resolve type info once via findTypes / getMarketTypes (already cached in src/market/type/MarketType.ts) — gives name, volume, etc.
  3. Appraise the structured type list per selected location via the location-aware GET /market/prices?types=…&locationId=… (currentPrices, mammon #38), one call per selected location.
  4. Pivot the per-location results into columns, keyed by the stacks from step 1.

Why parse client-side first (not one paste-appraise per location):

  • Parse the text once instead of re-parsing server-side for every location.
  • gemory holds the parsed stacks + type attributes, which enables per-item derivations the appraisal endpoint doesn't know about — notably logistics cost (volume × qty × freight rate) applied to a chosen location. See the separate logistics issue.
  • Uses the plain structured pricing endpoint, so no dependency on the paste-appraisal endpoint (#37) for this page.

Location selection: search-backed, not a hardcoded hub list

Do not ship a fixed Jita/Amarr/Dodixie/Rens/Hek enum. The user picks the locations to compare through a search select backed by mammon's universe location search (mammon #41), which is already exposed on the generated client:

  • searchLocations(name, limit?)GET /universe/locations — type-ahead search returning MarketLocationResponse (id = station/market-location id, name, systemName, regionName, …). Feeds the select's options as the user types.
  • resolveLocation(id) → resolve a single location by id (e.g. to rehydrate a selection).

UI: a multi-select of locations where each entry is added via a debounced search input hitting searchLocations. The selected locations become the compare columns. The id from MarketLocationResponse is exactly the locationId currentPrices / pastePrices expect — no id-shape translation needed (that was the whole point of resolving via universe rather than mapping region ids by hand).

An empty selection can default to Jita (mammon defaults to Jita when locationId is omitted), but there is no baked-in hub list beyond that fallback.

Proposed design

  • New route under /market (e.g. /market/appraisal/compare) + routeNames entry, or a tab on the base appraisal page (gemory #16).
  • Input: the pasted inventory block + a location search multi-select (N locations).
  • Output: a table, one row per item, a column per selected location showing buy/sell (and totals), highlighting the best location per line.
  • Reuse useAppraisalStore (its RegionalMarketCache is per-location, keyed by locationId) so re-comparing the same locations doesn't re-fetch.

Watch out: location representation

mammon's locationId is a station/market-location id (e.g. Jita 4-4 = 60003760), which is exactly what MarketLocationResponse.id from searchLocations returns — so the search-backed select hands the right id straight through. The store must be location-keyed (by locationId), not region-keyed; the earlier jitaId = 10000002 (The Forge region) key was a mismatch and should be dropped.

Acceptance criteria

  • Pasting a block parses once (/market/types/paste), then appraises the structured type list per selected location (locationId).
  • Locations are chosen through a search-backed multi-select using searchLocations (/universe/locations) — no hardcoded hub list.
  • Results show buy/sell per item across all selected locations, with a per-line best-location indicator.
  • The store/cache is keyed by locationId (station id), not region id.
  • Caching avoids refetching an already-priced location.
  • npm run build passes.

Depends on

mammon #40 (/market/types/paste) + #38 (per-location pricing) + #41 (universe location search) + the client regen (gemory #15). Base appraisal page (gemory #16) optional if this is a standalone route.

## Summary Add a page that appraises the same items across **multiple market locations side by side**, so you can compare what a pile is worth at several stations at once. Locations are **chosen by the user via a search-backed select** (mammon's universe location search), not a fixed trade-hub list. ## Flow: parse once, then appraise per location Rather than posting the raw text to a paste-appraisal endpoint once per location, gemory should: 1. **Parse the pasted block once** via `POST /market/types/paste` (`parseStacks`, mammon #40) → a list of `marketTypeId` + `quantity` stacks. 2. **Resolve type info once** via `findTypes` / `getMarketTypes` (already cached in `src/market/type/MarketType.ts`) — gives name, `volume`, etc. 3. **Appraise the structured type list per selected location** via the location-aware `GET /market/prices?types=…&locationId=…` (`currentPrices`, mammon #38), one call per selected location. 4. **Pivot** the per-location results into columns, keyed by the stacks from step 1. Why parse client-side first (not one paste-appraise per location): - Parse the text **once** instead of re-parsing server-side for every location. - gemory holds the parsed stacks + type attributes, which enables per-item derivations the appraisal endpoint doesn't know about — notably **logistics cost** (volume × qty × freight rate) applied to a chosen location. See the separate logistics issue. - Uses the plain structured pricing endpoint, so no dependency on the paste-appraisal endpoint (#37) for this page. ## Location selection: search-backed, not a hardcoded hub list Do **not** ship a fixed Jita/Amarr/Dodixie/Rens/Hek enum. The user picks the locations to compare through a **search select** backed by mammon's universe location search (mammon #41), which is already exposed on the generated client: - `searchLocations(name, limit?)` → `GET /universe/locations` — type-ahead search returning `MarketLocationResponse` (`id` = station/market-location id, `name`, `systemName`, `regionName`, …). Feeds the select's options as the user types. - `resolveLocation(id)` → resolve a single location by id (e.g. to rehydrate a selection). UI: a **multi-select of locations** where each entry is added via a debounced search input hitting `searchLocations`. The selected locations become the compare columns. The `id` from `MarketLocationResponse` is exactly the `locationId` `currentPrices` / `pastePrices` expect — no id-shape translation needed (that was the whole point of resolving via universe rather than mapping region ids by hand). An empty selection can default to Jita (mammon defaults to Jita when `locationId` is omitted), but there is no baked-in hub list beyond that fallback. ## Proposed design - New route under `/market` (e.g. `/market/appraisal/compare`) + `routeNames` entry, or a tab on the base appraisal page (gemory #16). - Input: the pasted inventory block + a **location search multi-select** (N locations). - Output: a table, one row per item, a column per selected location showing buy/sell (and totals), highlighting the best location per line. - Reuse `useAppraisalStore` (its `RegionalMarketCache` is per-location, keyed by `locationId`) so re-comparing the same locations doesn't re-fetch. ## Watch out: location representation mammon's `locationId` is a **station/market-location id** (e.g. Jita 4-4 = `60003760`), which is exactly what `MarketLocationResponse.id` from `searchLocations` returns — so the search-backed select hands the right id straight through. The store must be **location-keyed** (by `locationId`), not region-keyed; the earlier `jitaId = 10000002` (The Forge region) key was a mismatch and should be dropped. ## Acceptance criteria - [x] Pasting a block parses **once** (`/market/types/paste`), then appraises the structured type list per selected location (`locationId`). - [x] Locations are chosen through a **search-backed multi-select** using `searchLocations` (`/universe/locations`) — no hardcoded hub list. - [x] Results show buy/sell per item across all selected locations, with a per-line best-location indicator. - [x] The store/cache is keyed by `locationId` (station id), not region id. - [x] Caching avoids refetching an already-priced location. - [x] `npm run build` passes. ## Depends on mammon #40 (`/market/types/paste`) + #38 (per-location pricing) + #41 (universe location search) + the client regen (gemory #15). Base appraisal page (gemory #16) optional if this is a standalone route.
Sirttas added the item-comparison label 2026-07-08 22:36:50 +02:00
Sirttas added a new dependency 2026-07-08 22:43:38 +02:00
Sirttas added a new dependency 2026-07-08 22:43:47 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Reference: eveal/gemory#17