Skip to content

Collection, pricing, and alerts backend plan ​

Date: 2026-05-18. Status: product/backend architecture plan before pricing-provider implementation.

SKN must become more than a scanner. The scanner identifies the object; the collection, pricing, and alert engines turn that object into a private portfolio that helps the user decide when to buy, hold, sell, or list.

This plan deliberately separates the backend foundation from provider-specific pricing formulas. The exact weighting model should wait for the dedicated pricing documentation. The architecture below defines where that model will run and what data it needs.

Product Thesis ​

The core user loop is:

text
scan or search item
-> confirm identity, condition, language, grade, quantity
-> add to private collection
-> attach acquisition cost and proof/photos
-> compute valuation and confidence
-> track movements over time
-> notify user on meaningful pump/drop/opportunity

The app should feel closer to a portfolio terminal than to a static binder. But it must not pretend to be a financial market if the underlying data is weak. Every valuation and alert needs freshness, confidence, and source explanation.

Scope Boundary ​

In Scope ​

  • private user collections;
  • raw cards, slabs, and sealed products as separate item classes;
  • acquisition cost and portfolio gain/loss;
  • market observations from normalized providers;
  • weighted price estimates with confidence;
  • valuation snapshots per holding and portfolio;
  • alert rules and alert events for drops, pumps, and watched items;
  • provider adapters behind normalized contracts;
  • audit trail for sensitive user collection changes.

Out of Scope for Now ​

  • marketplace checkout, payment, payout, escrow, disputes;
  • public collection sharing by default;
  • automatic buy/sell execution;
  • unreviewed scraping as a production dependency;
  • public redistribution of images or market data that providers do not allow;
  • promising real-time alerts when provider quotas only support polling.

Backend Domains ​

1. User and Collection Domain ​

The Collection Engine answers: "What does this user own?"

Core tables:

TablePurpose
usersAccount identity. Can start simple, but holdings must be user-scoped from day one.
collection_itemsOne owned holding line: item reference, type, quantity, condition, language, grade, sealed state.
collection_item_photosUser-owned proof photos, display photos, and scan captures.
collection_item_acquisitionsPurchase price, purchase date, seller/source, shipping, fees, notes.
collection_item_eventsAudit trail: created, edited, quantity changed, sold, deleted, restored.
collection_item_valuation_snapshotsLatest and historical value for a holding.
collection_exportsExplicit export jobs for user-owned data.

Privacy requirements:

  • collection data is private by default;
  • every query is scoped by user ID;
  • location/storage labels are never public by default;
  • acquisition cost is private and must not leak into marketplace listings later;
  • EXIF should be stripped from uploaded photos;
  • deletes should be soft-delete first, with restore/audit.

Remote backend should be canonical for a professional app. Mobile can cache locally later, but the backend should own identity, audit, valuation, alerts, and provider refresh state.

2. Item Identity Domain ​

The Pricing and Collection Engines must not store raw provider shapes directly. They reference normalized SKN identities.

Core identities:

Item classIdentity fields
raw_cardTCG, set, collector number, language, variant, finish, condition.
graded_card / slabinner card identity, grader, grade, cert number, label metadata.
sealed_productproduct type, set/series, language, region, SKU/barcode when available.

The Identity Engine remains responsible for resolving a scan into one of these classes. The Collection Engine stores the user's owned instance of that identity.

3. Pricing Observation Domain ​

The Pricing Engine answers: "What market evidence exists?"

Core tables:

TablePurpose
price_sourcesProvider registry: Cardmarket, eBay, TCGPlayer, PriceCharting, provider APIs, manual imports.
price_provider_itemsMapping between SKN item identity and provider SKU/listing/category IDs.
price_observationsRaw normalized market observation: sold comp, active listing, asking price, graded comp, sealed comp.
price_observation_feesShipping, buyer fee, seller fee, taxes when known.
price_observation_flagsOutlier, low-confidence, wrong-language, damaged, incomplete, suspicious.
price_import_runsIngestion job tracking, provider freshness, errors, quotas.

