Files
SBrainCO/docs/智脑实施方法论/03-指标体系与API开发.md
T
freedakgmail c2a9e27e49 docs: 新增智脑实施方法论文档体系(7篇) + fix: 态势感知关联分析修复
文档:
- 01-项目概述与架构: 技术架构、数据流、部署拓扑
- 02-数据接入与治理: 原始数据导入、物化视图、门店名映射
- 03-指标体系与API开发: 指标分层、SQL模式、常见陷阱
- 04-前端页面开发: 组件规范、页面模板、月份参数管理
- 05-部署与运维: 部署脚本、FRP隧道、PM2、备份
- 06-调试排查手册: 问题分类、8个实际案例、工具速查
- 07-通用方法论: 核心原则、实施阶段、快速复制Checklist

修复:
- situational-awareness.ts: salary_month→salary_period, 日期格式改中文
- 客流-人力匹配: 改用attendance_records打卡数据解析在岗人数
- 客流数据除以30天对齐日均
2026-08-12 11:00:54 +08:00

187 lines
5.3 KiB
Markdown
Raw 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.
# 03 · 指标体系与API开发
## 1. 指标分层设计
```
L0: 原始数据层 (bill_records, salary_detail_records, attendance_records)
L1: 预聚合层 (物化视图 mv_*)
L2: API层 (后端路由,SQL查询 + 业务逻辑)
L3: 展示层 (前端页面,图表 + 表格)
```
## 2. 后端API规范
### 2.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天对齐。