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:
freedakgmail
2026-08-05 20:09:39 +08:00
parent 814abf4a68
commit d04e837d9c
30 changed files with 5052 additions and 144 deletions
+212
View File
@@ -0,0 +1,212 @@
-- ============================================================
-- 12_v3_target_tables.sql
-- V3.0 目标管理系统表族 + 调度任务表 + 预警规则表
-- ============================================================
-- ============================================================
-- 1. 目标管理系统表族 (T-101 ~ T-105)
-- ============================================================
-- T-101: 年度战略目标
CREATE TABLE IF NOT EXISTS analytics.v3_annual_target (
id SERIAL PRIMARY KEY,
year INT NOT NULL,
metric TEXT NOT NULL, -- revenue / profit / store_count / member_count
target_value NUMERIC(14,2) NOT NULL,
description TEXT,
created_by TEXT DEFAULT 'system',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(year, metric)
);
-- T-102: 区域年度目标
CREATE TABLE IF NOT EXISTS analytics.v3_regional_target (
id SERIAL PRIMARY KEY,
year INT NOT NULL,
region TEXT NOT NULL,
revenue_target NUMERIC(14,2) NOT NULL,
profit_target NUMERIC(14,2),
store_count_target INT,
member_count_target INT,
weight_pct NUMERIC(5,2) DEFAULT 100, -- 区域占比权重
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(year, region)
);
-- T-103: 门店月度目标(兼容现有 dim_store_target
CREATE TABLE IF NOT EXISTS analytics.v3_store_monthly_target (
id SERIAL PRIMARY KEY,
year INT NOT NULL,
month DATE NOT NULL, -- YYYY-MM-01
store_code TEXT NOT NULL,
store_name TEXT,
region TEXT,
grade TEXT DEFAULT 'B', -- A/B/C/D 分级
revenue_target NUMERIC(14,2) NOT NULL,
bill_count_target INT,
avg_bill_value_target NUMERIC(8,2),
member_penetration_target NUMERIC(5,2),
cost_rate_target NUMERIC(5,2),
profit_target NUMERIC(14,2),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(month, store_code)
);
-- T-104: 日级目标
CREATE TABLE IF NOT EXISTS analytics.v3_daily_target (
id SERIAL PRIMARY KEY,
target_date DATE NOT NULL,
store_code TEXT NOT NULL,
store_name TEXT,
revenue_target NUMERIC(12,2) NOT NULL,
bill_count_target INT,
avg_bill_value_target NUMERIC(8,2),
package_target NUMERIC(12,2), -- 套餐目标
loss_target NUMERIC(8,2), -- 损耗目标
arrival_target NUMERIC(8,2), -- 到货目标
weight_pct NUMERIC(5,2) DEFAULT 0, -- 当日占月度权重
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(target_date, store_code)
);
-- T-105: 个人任务
CREATE TABLE IF NOT EXISTS analytics.v3_personal_task (
id SERIAL PRIMARY KEY,
target_date DATE NOT NULL,
store_code TEXT NOT NULL,
employee_name TEXT NOT NULL,
position TEXT, -- 岗位
metric TEXT NOT NULL, -- 指标名
target_value NUMERIC(12,2) NOT NULL,
actual_value NUMERIC(12,2),
completion_rate NUMERIC(5,2), -- 完成率%
status TEXT DEFAULT '待执行',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- ============================================================
-- 2. 调度任务注册表 (T-121)
-- ============================================================
CREATE TABLE IF NOT EXISTS analytics.v3_scheduled_task (
id SERIAL PRIMARY KEY,
task_name TEXT NOT NULL UNIQUE,
description TEXT,
cron_expr TEXT NOT NULL, -- cron 表达式
handler TEXT NOT NULL, -- 处理函数标识
is_enabled BOOLEAN DEFAULT true,
last_run_at TIMESTAMPTZ,
next_run_at TIMESTAMPTZ,
last_status TEXT, -- success / failed / running
last_error TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- ============================================================
-- 3. 预警规则引擎表族 (T-130 ~ T-131)
-- ============================================================
-- T-130: 预警规则
CREATE TABLE IF NOT EXISTS analytics.v3_alert_rule (
id SERIAL PRIMARY KEY,
rule_name TEXT NOT NULL,
description TEXT,
metric TEXT NOT NULL, -- 监控指标
operator TEXT NOT NULL DEFAULT '<', -- < / <= / > / >= / =
threshold NUMERIC(14,2) NOT NULL, -- 阈值
severity TEXT DEFAULT 'yellow', -- red / yellow
push_targets TEXT, -- 推送对象(逗号分隔角色)
escalation_path TEXT, -- 升级路径
is_enabled BOOLEAN DEFAULT true,
check_interval TEXT DEFAULT 'hourly', -- hourly / daily / weekly / monthly
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- T-131: 预警日志
CREATE TABLE IF NOT EXISTS analytics.v3_alert_log (
id SERIAL PRIMARY KEY,
rule_id INT REFERENCES analytics.v3_alert_rule(id) ON DELETE CASCADE,
rule_name TEXT,
store_code TEXT,
store_name TEXT,
metric_value NUMERIC(14,2),
threshold NUMERIC(14,2),
severity TEXT,
push_status TEXT DEFAULT 'pending', -- pending / sent / failed
handle_status TEXT DEFAULT 'pending', -- pending / handling / resolved / ignored
handle_comment TEXT,
triggered_at TIMESTAMPTZ DEFAULT NOW(),
handled_at TIMESTAMPTZ
);
-- ============================================================
-- 4. 门店自动分级表 (T-160)
-- ============================================================
CREATE TABLE IF NOT EXISTS analytics.v3_store_grade (
id SERIAL PRIMARY KEY,
grade_month DATE NOT NULL,
store_code TEXT NOT NULL,
store_name TEXT,
region TEXT,
grade TEXT NOT NULL, -- A / B / C / D
revenue_achievement_pct NUMERIC(5,2),
inspection_score NUMERIC(5,2),
complaint_rate NUMERIC(5,2),
overall_score NUMERIC(5,2),
reason TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(grade_month, store_code)
);
-- ============================================================
-- 索引
-- ============================================================
CREATE INDEX IF NOT EXISTS idx_v3_annual_target_year ON analytics.v3_annual_target(year);
CREATE INDEX IF NOT EXISTS idx_v3_regional_target_year ON analytics.v3_regional_target(year);
CREATE INDEX IF NOT EXISTS idx_v3_store_monthly_target_month ON analytics.v3_store_monthly_target(month);
CREATE INDEX IF NOT EXISTS idx_v3_store_monthly_target_store ON analytics.v3_store_monthly_target(store_code);
CREATE INDEX IF NOT EXISTS idx_v3_daily_target_date ON analytics.v3_daily_target(target_date);
CREATE INDEX IF NOT EXISTS idx_v3_daily_target_store ON analytics.v3_daily_target(store_code);
CREATE INDEX IF NOT EXISTS idx_v3_personal_task_date ON analytics.v3_personal_task(target_date);
CREATE INDEX IF NOT EXISTS idx_v3_personal_task_store ON analytics.v3_personal_task(store_code);
CREATE INDEX IF NOT EXISTS idx_v3_scheduled_task_enabled ON analytics.v3_scheduled_task(is_enabled);
CREATE INDEX IF NOT EXISTS idx_v3_alert_rule_enabled ON analytics.v3_alert_rule(is_enabled);
CREATE INDEX IF NOT EXISTS idx_v3_alert_log_rule ON analytics.v3_alert_log(rule_id);
CREATE INDEX IF NOT EXISTS idx_v3_alert_log_triggered ON analytics.v3_alert_log(triggered_at);
CREATE INDEX IF NOT EXISTS idx_v3_alert_log_handle_status ON analytics.v3_alert_log(handle_status);
CREATE INDEX IF NOT EXISTS idx_v3_store_grade_month ON analytics.v3_store_grade(grade_month);
CREATE INDEX IF NOT EXISTS idx_v3_store_grade_store ON analytics.v3_store_grade(store_code);
-- ============================================================
-- 初始化默认调度任务
-- ============================================================
INSERT INTO analytics.v3_scheduled_task (task_name, description, cron_expr, handler, is_enabled) VALUES
('daily_report', '每日08:30生成昨日日报', '30 8 * * *', 'dailyReport', true),
('daily_target_card', '每日09:00生成日目标卡', '0 9 * * *', 'dailyTargetCard', true),
('hourly_achievement_check', '每小时比对日目标达成率', '0 * * * *', 'hourlyAchievementCheck', true),
('daily_review_template', '每日22:30生成日复盘模板', '30 22 * * *', 'dailyReviewTemplate', false),
('weekly_data_package', '每周一09:00生成上周数据包', '0 9 * * 1', 'weeklyDataPackage', false),
('monthly_business_report', '每月1日09:00生成月度经营分析报告', '0 9 1 * *', 'monthlyBusinessReport', false),
('monthly_mid_progress', '每月15日14:00生成月中进度报告', '0 14 15 * *', 'monthlyMidProgress', false),
('alert_engine_scan', '每5分钟扫描预警规则', '*/5 * * * *', 'alertEngineScan', true)
ON CONFLICT (task_name) DO NOTHING;
-- ============================================================
-- 初始化默认预警规则
-- ============================================================
INSERT INTO analytics.v3_alert_rule (rule_name, description, metric, operator, threshold, severity, push_targets, check_interval) VALUES
('营收达成率红灯', '日营收达成率低于80%触发红灯', 'revenue_achievement_pct', '<', 80, 'red', 'store,regional', 'hourly'),
('营收达成率黄灯', '日营收达成率低于90%触发黄灯', 'revenue_achievement_pct', '<', 90, 'yellow', 'store', 'hourly'),
('毛利率异常', '理论毛利率低于40%', 'theoretical_margin_pct', '<', 40, 'yellow', 'store,regional', 'daily'),
('会员渗透率低', '会员账单占比低于20%', 'member_bill_share_pct', '<', 20, 'yellow', 'store', 'daily'),
('客诉率偏高', '客诉率超过5%', 'complaint_rate', '>', 5, 'red', 'store,regional,hq', 'daily'),
('巡检低分', '食安巡检得分低于90', 'inspection_score', '<', 90, 'red', 'store,regional', 'daily'),
('离职率偏高', '月度离职率超过15%', 'turnover_rate', '>', 15, 'red', 'regional,hq', 'monthly')
ON CONFLICT DO NOTHING;
+126
View File
@@ -0,0 +1,126 @@
-- ============================================================
-- 13_v3_phase1_remaining.sql
-- Phase 1 剩余表: 产品生命周期 + 评价归因 + 数据管道 + 审计
-- ============================================================
-- T-170: 产品生命周期
CREATE TABLE IF NOT EXISTS analytics.v3_product_lifecycle (
id SERIAL PRIMARY KEY,
sku_code TEXT NOT NULL,
sku_name TEXT,
launch_date DATE NOT NULL,
lifecycle_stage TEXT DEFAULT '爬坡', -- 爬坡/成熟/衰退/淘汰
survival_status TEXT, -- 存活/淘汰/待判定
first_week_report_date DATE,
day90_report_date DATE,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(sku_code)
);
-- T-171: 顾客评价数据
CREATE TABLE IF NOT EXISTS analytics.v3_customer_review (
id SERIAL PRIMARY KEY,
review_source TEXT NOT NULL, -- 美团/饿了么/京东/门店
store_code TEXT,
store_name TEXT,
sku_code TEXT,
sku_name TEXT,
rating INT,
content TEXT,
nlp_category TEXT, -- 口味/服务/环境/异物/分量
nlp_sentiment TEXT, -- 正面/负面/中性
review_date DATE NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- T-150: 数据导入日志
CREATE TABLE IF NOT EXISTS analytics.v3_data_import_log (
id SERIAL PRIMARY KEY,
import_type TEXT NOT NULL, -- POS/外卖/库存/费用/薪资
status TEXT DEFAULT 'pending', -- pending/running/success/failed
records_count INT DEFAULT 0,
error_message TEXT,
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- T-153: 数据清洗规则
CREATE TABLE IF NOT EXISTS analytics.v3_data_quality_rule (
id SERIAL PRIMARY KEY,
rule_name TEXT NOT NULL,
table_name TEXT NOT NULL,
column_name TEXT,
rule_type TEXT NOT NULL, -- range/null_check/regex/custom
rule_config JSONB,
is_enabled BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- T-154: 库存快照
CREATE TABLE IF NOT EXISTS analytics.v3_inventory_snapshot (
id SERIAL PRIMARY KEY,
snapshot_date DATE NOT NULL,
store_code TEXT NOT NULL,
sku_code TEXT NOT NULL,
sku_name TEXT,
quantity NUMERIC(12,2),
unit TEXT,
expiry_date DATE,
is_near_expiry BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(snapshot_date, store_code, sku_code)
);
-- T-195: 月度经营分析报告
CREATE TABLE IF NOT EXISTS analytics.v3_monthly_report (
id SERIAL PRIMARY KEY,
report_month DATE NOT NULL,
report_type TEXT NOT NULL, -- monthly/mid_month/weekly/daily
store_code TEXT,
region TEXT,
content JSONB,
summary TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(report_month, report_type, store_code)
);
-- T-112: 目标偏差分析
CREATE TABLE IF NOT EXISTS analytics.v3_target_variance (
id SERIAL PRIMARY KEY,
report_month DATE NOT NULL,
store_code TEXT NOT NULL,
store_name TEXT,
revenue_target NUMERIC(14,2),
revenue_actual NUMERIC(14,2),
achievement_pct NUMERIC(5,2),
variance_amount NUMERIC(14,2),
variance_reasons TEXT, -- 客流/客单/成本/损耗
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(report_month, store_code)
);
-- T-113: 预算锁定
CREATE TABLE IF NOT EXISTS analytics.v3_budget_lock (
id SERIAL PRIMARY KEY,
lock_month DATE NOT NULL,
store_code TEXT NOT NULL,
lock_reason TEXT,
root_cause_analysis TEXT,
approval_status TEXT DEFAULT 'pending', -- pending/approved/rejected
approved_by TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(lock_month, store_code)
);
-- 索引
CREATE INDEX IF NOT EXISTS idx_v3_product_lifecycle_stage ON analytics.v3_product_lifecycle(lifecycle_stage);
CREATE INDEX IF NOT EXISTS idx_v3_customer_review_date ON analytics.v3_customer_review(review_date);
CREATE INDEX IF NOT EXISTS idx_v3_customer_review_store ON analytics.v3_customer_review(store_code);
CREATE INDEX IF NOT EXISTS idx_v3_customer_review_nlp ON analytics.v3_customer_review(nlp_category);
CREATE INDEX IF NOT EXISTS idx_v3_data_import_log_type ON analytics.v3_data_import_log(import_type);
CREATE INDEX IF NOT EXISTS idx_v3_inventory_snapshot_date ON analytics.v3_inventory_snapshot(snapshot_date);
CREATE INDEX IF NOT EXISTS idx_v3_monthly_report_month ON analytics.v3_monthly_report(report_month);
CREATE INDEX IF NOT EXISTS idx_v3_target_variance_month ON analytics.v3_target_variance(report_month);
CREATE INDEX IF NOT EXISTS idx_v3_budget_lock_month ON analytics.v3_budget_lock(lock_month);
+244
View File
@@ -0,0 +1,244 @@
-- ============================================================
-- 14_v3_phase2_tables.sql
-- Phase 2: 供应链SRM + MES + 审批流 + 消息推送 + HR + LMS + 营销 + 财务 + 扫码 + 闭环验证 + 安全
-- ============================================================
-- T-201: 供应商主数据
CREATE TABLE IF NOT EXISTS analytics.v3_supplier_master (
id SERIAL PRIMARY KEY,
supplier_code TEXT NOT NULL UNIQUE,
supplier_name TEXT NOT NULL,
category TEXT,
grade TEXT DEFAULT '合格', -- 战略/优选/合格/淘汰
contact_person TEXT,
contact_phone TEXT,
address TEXT,
delivery_on_time_rate NUMERIC(5,2),
quality_score NUMERIC(5,2),
price_score NUMERIC(5,2),
service_score NUMERIC(5,2),
food_safety_score NUMERIC(5,2),
overall_score NUMERIC(5,2),
is_strategic BOOLEAN DEFAULT false,
backup_supplier_code TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- T-203: 采购订单
CREATE TABLE IF NOT EXISTS analytics.v3_purchase_order (
id SERIAL PRIMARY KEY,
po_number TEXT NOT NULL UNIQUE,
supplier_code TEXT NOT NULL,
store_code TEXT,
order_date DATE NOT NULL,
expected_delivery_date DATE,
actual_delivery_date DATE,
status TEXT DEFAULT 'draft', -- draft/submitted/approved/executed/delivered/settled
total_amount NUMERIC(14,2),
approval_status TEXT DEFAULT 'pending',
created_by TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- T-203: 采购订单明细
CREATE TABLE IF NOT EXISTS analytics.v3_purchase_order_item (
id SERIAL PRIMARY KEY,
po_id INT REFERENCES analytics.v3_purchase_order(id) ON DELETE CASCADE,
sku_code TEXT NOT NULL,
sku_name TEXT,
quantity NUMERIC(12,2) NOT NULL,
unit TEXT,
unit_price NUMERIC(10,2),
total_price NUMERIC(14,2),
received_quantity NUMERIC(12,2)
);
-- T-220: 审批申请
CREATE TABLE IF NOT EXISTS analytics.v3_approval_request (
id SERIAL PRIMARY KEY,
request_type TEXT NOT NULL, -- salary_adjust/purchase/promotion/recipe/supplier_close/store_close
applicant_id TEXT NOT NULL,
applicant_name TEXT,
store_code TEXT,
status TEXT DEFAULT 'pending', -- pending/approved/rejected/cancelled
current_step INT DEFAULT 1,
total_steps INT,
request_data JSONB,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- T-221: 审批步骤
CREATE TABLE IF NOT EXISTS analytics.v3_approval_step (
id SERIAL PRIMARY KEY,
request_id INT REFERENCES analytics.v3_approval_request(id) ON DELETE CASCADE,
step_order INT NOT NULL,
approver_role TEXT NOT NULL,
approver_id TEXT,
approver_name TEXT,
result TEXT, -- approved/rejected/pending
comment TEXT,
approved_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- T-222: 审批规则
CREATE TABLE IF NOT EXISTS analytics.v3_approval_rule (
id SERIAL PRIMARY KEY,
request_type TEXT NOT NULL,
step_order INT NOT NULL,
approver_role TEXT NOT NULL,
escalation_condition JSONB,
escalation_role TEXT,
is_enabled BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(request_type, step_order)
);
-- T-241: 站内消息
CREATE TABLE IF NOT EXISTS analytics.v3_notification (
id SERIAL PRIMARY KEY,
user_id TEXT,
user_role TEXT,
title TEXT NOT NULL,
content TEXT,
notification_type TEXT, -- alert/task/report/approval
related_id INT,
is_read BOOLEAN DEFAULT false,
is_handled BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- T-260: 培训课程
CREATE TABLE IF NOT EXISTS analytics.v3_training_course (
id SERIAL PRIMARY KEY,
course_name TEXT NOT NULL,
course_type TEXT, -- SOP/技能/食安/管理
content_url TEXT,
exam_enabled BOOLEAN DEFAULT false,
pass_score INT DEFAULT 60,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- T-261: 学习记录
CREATE TABLE IF NOT EXISTS analytics.v3_learning_record (
id SERIAL PRIMARY KEY,
course_id INT REFERENCES analytics.v3_training_course(id) ON DELETE CASCADE,
employee_name TEXT NOT NULL,
store_code TEXT,
progress_pct NUMERIC(5,2) DEFAULT 0,
completion_status TEXT DEFAULT '未开始', -- 未开始/进行中/已完成/未通过
exam_score INT,
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- T-270: 营销活动
CREATE TABLE IF NOT EXISTS analytics.v3_marketing_campaign (
id SERIAL PRIMARY KEY,
campaign_name TEXT NOT NULL,
campaign_type TEXT, -- 促销/拉新/召回/品牌
start_date DATE NOT NULL,
end_date DATE NOT NULL,
budget NUMERIC(14,2),
actual_spend NUMERIC(14,2),
roi NUMERIC(8,2),
target_stores TEXT,
status TEXT DEFAULT 'draft', -- draft/active/completed/cancelled
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- T-271: 券码
CREATE TABLE IF NOT EXISTS analytics.v3_coupon (
id SERIAL PRIMARY KEY,
campaign_id INT REFERENCES analytics.v3_marketing_campaign(id) ON DELETE CASCADE,
coupon_code TEXT NOT NULL UNIQUE,
coupon_type TEXT, -- 满减/折扣/赠品
face_value NUMERIC(10,2),
min_spend NUMERIC(10,2),
status TEXT DEFAULT 'unused', -- unused/used/expired/refunded
issued_to TEXT,
used_at TIMESTAMPTZ,
expiry_date DATE,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- T-280: 预算管理
CREATE TABLE IF NOT EXISTS analytics.v3_budget (
id SERIAL PRIMARY KEY,
year INT NOT NULL,
month DATE,
store_code TEXT,
department TEXT,
budget_type TEXT, -- revenue/expense/capex
budget_amount NUMERIC(14,2),
actual_amount NUMERIC(14,2),
execution_rate NUMERIC(5,2),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- T-302: 审计日志
CREATE TABLE IF NOT EXISTS analytics.v3_audit_log (
id SERIAL PRIMARY KEY,
user_id TEXT,
user_name TEXT,
user_role TEXT,
action TEXT NOT NULL, -- create/update/delete/view
resource_type TEXT,
resource_id TEXT,
details JSONB,
ip_address TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- T-295: 闭环验证
CREATE TABLE IF NOT EXISTS analytics.v3_loop_verification (
id SERIAL PRIMARY KEY,
loop_name TEXT NOT NULL,
store_code TEXT,
check_date DATE NOT NULL,
status TEXT DEFAULT 'open', -- open/completed/escalated
completion_rate NUMERIC(5,2),
cycle_days INT,
bottleneck TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- 索引
CREATE INDEX IF NOT EXISTS idx_v3_supplier_grade ON analytics.v3_supplier_master(grade);
CREATE INDEX IF NOT EXISTS idx_v3_po_supplier ON analytics.v3_purchase_order(supplier_code);
CREATE INDEX IF NOT EXISTS idx_v3_po_status ON analytics.v3_purchase_order(status);
CREATE INDEX IF NOT EXISTS idx_v3_approval_req_status ON analytics.v3_approval_request(status);
CREATE INDEX IF NOT EXISTS idx_v3_approval_step_req ON analytics.v3_approval_step(request_id);
CREATE INDEX IF NOT EXISTS idx_v3_notification_user ON analytics.v3_notification(user_id);
CREATE INDEX IF NOT EXISTS idx_v3_notification_read ON analytics.v3_notification(is_read);
CREATE INDEX IF NOT EXISTS idx_v3_training_course_type ON analytics.v3_training_course(course_type);
CREATE INDEX IF NOT EXISTS idx_v3_learning_record_course ON analytics.v3_learning_record(course_id);
CREATE INDEX IF NOT EXISTS idx_v3_learning_record_employee ON analytics.v3_learning_record(employee_name);
CREATE INDEX IF NOT EXISTS idx_v3_campaign_status ON analytics.v3_marketing_campaign(status);
CREATE INDEX IF NOT EXISTS idx_v3_coupon_code ON analytics.v3_coupon(coupon_code);
CREATE INDEX IF NOT EXISTS idx_v3_budget_month ON analytics.v3_budget(year, month);
CREATE INDEX IF NOT EXISTS idx_v3_audit_log_user ON analytics.v3_audit_log(user_id);
CREATE INDEX IF NOT EXISTS idx_v3_audit_log_created ON analytics.v3_audit_log(created_at);
CREATE INDEX IF NOT EXISTS idx_v3_loop_verification_date ON analytics.v3_loop_verification(check_date);
-- 初始化默认审批规则
INSERT INTO analytics.v3_approval_rule (request_type, step_order, approver_role, escalation_condition, escalation_role) VALUES
('salary_adjust', 1, 'regional', '{"amount": 5000}', 'hq'),
('salary_adjust', 2, 'hq', NULL, NULL),
('purchase', 1, 'regional', '{"amount": 10000}', 'hq'),
('purchase', 2, 'hq', NULL, NULL),
('promotion', 1, 'regional', NULL, NULL),
('promotion', 2, 'hq', NULL, NULL),
('recipe_change', 1, 'hq', '{"cost_change_pct": 5}', 'platform_admin'),
('supplier_close', 1, 'hq', NULL, 'platform_admin'),
('store_close', 1, 'hq', '{"consecutive_loss_months": 3}', 'platform_admin')
ON CONFLICT DO NOTHING;
+96
View File
@@ -0,0 +1,96 @@
-- ============================================================
-- 15_v3_phase3_tables.sql
-- Phase 3: IoT + AI决策 + 全链路 + 实时数据 + 战略驾驶舱
-- ============================================================
-- T-310: IoT设备数据
CREATE TABLE IF NOT EXISTS analytics.v3_iot_device (
id SERIAL PRIMARY KEY,
device_code TEXT NOT NULL UNIQUE,
device_name TEXT,
device_type TEXT, -- 温控/湿度/秤/扫码枪
store_code TEXT,
location TEXT,
status TEXT DEFAULT 'offline', -- online/offline/alarm
last_reading JSONB,
last_reading_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- T-310: IoT温度记录
CREATE TABLE IF NOT EXISTS analytics.v3_iot_temperature (
id SERIAL PRIMARY KEY,
device_code TEXT NOT NULL,
store_code TEXT,
temperature NUMERIC(5,2),
humidity NUMERIC(5,2),
recorded_at TIMESTAMPTZ NOT NULL,
is_alarm BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- T-320: AI目标测算
CREATE TABLE IF NOT EXISTS analytics.v3_ai_target_adjustment (
id SERIAL PRIMARY KEY,
target_date DATE NOT NULL,
store_code TEXT NOT NULL,
original_target NUMERIC(12,2),
adjusted_target NUMERIC(12,2),
factors JSONB, -- 天气/节假日/商圈活动
confidence NUMERIC(5,2),
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(target_date, store_code)
);
-- T-321: 趋势预测
CREATE TABLE IF NOT EXISTS analytics.v3_forecast (
id SERIAL PRIMARY KEY,
forecast_type TEXT NOT NULL, -- revenue/traffic/sales
target_date DATE NOT NULL,
store_code TEXT,
predicted_value NUMERIC(14,2),
confidence_lower NUMERIC(14,2),
confidence_upper NUMERIC(14,2),
model_type TEXT, -- linear/arima/ema
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- T-350: 投资回报模型
CREATE TABLE IF NOT EXISTS analytics.v3_store_roi (
id SERIAL PRIMARY KEY,
store_code TEXT NOT NULL,
store_name TEXT,
initial_investment NUMERIC(14,2),
monthly_revenue NUMERIC(14,2),
monthly_profit NUMERIC(14,2),
payback_months NUMERIC(8,2),
roi_pct NUMERIC(8,2),
city_penetration_pct NUMERIC(5,2),
calculated_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(store_code)
);
-- T-352: 品牌资产
CREATE TABLE IF NOT EXISTS analytics.v3_brand_asset (
id SERIAL PRIMARY KEY,
tracking_date DATE NOT NULL,
nps_score NUMERIC(5,2),
search_index NUMERIC(8,2),
sentiment_health NUMERIC(5,2),
positive_mentions INT,
negative_mentions INT,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(tracking_date)
);
-- 索引
CREATE INDEX IF NOT EXISTS idx_v3_iot_device_store ON analytics.v3_iot_device(store_code);
CREATE INDEX IF NOT EXISTS idx_v3_iot_device_status ON analytics.v3_iot_device(status);
CREATE INDEX IF NOT EXISTS idx_v3_iot_temp_device ON analytics.v3_iot_temperature(device_code);
CREATE INDEX IF NOT EXISTS idx_v3_iot_temp_recorded ON analytics.v3_iot_temperature(recorded_at);
CREATE INDEX IF NOT EXISTS idx_v3_ai_target_date ON analytics.v3_ai_target_adjustment(target_date);
CREATE INDEX IF NOT EXISTS idx_v3_forecast_date ON analytics.v3_forecast(target_date);
CREATE INDEX IF NOT EXISTS idx_v3_store_roi_store ON analytics.v3_store_roi(store_code);
CREATE INDEX IF NOT EXISTS idx_v3_brand_asset_date ON analytics.v3_brand_asset(tracking_date);
+258
View File
@@ -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;