Every observation should keep:

  • source and source URL or external ID;
  • sold vs active listing;
  • price and currency;
  • shipping/fees treatment;
  • sale/listing timestamp;
  • region;
  • language;
  • condition;
  • item class;
  • grader and grade when relevant;
  • raw payload pointer or checksum for audit;
  • terms/licensing status for cache/display.

Provider-specific adapters can be written later, but the observation schema should exist before many providers are added.

4. Pricing Estimate Domain ​

The Pricing Engine answers: "What is the current estimated value?"

Core tables:

TablePurpose
price_estimatesCurrent estimate for one SKN item identity and market context.
price_estimate_componentsBreakdown by source/signal: Cardmarket, eBay sold, active listings, graded source, manual override.
price_estimate_historyTime series of estimates for charting and alert comparisons.
price_estimate_explanationsHuman-readable explanation payload for the app.

Estimate output should include:

  • estimated low / mid / high;
  • currency;
  • confidence score;
  • liquidity score;
  • sample size;
  • freshness;
  • strongest sources;
  • warnings: stale data, low comps, language mismatch, grade mismatch, outlier-heavy data.

Do not start with a "magic average". The first version can be simple but must be explainable:

  • sold comps weigh more than active listings;
  • recent observations weigh more than old observations;
  • EU/FR signals weigh more for FR/EU users than US-only data;
  • exact language/condition/grade matches weigh more than mismatches;
  • active listings can inform ask pressure but should not dominate sold value;
  • low sample size reduces confidence even when prices look high.

5. Valuation Domain ​

The Valuation Engine applies price estimates to the user's actual holdings.

It answers:

  • total portfolio value;
  • value by item class: raw, slab, sealed;
  • unrealized gain/loss vs acquisition cost;
  • top movers;
  • stale valuation count;
  • low-confidence valuation count.

Core tables:

TablePurpose
portfolio_snapshotsAggregated portfolio value per user over time.
collection_item_valuation_snapshotsHolding-level estimated value over time.
portfolio_breakdownsSplit by item class, TCG, set, condition, grade, language.

This layer should not recompute provider data live on every mobile screen. It should read precomputed estimates and snapshots, then show freshness.

6. Alert Domain ​

The Alert Engine answers: "What changed enough to matter?"

Alert types:

AlertTrigger
Price dropWatched item or wishlist item drops below threshold.
Price pumpOwned item rises above movement threshold.
Portfolio moverA holding becomes a top daily/weekly mover.
Spread opportunitySold comps and active listings create a possible buy/sell spread.
Stale dataImportant holding has no fresh market data.
Missing pricingUser owns an item that has no estimate yet.

Core tables:

TablePurpose
alert_rulesUser-defined or system-suggested rules.
alert_rule_targetsItem, holding, search query, set, product class, or portfolio segment.
alert_eventsConcrete fired alerts with payload and source estimate IDs.
alert_deliveriesPush/email/in-app delivery state and retry info.
alert_deduplication_keysCooldowns to avoid spam on noisy price feeds.

Near-real-time should mean "fresh enough for marketplace behavior", not fake tick-by-tick trading. Provider permissions and quotas decide whether this is polling, webhook, batch refresh, or a hybrid. The backend should expose freshness honestly.

Price Refresh Cadence ​

Pricing freshness must be tiered. Pokémon has too many cards, variants, slabs, and sealed products to refresh everything continuously.

Current source constraints:

  • Cardmarket's public price guide is documented as updated once daily, while the product catalogue updates when new releases are added. This makes Cardmarket a strong daily baseline, not a tick-by-tick source. Source: Cardmarket announcement, 2024-06-05.
  • eBay Browse APIs can search active listings and support discovery/refinement use cases, but sold-history access is constrained. eBay documents many Buy APIs as limited release and notes that Marketplace Insights is restricted and not open to new users at this time. eBay's Feed API can provide hourly changed-item snapshots, but it is also limited release. Sources: eBay Buy APIs overview, Buy API support by marketplace, Feed API resources.

