Part of the Simple Agility stack. Canonical copy:
~/source/stockroom/docs/superpowers/specs/2026-10-03-stockroom-intake-inventory-pricing-design.md. This page mirrors it for retrieval. Status: approved 2026-10-03; implementation not started.
The operator runs two small businesses — a reseller business and a short-term rental in Pacific Beach — on a self-built software stack, Simple Agility: Wikantik (knowledge and business context), Roller (outreach blogs) and jakemon (the observability plane). Everything built so far sits upstream of the transaction — knowledge, outreach and signals. Nothing in the stack records the things the businesses actually do.
Asked where the most manual time or lost money goes, the operator named reseller listing work, with photographing, writing, pricing, crosslisting and delisting all roughly equal. This design covers the shared front half of that pipeline — everything every listing needs, regardless of where it is eventually posted:
item in hand → identify → inventory record → price per route → triage → channel-neutral listing content
Price is the only part allowed to vary by channel. Stockroom is a new Simple Agility component in its own repository, ~/source/stockroom. A lightweight warehouse-management (WMS) capability will grow there later; this design leaves room for it without building it.
| Decision | Choice |
|---|---|
| Repository and stack | ~/source/stockroom, following Wealthview: Java 25, Spring Boot, Maven multi-module, React 19 + Vite, PostgreSQL 18, Flyway, Docker Compose |
| Client | One responsive web app; React Native only if phone camera scanning proves to matter |
| Boundary with Amazon tools | Every item enters Stockroom; triage compares FBA with every other route; FBA-bound items continue into the existing FBA tool (handoff out of scope) |
| Intake shape | Two passes: a fast scan pass, then pricing + triage, then preparation only for routes that need it |
| Triage | A pure function of (item facts, quotes, rules version) → decision; no I/O |
| Business rules | Numeric thresholds in a versioned triage_rules table; prose rules and rationale in Wikantik pages, read by name |
| GenAI | ListingDrafter and ItemIdentifier ports with Claude and Ollama adapters, chosen per task by configuration |
| Default model | Claude Opus 5.5 (claude-opus-5-5) through the official Anthropic Java SDK |
| Money | Integer cents, USD |
| Telemetry | Simple Agility telemetry contract from the first commit |
phone / desk browser (responsive web app)
│ scan ISBN/UPC · photos · cost · bin
▼
┌──────────────── stockroom (Spring Boot) ─────────────────┐
│ Intake ──► Catalog lookup ──► Pricing ──► Triage │──► Amazon SP-API · eBay Browse
│ │ (Open Library, (source (pure core) │ · buyback quotes
│ │ Google Books) adapters) │ │
│ ▼ ▼ │
│ Inventory (Postgres) ◄──────────────── Listing preparer ──│──► Wikantik (rules pages)
│ photos on a volume (Claude | Ollama)│
└───────────────────────────────────────────────────────────┘
/metrics · JSON logs · /healthz ──► jakemon
| Unit | Does | Depends on |
|---|---|---|
| Intake | Creates items from a scan or photos, inside an acquisition session | Inventory |
| Catalog lookup | Identifier → facts, cached per identifier | CatalogSource adapters |
| Pricing | Collects quotes per route | PriceSource adapters, rules |
| Triage | Picks a route and explains why | Nothing — pure |
| Listing preparer | Writes channel-neutral content; FBA notes by template | ListingDrafter, RulesSource |
| Identification | Photo-first identification proposals | ItemIdentifier |
| Inventory | Items, acquisitions, locations, events, photos | PostgreSQL, photo volume |
| Work runner | Durable background jobs with per-source rate limits | PostgreSQL |
Calls to Amazon, eBay and buyback vendors are functional inputs to pricing, not observation, so they belong to Stockroom and not to jakemon — per the rule that decides where data lives on SimpleAgilityObservabilityPlane. Wikantik supplies context, not storage: inventory never lives in wiki pages.
All tables come from Flyway migrations.
acquisition — one purchase (a library-sale haul, an estate-sale box): source name and type, date, total_cost_cents (split evenly across its items unless an item's cost is overridden), a default location for the session.location — hierarchical (room → shelf → bin) with a short printable code; the WMS seam.catalog_record — facts for one identifier (isbn13 · upc · ean · none), shared by copies: title, facts JSONB validated per item kind, source, fetch time, raw response.item — one physical thing: short sku, kind (book · electronics · tool · collectible · other · lot), status, current route, condition grade (bookseller scale plus dust jacket, or tested / untested / for parts) and flaw flags, weight and its source, cost, location, parent_lot_id, the look-closer flag.item_event — append-only: created, identified, priced, routed, route overridden, moved (from/to location), content approved, status changed.photo — ordered, with a role (cover, copyright page, flaw, detail, label, lot); originals kept on a volume.listing_content — versioned per item: origin (ai_draft · human_edit · template), title, description, condition text, specifics, keywords, claims (each specific's source and whether it needs confirming), provider, model, prompt version, and the rules pages used (slug, canonical id, version, stale flag).price_quote — append-only, one row per observation: route, source, list price, fees, shipping, other costs, net, number of comparables, confidence, as-of, evidence.triage_decision — rules version, recommended route, rule fired, expected net (the prediction), quotes used, the routes passed over (each as a route, reason, measured value and required value), final route and any override reason.triage_rules — versioned thresholds, factors, fee configuration, shipping-by-weight table and weight-estimate coefficients.category_rules_page — maps an item kind, optionally narrowed by a catalog subject, to the Wikantik rules pages that govern it; most specific match wins.intake ─► identified ─► (pricing + triage) ─┬─► needs_prep ─► ready (ebay, local, lot member)
│ ├─► ready (fba, buyback)
└─► needs_identification ├─► needs_price
└─► needs_decision ─► (you pick a route)
| Status | Meaning | Work queue |
|---|---|---|
intake | created; lookup running | — |
needs_identification | lookup failed or record too thin | Needs identification |
identified | facts known; pricing pending | Ready to price |
needs_price | no usable quotes | Needs price |
needs_decision | high value or flagged; triage deferred to the operator | Look closer |
needs_prep | routed somewhere that needs photos or content | Needs prep |
ready | has everything its route needs | Routed |
Later components (posting, sales) add statuses after ready without changing these.
Intake starts by opening an acquisition session — e.g. "Library sale, $40, bin B-2" — so every item scanned gets its cost share and default location.
Pass 1 — scan. The code is normalised (ISBN-10 → 13, check digits, price add-on stripped, 978/979 EANs treated as ISBNs) and the item is created immediately. Catalog lookup runs in the background (cache, then Open Library, then Google Books), so scanning never waits on the network. Grading is one tap (defaulting to the last grade) with optional flaw chips. Suspected first editions, signed copies and oddities get a look-closer flag; pricing also sets it above the high-value threshold. Scanning the same identifier twice within 10 seconds asks whether it is another copy.
Photo-first path. For pre-ISBN books, vintage, collectibles and tools without a barcode: 1–3 photos (for books the copyright page matters most — the printing line identifies the edition). The identifier model proposes an identification with a confidence level; the operator confirms or corrects it.
Weight is estimated for books from page count and binding, entered for everything else, and always editable.
Preparation runs only for items that need it. FBA condition notes come from the grade and flags by template — no model call. Buyback needs nothing. eBay and local get photos and a full draft; a lot gets one lot photo and a lot draft.
Draft inputs are the catalog facts, grade and flags, photos, and rules pages from Wikantik read by slug, never searched — a general style guide plus category pages in a reselling cluster (hub ResellingHub, created with phase 3). Page versions are recorded with each draft. If Wikantik is unreachable the last cached copy is used and marked stale.
The output is one channel-neutral, schema-validated version: title, description, condition text, item specifics, keywords and claims. Honesty rules, enforced in the prompt and checked after generation:
Drafts are reviewed beside their photos; approving marks that version approved, edits create new versions, and books can be approved in bulk.
Two ports — ItemIdentifier and ListingDrafter — each with a Claude adapter (official Java SDK, claude-opus-5-5, structured output, explicit effort per task, prompt caching on the system prompt and rules, explicit refusal handling with server-side fallbacks) and an Ollama adapter (/api/chat with base64 images, the JSON schema in format, and think: false). The provider and model are chosen per task by configuration; both adapters share the same prompt templates and must satisfy the same schemas, and every draft records which provider and model produced it.
A golden set of about 20 real items, run on demand against either provider, scores schema validity, field accuracy and honesty-rule violations — the measured answer to "is the local model good enough?" before switching. With Claude, cost is on the order of cents per draft and a few dollars a week at this volume.
| Route | Source | Net to the operator |
|---|---|---|
| FBA | Amazon SP-API: Catalog Items (ISBN → ASIN, sales rank), Product Pricing, Product Fees, Listings Restrictions | price − Amazon fees − inbound shipping per unit |
| eBay | eBay Browse API: active listings by GTIN/ISBN, comparable condition | price − fees − shipping from a weight table |
| Local | Derived: eBay/Amazon prices × a local discount; low confidence until own history exists | price |
| Buyback | A vendor quote API if one is available; otherwise manual entry | quote |
| Lot | Members' local values × a lot factor; low confidence | price |
Responses are cached per identifier, condition and day; stale quotes are refreshed before triage.
Triage is a pure function, checked in order:
needs_decision, never automatic.needs_price, with the reason.Every decision records the rule that fired, the quotes used, the expected net and every route passed over with measured versus required values — meeting the three mandatory properties of the Simple Agility feedback-loop pattern. Overrides record a reason and never erase the recommendation.
A DB-backed job queue runs lookups, quotes, triage and drafts with per-source rate limits and backoff; exhausted retries land the item in the matching work queue with the reason shown. A failure never blocks other items and never loses a scan. Credentials come from the environment or Compose secrets. Telemetry follows the contract from the first deploy: JSON log envelope with correlation_id, /metrics, /healthz, and events as log lines.
Test-driven throughout: table-driven triage tests plus invariants (never automatic above the high-value threshold, never a route without a quote, the FBA rule honours rank and restrictions); identifier-normalisation tests; source adapters tested against recorded responses; Testcontainers PostgreSQL with real migrations; the honesty validator unit-tested; the golden set on demand; a ratcheting coverage floor.
reselling rules pages, review screen.Each phase is usable on its own and gets its own implementation plan.