Files
SBrainCO/docs/智脑实施方法论/07-步骤六-AI协同后端API构建与验证.md

214 lines
8.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 07 · 步骤六:AI协同后端API构建与验证
> 目标:AI参与API开发,逐个验证——快速交付能用的代码,不追求完美架构。 `[Delta主导,AI为主]`
## 1. 指标分层设计
```
L0: 原始数据层 (bill_records, salary_detail_records, attendance_records)
L1: 预聚合层 (物化视图 mv_*)
L2: API层 (后端路由,SQL查询 + 业务逻辑)
L3: 展示层 (前端页面,图表 + 表格)
```
## 2. 页面与API总览
| 页面 | 文件 | 调用的 API |
|------|------|-----------|
| 总部驾驶舱 | DashboardPage.tsx | /overview, /overview/daily, /stores/risk, /stores/priority, /tasks/loop-health, /stores/quadrant, /platform/economics, /situational-awareness/alerts, /store-expense/overview, /overview/yoy, /overview/mom, /overview/trend, /overview/profit-trend |
| 老板驾驶舱 | BossPage.tsx | /overview, /overview/daily, /store-expense/overview, /overview/profit-waterfall, /stores/risk, /stores/priority, /cost-analysis/store-overview, /store-expense/expense-structure, /overview/store-profit-ranking, /overview/profit-opportunity |
| 门店工作台 | StorePage.tsx | /stores/risk, /stores/priority, /tasks/stores/:code/daily-card, /tasks, /stores/:code/daily, /tasks/followup, /sku/attach, /sku/abc, /stores/:code, /situational-awareness/health-score, /stores/:code/meal-period, /stores/:code/category-mix, /stores/:code/cost, /stores/:code/member, /stores/:code/anomalies, /stores/:code/staffing |
| 成本分析 | CostPage.tsx | /cost/comparison, /cost/inventory, /cost/category-benchmark |
| 成本分析(Tab) | cost-analysis/*.tsx | /cost-analysis/overview, /category-comparison, /margin-deviation, /variance-top, /menu-engineering, /profitability, /pricing, /store-overview, /store-ranking, /bom-*, /packaging-*, /material-*, /data-quality, /unmatched-* |
| 费用分析(Tab) | store-expense/*.tsx | /store-expense/overview, /expense-structure, /store-ranking, /store-contribution, /break-even, /loss-diagnosis, /delivery-commission, /rent-risk, /fixed-variable, /efficiency, /store-evaluation |
| 风险监控 | RiskPage.tsx | /risk/anomaly, /risk/zero-received, /risk/cashier |
| 态势感知 | SituationalAwarenessPage.tsx | /situational-awareness/health-score, /alerts, /correlation, /forecast |
| 会员复购 | MemberPage.tsx | /member/comparison, /member/repeat |
| SKU分析 | SKUPage.tsx | /sku/abc, /sku/category |
| 菜单工程 | MenuEngineeringPage.tsx | /analytics-enhanced/menu-engineering/actions |
| 产品生命周期 | ProductLifecyclePage.tsx | /product/products, /product/reviews/overview, /product/reviews |
| BOM穿透 | BomPenetrationPage.tsx | /central-kitchen/bom-penetration |
| 分销对账 | DistributionReconciliationPage.tsx | /distribution/reconciliation |
| 生产计划 | ProductionPlanPage.tsx | /sales-driven/production-plan |
| 任务闭环 | TasksPage.tsx | /tasks |
| 数据导入 | DataImportPage.tsx | /product/import-logs, /product/quality-rules |
| 本体浏览 | OntologyPage.tsx | /tasks/ontology/* |
> 详细的逐页面→API→SQL→数据源字段映射,见 [08-数据溯源与API全量清单.md](08-数据溯源与API全量清单.md)
## 3. 后端API规范
### 3.1 路由组织
```
server/src/routes/
├── data.ts # 核心数据API(概览、门店、风险明细等)
├── situational-awareness.ts # 态势感知(健康度、预警、关联分析)
├── smart-scheduling.ts # 智能排班
├── cost-analysis.ts # 成本分析
├── store-expense.ts # 门店费用
├── analytics-enhanced.ts # 增强分析
├── admin.ts # 管理
└── tts.ts # 语音
```
### 2.2 通用模式
```typescript
// 月份参数解析
const month = parseMonth(req) // 从 query 参数获取,默认 '2026-04'
// 分页
const { page, pageSize, offset } = parsePagination(req)
// 数据权限
const scope = getDataScope(req) // 返回 store_names 数组或 null(全部)
// 响应格式
sendSuccess(res, data, meta?) // { success: true, data, meta }
sendError(res, message) // { success: false, error: message }
```
### 2.3 SQL查询模式
**安全参数化**
```typescript
// 正确:参数化查询
const result = await query(`SELECT * FROM mv_risk_anomaly WHERE month = to_char($1::date, 'YYYY-MM')`, [month])
// 错误:字符串拼接
const result = await query(`SELECT * FROM mv_risk_anomaly WHERE month = '${month}'`)
```
**动态条件构建**
```typescript
const conditions: string[] = [`month = to_char($1::date, 'YYYY-MM')`]
const params: any[] = [month]
let paramIdx = 2
if (storeName) {
conditions.push(`store_name = $${paramIdx}`)
params.push(storeName)
paramIdx++
}
const whereClause = conditions.join(' AND ')
```
**聚合汇总+分页**
```typescript
// 先查总数和汇总
const countResult = await query(`SELECT count(*) AS total, sum(consumption) AS sum_consumption FROM mv_risk_anomaly WHERE ${whereClause}`, params)
// 再查明细
const result = await query(`SELECT * FROM mv_risk_anomaly WHERE ${whereClause} ORDER BY ${sortCol} ${order} LIMIT $${paramIdx} OFFSET $${paramIdx + 1}`, [...params, pageSize, offset])
```
## 3. 常见SQL陷阱与修复
### 3.1 Schema前缀错误
**症状**`relation "analytics.mv_channel_daily" does not exist`
**排查**
```sql
SELECT schemaname, matviewname FROM pg_matviews WHERE matviewname LIKE '%channel%';
```
**修复**:确认实际schema,移除或修正前缀
```typescript
// 错误:FROM analytics.mv_channel_daily
// 正确:FROM mv_channel_daily (实际在public schema)
```
### 3.2 列名不存在
**症状**`column "risk_score" does not exist`
**排查**
```sql
SELECT column_name FROM information_schema.columns WHERE table_name = 'mv_store_risk_rating_monthly';
```
**修复策略**
- 策略A:改用 `SELECT *`(当不需要特定列名时)
- 策略B:用 `0 AS column_name` 替代不存在的列(当前端期望该字段时)
- 策略C:修正为正确的列名
### 3.3 日期格式不匹配
**症状**:薪资相关查询返回0条
**排查**
```sql
SELECT DISTINCT salary_period FROM salary_detail_records ORDER BY 1 DESC LIMIT 5;
-- 结果:2026年4月(非2026-04
```
**修复**
```typescript
// 错误:s.salary_month = to_char($1::date, 'YYYY-MM')
// 正确:s.salary_period = to_char($1::date, 'YYYY"年"FMMM"月"')
```
### 3.4 物化视图stale
**症状**API返回数据与直接查原始表不一致
**排查**:对比物化视图和原始表的count
```sql
SELECT count(*) FROM mv_risk_anomaly WHERE month = '2026-04';
SELECT count(*) FROM bill_records WHERE to_char(c176::timestamp, 'YYYY-MM') = '2026-04' AND ...;
```
**修复**:重建物化视图
```sql
DROP MATERIALIZED VIEW mv_risk_anomaly;
CREATE MATERIALIZED VIEW mv_risk_anomaly AS ...;
CREATE INDEX idx_mv_risk_anomaly_month ON mv_risk_anomaly(month);
```
## 4. 考勤打卡数据解析
### 4.1 department路径解析门店名
```
北京西部马华餐饮有限公司/西部马华品牌门店/胡庆鹏区/HQP2区/双安店/前厅/服务组
```
**提取逻辑**(从后往前找以"店"结尾的层级):
```typescript
function extractStore(dept: string): string {
const parts = dept.split('/')
for (let i = parts.length - 1; i >= 0; i--) {
if (parts[i].endsWith('店')) return parts[i]
}
return ''
}
```
### 4.2 打卡时间解析
格式:`08:43(考勤机:指纹)\n21:23(考勤机:指纹)`
```typescript
function parseClockTimes(raw: string): { start: number; end: number } | null {
const times = raw.match(/(\d{1,2}):(\d{2})/g)
if (!times || times.length < 2) return null
const startHour = parseInt(times[0].match(/(\d{1,2}):/)[1])
let endHour = parseInt(times[times.length - 1].match(/(\d{1,2}):/)[1])
if (endHour < startHour) endHour = 23 // 跨天
return { start: startHour, end: endHour }
}
```
### 4.3 日均在岗人数估算
```typescript
// 每个员工每天打卡算1人在岗,按小时累计
// 月度汇总后除以30天得到日均同时在岗人数
staffSummary[store][hour] = Math.round(totalPersonHours[store][hour] / 30)
```
**关键点**:客流数据 `mv_bill_hourly.bills` 也是月度汇总,展示时需除以30天对齐。