feat(brands): Add margin estimation to stores/performance endpoint
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

- Add ?margin_pct query param (default 50% industry standard)
- Returns margin_pct and margin_est per store
- Includes margin_pct_assumed in response metadata

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Kelly
2025-12-15 12:02:36 -07:00
parent 17defa046c
commit 231d49e3e8

View File

@@ -710,6 +710,8 @@ export function createBrandsRouter(pool: Pool): Router {
const stateCode = req.query.state as string | undefined;
const limit = parseLimit(req.query.limit as string, 100);
const offset = parseOffset(req.query.offset as string);
// Margin assumption - default 50% (industry standard)
const marginPct = Math.min(Math.max(parseFloat(req.query.margin_pct as string) || 50, 0), 100);
const startDate = new Date();
startDate.setDate(startDate.getDate() - days);
@@ -879,11 +881,15 @@ export function createBrandsRouter(pool: Pool): Router {
brand: brandName,
period_days: days,
state: stateCode || 'all',
margin_pct_assumed: marginPct,
summary: {
total_stores: parseInt(totals.total_stores) || 0,
total_oos: parseInt(totals.total_oos) || 0,
},
stores: result.rows.map(row => ({
stores: result.rows.map(row => {
const totalSalesEst = row.total_sales_est ? parseFloat(row.total_sales_est) : null;
const marginEst = totalSalesEst ? totalSalesEst * (marginPct / 100) : null;
return {
store_id: row.store_id,
store_name: row.store_name,
state_code: row.state_code,
@@ -896,7 +902,10 @@ export function createBrandsRouter(pool: Pool): Router {
oos_pct: parseInt(row.oos_pct) || 0,
// Velocity & Sales
avg_daily_units: row.avg_daily_units ? parseFloat(row.avg_daily_units) : null,
total_sales_est: row.total_sales_est ? parseFloat(row.total_sales_est) : null,
total_sales_est: totalSalesEst,
// Margin (estimated)
margin_pct: marginPct,
margin_est: marginEst ? parseFloat(marginEst.toFixed(2)) : null,
// Inventory
avg_days_on_hand: row.avg_days_on_hand ? parseFloat(row.avg_days_on_hand) : null,
total_stock: row.total_stock ? parseInt(row.total_stock) : null,
@@ -906,7 +915,7 @@ export function createBrandsRouter(pool: Pool): Router {
lost_opportunity: row.lost_opportunity ? parseFloat(row.lost_opportunity) : null,
// Categories breakdown
categories: row.categories || {},
})),
};}),
pagination: {
limit,
offset,