feat(loyalty): Phase 7 — advanced analytics (cohort, churn, revenue)
New analytics_service.py with three analytics features:
- Cohort retention: groups cards by enrollment month, tracks % with
any transaction in each subsequent month. Returns matrix suitable
for Chart.js heatmap. GET /analytics/cohorts?months_back=6
- Churn detection: flags cards as "at risk" when inactive > 2x their
average inter-transaction interval (default 60d for new cards).
Returns ranked list. GET /analytics/churn?limit=50
- Revenue attribution: monthly and per-store aggregation of point-
earning transactions. GET /analytics/revenue?months_back=6
Endpoints added to both admin API (/admin/loyalty/merchants/{id}/
analytics/*) and store API (/store/loyalty/analytics/*) so merchants
can see their own analytics.
342 tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -198,6 +198,51 @@ def get_merchant_stats(
|
||||
return MerchantStatsResponse(**stats)
|
||||
|
||||
|
||||
@router.get("/analytics/cohorts")
|
||||
def get_cohort_retention(
|
||||
months_back: int = Query(6, ge=1, le=24),
|
||||
current_user: User = Depends(get_current_store_api),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Cohort retention matrix for this merchant's loyalty program."""
|
||||
from app.modules.loyalty.services.analytics_service import analytics_service
|
||||
|
||||
merchant_id = get_store_merchant_id(db, current_user.token_store_id)
|
||||
return analytics_service.get_cohort_retention(
|
||||
db, merchant_id, months_back=months_back
|
||||
)
|
||||
|
||||
|
||||
@router.get("/analytics/churn")
|
||||
def get_at_risk_cards(
|
||||
limit: int = Query(50, ge=1, le=200),
|
||||
current_user: User = Depends(get_current_store_api),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Cards at risk of churn for this merchant."""
|
||||
from app.modules.loyalty.services.analytics_service import analytics_service
|
||||
|
||||
merchant_id = get_store_merchant_id(db, current_user.token_store_id)
|
||||
return analytics_service.get_at_risk_cards(
|
||||
db, merchant_id, limit=limit
|
||||
)
|
||||
|
||||
|
||||
@router.get("/analytics/revenue")
|
||||
def get_revenue_attribution(
|
||||
months_back: int = Query(6, ge=1, le=24),
|
||||
current_user: User = Depends(get_current_store_api),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Revenue attribution from loyalty transactions."""
|
||||
from app.modules.loyalty.services.analytics_service import analytics_service
|
||||
|
||||
merchant_id = get_store_merchant_id(db, current_user.token_store_id)
|
||||
return analytics_service.get_revenue_attribution(
|
||||
db, merchant_id, months_back=months_back
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Staff PINs
|
||||
# =============================================================================
|
||||
|
||||
Reference in New Issue
Block a user