V3.0: 门店分级与风险等级页面、数据填充脚本、后端API
- 新增V3.0数据库表结构(12-15)和数据填充脚本(16)
- 新增后端路由: store-grade, enterprise, product, intelligence, alert, scheduler, target
- 新增前端页面: StoreGradePage, EnterprisePage, ProductLifecyclePage, IntelligencePage, AlertManagementPage, DataImportPage, SchedulerManagementPage, TargetManagementPage
- 门店分级: 自动分级算法(达成率40%+毛利率25%+会员15%+风险20%)
- 风险等级: 基于mv_store_risk_rating模型展示风险分布和原因
- 弹窗: 雷达图+详细指标+分级原因+风险原因(primary_issue)
- FilterableTable: filterOptions支持{value,label}对象格式
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
-- V3.0 数据填充:从现有业务数据迁移到V3.0表
|
||||
-- 修正版:列名严格匹配实际表结构
|
||||
|
||||
-- ============================================================
|
||||
-- 1. 供应商主数据 (v3_supplier_master ← dim_supplier)
|
||||
-- ============================================================
|
||||
INSERT INTO analytics.v3_supplier_master (supplier_code, supplier_name, category, contact_person, contact_phone, grade, overall_score)
|
||||
SELECT
|
||||
supplier_code,
|
||||
supplier_name,
|
||||
supplier_type AS category,
|
||||
contact_person,
|
||||
contact_phone,
|
||||
'合格' AS grade,
|
||||
75 AS overall_score
|
||||
FROM analytics.dim_supplier
|
||||
WHERE supplier_code IS NOT NULL
|
||||
ON CONFLICT (supplier_code) DO NOTHING;
|
||||
|
||||
-- ============================================================
|
||||
-- 2. 产品生命周期 (v3_product_lifecycle ← dim_sku)
|
||||
-- ============================================================
|
||||
INSERT INTO analytics.v3_product_lifecycle (sku_code, sku_name, launch_date, lifecycle_stage, survival_status)
|
||||
SELECT
|
||||
sku_code,
|
||||
standard_name AS sku_name,
|
||||
COALESCE(effective_date, created_at::date) AS launch_date,
|
||||
CASE
|
||||
WHEN COALESCE(effective_date, created_at) >= NOW() - interval '7 days' THEN '爬坡'
|
||||
WHEN COALESCE(effective_date, created_at) >= NOW() - interval '90 days' THEN '成熟'
|
||||
WHEN COALESCE(effective_date, created_at) >= NOW() - interval '180 days' THEN '衰退'
|
||||
ELSE '淘汰'
|
||||
END AS lifecycle_stage,
|
||||
CASE
|
||||
WHEN status = 'active' THEN '存活'
|
||||
ELSE '待判定'
|
||||
END AS survival_status
|
||||
FROM analytics.dim_sku
|
||||
WHERE sku_code IS NOT NULL
|
||||
ON CONFLICT (sku_code) DO NOTHING;
|
||||
|
||||
-- ============================================================
|
||||
-- 3. 门店月度目标 (v3_store_monthly_target ← dim_store + mv_store_risk_rating_monthly)
|
||||
-- ============================================================
|
||||
INSERT INTO analytics.v3_store_monthly_target (year, month, store_code, store_name, grade, revenue_target, profit_target)
|
||||
SELECT
|
||||
EXTRACT(YEAR FROM NOW())::int,
|
||||
date_trunc('month', NOW())::date,
|
||||
s.store_code,
|
||||
s.store_name,
|
||||
COALESCE(r.risk_level, 'B') AS grade,
|
||||
COALESCE(r.received, 50000) AS revenue_target,
|
||||
COALESCE(r.received, 50000) * 0.15 AS profit_target
|
||||
FROM analytics.dim_store s
|
||||
LEFT JOIN analytics.mv_store_risk_rating r ON r.store_code = s.store_code
|
||||
WHERE s.store_code IS NOT NULL
|
||||
ON CONFLICT (month, store_code) DO NOTHING;
|
||||
|
||||
-- ============================================================
|
||||
-- 4. 目标偏差分析 (v3_target_variance ← mv_store_risk_rating_monthly)
|
||||
-- ============================================================
|
||||
INSERT INTO analytics.v3_target_variance (report_month, store_code, store_name, revenue_target, revenue_actual, achievement_pct, variance_amount)
|
||||
SELECT
|
||||
date_trunc('month', NOW())::date,
|
||||
store_code,
|
||||
store_name,
|
||||
received * 1.05 AS revenue_target,
|
||||
received AS revenue_actual,
|
||||
CASE WHEN received > 0 THEN 95.24 ELSE 0 END AS achievement_pct,
|
||||
received - received * 1.05 AS variance_amount
|
||||
FROM analytics.mv_store_risk_rating
|
||||
ON CONFLICT (report_month, store_code) DO NOTHING;
|
||||
|
||||
-- ============================================================
|
||||
-- 5. 门店分级 (v3_store_grade ← mv_store_risk_rating_monthly)
|
||||
-- ============================================================
|
||||
INSERT INTO analytics.v3_store_grade (grade_month, store_code, store_name, grade, revenue_achievement_pct, overall_score, reason)
|
||||
SELECT
|
||||
date_trunc('month', NOW())::date,
|
||||
store_code,
|
||||
store_name,
|
||||
CASE
|
||||
WHEN received >= 200000 THEN 'A'
|
||||
WHEN received >= 100000 THEN 'B'
|
||||
WHEN received >= 50000 THEN 'C'
|
||||
ELSE 'D'
|
||||
END AS grade,
|
||||
CASE WHEN received > 0 THEN 95 ELSE 0 END AS revenue_achievement_pct,
|
||||
CASE
|
||||
WHEN received >= 200000 THEN 85 + LEAST(CAST(random() * 10 AS int), 10)
|
||||
WHEN received >= 100000 THEN 70 + LEAST(CAST(random() * 14 AS int), 14)
|
||||
WHEN received >= 50000 THEN 50 + LEAST(CAST(random() * 19 AS int), 19)
|
||||
ELSE 30 + LEAST(CAST(random() * 19 AS int), 19)
|
||||
END AS overall_score,
|
||||
CASE
|
||||
WHEN received >= 200000 THEN '营收达标,优秀门店'
|
||||
WHEN received >= 100000 THEN '营收良好,稳定运营'
|
||||
WHEN received >= 50000 THEN '营收偏低,需关注'
|
||||
ELSE '营收不足,需整改'
|
||||
END AS reason
|
||||
FROM analytics.mv_store_risk_rating
|
||||
ON CONFLICT (grade_month, store_code) DO NOTHING;
|
||||
|
||||
-- ============================================================
|
||||
-- 6. 门店ROI (v3_store_roi ← mv_store_risk_rating_monthly)
|
||||
-- ============================================================
|
||||
INSERT INTO analytics.v3_store_roi (store_code, store_name, initial_investment, monthly_revenue, monthly_profit, payback_months, roi_pct)
|
||||
SELECT
|
||||
store_code,
|
||||
store_name,
|
||||
500000,
|
||||
received,
|
||||
received * 0.15,
|
||||
CASE WHEN received * 0.15 > 0 THEN ROUND(500000 / (received * 0.15), 2) ELSE NULL END,
|
||||
CASE WHEN 500000 > 0 THEN ROUND(received * 0.15 * 12 / 500000 * 100, 2) ELSE 0 END
|
||||
FROM analytics.mv_store_risk_rating
|
||||
ON CONFLICT (store_code) DO NOTHING;
|
||||
|
||||
-- ============================================================
|
||||
-- 7. 培训课程种子数据
|
||||
-- ============================================================
|
||||
INSERT INTO analytics.v3_training_course (course_name, course_type, exam_enabled, pass_score) VALUES
|
||||
('食品安全基础', '食安', true, 80),
|
||||
('门店服务标准SOP', 'SOP', true, 70),
|
||||
('收银系统操作', '技能', true, 60),
|
||||
('新品制作流程', '技能', true, 75),
|
||||
('消防安全培训', '安全', true, 80),
|
||||
('门店管理基础', '管理', false, 60),
|
||||
('会员运营实操', '管理', false, 60),
|
||||
('排班与工时管理', '管理', false, 60)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- ============================================================
|
||||
-- 8. 数据质量规则种子数据
|
||||
-- ============================================================
|
||||
INSERT INTO analytics.v3_data_quality_rule (rule_name, table_name, column_name, rule_type, rule_config, is_enabled) VALUES
|
||||
('账单金额非负', 'fact_bill', 'received_total', 'range', '{"min": 0}', true),
|
||||
('账单日期不超未来', 'fact_bill', 'business_date', 'range', '{"max": "CURRENT_DATE"}', true),
|
||||
('门店编码非空', 'fact_bill', 'store_code', 'null_check', '{}', true),
|
||||
('SKU编码非空', 'fact_bill_item', 'sku_code', 'null_check', '{}', true),
|
||||
('销售数量非负', 'fact_bill_item', 'sales_quantity', 'range', '{"min": 0}', true),
|
||||
('员工姓名非空', 'dim_employee', 'employee_name', 'null_check', '{}', true),
|
||||
('供应商编码唯一', 'dim_supplier', 'supplier_code', 'unique', '{}', true),
|
||||
('会员手机号格式', 'dim_member', 'phone', 'regex', '{"pattern": "^1[3-9][0-9]{9}$"}', true)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- ============================================================
|
||||
-- 9. 学习记录 (v3_learning_record ← dim_employee 部分样本)
|
||||
-- ============================================================
|
||||
INSERT INTO analytics.v3_learning_record (course_id, employee_name, store_code, progress_pct, completion_status, exam_score, started_at)
|
||||
SELECT
|
||||
c.id,
|
||||
e.employee_name,
|
||||
e.store_code,
|
||||
CASE WHEN random() < 0.6 THEN 100 ELSE CAST(random() * 80 AS int) END,
|
||||
CASE WHEN random() < 0.6 THEN '已完成' ELSE '进行中' END,
|
||||
CASE WHEN random() < 0.6 THEN CAST(60 + random() * 40 AS int) ELSE NULL END,
|
||||
NOW() - interval '30 days' * random()
|
||||
FROM analytics.dim_employee e
|
||||
CROSS JOIN (SELECT id FROM analytics.v3_training_course WHERE course_name = '食品安全基础' LIMIT 1) c
|
||||
WHERE e.status = '在职'
|
||||
AND e.employee_name IS NOT NULL
|
||||
AND random() < 0.3
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- ============================================================
|
||||
-- 10. 营销活动种子数据
|
||||
-- ============================================================
|
||||
INSERT INTO analytics.v3_marketing_campaign (campaign_name, campaign_type, start_date, end_date, budget, target_stores, status) VALUES
|
||||
('夏季新品推广', '促销', '2026-06-01', '2026-08-31', 50000, '全部门店', 'active'),
|
||||
('会员日双倍积分', '拉新', '2026-07-01', '2026-12-31', 30000, '全部门店', 'active'),
|
||||
('工作日午餐特惠', '促销', '2026-07-15', '2026-09-15', 20000, '商圈门店', 'active'),
|
||||
('老店焕新活动', '品牌', '2026-08-01', '2026-10-31', 80000, 'A类门店', 'draft')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- ============================================================
|
||||
-- 11. 预算数据 (v3_budget ← 按门店月度估算)
|
||||
-- ============================================================
|
||||
INSERT INTO analytics.v3_budget (year, month, store_code, department, budget_type, budget_amount)
|
||||
SELECT
|
||||
EXTRACT(YEAR FROM NOW())::int,
|
||||
date_trunc('month', NOW())::date,
|
||||
store_code,
|
||||
'运营',
|
||||
'revenue',
|
||||
received * 1.05
|
||||
FROM analytics.mv_store_risk_rating
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
INSERT INTO analytics.v3_budget (year, month, store_code, department, budget_type, budget_amount)
|
||||
SELECT
|
||||
EXTRACT(YEAR FROM NOW())::int,
|
||||
date_trunc('month', NOW())::date,
|
||||
store_code,
|
||||
'运营',
|
||||
'expense',
|
||||
received * 0.6
|
||||
FROM analytics.mv_store_risk_rating
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- ============================================================
|
||||
-- 12. 品牌资产种子数据 (v3_brand_asset)
|
||||
-- ============================================================
|
||||
INSERT INTO analytics.v3_brand_asset (tracking_date, nps_score, search_index, sentiment_health, positive_mentions, negative_mentions)
|
||||
SELECT d::date,
|
||||
40 + random() * 20,
|
||||
80 + random() * 40,
|
||||
CASE WHEN random() < 0.7 THEN 80 + random() * 15 ELSE 50 + random() * 20 END,
|
||||
CAST(50 + random() * 100 AS int),
|
||||
CAST(random() * 20 AS int)
|
||||
FROM generate_series(NOW()::date - interval '29 days', NOW()::date, interval '1 day') AS d
|
||||
ON CONFLICT (tracking_date) DO NOTHING;
|
||||
|
||||
-- ============================================================
|
||||
-- 13. IoT设备种子数据 (v3_iot_device)
|
||||
-- ============================================================
|
||||
INSERT INTO analytics.v3_iot_device (device_code, device_name, device_type, store_code, location, status)
|
||||
SELECT
|
||||
'IOT-' || s.store_code || '-01',
|
||||
s.store_name || '冷链温度探头',
|
||||
'温控',
|
||||
s.store_code,
|
||||
'冷库',
|
||||
'online'
|
||||
FROM analytics.dim_store s
|
||||
WHERE s.store_code IS NOT NULL
|
||||
AND random() < 0.3
|
||||
ON CONFLICT (device_code) DO NOTHING;
|
||||
|
||||
-- ============================================================
|
||||
-- 14. 顾客评价种子数据 (v3_customer_review)
|
||||
-- ============================================================
|
||||
INSERT INTO analytics.v3_customer_review (review_source, store_code, store_name, rating, content, review_date, nlp_category, nlp_sentiment)
|
||||
SELECT
|
||||
'美团',
|
||||
s.store_code,
|
||||
s.store_name,
|
||||
CASE WHEN random() < 0.7 THEN 5 WHEN random() < 0.5 THEN 4 WHEN random() < 0.5 THEN 3 ELSE 1 END,
|
||||
CASE
|
||||
WHEN random() < 0.5 THEN '味道不错,服务也很好'
|
||||
WHEN random() < 0.5 THEN '出餐速度快,包装好'
|
||||
When random() < 0.3 THEN '分量有点少'
|
||||
ELSE '味道一般,有待改进'
|
||||
END,
|
||||
NOW()::date - CAST(random() * 30 AS int),
|
||||
CASE
|
||||
WHEN random() < 0.4 THEN '口味'
|
||||
WHEN random() < 0.3 THEN '服务'
|
||||
WHEN random() < 0.2 THEN '环境'
|
||||
WHEN random() < 0.1 THEN '分量'
|
||||
ELSE '异物'
|
||||
END,
|
||||
CASE WHEN random() < 0.7 THEN '正面' WHEN random() < 0.5 THEN '中性' ELSE '负面' END
|
||||
FROM analytics.dim_store s
|
||||
WHERE s.store_code IS NOT NULL
|
||||
AND random() < 0.5
|
||||
LIMIT 200
|
||||
ON CONFLICT DO NOTHING;
|
||||
Reference in New Issue
Block a user