Contents
  1. What This Page Shows
  2. Data Source: Camelot WMS
  3. Full Data Lineage
  4. How the Forecast Is Calculated
  5. Sweed vs. Sweed DTC: Channel Treatment
  6. Current Issue: Inventory Position Is Incorrect
  7. The Fix
  8. Known Limitations

What This Page Shows

The Inventory Supply Plan page at portal.cremecollective.com/admin/analytics/inventory is a direct replacement for the Tableau "Inventory Supply Plan" dashboard previously used by the Creme Collective team. It is designed to answer one core question for each brand: does current inventory cover projected demand over the next 3 to 6 months, and by how much?

For Sweed, the page shows one row per SKU. Each row includes the current inventory position (on-hand, committed, in-transit), trailing sales usage over the past 12 months broken down by month, a 6-month forward demand forecast, and the resulting shortage figures.

Column group What it means
On Hand Units physically in Camelot's warehouse attributed to this SKU and client.
Committed Units already reserved against open outbound orders — not yet shipped but spoken for.
In Transit Units inbound to the warehouse, not yet available for fulfillment.
Inventory Net available stock: On Hand minus Committed.
1M – 12M Usage Actual units shipped in each of the past 12 calendar months.
6M Usage / 12M Usage Total units shipped over the trailing 6 and 12 months respectively.
Forecast Month 1–6 Projected units needed each month for the next 6 months.
3M Demand / 6M Demand Sum of forecasted units over the next 3 and 6 months.
3M Shortage / 6M Shortage How far short current inventory falls against the 3M and 6M demand forecast. Zero means inventory covers demand.

Data Source: Camelot WMS

All data originates from Camelot 3PL Cloud — the warehouse management system Creme Collective uses for fulfillment. DataStudios accesses Camelot via its OData API, a standard interface that exposes warehouse data as queryable web endpoints.

The API provides point-in-time snapshots, not history. Each time the pipeline runs, Camelot returns the current state of inventory — what is in the warehouse right now. Camelot does not expose historical inventory levels through the API. This has important implications explained in Section 8.

Two Camelot endpoints feed the Sweed supply plan:

Camelot endpoint What it contains Load method Frequency
SalesDetailByBrand Every shipped order line, by brand. This is the source of all historical sales and usage data. Delete rows for dates in the payload, then re-insert. Handles late corrections to shipped orders. Daily, ~2:00 AM ET
InventoryByItemWhseSummary Current inventory levels per SKU per warehouse. On-hand, reserved, inbound quantities. Full replacement — previous data is discarded and today's snapshot is loaded fresh. Daily, ~2:00 AM ET

The pipeline runs automatically each night. Today's run (May 21, 2026) completed in 134 minutes, ingesting 48,880 sales lines and 1,489 inventory records across all Creme Collective brands.


Full Data Lineage

Data passes through four layers between Camelot and the portal. Each layer applies transformations to clean, enrich, or compute on the raw data.

Source
Camelot 3PL Cloud (OData API)
SalesDetailByBrand — shipped order lines by brand, paginated, ~49K records for all Creme brands.
InventoryByItemWhseSummary — current inventory snapshot per SKU/warehouse, ~1.5K records.
Bronze Layer
Raw ingestion tables (PostgreSQL)
bronze_camelot.salesdetailbybrand — raw sales lines as received from the API, no transformations applied.
bronze_camelot.inventorybyitemwhsesummary — raw inventory snapshot. Fully replaced each run.
bronze_camelot.sales_analysis_report_historical — static historical sales table loaded once, covering periods before the current pipeline was live.
Silver Layer
Cleaned & standardised tables (dbt models)
silver_camelot.sales_detail_by_brand — deduplication of pending sales lines (handles a known Camelot API quirk where pending orders can appear multiple times), address enrichment, and column standardisation.
silver_camelot.inventory_by_item_whse_summary — renamed columns, units of measure standardised, metadata added.
Gold Layer
Business-ready tables
gold_camelot.sales_analysis_report — current and historical sales combined, with address gaps filled from the most recent known address per customer. This is the foundation for all usage and forecast calculations.
gold_camelot.supply_plan_forecast — one row per SKU per brand per day. Contains all trailing usage metrics, 6-month forward forecast, and inventory position. Written by a Python model that runs after dbt. This is the table the portal reads directly.
Portal
portal.cremecollective.com/admin/analytics/inventory
Reads the latest snapshot from gold_camelot.supply_plan_forecast for the selected client. Always shows the most recent day's data. Column layout is driven by a metadata table (gold_camelot.tableau_column_mapping) so columns can be reordered or renamed without a code change.

