πŸ“– Dashboard Logic & Rules

← Back to Dashboard

The rules the dashboard applies to raw data before it shows a number β€” the “why does this figure look like that” reference.

1Data sources & precedence

SignalSourceNotes
Sales, targets, bills, daily breakupGoogle Sheets MW - <MONTH> tabsprimary
Item-level / channel mixMySQL tbl_salessecondary, /api/sql/*
Walk-insAI-camera audit sheet onlynever DSR β€” see Β§2
Store master (market, RM, cohort…)STORE MAPPING tabjoined server-side

Sales actuals can come from the DSR sheet or SQL (the “Actuals” toggle), but targets always come from the DSR sheet.

2Walk-ins β€” strict audit-only server.js:525 getAuditedWalkins()

Walk-ins are read only from the AI-camera audit sheet (tabs named <Month>-<Year>, e.g. August-2026). We never fall back to the DSR walk-in column β€” a missing audit tab means “not captured yet”, not “use the old number”.

2a Β· Camera-outage gap-fill server.js:590

A store's camera occasionally drops its feed (internet / device outage), producing a day with bills > 0 but walk-ins = 0 β€” which would make conversion% infinite (bills Γ· 0).

The rule: a zero-walk-in day that still had bills gets the store's average-day walk-ins.

2b Β· T-2 cutoff (current month only) server.js:495 tMinusTwoCutoff()

Audit data lags reality by ~2 days. For the current month we only count days up to today βˆ’ 2; later days are ignored (not yet audited). Past months have no cutoff.

2c Β· When a day is still zero after gap-fill server.js:619

Only happens if the store had no positive-walk-in day all month (camera down the whole month). We don't invent a number β€” we count it:

2d Β· Source states server.js:826 / :901

audit_meta.source tells the UI exactly what it's looking at:

StateMeaning
auditedreal audit tab found and used
awaitingaudit sheet configured but this month's tab missing/empty β†’ walk-ins = 0
not-configuredno audit sheet wired up at all
unmatchedstore in DSR but not in the audit tab β†’ walk-ins = 0, flagged

3Conversion % server.js:857

conversion% = (bills on walk-in days) Γ· (walk-ins on walk-in days) Γ— 100

Days with zero walk-ins are excluded from both numerator and denominator, so a cam-down day can't distort the ratio. Network conversion is recomputed the same way from the audit totals server.js:939, matching how the audit team computes it.

4Store cohort server.js:734 resolveStoreCohort()

Cohort is derived from STORE MAPPING col K, by substring match (tolerates the emoji prefix πŸ”„ LFL / πŸ“† LY / πŸ†• NEW):

Col K containsCohort
NEWNEW
LYLY
LFLLFL
blank / anything elseUnknown

Blank cells become Unknown (red badge) β€” never silently LFL. Resolution order: exact name β†’ normalized name β†’ Unknown. Routing every endpoint through this one helper is what stopped cohort counts drifting between pages (was 97 vs 100).

5Store-name normalization server.js:473 normStoreName()

Every cross-sheet join runs names through this so the same store matches everywhere:

Store codes have their own normalizer (normStoreCode, server.js:489): strip all whitespace + unify dashes, so KFJ-127 / KFJ - 127 / KFJ – 127 collapse to one key.

6KPI value formatting bh/js/common.js

fmtKpiMain(n, kind) β€” every KPI-tile main value common.js:429. Wraps β‚Ή and the unit (Cr/L/%) in muted spans so the number is visually dominant.

kindoutput
moneyβ‚Ή1.09Cr / β‚Ή47.5L / β‚Ή8K (auto-scaled)
money-plainβ‚Ή3,564 (never scaled β€” ABV)
count3,07,044 (Indian grouping)
pct37.0%
ratio2.65
null / non-finiteβ€” (mute)

fCompact(n) common.js:412 β€” compact β‚Ή for dense tables/mobile: β‰₯1Cr β†’ Cr, β‰₯1L β†’ L, β‰₯1K β†’ K, else grouped. Zero/empty β†’ β€”.

ABV = net_sales Γ· total_bills server.js:933.

7Color bands β€” canonical thresholds bh/js/common.js

Every cell that colors by a number uses these β€” no hardcoded ternaries at call sites.

achBand(pct) β€” achievement vs target common.js:453

ach%tier
β‰₯ 110excellent
β‰₯ 100good
β‰₯ 90ok
β‰₯ 70warn
β‰₯ 50bad
< 50critical

deltaBand(pp) β€” pp deltas, e.g. conversion TY-vs-LY common.js:466: β‰₯3 strong green, >0 green, 0 neutral, β‰₯βˆ’3 amber, <βˆ’3 red.

growthBand(pct) β€” YoY growth common.js:477: β‰₯10 strong green, >0 green, β‰₯βˆ’10 amber/red, <βˆ’10 red. (10% is the “big deal” retail benchmark.)

8Day pro-rating bh/js/common.js:489

daysInfo carries {done, total} for the selected month.

Missing di β†’ 0 (callers don't crash mid-render).

9Sheet-read conventions server.js

10Data-hygiene flags β€” surfaced, not silently fixed

These are sheet problems the app deliberately makes visible instead of papering over:

11Banned patterns β€” why numbers stay trustworthy

Retail Dashboard Β· Kushals Fashion Jewellery β€” back to dashboard