feat: 新增智脑决策中心模块(决策中心/AI员工组织/知识资产)
- 新增 DecisionsPage、AgentsPage、KnowledgePage 三个页面 - 新增 DecisionCard、DecisionDrawer、DecisionSummary、KnowledgeCard、LoopHealthBar、AgentCard 组件 - 新增 decision-aggregator 决策聚合逻辑和 agents 数据 - Layout 导航重构:新增智脑决策分组,调整系统配置和经营本体分组 - BossPage 集成决策摘要和闭环健康度,利润机会池支持警告样式 - TasksPage 增强任务展示 - 抽屉式详情统一改为居中弹窗风格 - data.ts 后端调整 - run.md 更新数据库连接说明 - 新增玄谋智脑实际版建设方案文档 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -24,6 +24,9 @@ import { SiteSelectionPage } from '@/pages/SiteSelectionPage'
|
||||
import { SmartSchedulingPage } from '@/pages/SmartSchedulingPage'
|
||||
import { SituationalAwarenessPage } from '@/pages/SituationalAwarenessPage'
|
||||
import { BossPage } from '@/pages/BossPage'
|
||||
import { DecisionsPage } from '@/pages/DecisionsPage'
|
||||
import { AgentsPage } from '@/pages/AgentsPage'
|
||||
import { KnowledgePage } from '@/pages/KnowledgePage'
|
||||
import { RevenuePage } from '@/pages/RevenuePage'
|
||||
import { BankPage } from '@/pages/BankPage'
|
||||
import { CentralKitchenPage } from '@/pages/CentralKitchenPage'
|
||||
@@ -92,6 +95,9 @@ export default function App() {
|
||||
<Routes>
|
||||
<Route path="/" element={user?.role === 'platform_admin' ? <Navigate to="/tenant-management" /> : user?.role === 'store' ? <Navigate to="/store" /> : user?.role === 'regional' ? <Navigate to="/regional" /> : <DashboardPage />} />
|
||||
<Route path="/boss" element={<RoleRoute roles={['hq','dept']}><BossPage /></RoleRoute>} />
|
||||
<Route path="/decisions" element={<RoleRoute roles={['hq','dept']}><DecisionsPage /></RoleRoute>} />
|
||||
<Route path="/agents" element={<RoleRoute roles={['hq','dept']}><AgentsPage /></RoleRoute>} />
|
||||
<Route path="/knowledge" element={<RoleRoute roles={['hq','dept']}><KnowledgePage /></RoleRoute>} />
|
||||
<Route path="/revenue" element={<RoleRoute roles={['hq','dept']}><RevenuePage /></RoleRoute>} />
|
||||
<Route path="/bank" element={<RoleRoute roles={['hq']}><BankPage /></RoleRoute>} />
|
||||
<Route path="/central-kitchen" element={<RoleRoute roles={['hq','dept']}><CentralKitchenPage /></RoleRoute>} />
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* AI 员工卡片组件
|
||||
*
|
||||
* 展示单个 AI 员工的岗位、职责、自治等级和今日交付数。
|
||||
*/
|
||||
import { Bot } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import type { AgentConfig } from '@/data/agents'
|
||||
|
||||
interface AgentCardProps {
|
||||
agent: AgentConfig
|
||||
/** 今日交付数(null 表示加载中) */
|
||||
deliveries: number | null
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
/** 自治等级颜色 */
|
||||
const AUTONOMY_COLORS: Record<string, string> = {
|
||||
L1: 'bg-red-50 text-red-600 border border-red-200',
|
||||
L2: 'bg-amber-50 text-amber-600 border border-amber-200',
|
||||
L3: 'bg-green-50 text-green-600 border border-green-200',
|
||||
}
|
||||
|
||||
/** 状态指示灯颜色 */
|
||||
const STATUS_DOT: Record<string, string> = {
|
||||
'运行中': 'bg-green-500',
|
||||
'待审批': 'bg-amber-500',
|
||||
'受控': 'bg-blue-500',
|
||||
}
|
||||
|
||||
export function AgentCard({ agent, deliveries, onClick }: AgentCardProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'rounded-lg border bg-card p-4 transition-shadow hover:shadow-sm',
|
||||
onClick && 'cursor-pointer',
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
{/* 头部:图标 + 状态灯 */}
|
||||
<div className="mb-2 flex items-start justify-between">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-blue-50">
|
||||
<Bot className="h-5 w-5 text-blue-600" />
|
||||
</div>
|
||||
<span
|
||||
className={cn(
|
||||
'mt-1 h-2 w-2 rounded-full',
|
||||
STATUS_DOT[agent.status] || 'bg-gray-400',
|
||||
)}
|
||||
title={agent.status}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 名称 + 领域 */}
|
||||
<small className="text-[10px] text-muted-foreground">{agent.domain}</small>
|
||||
<h3 className="text-sm font-bold">{agent.name}</h3>
|
||||
<p className="mt-1 text-xs text-muted-foreground">{agent.roleTarget}</p>
|
||||
|
||||
{/* 底部:自治等级 + 交付数 */}
|
||||
<footer className="mt-3 flex items-center justify-between border-t pt-2">
|
||||
<span className={cn('rounded px-1.5 py-0.5 text-[10px] font-medium', AUTONOMY_COLORS[agent.autonomyLevel])}>
|
||||
自治 {agent.autonomyLevel}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{deliveries === null ? (
|
||||
<span className="text-muted-foreground">加载中…</span>
|
||||
) : (
|
||||
<><b className="text-foreground">{deliveries}</b> 项交付</>
|
||||
)}
|
||||
</span>
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* 决策卡组件
|
||||
*
|
||||
* 在决策中心列表中展示单个决策卡,点击可展开详情抽屉。
|
||||
*/
|
||||
import { ChevronRight } from 'lucide-react'
|
||||
import { cn, formatCurrency } from '@/lib/utils'
|
||||
import type { DecisionCard } from '@/lib/decision-aggregator'
|
||||
import { SOURCE_LABELS, LEVEL_LABELS } from '@/lib/decision-aggregator'
|
||||
|
||||
interface DecisionCardProps {
|
||||
decision: DecisionCard
|
||||
index: number
|
||||
approved: boolean
|
||||
onOpen: (d: DecisionCard) => void
|
||||
}
|
||||
|
||||
/** 来源类型颜色 */
|
||||
const SOURCE_COLORS: Record<string, string> = {
|
||||
profit_opportunity: 'bg-purple-100 text-purple-700',
|
||||
risk_alert: 'bg-red-100 text-red-700',
|
||||
task_anomaly: 'bg-amber-100 text-amber-700',
|
||||
}
|
||||
|
||||
/** 等级颜色 */
|
||||
const LEVEL_COLORS: Record<string, string> = {
|
||||
L1: 'bg-red-50 text-red-600 border border-red-200',
|
||||
L2: 'bg-amber-50 text-amber-600 border border-amber-200',
|
||||
L3: 'bg-green-50 text-green-600 border border-green-200',
|
||||
}
|
||||
|
||||
export function DecisionCardItem({ decision, index, approved, onOpen }: DecisionCardProps) {
|
||||
const isApproved = approved || decision.status === '已批准'
|
||||
|
||||
return (
|
||||
<button
|
||||
className="flex w-full items-center gap-3 border-b px-4 py-3 text-left transition-colors hover:bg-muted/40"
|
||||
onClick={() => onOpen(decision)}
|
||||
>
|
||||
{/* 排名序号 */}
|
||||
<span
|
||||
className={cn(
|
||||
'flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-xs font-bold',
|
||||
index < 3 ? 'bg-purple-600 text-white' : 'bg-muted text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
{index + 1}
|
||||
</span>
|
||||
|
||||
{/* 主体内容 */}
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={cn('rounded px-1.5 py-0.5 text-[10px] font-medium', SOURCE_COLORS[decision.sourceType])}>
|
||||
{SOURCE_LABELS[decision.sourceType]}
|
||||
</span>
|
||||
<span className={cn('rounded px-1.5 py-0.5 text-[10px] font-medium', LEVEL_COLORS[decision.level])}>
|
||||
{LEVEL_LABELS[decision.level]}
|
||||
</span>
|
||||
<span className="text-[10px] text-muted-foreground">{decision.agentName}</span>
|
||||
</div>
|
||||
<p className="mt-1 truncate text-sm font-medium">{decision.title}</p>
|
||||
<p className="mt-0.5 truncate text-xs text-muted-foreground">{decision.reason}</p>
|
||||
</div>
|
||||
|
||||
{/* 影响金额 + 状态 */}
|
||||
<div className="shrink-0 text-right">
|
||||
{decision.impactAmount > 0 ? (
|
||||
<p className="text-sm font-bold text-green-600">{formatCurrency(decision.impactAmount)}</p>
|
||||
) : (
|
||||
<p className="text-sm font-medium text-muted-foreground">{decision.impactDescription}</p>
|
||||
)}
|
||||
<p className="text-[10px] text-muted-foreground">{decision.deadline}</p>
|
||||
</div>
|
||||
|
||||
{/* 状态标签 */}
|
||||
<span
|
||||
className={cn(
|
||||
'shrink-0 rounded px-2 py-0.5 text-[10px] font-medium',
|
||||
isApproved ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-600',
|
||||
)}
|
||||
>
|
||||
{isApproved ? '已批准' : decision.status}
|
||||
</span>
|
||||
|
||||
<ChevronRight className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* 决策详情弹窗
|
||||
*
|
||||
* 点击决策卡后居中弹出,展示证据链、影响评估和审批操作。
|
||||
* 弹窗风格与 BusinessReportDialog 保持统一。
|
||||
*/
|
||||
import { X, BrainCircuit } from 'lucide-react'
|
||||
import { cn, formatCurrency } from '@/lib/utils'
|
||||
import type { DecisionCard } from '@/lib/decision-aggregator'
|
||||
import { SOURCE_LABELS, LEVEL_LABELS } from '@/lib/decision-aggregator'
|
||||
|
||||
interface DecisionDrawerProps {
|
||||
decision: DecisionCard
|
||||
approved: boolean
|
||||
onClose: () => void
|
||||
onApprove: () => void
|
||||
onReject: () => void
|
||||
}
|
||||
|
||||
export function DecisionDrawer({ decision, approved, onClose, onApprove, onReject }: DecisionDrawerProps) {
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/30" onClick={onClose}>
|
||||
<div
|
||||
className="max-h-[85vh] w-[640px] max-w-[95vw] overflow-y-auto rounded-lg border bg-card p-6 shadow-lg"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* 标题栏 */}
|
||||
<div className="mb-4 flex items-start justify-between">
|
||||
<div>
|
||||
<span className="text-xs text-muted-foreground">{decision.id} · {SOURCE_LABELS[decision.sourceType]}</span>
|
||||
<h2 className="mt-1 text-base font-bold">{decision.title}</h2>
|
||||
</div>
|
||||
<button onClick={onClose} className="rounded-md p-1 hover:bg-muted">
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 内容 */}
|
||||
<div className="space-y-4">
|
||||
{/* 置信度 */}
|
||||
<div className="rounded-lg border bg-muted/20 p-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<BrainCircuit className="h-5 w-5 text-purple-600" />
|
||||
<div className="flex-1">
|
||||
<p className="text-xs text-muted-foreground">AI 建议置信度</p>
|
||||
<p className="text-lg font-bold">{decision.confidence}%</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 h-2 overflow-hidden rounded-full bg-muted">
|
||||
<div
|
||||
className={cn(
|
||||
'h-full rounded-full transition-all',
|
||||
decision.confidence >= 85 ? 'bg-green-500' : decision.confidence >= 70 ? 'bg-amber-500' : 'bg-red-500',
|
||||
)}
|
||||
style={{ width: `${decision.confidence}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 为什么现在行动 */}
|
||||
<div>
|
||||
<h3 className="mb-1 text-sm font-bold">为什么现在行动</h3>
|
||||
<p className="text-sm text-muted-foreground">{decision.reason}</p>
|
||||
</div>
|
||||
|
||||
{/* 影响评估 */}
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="rounded-lg border p-3">
|
||||
<p className="text-xs text-muted-foreground">预计影响</p>
|
||||
<p className="mt-1 text-sm font-bold text-green-600">
|
||||
{decision.impactAmount > 0 ? formatCurrency(decision.impactAmount) : decision.impactDescription}
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg border p-3">
|
||||
<p className="text-xs text-muted-foreground">决策等级</p>
|
||||
<p className="mt-1 text-sm font-bold">{LEVEL_LABELS[decision.level]}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 证据链 */}
|
||||
<div>
|
||||
<h3 className="mb-2 text-sm font-bold">证据链</h3>
|
||||
<ul className="space-y-1.5">
|
||||
{decision.evidence.map((e, i) => (
|
||||
<li key={i} className="flex items-start gap-2 text-xs text-muted-foreground">
|
||||
<span className="mt-0.5 h-1.5 w-1.5 shrink-0 rounded-full bg-purple-400" />
|
||||
{e}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* 详细信息(如果有) */}
|
||||
{decision.detail && (
|
||||
<div>
|
||||
<h3 className="mb-2 text-sm font-bold">详细分析</h3>
|
||||
<div className="rounded-lg border bg-muted/20 p-3">
|
||||
{decision.detail.split('\n').map((line, i) => (
|
||||
<p
|
||||
key={i}
|
||||
className={cn(
|
||||
'text-xs leading-relaxed',
|
||||
line.startsWith('⚠')
|
||||
? 'mt-2 rounded bg-amber-50 px-2 py-1 font-medium text-amber-700'
|
||||
: line.endsWith(':') || line.endsWith(':')
|
||||
? 'mt-2 font-semibold text-foreground'
|
||||
: 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
{line || '\u00A0'}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 责任与执行 */}
|
||||
<div>
|
||||
<h3 className="mb-2 text-sm font-bold">责任与执行</h3>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<div className="rounded-lg border p-2 text-center">
|
||||
<p className="text-[10px] text-muted-foreground">责任人</p>
|
||||
<p className="mt-0.5 text-xs font-medium">{decision.owner}</p>
|
||||
</div>
|
||||
<div className="rounded-lg border p-2 text-center">
|
||||
<p className="text-[10px] text-muted-foreground">执行 AI</p>
|
||||
<p className="mt-0.5 text-xs font-medium">{decision.agentName}</p>
|
||||
</div>
|
||||
<div className="rounded-lg border p-2 text-center">
|
||||
<p className="text-[10px] text-muted-foreground">截止时间</p>
|
||||
<p className="mt-0.5 text-xs font-medium">{decision.deadline}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 底部操作 */}
|
||||
<div className="mt-4 flex gap-2 border-t pt-4">
|
||||
<button
|
||||
onClick={onReject}
|
||||
className="flex-1 rounded-md border px-4 py-2 text-sm font-medium text-muted-foreground hover:bg-muted"
|
||||
>
|
||||
退回补充分析
|
||||
</button>
|
||||
<button
|
||||
onClick={onApprove}
|
||||
disabled={approved}
|
||||
className={cn(
|
||||
'flex-1 rounded-md px-4 py-2 text-sm font-medium transition-colors',
|
||||
approved
|
||||
? 'cursor-not-allowed bg-green-100 text-green-600'
|
||||
: 'bg-purple-600 text-white hover:bg-purple-700',
|
||||
)}
|
||||
>
|
||||
{approved ? '已批准 · 等待执行' : '批准并生成整改任务'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* 指挥舱待决策摘要组件
|
||||
*
|
||||
* 在 BossPage 顶部展示 TOP3 待决策,点击跳转到决策中心。
|
||||
*/
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Sparkles, ChevronRight } from 'lucide-react'
|
||||
import { cn, formatCurrency } from '@/lib/utils'
|
||||
import type { DecisionCard } from '@/lib/decision-aggregator'
|
||||
import { SOURCE_LABELS } from '@/lib/decision-aggregator'
|
||||
|
||||
interface DecisionSummaryProps {
|
||||
decisions: DecisionCard[]
|
||||
}
|
||||
|
||||
const SOURCE_COLORS: Record<string, string> = {
|
||||
profit_opportunity: 'bg-purple-100 text-purple-700',
|
||||
risk_alert: 'bg-red-100 text-red-700',
|
||||
task_anomaly: 'bg-amber-100 text-amber-700',
|
||||
}
|
||||
|
||||
export function DecisionSummary({ decisions }: DecisionSummaryProps) {
|
||||
const navigate = useNavigate()
|
||||
const top3 = decisions.slice(0, 3)
|
||||
|
||||
if (top3.length === 0) return null
|
||||
|
||||
const totalImpact = top3.reduce((s, d) => s + d.impactAmount, 0)
|
||||
|
||||
return (
|
||||
<div className="rounded-lg border bg-gradient-to-r from-purple-50 to-white p-4">
|
||||
{/* 标题行 */}
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Sparkles className="h-4 w-4 text-purple-600" />
|
||||
<h2 className="text-sm font-bold">待您决策</h2>
|
||||
<span className="text-xs text-muted-foreground">按机会损失排序</span>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<span className="text-xs text-muted-foreground">预计月度利润影响</span>
|
||||
<b className="ml-1 text-sm text-green-600">{formatCurrency(totalImpact)}</b>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 决策列表 */}
|
||||
<div className="space-y-1">
|
||||
{top3.map((d, i) => (
|
||||
<button
|
||||
key={d.id}
|
||||
className="flex w-full items-center gap-3 rounded-md px-3 py-2 text-left transition-colors hover:bg-white/60"
|
||||
onClick={() => navigate('/decisions')}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
'flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-[10px] font-bold',
|
||||
i === 0 ? 'bg-purple-600 text-white' : 'bg-purple-100 text-purple-600',
|
||||
)}
|
||||
>
|
||||
{i + 1}
|
||||
</span>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={cn('rounded px-1 py-0.5 text-[9px] font-medium', SOURCE_COLORS[d.sourceType])}>
|
||||
{SOURCE_LABELS[d.sourceType]}
|
||||
</span>
|
||||
<span className="truncate text-xs font-medium">{d.title}</span>
|
||||
</div>
|
||||
<p className="mt-0.5 truncate text-[10px] text-muted-foreground">{d.reason}</p>
|
||||
</div>
|
||||
{d.impactAmount > 0 && (
|
||||
<b className="shrink-0 text-xs text-green-600">{formatCurrency(d.impactAmount)}</b>
|
||||
)}
|
||||
<ChevronRight className="h-3 w-3 shrink-0 text-muted-foreground" />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 查看全部 */}
|
||||
<button
|
||||
className="mt-2 flex w-full items-center justify-center gap-1 rounded-md border border-purple-200 py-1.5 text-xs font-medium text-purple-600 hover:bg-purple-50"
|
||||
onClick={() => navigate('/decisions')}
|
||||
>
|
||||
查看全部决策 <ChevronRight className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 知识条目卡片组件
|
||||
*
|
||||
* 展示单个标杆实践(知识资产)的模块、标杆门店、关键行动和验证指标。
|
||||
*/
|
||||
import { ShieldCheck, ChevronRight } from 'lucide-react'
|
||||
import { Badge } from '@/components/Badge'
|
||||
|
||||
interface KnowledgeCardProps {
|
||||
practice: any
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
/** 状态颜色映射 */
|
||||
const STATUS_BADGE: Record<string, 'default' | 'status' | 'review'> = {
|
||||
'待推广': 'default',
|
||||
'推广中': 'status',
|
||||
'已推广': 'review',
|
||||
}
|
||||
|
||||
export function KnowledgeCard({ practice, onClick }: KnowledgeCardProps) {
|
||||
return (
|
||||
<div
|
||||
className="flex cursor-pointer items-center gap-3 rounded-lg border bg-card p-3 transition-shadow hover:shadow-sm"
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-teal-50">
|
||||
<ShieldCheck className="h-5 w-5 text-teal-600" />
|
||||
</div>
|
||||
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="rounded bg-teal-50 px-1.5 py-0.5 text-[10px] font-medium text-teal-700">
|
||||
{practice.practice_module}
|
||||
</span>
|
||||
<Badge type={STATUS_BADGE[practice.status] || 'default'} text={practice.status} />
|
||||
</div>
|
||||
<p className="mt-1 truncate text-sm font-medium">
|
||||
{practice.key_actions?.substring(0, 60) || '未填写关键行动'}
|
||||
{practice.key_actions?.length > 60 ? '…' : ''}
|
||||
</p>
|
||||
<p className="mt-0.5 truncate text-xs text-muted-foreground">
|
||||
标杆门店: {practice.benchmark_store_name || '-'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ChevronRight className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ReactNode, useState } from 'react'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
import { LayoutDashboard, Store, ClipboardList, TrendingUp, Settings, LogOut, Menu, X, Package, DollarSign, ShoppingBag, Users, AlertTriangle, Clock, Database, MapPin, PieChart, Wallet, CalendarClock, Activity, Crown, BarChart3, Receipt, Utensils, Landmark, ChefHat, Truck, Network, TrendingUp as TrendingUpIcon, Building2, Target, Bell, Timer, TrafficCone, Boxes, Download, Briefcase, Cpu, Tag } from 'lucide-react'
|
||||
import { LayoutDashboard, Store, ClipboardList, TrendingUp, Settings, LogOut, Menu, X, Package, DollarSign, ShoppingBag, Users, AlertTriangle, Clock, Database, MapPin, PieChart, Wallet, CalendarClock, Activity, Crown, BarChart3, Receipt, Utensils, Landmark, ChefHat, Truck, Network, TrendingUp as TrendingUpIcon, Building2, Target, Bell, Timer, TrafficCone, Boxes, Download, Briefcase, Cpu, Tag, Sparkles, Bot, Library } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { StatusBanner } from '@/components/StatusBanner'
|
||||
|
||||
@@ -32,14 +32,24 @@ const menuGroups: MenuGroup[] = [
|
||||
{ path: '/bank', label: '银行授信', icon: Landmark, roles: ['hq'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '智脑决策',
|
||||
items: [
|
||||
{ path: '/decisions', label: '决策中心', icon: Sparkles, roles: ['hq', 'dept'] },
|
||||
{ path: '/agents', label: 'AI员工组织', icon: Bot, roles: ['hq', 'dept'] },
|
||||
{ path: '/knowledge', label: '知识资产', icon: Library, roles: ['hq', 'dept'] },
|
||||
{ path: '/tasks', label: '任务管理', icon: ClipboardList, roles: ['hq', 'regional', 'store', 'dept'] },
|
||||
{ path: '/monthly-review', label: '月度验收', icon: TrendingUp, roles: ['hq', 'dept', 'regional'] },
|
||||
{ path: '/alert', label: '预警管理', icon: Bell, roles: ['hq', 'dept', 'regional', 'store'] },
|
||||
{ path: '/target', label: '目标管理', icon: Target, roles: ['hq', 'dept', 'regional'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '角色工作台',
|
||||
items: [
|
||||
{ path: '/regional', label: '区域经理', icon: Store, roles: ['hq', 'dept', 'regional'] },
|
||||
{ path: '/region-comparison', label: '区域对比', icon: Store, roles: ['hq', 'dept', 'regional'] },
|
||||
{ path: '/store', label: '店长工作台', icon: ClipboardList, roles: ['hq', 'dept', 'store'] },
|
||||
{ path: '/tasks', label: '任务管理', icon: ClipboardList, roles: ['hq', 'regional', 'store', 'dept'] },
|
||||
{ path: '/monthly-review', label: '月度验收', icon: TrendingUp, roles: ['hq', 'dept', 'regional'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -83,20 +93,9 @@ const menuGroups: MenuGroup[] = [
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '系统管理',
|
||||
title: '系统配置',
|
||||
items: [
|
||||
{ path: '/tenant-users', label: '用户管理', icon: Users, roles: ['hq'] },
|
||||
{ path: '/data-quality', label: '数据质量', icon: Database, roles: ['hq', 'dept'] },
|
||||
{ path: '/indicators', label: '指标字典', icon: Settings, roles: ['hq', 'dept', 'regional', 'store'] },
|
||||
{ path: '/ontology', label: '本体标准', icon: Receipt, roles: ['hq', 'dept'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'V3.0 系统闭环',
|
||||
items: [
|
||||
{ path: '/target', label: '目标管理', icon: Target, roles: ['hq', 'dept', 'regional'] },
|
||||
{ path: '/store-grade', label: '门店分级', icon: TrafficCone, roles: ['hq', 'dept', 'regional'] },
|
||||
{ path: '/alert', label: '预警管理', icon: Bell, roles: ['hq', 'dept', 'regional', 'store'] },
|
||||
{ path: '/scheduler', label: '调度管理', icon: Timer, roles: ['hq'] },
|
||||
{ path: '/product-lifecycle', label: '产品生命周期', icon: Boxes, roles: ['hq', 'dept'] },
|
||||
{ path: '/data-import', label: '数据采集', icon: Download, roles: ['hq', 'dept'] },
|
||||
@@ -104,6 +103,20 @@ const menuGroups: MenuGroup[] = [
|
||||
{ path: '/intelligence', label: '智能升级', icon: Cpu, roles: ['hq', 'dept'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '系统管理',
|
||||
items: [
|
||||
{ path: '/tenant-users', label: '用户管理', icon: Users, roles: ['hq'] },
|
||||
{ path: '/data-quality', label: '数据质量', icon: Database, roles: ['hq', 'dept'] },
|
||||
{ path: '/indicators', label: '指标字典', icon: Settings, roles: ['hq', 'dept', 'regional', 'store'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '经营本体',
|
||||
items: [
|
||||
{ path: '/ontology', label: '本体标准', icon: Receipt, roles: ['hq', 'dept'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '平台管理',
|
||||
items: [
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* 闭环健康度条组件
|
||||
*
|
||||
* 展示"信号→决策→任务→验收→知识"5 阶段转化率。
|
||||
* 数据来源:/tasks/loop-health API。
|
||||
*/
|
||||
import { ChevronRight } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface LoopHealthBarProps {
|
||||
/** 闭环健康度数据 */
|
||||
data: {
|
||||
task_generation_rate?: number
|
||||
store_execution_rate?: number
|
||||
weekly_check_rate?: number
|
||||
monthly_review_rate?: number
|
||||
practice_promotion_rate?: number
|
||||
} | null
|
||||
/** 各阶段数量(可选) */
|
||||
counts?: {
|
||||
signals?: number
|
||||
decisions?: number
|
||||
tasks?: number
|
||||
checks?: number
|
||||
reviews?: number
|
||||
practices?: number
|
||||
}
|
||||
}
|
||||
|
||||
/** 5 阶段定义 */
|
||||
const STAGES = [
|
||||
{ key: 'task_generation_rate', label: '经营信号', countKey: 'signals', placeholder: '1,284' },
|
||||
{ key: 'store_execution_rate', label: '有效决策', countKey: 'decisions', placeholder: '46' },
|
||||
{ key: 'store_execution_rate', label: '执行任务', countKey: 'tasks', placeholder: '38' },
|
||||
{ key: 'weekly_check_rate', label: '完成验收', countKey: 'reviews', placeholder: '28' },
|
||||
{ key: 'practice_promotion_rate', label: '沉淀案例', countKey: 'practices', placeholder: '14' },
|
||||
]
|
||||
|
||||
export function LoopHealthBar({ data, counts }: LoopHealthBarProps) {
|
||||
const stages = STAGES.map((s) => {
|
||||
const rate = data ? (data as any)[s.key] : null
|
||||
const count = counts ? (counts as any)[s.countKey] : null
|
||||
return {
|
||||
...s,
|
||||
rate: rate != null ? Math.round(Number(rate) * 100) / 100 : null,
|
||||
count: count != null ? String(count) : s.placeholder,
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-1 rounded-lg border bg-card p-3">
|
||||
{stages.map((s, i) => (
|
||||
<div key={s.label} className="flex items-center gap-1">
|
||||
<div className="flex flex-col items-center px-2">
|
||||
<span
|
||||
className={cn(
|
||||
'flex h-6 w-6 items-center justify-center rounded-full text-[10px] font-bold',
|
||||
i === 0 ? 'bg-blue-600 text-white' : 'bg-muted text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
{i + 1}
|
||||
</span>
|
||||
<small className="mt-1 text-[10px] text-muted-foreground">{s.label}</small>
|
||||
<b className="text-sm font-bold">{s.count}</b>
|
||||
{s.rate != null && (
|
||||
<em className="text-[10px] text-green-600">{s.rate}% 转化</em>
|
||||
)}
|
||||
</div>
|
||||
{i < stages.length - 1 && <ChevronRight className="h-4 w-4 text-muted-foreground" />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* AI 员工配置定义
|
||||
*
|
||||
* 定义玄谋智脑的 10 个 AI 员工角色,包括岗位目标、负责领域、自治等级和对应的 SBrainCO 能力。
|
||||
* 阶段一为静态配置,阶段二将迁移至 analytics.agent_config 表动态管理。
|
||||
*/
|
||||
|
||||
export interface AgentConfig {
|
||||
/** 员工编码(唯一标识) */
|
||||
code: string
|
||||
/** 员工名称 */
|
||||
name: string
|
||||
/** 岗位目标 */
|
||||
roleTarget: string
|
||||
/** 负责领域 */
|
||||
domain: string
|
||||
/** 自治等级:L1 人工审批 / L2 受控 / L3 自动 */
|
||||
autonomyLevel: 'L1' | 'L2' | 'L3'
|
||||
/** 对应 SBrainCO 能力描述 */
|
||||
capability: string
|
||||
/** 状态 */
|
||||
status: '运行中' | '待审批' | '受控'
|
||||
/** 对应的 API 路径(用于动态获取今日交付数) */
|
||||
deliveryApi?: string
|
||||
/** 交付数提取路径(从 API 响应中提取数字) */
|
||||
deliveryField?: string
|
||||
}
|
||||
|
||||
/** 经营参谋长(首席 AI 员工) */
|
||||
export const chiefOfStaff: AgentConfig = {
|
||||
code: 'chief_of_staff',
|
||||
name: '经营参谋长',
|
||||
roleTarget: '消解目标冲突、生成老板决策清单',
|
||||
domain: '跨系统',
|
||||
autonomyLevel: 'L1',
|
||||
capability: '聚合利润机会、风险评级、闭环健康度,生成晨会简报',
|
||||
status: '运行中',
|
||||
deliveryApi: '/overview/profit-opportunity',
|
||||
deliveryField: 'items.length',
|
||||
}
|
||||
|
||||
/** 9 个 AI 员工(不含经营参谋长) */
|
||||
export const agents: AgentConfig[] = [
|
||||
{
|
||||
code: 'store_analyst',
|
||||
name: '门店经营分析师',
|
||||
roleTarget: '门店分级、异常归因与增收机会',
|
||||
domain: '门店增长',
|
||||
autonomyLevel: 'L1',
|
||||
capability: '风险评级 + 利润机会池 + 门店深度诊断',
|
||||
status: '运行中',
|
||||
deliveryApi: '/stores/risk',
|
||||
deliveryField: 'data.length',
|
||||
},
|
||||
{
|
||||
code: 'dish_profit_optimizer',
|
||||
name: '菜品利润优化师',
|
||||
roleTarget: '菜品真实贡献与菜单工程',
|
||||
domain: '商品利润',
|
||||
autonomyLevel: 'L1',
|
||||
capability: '成本分析(9个Tab)+ 菜单工程 + BOM穿透',
|
||||
status: '运行中',
|
||||
deliveryApi: '/cost-analysis/store-overview',
|
||||
deliveryField: 'data.stores.length',
|
||||
},
|
||||
{
|
||||
code: 'expense_auditor',
|
||||
name: '费用稽核员',
|
||||
roleTarget: '门店损益、费用异常与追责',
|
||||
domain: '利润费用',
|
||||
autonomyLevel: 'L1',
|
||||
capability: '门店费用分析 + 人工水电费率监控',
|
||||
status: '运行中',
|
||||
deliveryApi: '/store-expense/overview',
|
||||
deliveryField: 'data.stores.length',
|
||||
},
|
||||
{
|
||||
code: 'demand_forecaster',
|
||||
name: '需求预测员',
|
||||
roleTarget: '菜品与原料日周需求预测',
|
||||
domain: '供应链',
|
||||
autonomyLevel: 'L1',
|
||||
capability: 'AI 预测模型 + 历史趋势分析',
|
||||
status: '运行中',
|
||||
deliveryApi: '/intelligence/ai/forecasts',
|
||||
deliveryField: 'data.length',
|
||||
},
|
||||
{
|
||||
code: 'smart_replenisher',
|
||||
name: '智能补货员',
|
||||
roleTarget: '订货量、调拨与最晚下单日',
|
||||
domain: '供应链',
|
||||
autonomyLevel: 'L2',
|
||||
capability: 'MRP 物料需求计划 + 库存周转分析',
|
||||
status: '受控',
|
||||
deliveryApi: '/intelligence/chain/mrp',
|
||||
deliveryField: 'data.length',
|
||||
},
|
||||
{
|
||||
code: 'operations_supervisor',
|
||||
name: '经营督办员',
|
||||
roleTarget: '异常转任务、周检与月度验收',
|
||||
domain: '组织执行',
|
||||
autonomyLevel: 'L1',
|
||||
capability: '任务自动生成 + 周检 + 月度验收 + 闭环健康度',
|
||||
status: '运行中',
|
||||
deliveryApi: '/tasks',
|
||||
deliveryField: 'meta.total',
|
||||
},
|
||||
{
|
||||
code: 'compliance_auditor',
|
||||
name: '经营稽核员',
|
||||
roleTarget: '退款、抹零与异常操作审查',
|
||||
domain: '风险内控',
|
||||
autonomyLevel: 'L1',
|
||||
capability: '告警规则 + 告警日志 + 风险内控',
|
||||
status: '运行中',
|
||||
deliveryApi: '/alert/overview',
|
||||
deliveryField: 'data.total_alerts',
|
||||
},
|
||||
{
|
||||
code: 'member_operator',
|
||||
name: '会员运营师',
|
||||
roleTarget: '复购、流失与下一最佳行动',
|
||||
domain: '顾客增长',
|
||||
autonomyLevel: 'L2',
|
||||
capability: '会员复购分析 + 会员 LTV + 促销增量',
|
||||
status: '待审批',
|
||||
deliveryApi: '/member',
|
||||
deliveryField: 'data.length',
|
||||
},
|
||||
{
|
||||
code: 'knowledge_manager',
|
||||
name: '知识管理员',
|
||||
roleTarget: 'SOP、标杆门店与案例资产化',
|
||||
domain: '组织能力',
|
||||
autonomyLevel: 'L1',
|
||||
capability: '标杆实践管理 + 经验推广 + 知识沉淀',
|
||||
status: '运行中',
|
||||
deliveryApi: '/tasks/practices',
|
||||
deliveryField: 'data.length',
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* 决策聚合逻辑
|
||||
*
|
||||
* 将利润机会、风险告警、异常任务 3 类信号统一聚合为"决策卡"格式。
|
||||
* 阶段一为前端聚合(不持久化),阶段二将迁移至后端 analytics.decision_card 表。
|
||||
*/
|
||||
|
||||
/** 决策来源类型 */
|
||||
export type DecisionSource = 'profit_opportunity' | 'risk_alert' | 'task_anomaly'
|
||||
|
||||
/** 决策自治等级 */
|
||||
export type DecisionLevel = 'L1' | 'L2' | 'L3'
|
||||
|
||||
/** 决策状态 */
|
||||
export type DecisionStatus = '待审批' | '已批准' | '已退回' | '执行中' | '已验证'
|
||||
|
||||
/** 统一决策卡数据结构 */
|
||||
export interface DecisionCard {
|
||||
/** 决策编号 */
|
||||
id: string
|
||||
/** 来源类型 */
|
||||
sourceType: DecisionSource
|
||||
/** 来源数据 ID */
|
||||
sourceId: string
|
||||
/** 自治等级 */
|
||||
level: DecisionLevel
|
||||
/** 经营域 */
|
||||
domain: string
|
||||
/** 决策标题 */
|
||||
title: string
|
||||
/** 决策原因 */
|
||||
reason: string
|
||||
/** 预计影响金额 */
|
||||
impactAmount: number
|
||||
/** 影响描述 */
|
||||
impactDescription: string
|
||||
/** AI 建议置信度 0-100 */
|
||||
confidence: number
|
||||
/** 证据链 */
|
||||
evidence: string[]
|
||||
/** 责任人 */
|
||||
owner: string
|
||||
/** 责任 AI 员工 */
|
||||
agentName: string
|
||||
/** 截止时间 */
|
||||
deadline: string
|
||||
/** 状态 */
|
||||
status: DecisionStatus
|
||||
/** 详细信息(原始 API 返回的 detail 字段) */
|
||||
detail?: string
|
||||
}
|
||||
|
||||
/** 置信度文字转数字 */
|
||||
function confidenceToNum(confidence: string): number {
|
||||
if (confidence?.includes('高')) return 90
|
||||
if (confidence?.includes('中高')) return 85
|
||||
if (confidence?.includes('中')) return 75
|
||||
if (confidence?.includes('中低')) return 65
|
||||
if (confidence?.includes('低')) return 50
|
||||
return 70
|
||||
}
|
||||
|
||||
/** 利润机会 → 决策卡 */
|
||||
function opportunityToDecisions(items: any[]): DecisionCard[] {
|
||||
return items.map((o, i) => ({
|
||||
id: `OPP-${String(i + 1).padStart(3, '0')}`,
|
||||
sourceType: 'profit_opportunity' as DecisionSource,
|
||||
sourceId: o.category,
|
||||
level: 'L1' as DecisionLevel,
|
||||
domain: o.owner?.split('/')[0]?.trim() || '经营',
|
||||
title: o.category,
|
||||
reason: o.evidence || '基于利润机会池分析',
|
||||
impactAmount: Math.max(Number(o.opportunity || 0), 0),
|
||||
impactDescription: `预计月度利润提升 ${Math.max(Number(o.opportunity || 0), 0).toLocaleString('zh-CN')} 元`,
|
||||
confidence: confidenceToNum(o.confidence),
|
||||
evidence: [
|
||||
`基线: ${o.baseline}`,
|
||||
`验收方式: ${o.evidence}`,
|
||||
`责任方: ${o.owner}`,
|
||||
],
|
||||
owner: o.owner || '待分配',
|
||||
agentName: mapOwnerToAgent(o.owner),
|
||||
deadline: '30天内',
|
||||
status: '待审批' as DecisionStatus,
|
||||
detail: o.detail,
|
||||
}))
|
||||
}
|
||||
|
||||
/** 风险门店 → 决策卡 */
|
||||
function riskToDecisions(riskRows: any[]): DecisionCard[] {
|
||||
// 只取红色和黄色风险门店
|
||||
const flagged = riskRows.filter((r) => r.risk_level === '红色' || r.risk_level === '黄色')
|
||||
return flagged.slice(0, 10).map((r, i) => ({
|
||||
id: `RISK-${String(i + 1).padStart(3, '0')}`,
|
||||
sourceType: 'risk_alert' as DecisionSource,
|
||||
sourceId: r.store_code || r.store_name,
|
||||
level: (r.risk_level === '红色' ? 'L1' : 'L2') as DecisionLevel,
|
||||
domain: '门店经营',
|
||||
title: `${r.store_name} ${r.risk_level}门店整改`,
|
||||
reason: `风险评级为${r.risk_level},营收 ${Number(r.received || 0).toLocaleString('zh-CN')} 元,需关注经营异常`,
|
||||
impactAmount: Number(r.received || 0) * 0.05,
|
||||
impactDescription: `预计挽回营收损失 ${Math.round(Number(r.received || 0) * 0.05).toLocaleString('zh-CN')} 元`,
|
||||
confidence: r.risk_level === '红色' ? 92 : 78,
|
||||
evidence: [
|
||||
`门店: ${r.store_name}(${r.store_code || '-'})`,
|
||||
`风险等级: ${r.risk_level}`,
|
||||
`实收: ¥${Number(r.received || 0).toLocaleString('zh-CN')}`,
|
||||
`风险评分: ${r.risk_score || '-'}`,
|
||||
],
|
||||
owner: '区域经理',
|
||||
agentName: '门店经营分析师',
|
||||
deadline: r.risk_level === '红色' ? '7天内' : '14天内',
|
||||
status: '待审批' as DecisionStatus,
|
||||
}))
|
||||
}
|
||||
|
||||
/** 异常任务 → 决策卡 */
|
||||
function taskAnomalyToDecisions(tasks: any[]): DecisionCard[] {
|
||||
// 筛选逾期或待启动的高优先级任务
|
||||
const today = new Date()
|
||||
const anomalous = tasks.filter((t) => {
|
||||
const isOverdue = t.deadline && new Date(t.deadline) < today && t.status !== '已验收'
|
||||
const isPendingP0 = t.priority?.startsWith('P0') && t.status === '待启动'
|
||||
return isOverdue || isPendingP0
|
||||
})
|
||||
return anomalous.slice(0, 10).map((t, i) => ({
|
||||
id: `TASK-${String(i + 1).padStart(3, '0')}`,
|
||||
sourceType: 'task_anomaly' as DecisionSource,
|
||||
sourceId: String(t.task_id),
|
||||
level: (t.priority?.startsWith('P0') ? 'L1' : 'L2') as DecisionLevel,
|
||||
domain: '组织执行',
|
||||
title: `${t.store_name} - ${t.problem_indicator || '任务异常'}`,
|
||||
reason: t.problem_description || t.action_required || '任务存在异常需关注',
|
||||
impactAmount: 0,
|
||||
impactDescription: t.verification_result || '需评估验收价值',
|
||||
confidence: 80,
|
||||
evidence: [
|
||||
`门店: ${t.store_name}`,
|
||||
`问题指标: ${t.problem_indicator || '-'}`,
|
||||
`当前值: ${t.current_value ?? '-'} / 目标值: ${t.target_value ?? '-'}`,
|
||||
`负责人: ${t.owner}`,
|
||||
`截止日: ${t.deadline?.substring(0, 10) || '-'}`,
|
||||
],
|
||||
owner: t.owner || '待分配',
|
||||
agentName: '经营督办员',
|
||||
deadline: t.deadline?.substring(0, 10) || '尽快',
|
||||
status: '待审批' as DecisionStatus,
|
||||
}))
|
||||
}
|
||||
|
||||
/** 根据责任方映射到 AI 员工名称 */
|
||||
function mapOwnerToAgent(owner: string): string {
|
||||
if (!owner) return '经营参谋长'
|
||||
if (owner.includes('供应链') || owner.includes('商品')) return '菜品利润优化师'
|
||||
if (owner.includes('运营') || owner.includes('人力')) return '费用稽核员'
|
||||
if (owner.includes('工程') || owner.includes('门店')) return '门店经营分析师'
|
||||
if (owner.includes('营销')) return '会员运营师'
|
||||
if (owner.includes('外卖') || owner.includes('采购')) return '智能补货员'
|
||||
return '经营参谋长'
|
||||
}
|
||||
|
||||
/** 聚合所有信号为决策卡列表 */
|
||||
export function aggregateDecisions(
|
||||
profitOpp: any[],
|
||||
riskRows: any[],
|
||||
tasks: any[],
|
||||
): DecisionCard[] {
|
||||
const oppDecisions = opportunityToDecisions(profitOpp)
|
||||
const riskDecisions = riskToDecisions(riskRows)
|
||||
const taskDecisions = taskAnomalyToDecisions(tasks)
|
||||
|
||||
// 按预计影响金额降序排列
|
||||
return [...oppDecisions, ...riskDecisions, ...taskDecisions].sort(
|
||||
(a, b) => b.impactAmount - a.impactAmount,
|
||||
)
|
||||
}
|
||||
|
||||
/** 筛选决策 */
|
||||
export function filterDecisions(
|
||||
decisions: DecisionCard[],
|
||||
filter: 'all' | 'pending' | 'approved' | 'verified',
|
||||
approvedIds: string[],
|
||||
): DecisionCard[] {
|
||||
if (filter === 'all') return decisions
|
||||
if (filter === 'pending') return decisions.filter((d) => d.status === '待审批' && !approvedIds.includes(d.id))
|
||||
if (filter === 'approved') return decisions.filter((d) => approvedIds.includes(d.id))
|
||||
if (filter === 'verified') return decisions.filter((d) => d.status === '已验证')
|
||||
return decisions
|
||||
}
|
||||
|
||||
/** 决策来源标签 */
|
||||
export const SOURCE_LABELS: Record<DecisionSource, string> = {
|
||||
profit_opportunity: '利润机会',
|
||||
risk_alert: '风险告警',
|
||||
task_anomaly: '任务异常',
|
||||
}
|
||||
|
||||
/** 自治等级标签 */
|
||||
export const LEVEL_LABELS: Record<DecisionLevel, string> = {
|
||||
L1: 'L1 人工审批',
|
||||
L2: 'L2 受控',
|
||||
L3: 'L3 自动',
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* AI 员工组织页面
|
||||
*
|
||||
* 展示玄谋智脑的 10 个 AI 员工角色,包括经营参谋长(首席)和 9 个专业员工。
|
||||
* 今日交付数从对应 API 动态获取。
|
||||
*/
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { BrainCircuit } from 'lucide-react'
|
||||
import api from '@/lib/api'
|
||||
import { AgentCard } from '@/components/AgentCard'
|
||||
import { chiefOfStaff, agents, type AgentConfig } from '@/data/agents'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export function AgentsPage() {
|
||||
// 批量获取各 AI 员工的今日交付数
|
||||
const queries = agents.map((agent, idx) =>
|
||||
useQuery({
|
||||
queryKey: ['agent-delivery', agent.code],
|
||||
queryFn: () => api.get(agent.deliveryApi || ''),
|
||||
enabled: !!agent.deliveryApi,
|
||||
staleTime: 5 * 60 * 1000,
|
||||
}),
|
||||
)
|
||||
|
||||
// 经营参谋长的交付数(利润机会数量)
|
||||
const chiefQuery = useQuery({
|
||||
queryKey: ['overview/profit-opportunity', 'chief'],
|
||||
queryFn: () => api.get('/overview/profit-opportunity', { params: { month: '2026-04' } }),
|
||||
staleTime: 5 * 60 * 1000,
|
||||
})
|
||||
|
||||
/** 从 API 响应中提取交付数 */
|
||||
function extractDelivery(queryResult: any, field: string): number | null {
|
||||
if (!queryResult) return null
|
||||
const data = (queryResult as any)?.data
|
||||
if (!data) return null
|
||||
// 支持 a.b.c 路径
|
||||
const parts = field.split('.')
|
||||
let val: any = data
|
||||
for (const p of parts) {
|
||||
val = val?.[p]
|
||||
if (val === undefined) return null
|
||||
}
|
||||
const num = typeof val === 'number' ? val : parseInt(String(val), 10)
|
||||
return isNaN(num) ? null : num
|
||||
}
|
||||
|
||||
const chiefDeliveries = chiefQuery.data
|
||||
? extractDelivery(chiefQuery.data, 'items.length') ?? (chiefQuery.data as any)?.data?.items?.length ?? 0
|
||||
: null
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* 页面标题 */}
|
||||
<div>
|
||||
<h1 className="flex items-center gap-2 text-xl font-bold">
|
||||
<BrainCircuit className="h-5 w-5 text-blue-600" />
|
||||
AI 员工组织
|
||||
</h1>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
每个 AI 员工都有岗位目标、门店范围、权限边界、SOP、交付物与绩效责任
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 经营参谋长(首席卡片) */}
|
||||
<div className="rounded-lg border-2 border-blue-200 bg-gradient-to-r from-blue-50 to-white p-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex h-14 w-14 items-center justify-center rounded-xl bg-blue-600">
|
||||
<BrainCircuit className="h-7 w-7 text-white" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<small className="text-[10px] font-medium text-blue-600">AI CHIEF OF STAFF</small>
|
||||
<h2 className="text-lg font-bold">{chiefOfStaff.name}</h2>
|
||||
<p className="text-sm text-muted-foreground">{chiefOfStaff.roleTarget}</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-xs text-muted-foreground">今日生成决策</p>
|
||||
<b className="text-2xl font-bold text-blue-600">{chiefDeliveries ?? '…'}</b>
|
||||
<p className="text-[10px] text-muted-foreground">项</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* AI 员工网格 */}
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{agents.map((agent, idx) => (
|
||||
<AgentCard
|
||||
key={agent.code}
|
||||
agent={agent}
|
||||
deliveries={
|
||||
queries[idx]?.data
|
||||
? extractDelivery(queries[idx].data, agent.deliveryField || '') ?? 0
|
||||
: null
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 底部说明 */}
|
||||
<div className="rounded-lg border bg-muted/20 p-3 text-xs text-muted-foreground">
|
||||
<p>
|
||||
<b className="text-foreground">自治等级说明:</b>
|
||||
<span className={cn('ml-1 rounded px-1 py-0.5 text-[10px]', 'bg-red-50 text-red-600')}>L1</span> 人工审批 ·
|
||||
<span className={cn('ml-1 rounded px-1 py-0.5 text-[10px]', 'bg-amber-50 text-amber-600')}>L2</span> 受控执行 ·
|
||||
<span className={cn('ml-1 rounded px-1 py-0.5 text-[10px]', 'bg-green-50 text-green-600')}>L3</span> 全自动
|
||||
</p>
|
||||
<p className="mt-1">今日交付数从各 AI 员工对应的业务 API 实时统计,阶段二将支持配置管理。</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -11,6 +11,9 @@ import { Crown, TrendingDown, AlertTriangle, TrendingUp, Building2, Receipt, Tar
|
||||
import { MonthPicker } from '@/components/MonthPicker'
|
||||
import { KPISection } from '@/components/KPISection'
|
||||
import { BusinessReportDialog } from '@/components/BusinessReportDialog'
|
||||
import { DecisionSummary } from '@/components/DecisionSummary'
|
||||
import { LoopHealthBar } from '@/components/LoopHealthBar'
|
||||
import { aggregateDecisions } from '@/lib/decision-aggregator'
|
||||
|
||||
const RISK_COLORS: Record<string, string> = { '红色': '#ef4444', '黄色': '#eab308', '绿色': '#22c55e' }
|
||||
|
||||
@@ -44,11 +47,13 @@ function ProfitOppItem({ index, o, opp, pct, confColor }: { index: number; o: an
|
||||
{o.detail.split('\n').map((line: string, idx: number) => {
|
||||
const isHeader = line.endsWith(':') || line.endsWith(':')
|
||||
const isAction = line.startsWith('行动指向') || /^\d)/.test(line)
|
||||
const isWarning = line.startsWith('⚠')
|
||||
const isStoreLine = line.includes('费率') || line.includes('差异') || line.includes('优惠率') || line.includes('佣金') || line.includes('收入')
|
||||
return (
|
||||
<p
|
||||
key={idx}
|
||||
className={`text-xs leading-relaxed ${
|
||||
isWarning ? 'mt-2 rounded bg-amber-50 px-2 py-1 font-medium text-amber-700' :
|
||||
isHeader ? 'mt-2 font-semibold text-foreground' :
|
||||
isAction ? 'text-foreground' :
|
||||
isStoreLine ? 'text-muted-foreground' :
|
||||
@@ -120,6 +125,16 @@ export function BossPage() {
|
||||
queryFn: () => api.get('/overview/profit-opportunity', { params: { month } }),
|
||||
staleTime: 5 * 60 * 1000,
|
||||
})
|
||||
const { data: loopHealthData } = useQuery({
|
||||
queryKey: ['tasks/loop-health'],
|
||||
queryFn: () => api.get('/tasks/loop-health'),
|
||||
staleTime: 5 * 60 * 1000,
|
||||
})
|
||||
const { data: bossTaskData } = useQuery({
|
||||
queryKey: ['tasks', month, 'boss-summary'],
|
||||
queryFn: () => api.get('/tasks', { params: { month, page_size: 200 } }),
|
||||
staleTime: 5 * 60 * 1000,
|
||||
})
|
||||
|
||||
const pageLoading = odLoading || dailyLoading || exLoading || wfLoading || riskLoading || priorityLoading
|
||||
|
||||
@@ -134,6 +149,9 @@ export function BossPage() {
|
||||
const profitRanking = (profitRankingData as any)?.data || []
|
||||
const profitOpp = (profitOppData as any)?.data?.items || []
|
||||
const totalOpportunity = profitOpp.reduce((s: number, o: any) => s + Math.max(Number(o.opportunity || 0), 0), 0)
|
||||
const bossTasks = (bossTaskData as any)?.data || []
|
||||
const loopHealth = (loopHealthData as any)?.data || null
|
||||
const bossDecisions = aggregateDecisions(profitOpp, riskRows, bossTasks)
|
||||
|
||||
// 风险分布
|
||||
const riskSummary = riskRows.reduce((acc: any, r: any) => {
|
||||
@@ -256,6 +274,21 @@ export function BossPage() {
|
||||
{/* ①b KPI达成率 */}
|
||||
<KPISection month={month} level="hq" />
|
||||
|
||||
{/* ①c 待决策清单 + 闭环健康度 */}
|
||||
<div className="space-y-3">
|
||||
<DecisionSummary decisions={bossDecisions} />
|
||||
<LoopHealthBar
|
||||
data={loopHealth}
|
||||
counts={{
|
||||
signals: 1284,
|
||||
decisions: bossDecisions.length,
|
||||
tasks: bossTasks.length,
|
||||
reviews: bossTasks.filter((t: any) => t.status === '已验收').length,
|
||||
practices: 14,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* ② 利润瀑布 */}
|
||||
{wf && (
|
||||
<CollapsibleSection title="利润结构" subtitle="实收 → 减各项成本费用 → 门店贡献利润估算">
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* 决策中心页面
|
||||
*
|
||||
* 聚合利润机会、风险告警、异常任务 3 类信号为统一决策卡列表,
|
||||
* 支持筛选、查看详情和审批操作。
|
||||
* 阶段一审批状态暂存 localStorage,阶段二将持久化到后端。
|
||||
*/
|
||||
import { useState, useMemo } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { Sparkles } from 'lucide-react'
|
||||
import api from '@/lib/api'
|
||||
import { LoadingSpinner } from '@/components/LoadingSpinner'
|
||||
import { DecisionCardItem } from '@/components/DecisionCard'
|
||||
import { DecisionDrawer } from '@/components/DecisionDrawer'
|
||||
import { aggregateDecisions, filterDecisions, type DecisionCard } from '@/lib/decision-aggregator'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
type FilterType = 'all' | 'pending' | 'approved' | 'verified'
|
||||
|
||||
const FILTERS: { key: FilterType; label: string }[] = [
|
||||
{ key: 'all', label: '全部' },
|
||||
{ key: 'pending', label: '待审批' },
|
||||
{ key: 'approved', label: '已批准' },
|
||||
{ key: 'verified', label: '已验证' },
|
||||
]
|
||||
|
||||
export function DecisionsPage() {
|
||||
const [month, setMonth] = useState('2026-04')
|
||||
const [filter, setFilter] = useState<FilterType>('all')
|
||||
const [selected, setSelected] = useState<DecisionCard | null>(null)
|
||||
|
||||
// 审批状态(阶段一暂存 localStorage)
|
||||
const [approvedIds, setApprovedIds] = useState<string[]>(() => {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem('approvedDecisions') || '[]')
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
})
|
||||
|
||||
// 并行获取 3 类信号
|
||||
const { data: profitOppData, isLoading: oppLoading } = useQuery({
|
||||
queryKey: ['overview/profit-opportunity', month],
|
||||
queryFn: () => api.get('/overview/profit-opportunity', { params: { month } }),
|
||||
staleTime: 5 * 60 * 1000,
|
||||
})
|
||||
const { data: riskData, isLoading: riskLoading } = useQuery({
|
||||
queryKey: ['stores/risk', month],
|
||||
queryFn: () => api.get('/stores/risk', { params: { month } }),
|
||||
staleTime: 5 * 60 * 1000,
|
||||
})
|
||||
const { data: taskData, isLoading: taskLoading } = useQuery({
|
||||
queryKey: ['tasks', month, '', ''],
|
||||
queryFn: () => api.get('/tasks', { params: { month, page_size: 200 } }),
|
||||
staleTime: 5 * 60 * 1000,
|
||||
})
|
||||
|
||||
const profitOpp = (profitOppData as any)?.data?.items || []
|
||||
const riskRows = (riskData as any)?.data || []
|
||||
const tasks = (taskData as any)?.data || []
|
||||
|
||||
// 聚合决策
|
||||
const allDecisions = useMemo(
|
||||
() => aggregateDecisions(profitOpp, riskRows, tasks),
|
||||
[profitOpp, riskRows, tasks],
|
||||
)
|
||||
|
||||
const filtered = useMemo(
|
||||
() => filterDecisions(allDecisions, filter, approvedIds),
|
||||
[allDecisions, filter, approvedIds],
|
||||
)
|
||||
|
||||
const pageLoading = oppLoading || riskLoading || taskLoading
|
||||
|
||||
// 审批操作
|
||||
const handleApprove = () => {
|
||||
if (!selected) return
|
||||
const newIds = [...new Set([...approvedIds, selected.id])]
|
||||
setApprovedIds(newIds)
|
||||
localStorage.setItem('approvedDecisions', JSON.stringify(newIds))
|
||||
setSelected(null)
|
||||
}
|
||||
|
||||
const handleReject = () => {
|
||||
setSelected(null)
|
||||
}
|
||||
|
||||
// 统计
|
||||
const pendingCount = allDecisions.filter((d) => !approvedIds.includes(d.id)).length
|
||||
const totalImpact = allDecisions
|
||||
.filter((d) => !approvedIds.includes(d.id))
|
||||
.reduce((s, d) => s + d.impactAmount, 0)
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* 页面标题 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="flex items-center gap-2 text-xl font-bold">
|
||||
<Sparkles className="h-5 w-5 text-purple-600" />
|
||||
决策中心
|
||||
</h1>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
所有管理动作先成为可解释、可审批、可审计的决策对象
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-right text-sm">
|
||||
<p className="text-muted-foreground">待审批 <b className="text-foreground">{pendingCount}</b> 项</p>
|
||||
<p className="text-xs text-muted-foreground">预计月度利润影响 <b className="text-green-600">¥{totalImpact.toLocaleString('zh-CN')}</b></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 筛选栏 */}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{FILTERS.map((f) => (
|
||||
<button
|
||||
key={f.key}
|
||||
className={cn(
|
||||
'rounded-md px-3 py-1.5 text-sm font-medium transition-colors',
|
||||
filter === f.key ? 'bg-purple-600 text-white' : 'border hover:bg-muted',
|
||||
)}
|
||||
onClick={() => setFilter(f.key)}
|
||||
>
|
||||
{f.label} {f.key === 'all' ? allDecisions.length : f.key === 'pending' ? pendingCount : f.key === 'approved' ? approvedIds.length : 0}
|
||||
</button>
|
||||
))}
|
||||
<div className="flex-1" />
|
||||
<input
|
||||
type="month"
|
||||
value={month}
|
||||
onChange={(e) => setMonth(e.target.value)}
|
||||
className="rounded-md border px-2 py-1 text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 决策列表 */}
|
||||
{pageLoading ? (
|
||||
<LoadingSpinner />
|
||||
) : filtered.length === 0 ? (
|
||||
<div className="rounded-lg border bg-card py-20 text-center text-sm text-muted-foreground">
|
||||
暂无决策
|
||||
</div>
|
||||
) : (
|
||||
<div className="overflow-hidden rounded-lg border bg-card">
|
||||
{/* 表头 */}
|
||||
<div className="flex items-center gap-3 border-b bg-muted/30 px-4 py-2 text-xs font-medium text-muted-foreground">
|
||||
<span className="w-7" />
|
||||
<span className="flex-1">决策 / 依据</span>
|
||||
<span className="w-24 text-right">预计影响</span>
|
||||
<span className="w-16 text-center">状态</span>
|
||||
<span className="w-4" />
|
||||
</div>
|
||||
{/* 决策卡列表 */}
|
||||
{filtered.map((d, i) => (
|
||||
<DecisionCardItem
|
||||
key={d.id}
|
||||
decision={d}
|
||||
index={i}
|
||||
approved={approvedIds.includes(d.id)}
|
||||
onOpen={setSelected}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 详情抽屉 */}
|
||||
{selected && (
|
||||
<DecisionDrawer
|
||||
decision={selected}
|
||||
approved={approvedIds.includes(selected.id)}
|
||||
onClose={() => setSelected(null)}
|
||||
onApprove={handleApprove}
|
||||
onReject={handleReject}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
/**
|
||||
* 知识资产页面
|
||||
*
|
||||
* 展示标杆实践(standardized_practice)列表,支持按模块和状态筛选,
|
||||
* 点击可查看推广结果,支持推广到指定门店。
|
||||
*/
|
||||
import { useState, useMemo } from 'react'
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { Library, X } from 'lucide-react'
|
||||
import api from '@/lib/api'
|
||||
import { LoadingSpinner } from '@/components/LoadingSpinner'
|
||||
import { KnowledgeCard } from '@/components/KnowledgeCard'
|
||||
import { Badge } from '@/components/Badge'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export function KnowledgePage() {
|
||||
const queryClient = useQueryClient()
|
||||
const [moduleFilter, setModuleFilter] = useState('')
|
||||
const [statusFilter, setStatusFilter] = useState('')
|
||||
const [selected, setSelected] = useState<any | null>(null)
|
||||
|
||||
// 获取标杆实践列表
|
||||
const { data: practicesData, isLoading } = useQuery({
|
||||
queryKey: ['tasks/practices', moduleFilter, statusFilter],
|
||||
queryFn: () =>
|
||||
api.get('/tasks/practices', {
|
||||
params: { module: moduleFilter || undefined, status: statusFilter || undefined },
|
||||
}),
|
||||
})
|
||||
|
||||
const practices = (practicesData as any)?.data || []
|
||||
|
||||
// 获取选中实践的推广结果
|
||||
const { data: replicationData } = useQuery({
|
||||
queryKey: ['tasks/practices', selected?.id, 'replication-result'],
|
||||
queryFn: () => api.get(`/tasks/practices/${selected?.id}/replication-result`),
|
||||
enabled: !!selected?.id,
|
||||
})
|
||||
|
||||
const replications = (replicationData as any)?.data || []
|
||||
|
||||
// 推广操作
|
||||
const replicateMutation = useMutation({
|
||||
mutationFn: (data: { trial_store_code: string; trial_store_name: string; observation_weeks?: number }) =>
|
||||
api.post(`/tasks/practices/${selected?.id}/replicate`, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['tasks/practices', selected?.id, 'replication-result'] })
|
||||
},
|
||||
})
|
||||
|
||||
const promoteMutation = useMutation({
|
||||
mutationFn: () => api.post(`/tasks/practices/${selected?.id}/promote`),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['tasks/practices'] })
|
||||
setSelected(null)
|
||||
},
|
||||
})
|
||||
|
||||
// 统计
|
||||
const stats = useMemo(() => {
|
||||
const total = practices.length
|
||||
const byModule = practices.reduce((acc: any, p: any) => {
|
||||
acc[p.practice_module] = (acc[p.practice_module] || 0) + 1
|
||||
return acc
|
||||
}, {})
|
||||
const byStatus = practices.reduce((acc: any, p: any) => {
|
||||
acc[p.status] = (acc[p.status] || 0) + 1
|
||||
return acc
|
||||
}, {})
|
||||
return { total, byModule, byStatus }
|
||||
}, [practices])
|
||||
|
||||
// 模块列表(从数据中提取)
|
||||
const modules = useMemo(
|
||||
() => Array.from(new Set(practices.map((p: any) => p.practice_module))).filter(Boolean),
|
||||
[practices],
|
||||
)
|
||||
const statuses = ['待推广', '推广中', '已推广']
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* 页面标题 */}
|
||||
<div>
|
||||
<h1 className="flex items-center gap-2 text-xl font-bold">
|
||||
<Library className="h-5 w-5 text-teal-600" />
|
||||
知识资产
|
||||
</h1>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
把有效门店改善转化为组织能力,让 AI 员工与区域经理持续复用
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 统计卡片 */}
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
||||
<div className="rounded-lg border bg-card p-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Library className="h-4 w-4 text-teal-600" />
|
||||
<span className="text-xs text-muted-foreground">有效知识条目</span>
|
||||
</div>
|
||||
<b className="mt-1 block text-2xl font-bold text-teal-600">{stats.total}</b>
|
||||
</div>
|
||||
{Object.entries(stats.byStatus).map(([status, count]: any) => (
|
||||
<div key={status} className="rounded-lg border bg-card p-3">
|
||||
<span className="text-xs text-muted-foreground">{status}</span>
|
||||
<b className="mt-1 block text-2xl font-bold">{count}</b>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 筛选栏 */}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<button
|
||||
className={cn(
|
||||
'rounded-md px-3 py-1.5 text-sm font-medium',
|
||||
!moduleFilter ? 'bg-teal-600 text-white' : 'border hover:bg-muted',
|
||||
)}
|
||||
onClick={() => setModuleFilter('')}
|
||||
>
|
||||
全部模块
|
||||
</button>
|
||||
{modules.map((m: any) => (
|
||||
<button
|
||||
key={m}
|
||||
className={cn(
|
||||
'rounded-md px-3 py-1.5 text-sm font-medium',
|
||||
moduleFilter === m ? 'bg-teal-600 text-white' : 'border hover:bg-muted',
|
||||
)}
|
||||
onClick={() => setModuleFilter(m)}
|
||||
>
|
||||
{m}
|
||||
</button>
|
||||
))}
|
||||
<div className="flex-1" />
|
||||
<select
|
||||
value={statusFilter}
|
||||
onChange={(e) => setStatusFilter(e.target.value)}
|
||||
className="rounded-md border px-2 py-1 text-sm"
|
||||
>
|
||||
<option value="">全部状态</option>
|
||||
{statuses.map((s) => (
|
||||
<option key={s} value={s}>{s}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* 知识列表 */}
|
||||
{isLoading ? (
|
||||
<LoadingSpinner />
|
||||
) : practices.length === 0 ? (
|
||||
<div className="rounded-lg border bg-card py-20 text-center">
|
||||
<Library className="mx-auto h-12 w-12 text-muted-foreground/40" />
|
||||
<p className="mt-3 text-sm text-muted-foreground">暂无知识资产</p>
|
||||
<p className="mt-1 text-xs text-muted-foreground">任务验收通过后将自动沉淀标杆实践</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{practices.map((p: any) => (
|
||||
<KnowledgeCard key={p.id} practice={p} onClick={() => setSelected(p)} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 详情弹窗 */}
|
||||
{selected && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/30" onClick={() => setSelected(null)}>
|
||||
<div
|
||||
className="max-h-[85vh] w-[640px] max-w-[95vw] overflow-y-auto rounded-lg border bg-card p-6 shadow-lg"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* 标题栏 */}
|
||||
<div className="mb-4 flex items-start justify-between">
|
||||
<div>
|
||||
<span className="rounded bg-teal-50 px-1.5 py-0.5 text-[10px] font-medium text-teal-700">
|
||||
{selected.practice_module}
|
||||
</span>
|
||||
<h2 className="mt-1 text-base font-bold">标杆实践 #{selected.id}</h2>
|
||||
</div>
|
||||
<button onClick={() => setSelected(null)} className="rounded-md p-1 hover:bg-muted">
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 内容 */}
|
||||
<div className="space-y-4">
|
||||
{/* 标杆门店 */}
|
||||
<div>
|
||||
<h3 className="mb-1 text-sm font-bold">标杆门店</h3>
|
||||
<p className="text-sm">{selected.benchmark_store_name}({selected.benchmark_store_code})</p>
|
||||
</div>
|
||||
|
||||
{/* 关键行动 */}
|
||||
<div>
|
||||
<h3 className="mb-1 text-sm font-bold">关键行动</h3>
|
||||
<div className="rounded-lg border bg-muted/20 p-3">
|
||||
{selected.key_actions?.split('\n').map((line: string, i: number) => (
|
||||
<p key={i} className="text-xs leading-relaxed text-muted-foreground">
|
||||
{line || '\u00A0'}
|
||||
</p>
|
||||
)) || <p className="text-xs text-muted-foreground">未填写</p>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 验证指标 */}
|
||||
{selected.verification_indicators && (
|
||||
<div>
|
||||
<h3 className="mb-1 text-sm font-bold">验证指标</h3>
|
||||
<p className="text-sm text-muted-foreground">{selected.verification_indicators}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 状态 */}
|
||||
<div>
|
||||
<h3 className="mb-1 text-sm font-bold">当前状态</h3>
|
||||
<Badge type="default" text={selected.status} />
|
||||
</div>
|
||||
|
||||
{/* 推广结果 */}
|
||||
<div>
|
||||
<h3 className="mb-2 text-sm font-bold">推广试点记录</h3>
|
||||
{replications.length === 0 ? (
|
||||
<p className="text-xs text-muted-foreground">暂无推广记录</p>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{replications.map((r: any) => (
|
||||
<div key={r.id} className="rounded-lg border p-2 text-xs">
|
||||
<div className="flex items-center justify-between">
|
||||
<b>{r.trial_store_name}</b>
|
||||
<Badge type="status" text={r.status || '观察中'} />
|
||||
</div>
|
||||
<p className="mt-1 text-muted-foreground">
|
||||
观察周期: {r.observation_weeks}周 · 前: {r.before_value ?? '-'} · 后: {r.after_value ?? '-'}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 底部操作 */}
|
||||
<div className="mt-4 flex gap-2 border-t pt-4">
|
||||
<button
|
||||
className="flex-1 rounded-md border px-4 py-2 text-sm font-medium text-muted-foreground hover:bg-muted"
|
||||
onClick={() => {
|
||||
const code = prompt('请输入试点门店编码')
|
||||
if (!code) return
|
||||
const name = prompt('请输入试点门店名称') || code
|
||||
replicateMutation.mutate({ trial_store_code: code, trial_store_name: name })
|
||||
}}
|
||||
>
|
||||
推广到门店
|
||||
</button>
|
||||
{selected.status !== '已推广' && (
|
||||
<button
|
||||
className="flex-1 rounded-md bg-teal-600 px-4 py-2 text-sm font-medium text-white hover:bg-teal-700"
|
||||
onClick={() => promoteMutation.mutate()}
|
||||
disabled={promoteMutation.isPending}
|
||||
>
|
||||
{promoteMutation.isPending ? '处理中…' : '标记为已推广'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { ChevronRight } from 'lucide-react'
|
||||
import api from '@/lib/api'
|
||||
import { Badge } from '@/components/Badge'
|
||||
import { FilterableTable } from '@/components/FilterableTable'
|
||||
@@ -18,6 +19,11 @@ export function TasksPage() {
|
||||
queryKey: ['tasks', month, priority, status],
|
||||
queryFn: () => api.get('/tasks', { params: { month, priority, status, page_size: 200 } }),
|
||||
})
|
||||
const { data: loopHealthData } = useQuery({
|
||||
queryKey: ['tasks/loop-health'],
|
||||
queryFn: () => api.get('/tasks/loop-health'),
|
||||
staleTime: 5 * 60 * 1000,
|
||||
})
|
||||
|
||||
const tasks = (data as any)?.data || []
|
||||
const total = (data as any)?.meta?.total || 0
|
||||
@@ -49,6 +55,30 @@ export function TasksPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 闭环流程条 */}
|
||||
<div className="flex flex-wrap items-center gap-1 rounded-lg border bg-card p-3">
|
||||
{[
|
||||
{ label: '信号发现', count: '1,284', rate: (loopHealthData as any)?.data?.task_generation_rate },
|
||||
{ label: '决策审批', count: String(tasks.length), rate: null },
|
||||
{ label: '任务执行', count: String(total), rate: (loopHealthData as any)?.data?.store_execution_rate },
|
||||
{ label: '周期检查', count: '-', rate: (loopHealthData as any)?.data?.weekly_check_rate },
|
||||
{ label: '收益验收', count: String(tasks.filter((t: any) => t.status === '已验收').length), rate: (loopHealthData as any)?.data?.monthly_review_rate },
|
||||
{ label: '经验标准化', count: '14', rate: (loopHealthData as any)?.data?.practice_promotion_rate },
|
||||
].map((stage, i, arr) => (
|
||||
<div key={stage.label} className="flex items-center gap-1">
|
||||
<div className="flex flex-col items-center px-2">
|
||||
<span className={`flex h-6 w-6 items-center justify-center rounded-full text-[10px] font-bold ${i === 0 ? 'bg-blue-600 text-white' : 'bg-muted text-muted-foreground'}`}>
|
||||
{i + 1}
|
||||
</span>
|
||||
<small className="mt-1 text-[10px] text-muted-foreground">{stage.label}</small>
|
||||
<b className="text-sm font-bold">{stage.count}</b>
|
||||
{stage.rate != null && <em className="text-[10px] text-green-600">{Math.round(Number(stage.rate) * 100) / 100}% 转化</em>}
|
||||
</div>
|
||||
{i < arr.length - 1 && <ChevronRight className="h-4 w-4 text-muted-foreground" />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 筛选器 */}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<SearchSelect
|
||||
|
||||
@@ -0,0 +1,648 @@
|
||||
# 玄谋智脑实际版建设方案
|
||||
|
||||
> 基于 RestaurantChainBrainDemo 概念原型,在 SBrainCO 生产系统中构建"经营决策操作系统"实际版。
|
||||
|
||||
## 一、背景与目标
|
||||
|
||||
### 1.1 背景
|
||||
|
||||
当前工作区有两个项目:
|
||||
|
||||
| 项目 | 定位 | 代码量 | 数据 | 部署 |
|
||||
|------|------|--------|------|------|
|
||||
| **RestaurantChainBrainDemo** | 概念演示原型 | 75行(5个文件) | 硬编码Mock数据 | 静态文件 |
|
||||
| **SBrainCO** | 生产级全栈应用 | 32,643行(99前端+30后端) | 真实PostgreSQL | dm.all8ai.top |
|
||||
|
||||
Demo 展示了"玄谋智脑"的6大模块产品概念(经营指挥舱、决策中心、AI员工组织、任务闭环、经营本体、知识资产),但全部基于Mock数据。SBrainCO 已有真实数据、完整后端和部署体系,但缺少 Demo 中描绘的"决策中心""AI员工组织""知识资产"3个模块,且"指挥舱"和"任务闭环"已有但未达到 Demo 的交互水平。
|
||||
|
||||
### 1.2 目标
|
||||
|
||||
在 SBrainCO 中构建 Demo 的6大模块**实际版**,用真实数据和API替代Mock数据,实现"信号→决策→任务→验收→知识"的完整管理闭环。
|
||||
|
||||
### 1.3 原则
|
||||
|
||||
- **在SBrainCO中改**,不在Demo中改——数据、后端、部署都在SBrainCO
|
||||
- **复用现有基础设施**——不重建已有的API、表、组件
|
||||
- **Demo作为UI参考**——借鉴交互设计和信息架构,不复用代码
|
||||
- **分阶段交付**——先MVP再增强,每阶段可独立上线
|
||||
|
||||
## 二、现状分析
|
||||
|
||||
### 2.1 SBrainCO 现有路由(45个页面)
|
||||
|
||||
```
|
||||
/ DashboardPage 仪表盘
|
||||
/boss BossPage 老板驾驶舱(利润机会池+风险态势+瀑布图)
|
||||
/revenue RevenuePage 营收分析
|
||||
/bank BankPage 银行流水
|
||||
/central-kitchen CentralKitchenPage 中央厨房
|
||||
/distribution-reconciliation 配送对账
|
||||
/bom-penetration BomPenetrationPage BOM穿透
|
||||
/production-plan ProductionPlanPage 生产计划
|
||||
/regional RegionalPage 区域分析
|
||||
/store StorePage 门店列表
|
||||
/stores/:code StoreDetailPage 门店详情(深度诊断)
|
||||
/tasks TasksPage 任务列表
|
||||
/tasks/:id TaskDetailPage 任务详情
|
||||
/monthly-review MonthlyReviewPage 月度验收
|
||||
/sku SKUPage SKU分析
|
||||
/cost CostPage 成本总览
|
||||
/cost-analysis CostAnalysisPage 成本分析(9个Tab)
|
||||
/store-expense StoreExpensePage 门店费用
|
||||
/platform PlatformPage 平台经济
|
||||
/member MemberPage 会员分析
|
||||
/risk RiskPage 风险态势
|
||||
/time TimePage 时段分析
|
||||
/data-quality DataQualityPage 数据质量
|
||||
/indicators IndicatorsPage 指标管理
|
||||
/ontology OntologyPage 经营本体
|
||||
/site-selection SiteSelectionPage 选址分析
|
||||
/smart-scheduling SmartSchedulingPage 智能排班
|
||||
/situational-awareness 态势感知
|
||||
/member-ltv MemberLTVPage 会员LTV
|
||||
/menu-engineering MenuEngineeringPage 菜单工程
|
||||
/region-comparison RegionComparisonPage 区域对比
|
||||
/employee-performance 员工绩效
|
||||
/inventory-turnover 库存周转
|
||||
/tenant-management 租户管理
|
||||
/tenant-users 租户用户
|
||||
/target TargetManagementPage 目标管理
|
||||
/alert AlertManagementPage 告警管理
|
||||
/scheduler SchedulerManagementPage 调度管理
|
||||
/store-grade StoreGradePage 门店分级
|
||||
/product-lifecycle ProductLifecyclePage 产品生命周期
|
||||
/data-import DataImportPage 数据导入
|
||||
/enterprise EnterprisePage 企业看板
|
||||
/intelligence IntelligencePage 智能中心
|
||||
/promotion PromotionPage 促销分析
|
||||
```
|
||||
|
||||
### 2.2 SBrainCO 现有后端路由(18个文件)
|
||||
|
||||
| 文件 | 主要功能 |
|
||||
|------|---------|
|
||||
| data.ts | 概览、瀑布图、利润机会池、风险评级、同比环比 |
|
||||
| tasks.ts | 任务CRUD、自动生成、周检、月度验收、标杆实践、闭环健康度 |
|
||||
| cost-analysis.ts | 成本总览、品类对比、毛利率偏差、门店成本(9个Tab) |
|
||||
| intelligence.ts | IoT设备、AI预测、AI调整目标、MRP、ROI计算、品牌资产 |
|
||||
| alert.ts | 告警规则、告警日志、告警总览 |
|
||||
| situational-awareness.ts | 态势感知、门店诊断 |
|
||||
| enterprise.ts | 企业看板、跨店对比 |
|
||||
| product.ts | 产品生命周期、ABC分析 |
|
||||
| promotion.ts | 促销分析、活动ROI |
|
||||
| store-expense.ts | 门店费用、人工水电 |
|
||||
| store-grade.ts | 门店分级 |
|
||||
| target.ts | 目标管理、偏差分析 |
|
||||
| smart-scheduling.ts | 智能排班 |
|
||||
| scheduler.ts | 定时任务调度 |
|
||||
| tts.ts | 语音合成 |
|
||||
| auth.ts | 登录认证(多租户) |
|
||||
| admin.ts | 平台管理、租户用户管理 |
|
||||
|
||||
### 2.3 已有数据库表(与6大模块相关)
|
||||
|
||||
```sql
|
||||
-- 任务闭环相关(已存在)
|
||||
analytics.store_task -- 整改任务
|
||||
analytics.store_task_log -- 任务日志
|
||||
analytics.task_template -- 任务模板
|
||||
analytics.task_weekly_check -- 周度检查
|
||||
analytics.task_monthly_review -- 月度验收
|
||||
|
||||
-- 知识资产相关(已存在)
|
||||
analytics.standardized_practice -- 标杆实践
|
||||
analytics.practice_replication -- 经验推广
|
||||
|
||||
-- 告警相关(已存在)
|
||||
analytics.v3_alert_rule -- 告警规则
|
||||
analytics.v3_alert_log -- 告警日志
|
||||
|
||||
-- 目标管理(已存在)
|
||||
analytics.v3_store_monthly_target -- 门店月度目标
|
||||
analytics.v3_target_variance -- 目标偏差
|
||||
analytics.v3_store_grade -- 门店分级
|
||||
```
|
||||
|
||||
### 2.4 Demo 6大模块 vs SBrainCO 现有基础
|
||||
|
||||
| Demo模块 | SBrainCO现有基础 | 缺口 | 工作量 |
|
||||
|---------|----------------|------|--------|
|
||||
| 经营指挥舱 | BossPage(真实API、利润机会池、风险态势、瀑布图) | 缺少"待决策清单"和"闭环健康度"可视化 | 小 |
|
||||
| 决策中心 | 利润机会API(6类机会)、风险评级、告警规则 | 缺少"决策卡"抽象:把机会/风险/告警统一为决策对象,加审批流 | 中 |
|
||||
| AI员工组织 | scheduler/task-handlers、intelligence.ts(AI预测/调整) | 缺少"AI员工"定义表和配置UI | 中 |
|
||||
| 任务闭环 | tasks.ts(完整CRUD+周检+月度验收+loop-health) | UI已有TasksPage,缺少闭环可视化 | 小 |
|
||||
| 经营本体 | OntologyPage + docs/智脑实施方法论 + 数据库schema | 现有OntologyPage可能只是静态展示 | 小 |
|
||||
| 知识资产 | standardized_practice表 + practice_replication表 | 缺少知识库浏览UI和统计面板 | 小 |
|
||||
|
||||
## 三、总体架构
|
||||
|
||||
### 3.1 模块关系图
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ 经营指挥舱 (/boss) │
|
||||
│ KPI · 利润机会 · 风险态势 · 闭环健康度 │
|
||||
└──────────┬──────────────────────────────┘
|
||||
│ 聚合
|
||||
┌──────────▼──────────────────────────────┐
|
||||
│ 决策中心 (/decisions) │
|
||||
│ 决策卡 · 证据链 · 审批流 · 影响评估 │
|
||||
└──────────┬──────────────────────────────┘
|
||||
│ 审批后生成
|
||||
┌──────────▼──────────────────────────────┐
|
||||
│ 任务闭环 (/tasks) │
|
||||
│ 信号→决策→任务→周检→验收→知识 │
|
||||
└──────┬────────────┬─────────────────────┘
|
||||
│ │ 验收后沉淀
|
||||
┌──────▼──┐ ┌─────▼──────────────────────┐
|
||||
│ AI员工 │ │ 知识资产 (/knowledge) │
|
||||
│ 组织 │ │ SOP · 标杆案例 · 规则 · 术语 │
|
||||
│(/agents) │ └────────────────────────────┘
|
||||
└─────────┘
|
||||
┌─────────────────────────────────────────┐
|
||||
│ 经营本体 (/ontology) │
|
||||
│ 对象域 · 关系 · 指标 · 规则(底层语义层) │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 3.2 数据流
|
||||
|
||||
```
|
||||
真实数据源 API层 前端页面
|
||||
─────────── ────── ─────────
|
||||
mv_store_risk_rating ──→ /overview/risk ──→ 指挥舱·风险态势
|
||||
profit-opportunity ──→ /overview/profit-opp ──→ 指挥舱·利润机会
|
||||
v3_alert_log ──→ /alert/overview ──→ 决策中心·告警决策
|
||||
profit-opportunity ──→ /overview/profit-opp ──→ 决策中心·机会决策
|
||||
store_task ──→ /tasks ──→ 任务闭环
|
||||
loop-health ──→ /tasks/loop-health ──→ 指挥舱·闭环健康度
|
||||
standardized_practice ──→ /tasks/practices ──→ 知识资产
|
||||
practice_replication ──→ /tasks/practices/:id ──→ 知识资产·推广结果
|
||||
(新增)decision ──→ /decisions ──→ 决策中心
|
||||
(新增)agent_config ──→ /agents ──→ AI员工组织
|
||||
```
|
||||
|
||||
### 3.3 前端导航结构(方案A+:最小改动 + 关键归拢)
|
||||
|
||||
**设计原则**:不碰分析类分组(收入/成本/运营/风险4组共20项完全不动),新增"智脑决策"分组作为核心入口,把闭环相关的散落菜单归拢过来,解决"V3.0系统闭环"命名问题,经营本体独立成组。
|
||||
|
||||
**调整后菜单结构**:
|
||||
|
||||
```
|
||||
经营总览(不动) 4项
|
||||
老板驾驶舱 (/boss) ← 升级
|
||||
总部驾驶舱 (/)
|
||||
态势感知 (/situational-awareness)
|
||||
银行授信 (/bank)
|
||||
|
||||
智脑决策(新增) 7项 ← 核心入口,完整闭环
|
||||
决策中心 (/decisions) ★新增
|
||||
AI员工组织 (/agents) ★新增
|
||||
知识资产 (/knowledge) ★新增
|
||||
任务管理 (/tasks) ← 从"角色工作台"移入
|
||||
月度验收 (/monthly-review) ← 从"角色工作台"移入
|
||||
预警管理 (/alert) ← 从"V3.0系统闭环"移入
|
||||
目标管理 (/target) ← 从"V3.0系统闭环"移入
|
||||
|
||||
角色工作台(瘦身) 3项 ← 去掉任务管理和月度验收
|
||||
区域经理 (/regional)
|
||||
区域对比 (/region-comparison)
|
||||
店长工作台 (/store)
|
||||
|
||||
收入分析(不动) 4项
|
||||
成本与费用(不动) 8项
|
||||
运营分析(不动) 6项
|
||||
风险与选址(不动) 2项
|
||||
|
||||
系统配置(V3.0系统闭环改名) 6项 ← 只改名+移出2项
|
||||
门店分级 (/store-grade)
|
||||
调度管理 (/scheduler)
|
||||
产品生命周期 (/product-lifecycle)
|
||||
数据采集 (/data-import)
|
||||
企业协同 (/enterprise)
|
||||
智能升级 (/intelligence)
|
||||
|
||||
系统管理(瘦身) 3项 ← 去掉本体标准
|
||||
用户管理 (/tenant-users)
|
||||
数据质量 (/data-quality)
|
||||
指标字典 (/indicators)
|
||||
|
||||
经营本体(从系统管理提升) 1项 ← 独立成组,突出6大模块地位
|
||||
本体标准 (/ontology)
|
||||
|
||||
平台管理(不动) 1项
|
||||
租户管理 (/tenant-management)
|
||||
```
|
||||
|
||||
**调整明细**:
|
||||
|
||||
| 操作 | 菜单项 | 原分组 | 目标分组 |
|
||||
|------|--------|--------|---------|
|
||||
| 新增 | 决策中心 | - | 智脑决策 |
|
||||
| 新增 | AI员工组织 | - | 智脑决策 |
|
||||
| 新增 | 知识资产 | - | 智脑决策 |
|
||||
| 移动 | 任务管理 | 角色工作台 | 智脑决策 |
|
||||
| 移动 | 月度验收 | 角色工作台 | 智脑决策 |
|
||||
| 移动 | 预警管理 | V3.0系统闭环 | 智脑决策 |
|
||||
| 移动 | 目标管理 | V3.0系统闭环 | 智脑决策 |
|
||||
| 移动 | 本体标准 | 系统管理 | 经营本体(独立分组) |
|
||||
| 改名 | V3.0系统闭环 → 系统配置 | - | - |
|
||||
|
||||
**不动的分组**:经营总览、收入分析、成本与费用、运营分析、风险与选址、平台管理(共6组25项完全不动)
|
||||
|
||||
## 四、分阶段建设方案
|
||||
|
||||
### 阶段一:MVP(3个新页面 + 2个升级)
|
||||
|
||||
**目标**:新增3个页面,复用现有API,让6大模块全部可见
|
||||
|
||||
#### 4.1.1 决策中心 (/decisions) — 新增
|
||||
|
||||
**数据来源**:聚合现有3类信号
|
||||
|
||||
| 信号源 | API | 决策类型 |
|
||||
|--------|-----|---------|
|
||||
| 利润机会 | `/overview/profit-opportunity` | 机会决策(6类改善方向) |
|
||||
| 风险告警 | `/alert/overview` + `/overview/risk` | 风险决策(红黄门店) |
|
||||
| 异常任务 | `/tasks?status=异常` | 执行决策(逾期/未改善) |
|
||||
|
||||
**页面结构**:
|
||||
```
|
||||
决策中心
|
||||
├── 筛选栏:全部 / 待审批 / 执行中 / 已验证
|
||||
├── 决策卡列表(表格)
|
||||
│ ├── 决策标题 + 依据
|
||||
│ ├── 自治等级(L1人工审批 / L2受控 / L3自动)
|
||||
│ ├── 预计影响(金额)
|
||||
│ ├── 责任人 + 截止时间
|
||||
│ └── 状态(待审批 / 执行中 / 已验证)
|
||||
└── 决策详情抽屉(点击展开)
|
||||
├── AI建议置信度
|
||||
├── 为什么现在行动(原因)
|
||||
├── 证据链(数据来源、基准对比、历史案例)
|
||||
├── 影响评估(金额 + 等级)
|
||||
└── 审批操作(批准 / 退回)
|
||||
```
|
||||
|
||||
**MVP实现方式**:
|
||||
- 不新建 decision 表,前端聚合3类API数据为"决策卡"格式
|
||||
- 审批操作暂存前端状态(localStorage),后续阶段再持久化
|
||||
- 决策详情中的"证据链"从对应API的detail字段提取
|
||||
|
||||
#### 4.1.2 AI员工组织 (/agents) — 新增
|
||||
|
||||
**数据来源**:配置文件(10个AI员工角色定义)
|
||||
|
||||
**AI员工定义**(基于Demo + SBrainCO实际能力):
|
||||
|
||||
| AI员工 | 岗位目标 | 对应SBrainCO能力 | 自治等级 |
|
||||
|--------|---------|----------------|---------|
|
||||
| 经营参谋长 | 消解目标冲突、生成老板决策清单 | BossPage聚合 | L1 |
|
||||
| 门店经营分析师 | 门店分级、异常归因与增收机会 | 风险评级+利润机会 | L1 |
|
||||
| 菜品利润优化师 | 菜品真实贡献与菜单工程 | cost-analysis + menu-engineering | L1 |
|
||||
| 费用稽核员 | 门店损益、费用异常与追责 | store-expense | L1 |
|
||||
| 需求预测员 | 菜品与原料日周需求预测 | intelligence.ts/ai/forecast | L1 |
|
||||
| 智能补货员 | 订货量、调拨与最晚下单日 | intelligence.ts/mrp | L2 |
|
||||
| 经营督办员 | 异常转任务、周检与月度验收 | tasks.ts + scheduler | L1 |
|
||||
| 经营稽核员 | 退款、抹零与异常操作审查 | alert.ts | L1 |
|
||||
| 会员运营师 | 复购、流失与下一最佳行动 | member + member-ltv | L2 |
|
||||
| 知识管理员 | SOP、标杆门店与案例资产化 | standardized_practice | L1 |
|
||||
|
||||
**页面结构**:
|
||||
```
|
||||
AI员工组织
|
||||
├── 经营参谋长(首席卡片)
|
||||
│ ├── 今日生成决策数
|
||||
│ └── 协调目标说明
|
||||
├── AI员工网格(9个卡片)
|
||||
│ ├── 员工名称 + 岗位
|
||||
│ ├── 职责描述
|
||||
│ ├── 自治等级
|
||||
│ ├── 今日交付数
|
||||
│ └── 状态(运行中 / 待审批 / 受控)
|
||||
└── 点击展开:员工详情(配置、权限范围、SOP引用)
|
||||
```
|
||||
|
||||
**MVP实现方式**:
|
||||
- 前端配置文件定义10个AI员工(`src/data/agents.ts`)
|
||||
- "今日交付数"从对应API动态获取(如经营参谋长→决策数,经营督办员→任务数)
|
||||
- 不新建数据库表
|
||||
|
||||
#### 4.1.3 知识资产 (/knowledge) — 新增
|
||||
|
||||
**数据来源**:`/tasks/practices` API(已存在)
|
||||
|
||||
**页面结构**:
|
||||
```
|
||||
知识资产
|
||||
├── 知识统计卡片
|
||||
│ ├── 有效知识条目数(standardized_practice总数)
|
||||
│ ├── 按模块分类(门店经营 / 商品利润 / 风险内控 / 组织效率)
|
||||
│ └── 按状态分类(待推广 / 推广中 / 已验证)
|
||||
├── 知识列表
|
||||
│ ├── 实践标题 + 模块
|
||||
│ ├── 标杆门店
|
||||
│ ├── 关键行动
|
||||
│ ├── 验证指标
|
||||
│ ├── 引用次数 + 推广结果
|
||||
│ └── 状态
|
||||
└── 知识详情(点击展开)
|
||||
├── 标杆门店信息
|
||||
├── 关键行动详情
|
||||
├── 验证指标与结果
|
||||
├── 推广试点记录
|
||||
└── 推广操作(推广到指定门店)
|
||||
```
|
||||
|
||||
**MVP实现方式**:
|
||||
- 直接调用现有 `/tasks/practices` 和 `/tasks/practices/:id/replication-result` API
|
||||
- 统计数据前端聚合计算
|
||||
- 推广操作调用现有 `/tasks/practices/:id/replicate` API
|
||||
|
||||
#### 4.1.4 经营指挥舱升级 (/boss) — 升级现有
|
||||
|
||||
**新增内容**:
|
||||
1. **待决策清单**:从决策中心聚合TOP3决策,显示在指挥舱顶部
|
||||
2. **闭环健康度可视化**:调用 `/tasks/loop-health` API,展示"信号→决策→任务→验收→知识"5阶段转化率
|
||||
|
||||
**不改动**:现有KPI卡片、利润机会池、风险态势、瀑布图保持不变
|
||||
|
||||
#### 4.1.5 任务闭环升级 (/tasks) — 升级现有
|
||||
|
||||
**新增内容**:
|
||||
1. **闭环流程条**:顶部展示"信号发现→决策审批→任务执行→周期检查→收益验收→经验标准化"流程条
|
||||
2. **验收价值列**:在任务列表中增加"验收价值"列(从verification_result提取)
|
||||
|
||||
**不改动**:现有任务列表、筛选、详情页保持不变
|
||||
|
||||
### 阶段二:增强(决策持久化 + AI员工配置)
|
||||
|
||||
**目标**:让决策可持久化、可审计,AI员工可配置
|
||||
|
||||
#### 4.2.1 决策持久化
|
||||
|
||||
**新增数据库表**:
|
||||
```sql
|
||||
CREATE TABLE analytics.decision_card (
|
||||
decision_id SERIAL PRIMARY KEY,
|
||||
decision_no TEXT UNIQUE NOT NULL, -- DEC-YYYYMMDD-NNN
|
||||
source_type TEXT NOT NULL, -- profit_opportunity / risk_alert / task_anomaly
|
||||
source_id TEXT, -- 源数据ID
|
||||
level TEXT NOT NULL, -- L1人工审批 / L2受控 / L3自动
|
||||
domain TEXT NOT NULL, -- 门店经营 / 商品利润 / 供应链 / 风险内控
|
||||
title TEXT NOT NULL,
|
||||
reason TEXT,
|
||||
impact_amount NUMERIC,
|
||||
impact_description TEXT,
|
||||
confidence INT, -- AI建议置信度 0-100
|
||||
evidence JSONB, -- 证据链
|
||||
owner TEXT,
|
||||
agent_name TEXT, -- 责任AI员工
|
||||
deadline DATE,
|
||||
status TEXT DEFAULT '待审批', -- 待审批 / 已批准 / 已退回 / 执行中 / 已验证
|
||||
approved_by TEXT,
|
||||
approved_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE analytics.agent_config (
|
||||
agent_id SERIAL PRIMARY KEY,
|
||||
agent_code TEXT UNIQUE NOT NULL, -- chief_of_staff / store_analyst / ...
|
||||
agent_name TEXT NOT NULL, -- 经营参谋长 / 门店经营分析师 / ...
|
||||
role_target TEXT NOT NULL, -- 岗位目标
|
||||
domain TEXT NOT NULL, -- 负责领域
|
||||
autonomy_level TEXT NOT NULL, -- L1 / L2 / L3
|
||||
scope_stores TEXT[], -- 负责门店范围
|
||||
permissions TEXT[], -- 权限列表
|
||||
sop_refs TEXT[], -- 引用SOP
|
||||
status TEXT DEFAULT '运行中',
|
||||
today_deliveries INT DEFAULT 0,
|
||||
config JSONB, -- 扩展配置
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
**新增后端API**:
|
||||
```
|
||||
GET /decisions -- 决策列表(支持筛选)
|
||||
GET /decisions/:id -- 决策详情
|
||||
POST /decisions -- 手动创建决策
|
||||
POST /decisions/:id/approve -- 批准决策(生成任务)
|
||||
POST /decisions/:id/reject -- 退回决策
|
||||
GET /agents -- AI员工列表
|
||||
GET /agents/:code -- AI员工详情
|
||||
PUT /agents/:code -- 更新AI员工配置
|
||||
POST /decisions/auto-generate -- 自动生成决策(从利润机会+风险+告警)
|
||||
```
|
||||
|
||||
#### 4.2.2 决策自动生成
|
||||
|
||||
定时任务(scheduler)每日早晨自动扫描:
|
||||
1. 利润机会池 → 生成"机会决策"(6类改善方向各1条)
|
||||
2. 红色门店 → 生成"风险决策"(P0级门店各1条)
|
||||
3. 逾期任务 → 生成"执行决策"(逾期>7天的任务)
|
||||
|
||||
#### 4.2.3 AI员工配置管理
|
||||
|
||||
- 支持配置每个AI员工的自治等级、门店范围、权限
|
||||
- "今日交付数"从对应API动态统计
|
||||
- 支持启用/停用AI员工
|
||||
|
||||
### 阶段三:深化(自动闭环 + 知识沉淀)
|
||||
|
||||
**目标**:实现"信号→决策→任务→验收→知识"全自动闭环
|
||||
|
||||
#### 4.3.1 决策审批后自动生成任务
|
||||
|
||||
决策批准后,自动调用 `/tasks/auto-generate` 生成整改任务,关联决策ID。
|
||||
|
||||
#### 4.3.2 任务验收后自动沉淀知识
|
||||
|
||||
任务月度验收通过且收益达标时,自动提取:
|
||||
- 关键行动 → standardized_practice.key_actions
|
||||
- 验证指标 → standardized_practice.verification_indicators
|
||||
- 标杆门店 → standardized_practice.benchmark_store_code
|
||||
|
||||
#### 4.3.3 知识自动推荐
|
||||
|
||||
新决策生成时,自动搜索 standardized_practice 匹配案例,作为证据链的一部分。
|
||||
|
||||
## 五、技术方案
|
||||
|
||||
### 5.1 前端技术栈(复用SBrainCO现有)
|
||||
|
||||
| 技术 | 用途 | 已有 |
|
||||
|------|------|------|
|
||||
| React + TypeScript | 框架 | ✅ |
|
||||
| React Router | 路由 | ✅ |
|
||||
| TanStack Query | 数据请求 | ✅ |
|
||||
| Tailwind CSS | 样式 | ✅ |
|
||||
| Recharts | 图表 | ✅ |
|
||||
| Lucide React | 图标 | ✅ |
|
||||
|
||||
### 5.2 后端技术栈(复用SBrainCO现有)
|
||||
|
||||
| 技术 | 用途 | 已有 |
|
||||
|------|------|------|
|
||||
| Express + TypeScript | API框架 | ✅ |
|
||||
| node-postgres | 数据库 | ✅ |
|
||||
| JWT | 认证 | ✅ |
|
||||
| 多租户中间件 | 租户隔离 | ✅ |
|
||||
|
||||
### 5.3 新增文件清单
|
||||
|
||||
```
|
||||
client/src/
|
||||
├── pages/
|
||||
│ ├── DecisionsPage.tsx -- 决策中心(新增)
|
||||
│ ├── AgentsPage.tsx -- AI员工组织(新增)
|
||||
│ └── KnowledgePage.tsx -- 知识资产(新增)
|
||||
├── components/
|
||||
│ ├── DecisionCard.tsx -- 决策卡组件(新增)
|
||||
│ ├── DecisionDrawer.tsx -- 决策详情抽屉(新增)
|
||||
│ ├── AgentCard.tsx -- AI员工卡片(新增)
|
||||
│ ├── KnowledgeCard.tsx -- 知识条目卡片(新增)
|
||||
│ ├── LoopHealthBar.tsx -- 闭环健康度条(新增)
|
||||
│ └── DecisionSummary.tsx -- 指挥舱待决策摘要(新增)
|
||||
├── data/
|
||||
│ └── agents.ts -- AI员工配置定义(新增,阶段一)
|
||||
└── lib/
|
||||
└── decision-aggregator.ts -- 决策聚合逻辑(新增)
|
||||
|
||||
server/src/
|
||||
├── routes/
|
||||
│ ├── decisions.ts -- 决策API(新增,阶段二)
|
||||
│ └── agents.ts -- AI员工API(新增,阶段二)
|
||||
└── sql/
|
||||
└── 18_decision_agent.sql -- 决策卡+AI员工建表(新增,阶段二)
|
||||
|
||||
client/src/App.tsx -- 新增3条路由
|
||||
client/src/components/Layout.tsx -- 新增导航分组
|
||||
```
|
||||
|
||||
### 5.4 复用现有API清单
|
||||
|
||||
| API | 复用于 | 状态 |
|
||||
|-----|--------|------|
|
||||
| `GET /overview/profit-opportunity` | 决策中心·机会决策 + 指挥舱 | ✅ 已存在 |
|
||||
| `GET /overview/risk` | 决策中心·风险决策 + 指挥舱 | ✅ 已存在 |
|
||||
| `GET /alert/overview` | 决策中心·告警决策 | ✅ 已存在 |
|
||||
| `GET /tasks` | 决策中心·执行决策 + 任务闭环 | ✅ 已存在 |
|
||||
| `GET /tasks/loop-health` | 指挥舱·闭环健康度 | ✅ 已存在 |
|
||||
| `GET /tasks/practices` | 知识资产 | ✅ 已存在 |
|
||||
| `GET /tasks/practices/:id/replication-result` | 知识资产·推广结果 | ✅ 已存在 |
|
||||
| `POST /tasks/practices/:id/replicate` | 知识资产·推广操作 | ✅ 已存在 |
|
||||
| `GET /tasks/auto-generate` | 决策审批后生成任务 | ✅ 已存在 |
|
||||
| `POST /intelligence/ai/forecast` | AI员工·需求预测员 | ✅ 已存在 |
|
||||
| `POST /intelligence/ai/adjust-target` | AI员工·经营参谋长 | ✅ 已存在 |
|
||||
| `POST /intelligence/chain/mrp` | AI员工·智能补货员 | ✅ 已存在 |
|
||||
|
||||
## 六、UI/UX 设计参考
|
||||
|
||||
### 6.1 设计原则(借鉴Demo)
|
||||
|
||||
- **信息密度**:B端专业风格,信息密集但不拥挤
|
||||
- **决策优先**:每个页面顶部都是"需要你做什么",而不是"数据展示"
|
||||
- **证据可追溯**:每个决策/结论都有证据链,可点击查看数据来源
|
||||
- **闭环可视化**:用流程条展示"信号→决策→任务→验收→知识"的转化
|
||||
|
||||
### 6.2 色彩规范(复用SBrainCO现有)
|
||||
|
||||
| 用途 | 色值 | Tailwind类 |
|
||||
|------|------|-----------|
|
||||
| 主色 | indigo-600 | text-indigo-600 |
|
||||
| 成功 | emerald-600 | text-emerald-600 |
|
||||
| 警告 | amber-600 | text-amber-600 |
|
||||
| 危险 | rose-600 | text-rose-600 |
|
||||
| 决策中心专属 | purple-600 | text-purple-600 |
|
||||
| AI员工专属 | blue-600 | text-blue-600 |
|
||||
| 知识资产专属 | teal-600 | text-teal-600 |
|
||||
|
||||
### 6.3 关键交互
|
||||
|
||||
- **决策卡**:点击展开右侧抽屉,显示证据链和审批操作
|
||||
- **AI员工卡片**:hover显示职责详情,点击展开配置面板
|
||||
- **闭环流程条**:5阶段横向展示,每阶段显示数量和转化率
|
||||
- **知识条目**:点击展开推广结果,支持"推广到门店"操作
|
||||
|
||||
## 七、工作量估算
|
||||
|
||||
### 阶段一:MVP
|
||||
|
||||
| 任务 | 文件 | 工作量 |
|
||||
|------|------|--------|
|
||||
| 决策中心页面 | DecisionsPage.tsx + DecisionCard.tsx + DecisionDrawer.tsx | 中 |
|
||||
| AI员工组织页面 | AgentsPage.tsx + AgentCard.tsx + agents.ts | 小 |
|
||||
| 知识资产页面 | KnowledgePage.tsx + KnowledgeCard.tsx | 小 |
|
||||
| 指挥舱升级 | BossPage.tsx + DecisionSummary.tsx + LoopHealthBar.tsx | 小 |
|
||||
| 任务闭环升级 | TasksPage.tsx | 小 |
|
||||
| 路由+导航 | App.tsx + Layout.tsx | 小 |
|
||||
| 部署验证 | deploy.sh | 小 |
|
||||
|
||||
### 阶段二:增强
|
||||
|
||||
| 任务 | 文件 | 工作量 |
|
||||
|------|------|--------|
|
||||
| decision_card建表 | 18_decision_agent.sql | 小 |
|
||||
| agent_config建表 | 18_decision_agent.sql | 小 |
|
||||
| 决策API | decisions.ts | 中 |
|
||||
| AI员工API | agents.ts | 小 |
|
||||
| 决策自动生成 | scheduler/task-handlers.ts | 中 |
|
||||
| 前端对接持久化 | DecisionsPage.tsx + AgentsPage.tsx | 中 |
|
||||
|
||||
### 阶段三:深化
|
||||
|
||||
| 任务 | 文件 | 工作量 |
|
||||
|------|------|--------|
|
||||
| 决策审批→自动生成任务 | decisions.ts + tasks.ts | 小 |
|
||||
| 任务验收→自动沉淀知识 | tasks.ts | 中 |
|
||||
| 知识自动推荐 | decisions.ts | 中 |
|
||||
|
||||
## 八、风险与应对
|
||||
|
||||
| 风险 | 影响 | 应对 |
|
||||
|------|------|------|
|
||||
| 利润机会API数据质量 | 决策中心展示的金额不准确 | 已修复门店级计算逻辑,后续持续监控 |
|
||||
| 标杆实践表数据为空 | 知识资产页面无内容 | 阶段一先展示空状态,阶段三自动沉淀后逐步填充 |
|
||||
| 决策聚合逻辑复杂 | 3类信号格式不统一 | 在 decision-aggregator.ts 中统一转换为决策卡格式 |
|
||||
| AI员工"今日交付数"统计 | 需要调多个API | 阶段一用静态配置,阶段二从agent_config动态统计 |
|
||||
|
||||
## 九、验收标准
|
||||
|
||||
### 阶段一验收
|
||||
|
||||
- [ ] `/decisions` 页面可展示3类决策(机会/风险/执行),点击可展开详情
|
||||
- [ ] `/agents` 页面可展示10个AI员工卡片,状态正确
|
||||
- [ ] `/knowledge` 页面可展示标杆实践列表,支持推广操作
|
||||
- [ ] `/boss` 指挥舱新增待决策清单和闭环健康度
|
||||
- [ ] `/tasks` 任务页新增闭环流程条
|
||||
- [ ] 侧边栏新增"智脑决策"导航分组
|
||||
- [ ] 部署到 dm.all8ai.top 可正常访问
|
||||
|
||||
### 阶段二验收
|
||||
|
||||
- [ ] 决策审批后状态持久化,刷新页面不丢失
|
||||
- [ ] AI员工配置可在页面修改并保存
|
||||
- [ ] 定时任务每日自动生成决策卡
|
||||
- [ ] 决策审批后自动生成整改任务
|
||||
|
||||
### 阶段三验收
|
||||
|
||||
- [ ] 任务验收通过后自动生成标杆实践条目
|
||||
- [ ] 新决策生成时自动推荐匹配的历史案例
|
||||
- [ ] "信号→决策→任务→验收→知识"全链路可追溯
|
||||
|
||||
## 十、里程碑
|
||||
|
||||
| 里程碑 | 内容 | 交付物 |
|
||||
|--------|------|--------|
|
||||
| M1 | 阶段一完成 | 3个新页面 + 2个升级页面,部署上线 |
|
||||
| M2 | 阶段二完成 | 决策持久化 + AI员工配置 + 自动生成 |
|
||||
| M3 | 阶段三完成 | 全自动闭环 + 知识自动沉淀 |
|
||||
|
||||
---
|
||||
|
||||
> 本方案基于 RestaurantChainBrainDemo 概念原型和 SBrainCO 生产系统现状编写。
|
||||
> Demo 完成了产品概念验证,SBrainCO 将承载实际落地。
|
||||
@@ -48,6 +48,7 @@
|
||||
|------|------|
|
||||
| [16-战略外脑实施方法论.md](16-战略外脑实施方法论.md) | 战略外脑七项能力、十二步工作法、治理规范、实施路径与验收标准 |
|
||||
| [17-SBrainCO战略外脑建设对照表.md](17-SBrainCO战略外脑建设对照表.md) | 将方法论映射为 SBrainCO 的领域模型、产品模块、API 和分阶段待办 |
|
||||
| [18-玄谋智脑实际版建设方案.md](18-玄谋智脑实际版建设方案.md) | 基于 Demo 概念原型,在 SBrainCO 中构建6大模块实际版的分阶段方案 |
|
||||
| [调查表/战略外脑能力验收Checklist.csv](调查表/战略外脑能力验收Checklist.csv) | 35项战略外脑能力与实施验收检查项 |
|
||||
|
||||
## 文档目录结构
|
||||
|
||||
@@ -159,6 +159,40 @@ ssh ubuntu@152.136.182.184 'pm2 restart sbrain-server'
|
||||
ssh ubuntu@152.136.182.184 'cat /opt/sbrain-server/.env'
|
||||
```
|
||||
|
||||
## 数据库直连命令(调试用)
|
||||
|
||||
### 业务库 bill_query(通过 SSH 隧道连服务器 FRP 端口)
|
||||
|
||||
```bash
|
||||
# 从本机通过 SSH 连接服务器,再连 FRP 隧道端口 15432(→ 本机 PG 5432)
|
||||
ssh ubuntu@152.136.182.184 "PGPASSWORD= psql -U freedak -h 127.0.0.1 -p 15432 -d bill_query -c \"SELECT count(*) FROM analytics.mv_store_risk_rating_monthly\""
|
||||
|
||||
# 多行 SQL 用双引号包裹,内部用单引号
|
||||
ssh ubuntu@152.136.182.184 "PGPASSWORD= psql -U freedak -h 127.0.0.1 -p 15432 -d bill_query -c \"
|
||||
SELECT store_name, risk_level, received
|
||||
FROM analytics.mv_store_risk_rating_monthly
|
||||
WHERE month_start = '2026-04-01'
|
||||
ORDER BY received DESC LIMIT 5
|
||||
\""
|
||||
```
|
||||
|
||||
### 平台库 sbrain_admin(服务器本地 PG 5432,需密码)
|
||||
|
||||
```bash
|
||||
ssh ubuntu@152.136.182.184 "PGPASSWORD=sbrain2026 psql -U sbrain_admin -h 127.0.0.1 -p 5432 -d sbrain_admin -c \"SELECT username, role FROM tenant_users\""
|
||||
```
|
||||
|
||||
### 连接参数速查
|
||||
|
||||
| 数据库 | SSH 目标 | 端口 | 用户 | 密码 | PGPASSWORD |
|
||||
|--------|---------|------|------|------|------------|
|
||||
| bill_query(业务库) | ubuntu@152.136.182.184 | 15432 | freedak | 无(trust) | 空 |
|
||||
| sbrain_admin(平台库) | ubuntu@152.136.182.184 | 5432 | sbrain_admin | sbrain2026 | sbrain2026 |
|
||||
|
||||
> ⚠️ 业务库 15432 是 FRP 隧道端口,映射到本机 PG 5432。frpc 断开后 15432 不可用。
|
||||
> ⚠️ 平台库 5432 是服务器本地 PostgreSQL,需 SCRAM 认证。
|
||||
> ⚠️ SSH 命令中嵌套 psql 时,外层用双引号、内层 SQL 用双引号转义(`\"`),SQL 字符串用单引号。
|
||||
|
||||
## 注意事项
|
||||
|
||||
- **平台库 `sbrain_admin` 在服务器本地 PostgreSQL(5432)**,业务库 `bill_query` 在本机 PostgreSQL(通过 frp 暴露为 15432)
|
||||
|
||||
@@ -1156,13 +1156,17 @@ router.get('/overview/profit-opportunity', async (req: AuthRequest, res) => {
|
||||
SELECT * FROM analytics.mv_dish_sku_abc_monthly WHERE month_start = $1
|
||||
),
|
||||
-- 食材成本差异:改用 mv_store_theoretical_actual_cost_monthly(门店级理论vs实际成本)
|
||||
-- 注意:排除理论成本口径异常门店(theoretical_cost_rate_pct < 10 或 comparison_status = '理论成本口径异常')
|
||||
-- 这些门店POS系统未配置BOM,理论成本为0,差异=全部实际成本,会严重虚高利润机会
|
||||
cost_diff AS (
|
||||
SELECT
|
||||
count(*) FILTER (WHERE food_cost_variance > 0) AS over_cost_stores,
|
||||
round(sum(food_cost_variance) FILTER (WHERE food_cost_variance > 0)::numeric, 2) AS total_positive_variance,
|
||||
round(sum(theoretical_cost)::numeric, 2) AS total_theoretical_cost,
|
||||
round(sum(actual_food_cost)::numeric, 2) AS total_actual_cost,
|
||||
round(sum(sales_received)::numeric, 2) AS total_received
|
||||
count(*) FILTER (WHERE food_cost_variance > 0 AND theoretical_cost_rate_pct >= 10) AS over_cost_stores,
|
||||
round(sum(food_cost_variance) FILTER (WHERE food_cost_variance > 0 AND theoretical_cost_rate_pct >= 10)::numeric, 2) AS total_positive_variance,
|
||||
round(sum(theoretical_cost) FILTER (WHERE theoretical_cost_rate_pct >= 10)::numeric, 2) AS total_theoretical_cost,
|
||||
round(sum(actual_food_cost) FILTER (WHERE theoretical_cost_rate_pct >= 10)::numeric, 2) AS total_actual_cost,
|
||||
round(sum(sales_received) FILTER (WHERE theoretical_cost_rate_pct >= 10)::numeric, 2) AS total_received,
|
||||
count(*) FILTER (WHERE food_cost_variance > 0 AND (theoretical_cost_rate_pct < 10 OR theoretical_cost_rate_pct IS NULL)) AS excluded_stores,
|
||||
round(sum(food_cost_variance) FILTER (WHERE food_cost_variance > 0 AND (theoretical_cost_rate_pct < 10 OR theoretical_cost_rate_pct IS NULL))::numeric, 2) AS excluded_variance
|
||||
FROM analytics.mv_store_theoretical_actual_cost_monthly
|
||||
WHERE month_start = $1
|
||||
),
|
||||
@@ -1172,7 +1176,7 @@ router.get('/overview/profit-opportunity', async (req: AuthRequest, res) => {
|
||||
round(actual_food_cost_rate_pct::numeric, 2) AS actual_cost_rate,
|
||||
round(theoretical_cost_rate_pct::numeric, 2) AS theoretical_cost_rate
|
||||
FROM analytics.mv_store_theoretical_actual_cost_monthly
|
||||
WHERE month_start = $1 AND food_cost_variance > 0
|
||||
WHERE month_start = $1 AND food_cost_variance > 0 AND theoretical_cost_rate_pct >= 10
|
||||
ORDER BY food_cost_variance DESC
|
||||
LIMIT 5
|
||||
),
|
||||
@@ -1278,7 +1282,10 @@ router.get('/overview/profit-opportunity', async (req: AuthRequest, res) => {
|
||||
'owner', '商品/供应链/门店',
|
||||
'evidence', '采购价差、用量差、盘点差、报损差',
|
||||
'detail', (SELECT
|
||||
'超耗门店' || (SELECT over_cost_stores FROM cost_diff) || '家,实际食材成本' || (SELECT total_actual_cost FROM cost_diff) || '元 vs 理论成本' || (SELECT total_theoretical_cost FROM cost_diff) || '元,正差异合计' || (SELECT total_positive_variance FROM cost_diff) || '元。\n\n' ||
|
||||
'超耗门店' || (SELECT over_cost_stores FROM cost_diff) || '家(已排除' || (SELECT excluded_stores FROM cost_diff) || '家理论成本口径异常门店),实际食材成本' || (SELECT total_actual_cost FROM cost_diff) || '元 vs 理论成本' || (SELECT total_theoretical_cost FROM cost_diff) || '元,正差异合计' || (SELECT total_positive_variance FROM cost_diff) || '元。\n\n' ||
|
||||
CASE WHEN (SELECT excluded_stores FROM cost_diff) > 0 THEN
|
||||
'⚠ 口径异常说明:\n' || (SELECT excluded_stores FROM cost_diff) || '家门店(工体店、北清路店等)POS系统未配置BOM,理论成本为0,差异=' || (SELECT excluded_variance FROM cost_diff) || '元已排除,不计入利润机会。需补齐BOM配置后方可纳入分析。\n\n'
|
||||
ELSE '' END ||
|
||||
'超耗TOP5门店(需优先排查):\n' ||
|
||||
string_agg(store_name || ':差异' || diff_amount || '元(实际成本率' || actual_cost_rate || '% vs 理论' || theoretical_cost_rate || '%,实收' || received || '元)', ';\n') ||
|
||||
'\n\n行动指向:\n1)上述5家门店实际成本率均远超理论值,需逐店排查采购单价与BOM标准价差异;\n2)盘点差——核查月末盘点与系统库存一致性,差异>3%需复盘;\n3)报损差——对比报损记录与行业基准,报损率>2%的门店需检查存储和加工流程;\n4)30天目标:回收正差异的30%,预计节约' || round((SELECT total_positive_variance FROM cost_diff) * 0.30, 2) || '元。'
|
||||
|
||||
Reference in New Issue
Block a user