(1) Inventory edit modal gains Cost + New/Used + a live Margin readout (price−cost, %); inv_edit accepts
cost_price + condition_type; inventory list returns them.
(2) Inertia/Warner SOH list = an xlsx that doubles as the order form (fill ORDER QTY, upload). New
/admin/intake/distro-sheet[/preview]: openpyxl parses header-mapped columns (ORDER QTY/UPC/ARTIST/TITLE/
FORMAT/PPD), takes rows with QTY>0 → shared _ingest_distro core (refactored out of the RareWaves path) →
staged NEW stock with cost_price=PPD, kind from FORMAT (cd/vinyl), catno kept. Upload+preview UI in New stock.
DistroItem gains kind+catno; RareWaves path now passes kind too. Verified on the real 3,368-row Warner sheet:
3 ordered titles → 4 copies, all resolved, kind=cd, cost from PPD. Added openpyxl to requirements.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Postgres answer to WowPlatter's sluggish rigid search. Was ILIKE '%..%' → Seq Scan (3038 cost),
zero typo tolerance, and multi-word phrases ('bob marley legend 2022') returned NOTHING (needs a
contiguous substring). Now: pg_trgm GIN index on disc_release.search_text (already populated) +
word_similarity operators — :q <% search_text (filter, GIN-accelerated) ranked by :q <<-> search_text.
Threshold pinned at 0.45 on the DB (recall vs speed sweet spot ~140ms; 0.3 too loose/slow, 0.6 too strict).
Added the missing disc_release_format(release_id) index (was seq-scanning per row) + inventory.title trgm
for stock-finder. All DDL idempotent in _STARTUP_DDL. Live: 'bob marly survivl' (2 typos) -> Survival in 117ms.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The WowPlatter self-heal idea, freed from its 3-hour import: one button scans in-stock items for
gaps (title/weight/cover) and backfills. Phase 1 = local backfill from the disc_cache mirror
(instant, unbounded UPDATE…FROM); Phase 2 = bounded Discogs re-fetch for release_ids missing from
the mirror or without cover art (rate-limited, re-run for the rest). Refactored _enrich → _fetch_release
so Heal can force-refetch (not just cache-miss). New /admin/intake/heal/{scan,run} + a 🩹 Heal admin view.
Live first run: 30,202 rows had no title/weight (migration never denormalized them → broke shipping
quotes); 30,193 healed instantly from the mirror, 239 needed Discogs (60/batch). Real win.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The intake sheet is read straight from the stored Google service account (discotag@…
inventory-sheets, migrated out of WowPlatter's wp_rmp_disc_google_service into the vault) — a
signed JWT (cryptography, no new dep) → Sheets API → private sheet, no public-share needed. Uses
the saved column_mappings (timestamp/release_id/media/sleeve/price/comment, 1-indexed). SKU from
the sheet's locale timestamp '2025-01-30, 14:01:02' → 20250130140102 (matches migrated stock).
Bulk import (both connected + paste-URL paths) now SKIPS SKUs already in inventory, so importing
the 34,752-row form log only stages the genuinely-new rows (live: 2,006 new / 32,746 skipped).
Preview shows new-of-total. Endpoints: GET/POST /admin/intake/gsheet/{preview,import}.
Also: .dockerignore now excludes disc_images/ (volume-mounted, was 916MB) — build context
918MB→1MB, deploy 13min(hung)→24s. The COPY . . was baking the image store into every image.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replaces the bare release-id box with a tabbed Intake that all feeds one _stage() core:
- Find & stage: search the local Discogs mirror (instant) with a Discogs-API fallback for new
titles; pick a release → condition/price → stage. Cards show cover/artist/year/label/format.
- Discogs collection: lists the OAuth token's own folders (mrp3, 152 with stock) → page through
releases, reads media/sleeve/price from the collection's custom fields, stage-all-on-page.
- Google Sheet: paste the share URL → public CSV export (no Google creds) → auto-detect columns
(release id/media/sleeve/price/comment/timestamp) → preview → bulk import. SKU from the
Google-Form timestamp (YYYYMMDDHHMMSS), matching migrated stock.
Staging enriches title/artist/cover on demand: a disc_cache miss fetches the release from the
Discogs API and grows the local mirror (disc_cache + disc_release + artist + label), so internal
search + storefront get richer with every intake. Verified end-to-end on the VPS (incl. enrich of
a not-in-cache release) + sheet-parser self-check.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>