Recommended refresh tiers:

TierCadenceScopePurpose
Full market baselineDailyCardmarket price guide, provider daily feeds, broad catalogueRecompute stable estimates and long-tail prices.
User portfolioEvery 1 to 6 hoursItems owned by users, high-value holdings, recently scanned itemsKeep the portfolio feeling alive without refreshing the entire market.
Watchlist / active alertsEvery 30 to 60 minutes when provider budget allowsWatched items, wishlist targets, items with active alert rulesCatch user-relevant movement faster than the daily baseline.
Hot queueEvery 5 to 30 minutes when legally and technically allowedItems that recently moved sharply or have many users watching themSupport near-real-time "pump/drop" behavior without pretending every item is live.
Long tailDaily or slowerItems with no owners, no watches, low liquidityAvoid wasting provider quota.

The app must display freshness next to estimates and alerts. A useful alert with "updated 22 minutes ago" is credible; a stale alert without freshness creates false confidence.

Alert Evaluation Model ​

Alerts should fire from estimate changes, not from raw provider observations alone.

Evaluation flow:

text
new observations ingested
-> estimate recalculated
-> estimate history updated
-> alert evaluator compares against baselines
-> confidence and liquidity gates applied
-> dedupe/cooldown checked
-> in-app/push alert created

Comparison baselines:

  • last reliable estimate;
  • 7-day average;
  • 30-day average;
  • user's acquisition price when the item is owned;
  • user-defined threshold for watchlist or wishlist items.

Default alert thresholds:

AlertSuggested default
Price drop-10% to -15% versus 7-day average, or below a user threshold.
Price pump+15% to +25% versus 7-day average, with enough liquidity.
Portfolio moverHolding enters the user's top daily/weekly movers.
Spread opportunityActive listing price materially below recent sold/estimated value.

Quality gates before notifying:

  • minimum observation count or strong trusted source;
  • source freshness inside the alert tier's SLA;
  • estimate confidence above the alert rule minimum;
  • movement above both percentage and absolute-value noise floors;
  • condition/language/grade match is acceptable;
  • suspicious outliers do not dominate the estimate;
  • no duplicate alert inside the cooldown window.

Example alert payload:

json
{
  "type": "price_pump",
  "title": "Charizard 4/102 FR is up 18%",
  "itemId": "raw_card:pokemon:base1:4:fr",
  "estimate": { "mid": 182.0, "currency": "EUR", "confidence": 0.76 },
  "movement": { "window": "7d", "percent": 18.0, "absolute": 27.8 },
  "primarySignals": ["ebay_sold", "cardmarket_daily"],
  "freshness": "2h",
  "action": "review_sell_or_list"
}

Product wording should help decisions without implying financial advice. Prefer "review sell/list opportunity" over "sell now".

Provider and MCP Strategy ​

Provider integrations should be runtime backend adapters, not mobile dependencies.

Candidate provider families:

  • Cardmarket for EU market context;
  • eBay sold comps and active listings;
  • TCGPlayer or provider APIs for US context;
  • PriceCharting or graded providers for slabs;
  • later Vinted / Leboncoin as French peer-to-peer signals if terms allow.

MCP connectors are useful for internal tooling, operations, backfills, and provider exploration. For product runtime, SKN should still store normalized observations in its backend database and serve the app through SKN APIs. The mobile app should never depend directly on provider-specific APIs or MCP tool shapes.

Before production use, every provider needs a terms review:

  • cache permission;
  • display permission;
  • refresh quota;
  • commercial use;
  • attribution;
  • rate limits;
  • redistribution constraints;
  • whether derived estimates can be stored and shown.

API Surface ​

Initial backend endpoints:

