Skip to content

Data model

Five tables hold imported data. Nine views sit on top of them and do the aggregation that the API exposes. Nothing is denormalised into a reporting table, which is why changing a cost price immediately changes every historical profit figure.

Tables

Table Holds
products One row per product per marketplace: name, category, your cost price
sales Completed sales with revenue, commission, logistics and net revenue
orders Orders as reported by the marketplace, including those not yet delivered
stocks Stock quantity per warehouse per date
card_stats Daily listing statistics: impressions, clicks, cart additions, orders

sales and orders are separate because a marketplace reports them at different points in its own lifecycle. Financial figures throughout the product come from sales.

Why source and sku together

Every table keys products on the pair (source, sku) rather than on sku alone.

Wildberries and Ozon allocate SKUs independently, so the same number can identify unrelated products on the two platforms. Beyond avoiding that collision, keeping them separate is what makes per-platform comparison possible: commission structures, logistics costs and listing statistics differ enough that merging an item's two listings into one row would destroy the comparison the product exists to provide.

The practical consequence for a seller is that an item listed on both marketplaces has its cost price set twice, once per platform.

Views

The API reads almost exclusively from views. Each one corresponds to a section of the dashboard.

View Feeds
v_profit_daily Daily, all-time and finance exports
v_profit_weekly Weekly profit series
v_profit_monthly Monthly profit series, including cost of goods
v_profit_quarterly Quarterly profit series
v_profitability Per-product economics and ROI
v_abc_analysis ABC classification by cumulative profit share
v_reorder_list Stock levels, days of cover and urgency status
v_card_performance Daily listing statistics with derived rates
v_platform_comparison Marketplace comparison with deductions as percentages

Because these are views rather than materialised tables, they reflect the current state of the underlying data on every query. There is no refresh step and no staleness, at the cost of doing the aggregation work each time a request arrives.

How profit is calculated

Three figures are easy to conflate, and the difference between them matters.

Revenue is what the buyer paid.

Net revenue is revenue minus the marketplace's commission and logistics charges. It is what the marketplace actually transfers to you.

Gross profit is net revenue minus the cost of the units sold, where cost comes from the cost_price you entered.

Products without a cost price contribute zero cost, so gross profit for them equals net revenue. This is why the dashboard overstates profit until cost prices are filled in, and why ROI is reported as null rather than as an inflated number.

Null semantics

Nulls in this data set are meaningful and are preserved deliberately rather than coalesced to zero.

Field null means
cost_price Not entered yet, so profit for this product is unreliable
roi_pct No cost price, so return on cost is undefined
days_of_stock No recent sales, so there is no rate to project from
revenue_wow, units_wow No preceding week, or the preceding week was zero
*_delta The comparison period was zero
category The marketplace supplied none

Rendering any of these as 0 turns "unknown" into a specific and wrong claim. Client code should show them as blank.

Time handling

Relative windows are computed from the server's local clock rather than from UTC. If the server's timezone differs from the one your marketplace account reports in, day boundaries shift accordingly, and a figure labelled "last 30 days" covers a window offset by that difference.

Setting the server to the same timezone as the seller account avoids the whole class of problem.