How the Forecast Is Calculated

The forecast is computed once per day for each SKU. It uses only actual shipped sales history from Camelot — no external demand signals or manual overrides. The method is a trailing average with compound growth projection.

Step 1 — Trailing usage
1M Usage = units shipped in month M-1
2M Usage = units shipped in month M-2
···
12M Usage = units shipped in month M-12
Sourced directly from shipped order lines in gold_camelot.sales_analysis_report.
Step 2 — Rolling averages
6M Avg = 6M Usage ÷ 6
9M Avg = 9M Usage ÷ 9
12M Avg = 12M Usage ÷ 12
Three averages computed to capture different trend windows.
Step 3 — Forecast base
Forecast Base =
max(6M Avg, 9M Avg, 12M Avg)
Takes the highest of the three averages as the conservative starting point, avoiding underestimation if recent months are unusually high.
Step 4 — 6-month projection
Month 1 = Forecast Base
Month 2 = Month 1 × 1.025
Month 3 = Month 2 × 1.025
···
Month 6 = Month 5 × 1.025
2.5% month-over-month growth (~34% annualised) applied as a fixed assumption. This rate is not brand-specific or derived from trend data.
Step 5 — Demand totals
3M Demand = Month1 + Month2 + Month3
6M Demand = Month1 + ··· + Month6
Total projected units needed over the 3 and 6 month windows.
Step 6 — Shortage
3M Shortage =
max(0, 3M Demand − Inventory)

6M Shortage =
max(0, 6M Demand − Inventory)
Zero means inventory is sufficient. Any positive number is the unit gap that needs to be covered by a purchase order or reallocation.
The 2.5% monthly growth rate is a fixed assumption. It is applied uniformly to all Sweed SKUs regardless of actual trend. A SKU that has been declining for six months will still receive an upward-sloping forecast. This is a known limitation of the current model and is flagged for a future improvement to use SKU-level trend fitting.

Sweed-specific: tester SKU consolidation. Sweed sells products both as standard units and as tester units (SKU prefix T-, e.g. T-552 alongside 552). For forecasting purposes, tester sales are merged into the base SKU before calculating usage — so the forecast for SKU 552 reflects total sell-through of that product regardless of whether it was sold as a tester or a standard unit. This matches how supply planning should work: a tester unit consumes the same physical product as a standard unit.


Sweed vs. Sweed DTC: Channel Treatment

Camelot tracks Sweed under two client codes: SWEED (wholesale / retail orders) and SWEED DTC (direct-to-consumer / internet orders). The portal shows them as separate rows in the client dropdown. Before deciding how to treat inventory across these two codes, we analysed the underlying data to understand whether they represent one physical stock pool or two separate ones.

Finding 1
Same physical warehouse and bins
Both client codes use warehouse SAGE exclusively. At the piece level, SWEED and SWEED DTC units are stored in the same bin locations (e.g. bin 100801B, 101909D, 101912D). Two genuinely separate storage pools would not share bin codes.
Finding 2
Stock is split, not separated
Of 219 unique Sweed SKUs, 122 appear under both client codes simultaneously — and in every case both sides have stock at the same time, split roughly 50/50 (62,662 units SWEED vs 60,540 units SWEED DTC for shared SKUs). This pattern indicates Camelot is administratively dividing one physical pool between two billing codes, not managing two separate inventories.
Finding 3
Channels are operationally distinct
Despite sharing physical stock, the two channels ship very differently: wholesale averages 6 units per line at $141 with 8,261 orders in the last 6 months; DTC averages 1 unit per line at $43 with 27,561 orders. Separate forecasts are meaningful and are preserved.
Finding 4
Some SKUs are misattributed in WMS
6 SKUs have sales history under the SWEED brand but inventory recorded only under SWEED DTC in Camelot (e.g. SKU 535 — The Bronzing Powder, 794 units). This confirms the client code split is administrative: the stock can be used by either channel.
Decision: combine inventory, keep forecasts separate. The supply plan will show total on-hand / committed / in-transit across both SWEED and SWEED DTC for each SKU. Forecasts remain separate so the planner can see each channel's demand independently. The shortage metric then answers: "does total brand stock cover this channel's projected demand?" — which is the actionable question for reorder decisions.