EndpointPurpose
POST /collection/itemsAdd holding from scan or manual search.
GET /collection/itemsList user holdings with filters.
PATCH /collection/items/:idUpdate condition, quantity, notes, storage, acquisition.
DELETE /collection/items/:idSoft-delete holding.
GET /collection/portfolioPortfolio totals and breakdowns.
GET /pricing/items/:identityId/estimateCurrent normalized estimate and explanation.
GET /pricing/items/:identityId/observationsDebug/provider evidence when user is allowed to see it.
POST /alerts/rulesCreate alert rule.
GET /alerts/eventsIn-app alert feed.
PATCH /alerts/events/:idMark read, dismissed, acted-on.

Scan integration:

text
scan result
-> confirmation screen
-> add to collection
-> collection item created
-> valuation snapshot attached if estimate exists
-> optional alert rules suggested

Implementation Roadmap ​

Phase 0 — Architecture Lock ​

Deliverables:

  • schema ADR for user holdings, price observations, estimates, and alerts;
  • provider terms checklist;
  • item identity contract shared by collection/pricing/scan;
  • decision on auth model for private holdings.

Phase 1 — Private Collection Foundation ​

Deliverables:

  • user-scoped collection tables;
  • add/edit/delete/list holdings;
  • acquisition cost and date;
  • item class support for raw_card, sealed_product, and future graded_card;
  • audit trail;
  • local tests around user isolation and soft-delete.

Phase 2 — Add From Scan ​

Deliverables:

  • backend endpoint to create a holding from scan result;
  • mobile confirmation flow;
  • manual fallback when scan result is wrong or ambiguous;
  • capture condition, language, quantity, acquisition price, notes.

Phase 3 — Pricing Schema With Mock Provider ​

Deliverables:

  • price_sources, price_observations, price_estimates;
  • synthetic/mock provider ingestion for tests;
  • first explainable estimator using deterministic rules;
  • no live provider dependency yet.

This phase proves the data model before Cardmarket/eBay complexity.

Phase 4 — Portfolio Valuation ​

Deliverables:

  • valuation snapshots per holding;
  • portfolio totals;
  • gain/loss vs acquisition;
  • breakdown by raw/slab/sealed;
  • stale/low-confidence labels.

Phase 5 — Alerts With Simulated Market Events ​

Deliverables:

  • alert rules;
  • pump/drop detection over estimate history;
  • dedupe and cooldown;
  • in-app alert feed;
  • tests using synthetic price movements.

This validates the product loop before real provider quotas are involved.

Phase 6 — First Real Provider Adapter ​

Deliverables:

  • one provider adapter behind PriceObservationSource;
  • import-run observability;
  • terms-compliant cache/display behavior;
  • provider mapping table;
  • source-specific confidence flags.

Choose the first provider only after the dedicated pricing documentation and terms review. Cardmarket/eBay are strong candidates, but the first production adapter should be the source with the best legal, quota, and data-quality fit.

Phase 7 — Near-Real-Time Alerts ​

Deliverables:

  • scheduled refresh tiers by item importance;
  • high-priority watchlist refresh;
  • event generation after estimate updates;
  • push notification integration;
  • alert explanations that show source, movement, confidence, and freshness.

First Development Slice Recommendation ​

Start with Phase 1: private collection foundation.

Why:

  • it is required for every later valuation and alert feature;
  • it is testable without provider risk;
  • it creates immediate product surface after scan;
  • it keeps user assets private from the beginning;
  • it avoids building pricing logic before there is a place to attach valuations.

The first backend PR should not integrate Cardmarket or eBay yet. It should create user-scoped holdings and enough API shape for "scan -> confirm -> add to collection".

Open Decisions ​

  1. What auth model is acceptable for the first private collection backend?
  2. Should collection photos be stored on the VPS filesystem first, object storage later, or object storage from day one?
  3. Should the first pricing provider target EU/Cardmarket relevance or broader eBay sold comps?
  4. What is the minimum alert latency that feels valuable: minutes, hourly, daily?
  5. What wording avoids implying financial advice while still giving strong buy/sell decision support?
  6. Which item class should get pricing first: raw cards, sealed products, or slabs?