## North Star: Weekly Active Customers (WAC)
Definition: Distinct customers who completed ≥1 order in trailing 7 days.
Why: Captures both acquisition and retention. Correlates with revenue but is harder to game.
Owner: CEO / Chief Product Officer
Refresh: Daily
## Level 1 — Business Health (5 metrics)
| Metric | Definition | Owner | Target |
|--------|-----------|-------|--------|
| New Customer Acquisition | Distinct customers placing first-ever order per week | Growth | +8% MoM |
| Repeat Purchase Rate | % of WAC with ≥2 orders in 30 days | Product | >45% |
| Average Order Value (AOV) | Revenue / Orders (7-day trailing) | Product | >$55 |
| Fulfilment Success Rate | Orders delivered on time / Orders shipped | Operations | >97% |
| Net Promoter Score (NPS) | Monthly survey, promoter % - detractor % | CX | >40 |
## Level 2 — Team Input Metrics (examples)
Growth team owns New Customer Acquisition:
- Traffic volume (sessions per week)
- New visitor conversion rate (orders / first-visit sessions)
- CAC by channel (spend / new customers per channel)
- Activation rate (% sign-ups placing first order within 7 days)
Product team owns Repeat Purchase Rate:
- D7 retention (% users returning within 7 days of first order)
- Category breadth (avg categories ordered from per customer)
- Recommendation click-through rate
- Wishlist-to-order conversion rate
## Guardrail Metrics
These must not degrade while optimising any target:
- Return rate (orders returned / orders delivered) — cap: <12%
- Customer support contacts per order — cap: <0.05
- App crash rate — cap: <0.1%
- Gross margin — floor: >35%
# Metric Definition: Weekly Active Customers (WAC)
**ID:** MTR-001
**Version:** 2.0
**Owner:** Data team / Product Analytics
**Last reviewed:** 2024-01-15
**Status:** Active
## Definition
**Plain English:** The number of distinct customers who placed at least one order that was successfully confirmed (not cancelled) in the trailing 7 calendar days, measured as of midnight UTC.
**Formula:**
## Data Source
- Table: `analytics.fact_orders`
- Refresh: Daily at 02:00 UTC (data for previous day complete)
- Latency SLA: Available by 06:00 UTC
## Breakdowns Available
| Breakdown | Values | Notes |
|-----------|--------|-------|
| Geography | Country, Region | Based on shipping address |
| Channel | Organic, Paid, Email, Direct | First-touch attribution |
| Customer cohort | New (first order ever) vs Returning | |
| Product category | Top-level category | SKU of first item in order |
## Exclusions
- Internal test orders (customer_id in test_customer_ids table)
- B2B accounts (account_type = 'business')
- Orders placed via API (source = 'api') unless flagged as customer
## Interpretation
- **Up is good** (higher = more active customers)
- Seasonality: +30–40% in November–December; -15% in January
- Expected week-over-week variance: ±5% (normal); >±15% = investigate
## Related Metrics
- WAC is the North Star. Decomposed into: New Customer Acquisition, Repeat Purchase Rate.
- Do not confuse with MAU (Monthly Active Users) — WAC counts orders, not sessions.
analytics.track('order_confirmed', {
order_id: string,
customer_id: string,
is_first_order: boolean,
order_value_usd: number,
item_count: number,
categories: string[],
acquisition_channel: string,
campaign_id?: string,
shipping_method: string,
estimated_delivery_date: string,
});
analytics.track('order_delivered', {
order_id: string,
customer_id: string,
confirmed_at: string,
delivered_at: string,
days_to_deliver: number,
on_time: boolean,
});
analytics.track('order_returned', {
order_id: string,
customer_id: string,
return_reason: string,
refund_amount_usd: number,
});
import pandas as pd
from dataclasses import dataclass
@dataclass
class AnomalyRule:
metric: str
window: str
method: str
threshold: float
severity: str
direction: str
ANOMALY_RULES = [
AnomalyRule('wac', '7d', 'pct_change', 0.15, 'p1', 'down'),
AnomalyRule('wac', '1d', 'pct_change', 0.30, 'p1', 'both'),
AnomalyRule('aov', '7d', 'pct_change', 0.10, 'p2', 'both'),
AnomalyRule('return_rate', '7d', 'absolute', 0.12, 'p1', 'up'),
AnomalyRule('nps', '30d', 'absolute', 30.0, 'p2', 'down'),
AnomalyRule('fulfilment', '1d', 'absolute', 0.95, 'p1', 'down'),
]
def check_anomalies(metric_name: str, current_value: float,
historical: pd.Series) -> list[dict]:
alerts = []
for rule in ANOMALY_RULES:
if rule.metric != metric_name: continue
if rule.method == 'pct_change':
prev = historical.iloc[-1]
change = (current_value - prev) / prev
triggered = (
(rule.direction == 'both' and abs(change) > rule.threshold) or
(rule.direction == 'down' and change < -rule.threshold) or
(rule.direction == 'up' and change > rule.threshold)
)
elif rule.method == 'absolute':
triggered = (
(rule.direction == 'down' and current_value < rule.threshold) or
(rule.direction == 'up' and current_value > rule.threshold)
)
if triggered:
alerts.append({
'metric': rule.metric,
'severity': rule.severity,
'current': current_value,
'threshold': rule.threshold,
'message': f"{rule.metric} anomaly: {current_value:.2f}"
})
return alerts