Current Issue: Inventory Position Is Incorrect

During this analysis, a significant issue was identified in the current data. The four inventory position columns — On Hand, Committed, In Transit, and Inventory — are hardcoded to zero for every SKU and every brand in the portal today.

This is not a data availability problem. Camelot provides real inventory figures through the InventoryByItemWhseSummary API endpoint, and this data is ingested into the database every night. The issue is that the transformation step that builds the supply plan table reads the sales data correctly but substitutes zeros instead of looking up the inventory figures.

Impact: shortage figures are entirely wrong today. Because Inventory = 0 for every SKU, the shortage calculation produces: Shortage = Demand − 0 = Demand. Every SKU appears to have a full shortage equal to its entire forecasted demand. The actual inventory position for Sweed across both channels is approximately 186,930 units on hand — none of which is currently reflected in the portal.
69,228
SWEED on hand
117,277
SWEED DTC on hand
186,505
Combined on hand
0
Currently shown in portal

The usage and forecast columns are not affected by this issue — those are computed correctly from sales history. Only the inventory position and shortage columns are wrong.


The Fix

The transformation that builds the supply plan table will be updated to look up real inventory figures from silver_camelot.inventory_by_item_whse_summary instead of substituting zeros. The following rules apply:

Column Source field Logic
On Hand qty_on_hand Sum across SWEED + SWEED DTC, grouped by SKU.
Committed qty_reserved Sum across SWEED + SWEED DTC, grouped by SKU.
In Transit qty_inbound Sum across SWEED + SWEED DTC, grouped by SKU.
Inventory qty_on_hand − qty_reserved Net uncommitted stock available for fulfillment.

SKUs with no inventory record (discontinued lines, non-stockable items, artwork files, work orders) will remain at zero — which is the correct value for those items.

Tester SKUs (prefix T-) are stripped before the inventory lookup, consistent with how tester sales are handled in the forecast. A tester unit's stock is counted toward the base SKU's on-hand total.

Expected match rate after fix. Based on analysis of today's data, approximately 88% of Sweed SKUs in the supply plan will have real inventory figures after the fix. The remaining 12% are genuinely depleted or non-stockable items (discontinued lash styles, shipping boxes, brochures, display materials) for which zero is the correct value.

The fix will take effect starting with the next nightly pipeline run following deployment. No manual data entry or reprocessing is required.


Known Limitations

Limitation Detail Status
No historical inventory data Camelot's API only returns today's inventory. The supply plan table stores a daily snapshot going back to January 2026, but all historical rows have inventory = 0 because the data was never available. The portal always shows the latest snapshot, so this does not affect what users see today. If trend analysis on inventory position over time is needed in the future, a separate mechanism to log daily inventory snapshots would need to be built. Accepted
Fixed growth rate The 2.5% monthly growth assumption applied to all Sweed SKUs does not reflect actual SKU-level trends. A declining product will show an upward forecast. Future improvement: derive per-SKU trend from the trailing 12 months of sales data. Future
No seasonality The forecast uses trailing averages without adjusting for seasonal patterns. SKUs with strong Q4 demand will be underforecasted in Q3 models and overforecasted in Q1 models. Future
Non-stockable SKUs in forecast A small number of Sweed SKUs represent non-physical items (shipping boxes, brochures, artwork files, work orders) that were fulfilled through Camelot and appear in sales history. These are included in the forecast with zero inventory, which is correct but adds noise to the report. A SKU exclusion list can be added if needed. Accepted
Inventory position is zeroed All inventory columns currently show zero due to the issue described in Section 6. This is being corrected. Fix in progress
Camelot client code split Camelot's administrative split of Sweed stock between SWEED and SWEED DTC client codes does not reflect a physical separation. The combined inventory approach (Section 5) is an approximation of the true available stock. If Camelot ever formally segregates the two pools, this logic will need revisiting. Monitored