The rules the dashboard applies to raw data before it shows a number β the “why does this figure look like that” reference.
| Signal | Source | Notes |
|---|---|---|
| Sales, targets, bills, daily breakup | Google Sheets MW - <MONTH> tabs | primary |
| Item-level / channel mix | MySQL tbl_sales | secondary, /api/sql/* |
| Walk-ins | AI-camera audit sheet only | never DSR β see Β§2 |
| Store master (market, RM, cohortβ¦) | STORE MAPPING tab | joined server-side |
Sales actuals can come from the DSR sheet or SQL (the “Actuals” toggle), but targets always come from the DSR sheet.
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”.
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).
monthAvgWi = average walk-ins across that store's days that had walk-ins > 0.bills > 0 && walk-ins == 0 is filled with monthAvgWi and flagged gap_filled.bills == 0 && walk-ins == 0 is a genuinely closed day β left untouched.gap_filled_days / gap_filled_walkins are surfaced in audit_meta so the UI can badge it.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.
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:
awaiting_fill_daysunfilled_post_close_daysaudit_meta.source tells the UI exactly what it's looking at:
| State | Meaning |
|---|---|
| audited | real audit tab found and used |
| awaiting | audit sheet configured but this month's tab missing/empty β walk-ins = 0 |
| not-configured | no audit sheet wired up at all |
| unmatched | store in DSR but not in the audit tab β walk-ins = 0, flagged |
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.
Cohort is derived from STORE MAPPING col K, by substring match (tolerates the emoji prefix π LFL / π LY / π NEW):
| Col K contains | Cohort |
|---|---|
| NEW | NEW |
| LY | LY |
| LFL | LFL |
| blank / anything else | Unknown |
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).
Every cross-sheet join runs names through this so the same store matches everywhere:
"X (ERDPRD )" β "X"β β) β -"X-Y", "X- Y", "X -Y" β "X - Y"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.
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.
| kind | output |
|---|---|
| money | βΉ1.09Cr / βΉ47.5L / βΉ8K (auto-scaled) |
| money-plain | βΉ3,564 (never scaled β ABV) |
| count | 3,07,044 (Indian grouping) |
| pct | 37.0% |
| ratio | 2.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.
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 |
|---|---|
| β₯ 110 | excellent |
| β₯ 100 | good |
| β₯ 90 | ok |
| β₯ 70 | warn |
| β₯ 50 | bad |
| < 50 | critical |
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.)
daysInfo carries {done, total} for the selected month.
dayProrate(target, di) = target Γ done Γ· total β MTD-expected portion (what target should be met by today). Used to judge pace mid-month.dayTarget(target, di) = target Γ· total β flat per-day target.Missing di β 0 (callers don't crash mid-render).
MW - <MONTH> tabs: row 1 title, row 3 network totals, row 6+ store rows.A6:AD500 inside fetchRange() so every caller shares one cache entry β read wide, ignore extra columns./api/sql/overview returns data_through = MAX(bill_date), shown as a chip.These are sheet problems the app deliberately makes visible instead of papering over:
Unknown red pill (fix = fill col K in STORE MAPPING).MUM - NEXUS SEAWOOD MALL typo in the walk-in sheet (should be SEAWOODS) β β UNMAPPED in Conversion until the sheet is corrected.unmatched, walk-ins 0.LFL or a stale DSR value.mapping[name]?.cohort || 'LFL' β always resolveStoreCohort().fmtCr / achShade / hardcoded >= 70 β use the canonical helpers above.Retail Dashboard Β· Kushals Fashion Jewellery β back to dashboard