- HANDOVER.md: 完整交接文档(架构/数据模型/API/业务流/已知坑/工作区状态/待办) - PRD-智能比价助手.md: 比价功能产品需求文档(供外部平台参考) - 数据维护脚本: 清理AI垃圾品类SQL、修复损坏标题SQL、线索重分类/重抓脚本
402 lines
13 KiB
Markdown
402 lines
13 KiB
Markdown
# 智能比价助手 - 产品需求文档 (PRD)
|
||
|
||
**版本**: v1.0
|
||
**日期**: 2025-08
|
||
**适用平台**: 参考设计文档,供其他平台二次开发/移植使用
|
||
**源项目**: 平台助手 - 智能比价助手模块
|
||
|
||
---
|
||
|
||
## 1. 产品概述
|
||
|
||
### 1.1 产品定位
|
||
**智能比价助手** 是面向采购场景的多供应商报价自动对齐与智能决策工具。核心价值:**将非结构化的供应商报价文件(Excel/CSV)转化为结构化可比价数据,通过算法自动生成最优采购方案(拆分/集中/限制供应商数),输出可执行的采购建议报告。**
|
||
|
||
### 1.2 核心用户场景
|
||
| 场景 | 痛点 | 解决方案 |
|
||
|------|------|----------|
|
||
| 采购员收到多家供应商报价单 | 格式不一、人工对齐耗时、易漏项 | 拖拽上传 → 自动解析 → 以商品名+规格自动对齐 |
|
||
| 不知道找哪家供应商最便宜 | 需人工逐行对比、易漏最优 | 自动计算每SKU最优/次优供应商、价差、降本率 |
|
||
| 纠结是集中采购还是拆分采购 | 缺量化依据、靠经验拍脑袋 | 三大策略对比:集中采购 / 无限制拆分 / 限制K家供应商 |
|
||
| 需向领导汇报采购方案 | 缺专业报告、数据支撑弱 | 一键生成含策略对比、SKU明细、供应商画像、降本测算的结构化报告 |
|
||
|
||
---
|
||
|
||
## 2. 系统架构
|
||
|
||
### 2.1 技术栈
|
||
| 层 | 技术选型 |
|
||
|----|----------|
|
||
| 后端框架 | NestJS 10 + TypeScript |
|
||
| 数据库 | SQLite (Prisma ORM) / 可替换为 PostgreSQL/MySQL |
|
||
| 文件解析 | Python 脚本 `parse_products.py` (pandas/openpyxl) |
|
||
| 前端框架 | Next.js 14 App Router + React 18 + Tailwind CSS 3 |
|
||
| 状态管理 | React useState + useEffect (SPA 无路由模式) |
|
||
| 文件上传 | Multer + FormData 拖拽上传 |
|
||
|
||
### 2.2 模块依赖
|
||
```
|
||
ComparisonModule
|
||
├── ComparisonController (REST API)
|
||
├── ComparisonService (核心业务逻辑)
|
||
├── PrismaModule (数据库)
|
||
└── (无外部服务依赖)
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 数据模型
|
||
|
||
### 3.1 核心实体:Product (供应商报价条目)
|
||
```prisma
|
||
model Product {
|
||
id String @id @default(uuid())
|
||
name String // 商品名称(去重键核心字段)
|
||
spec String // 规格型号
|
||
unit String // 单位(个/吨/米/卷...)
|
||
price Float // 单价
|
||
quantity Int // 数量/采购量
|
||
category String // 品类(用于筛选/统计)
|
||
brand String? // 品牌
|
||
supplier String // 供应商名称(去重键辅助字段)
|
||
minOrder Int? // 起订量
|
||
imageUrl String? // 图片URL
|
||
status String @default("draft") // draft/published
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
}
|
||
```
|
||
|
||
### 3.2 聚合视图:ComparisonItem (比价分析输出)
|
||
```typescript
|
||
interface ComparisonItem {
|
||
productName: string; // 商品名(去重后)
|
||
spec: string; // 规格
|
||
category: string; // 品类
|
||
quantity: number; // 采购量(取该商品所有报价的最大值)
|
||
suppliers: SupplierInfo[]; // 去重后的供应商报价列表
|
||
bestPrice: number; // 最低价
|
||
bestSupplier: string; // 最优供应商名
|
||
hasSupplier: boolean; // 是否有有效供应商报价
|
||
savings: number; // 平均价 - 最优价(单价维度)
|
||
priceSpread: number; // 最高价 - 最低价(价差)
|
||
}
|
||
|
||
interface SupplierInfo {
|
||
id: string;
|
||
name: string;
|
||
price: number;
|
||
unit: string;
|
||
spec: string;
|
||
brand?: string;
|
||
minOrder?: number;
|
||
imageUrl?: string;
|
||
}
|
||
```
|
||
|
||
### 3.3 采购建议报告
|
||
```typescript
|
||
interface SuggestionReport {
|
||
// 核心汇总
|
||
totalItems: number; // 参与比价的SKU数
|
||
totalSplitCost: number; // 无限制拆分总价
|
||
savingsVsSingle: number; // 拆分 vs 单一供应商节省金额
|
||
savingsPctVsSingle: number; // 综合降本比例
|
||
bestSingleSupplier: { name, total }; // 单一供应商最优方案
|
||
|
||
// 策略维度
|
||
supplierRanking: { name, total, skuCount }[]; // 单一供应商总价排名
|
||
splitPicks: SplitPick[]; // 无限制拆分时每SKU最优分配
|
||
splitByCount: { // 限制K家供应商时的最优组合
|
||
2: StrategyResult;
|
||
3: StrategyResult;
|
||
4: StrategyResult;
|
||
};
|
||
|
||
// 可执行建议
|
||
recommendations: string[]; // 自然语言建议列表
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 核心 API 设计
|
||
|
||
### 4.1 REST Endpoints
|
||
|
||
| 方法 | 路径 | 说明 | 请求/响应 |
|
||
|------|------|------|-----------|
|
||
| GET | `/api/comparison` | 获取所有商品分组比价数据 | 返回 `ComparisonItem[]` |
|
||
| POST | `/api/comparison/upload` | 上传供应商报价文件 | multipart/form-data: file + supplier |
|
||
| POST | `/api/comparison/suggestion` | 生成采购建议报告 | 返回 `SuggestionReport` |
|
||
|
||
### 4.2 请求/响应示例
|
||
|
||
**上传报价**
|
||
```bash
|
||
curl -X POST /api/comparison/upload \
|
||
-F "file=@quote.xlsx" \
|
||
-F "supplier=远东电缆"
|
||
```
|
||
```json
|
||
{ "count": 45, "supplier": "远东电缆" }
|
||
```
|
||
|
||
**获取建议**
|
||
```bash
|
||
curl -X POST /api/comparison/suggestion
|
||
```
|
||
```json
|
||
{
|
||
"totalItems": 12,
|
||
"totalSplitCost": 125600.00,
|
||
"savingsVsSingle": 18500.50,
|
||
"savingsPctVsSingle": 12.8,
|
||
"bestSingleSupplier": { "name": "远东电缆", "total": 144100.50 },
|
||
"supplierRanking": [
|
||
{ "name": "远东电缆", "total": 144100.50, "skuCount": 12 },
|
||
{ "name": "宝胜电缆", "total": 152300.00, "skuCount": 10 }
|
||
],
|
||
"splitPicks": [...],
|
||
"splitByCount": { "2": {...}, "3": {...}, "4": {...} },
|
||
"recommendations": [
|
||
"本次比价共涉及 12 个SKU,2 家供应商参与报价。",
|
||
"按SKU拆分下单比统一从「远东电缆」采购可再节省 ¥18500.50(降本 12.8%)。"
|
||
]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 核心算法逻辑
|
||
|
||
### 5.1 数据聚合与去重 (`getComparisonData`)
|
||
|
||
```typescript
|
||
// 核心步骤:
|
||
// 1. 读取所有 Product 记录
|
||
// 2. 按 name.trim().toLowerCase() 分组(商品名去重)
|
||
// 3. 组内按 supplier 去重 → 同一供应商保留最低价
|
||
// 4. 计算每组统计指标:
|
||
// - bestPrice / bestSupplier
|
||
// - savings = avgPrice - bestPrice
|
||
// - priceSpread = maxPrice - minPrice
|
||
```
|
||
|
||
**去重策略**:
|
||
- **商品级**:`name` 忽略大小写/首尾空格分组
|
||
- **供应商级**:组内按 `supplier` 去重,保留最低价
|
||
- **数量取最大值**:`quantity = max(所有报价的 quantity)`
|
||
|
||
### 5.2 无限制拆分策略 (`getSuggestion` → splitPicks)
|
||
|
||
每个 SKU 独立选择最低总价供应商:
|
||
```
|
||
对每个 SKU:
|
||
1. 计算各供应商总价 = price * quantity
|
||
2. 选总价最低者为最优
|
||
3. 次优 = 总价第二低者
|
||
4. 单品节省 = 次优总价 - 最优总价
|
||
```
|
||
|
||
**输出**:`splitPicks[]` + `totalSplitCost` + `supplierSplitCounts`(各供应商中标SKU数)
|
||
|
||
### 5.3 单一供应商策略 (`supplierRanking`)
|
||
|
||
```
|
||
对每个供应商:
|
||
总价 = Σ(该供应商所有SKU的 price * quantity)
|
||
SKU数 = 该供应商有报价的SKU数
|
||
按总价升序排序 → bestSingleSupplier = 第1名
|
||
```
|
||
|
||
### 5.4 限制 K 家供应商策略 (`splitByCount[K]`)
|
||
|
||
**组合枚举 + 贪心分配**:
|
||
|
||
```
|
||
输入:所有供应商名单 allNames,目标数量 K (2/3/4)
|
||
对于 allNames 中所有 C(n, K) 种组合:
|
||
对每个 SKU:
|
||
从组合中选价格最低的供应商
|
||
累加总价
|
||
记录总价最低的组合
|
||
```
|
||
|
||
**复杂度**:C(n, K) × m (SKU数)。n≤10, K≤4 时可接受(C(10,4)=210)。
|
||
|
||
**输出**:
|
||
- `bestSuppliers`: 实际使用的供应商列表(去除组合中未中标者)
|
||
- `totalCost`: 最优总价
|
||
- `assignments[]`: 每SKU的分配明细
|
||
- `supplierBreakdown`: 各供应商中标SKU数/金额占比
|
||
|
||
### 5.3 采购建议生成 (`recommendations`)
|
||
|
||
基于规则引擎生成自然语言建议:
|
||
1. **概况**:SKU数、供应商数
|
||
2. **拆分 vs 集中**:若 `savingsVsSingle > 0` → 推荐拆分 + 具体金额/比例
|
||
3. **优势供应商**:`supplierSplitCounts` Top 3
|
||
4. **最大价差SKU**:`splitPicks` 中 savings 最大者
|
||
5. **风险提示**:SKU>10 时提示交付协调风险
|
||
|
||
---
|
||
|
||
## 6. 前端交互流程
|
||
|
||
### 6.1 三步向导式交互
|
||
|
||
```
|
||
Step 1: 上传报价 (upload)
|
||
├── 输入供应商名称
|
||
├── 拖拽/点击上传 .xlsx/.xls/.csv
|
||
├── Python脚本解析 → 入库
|
||
└── 显示已上传供应商列表 + 报价条数
|
||
|
||
Step 2: 比价分析 (compare)
|
||
├── 表格展示:商品名 | 规格 | 数量 | 各供应商单价 | 最优价 | 价差
|
||
├── 最优价绿色高亮,缺报显示 "-"
|
||
├── 支持按价格/名称/节省金额排序
|
||
└── 无供应商对比数据单独展示
|
||
|
||
Step 3: 采购建议 (suggestion)
|
||
├── Hero汇总卡:拆分总价 / 节省金额 / 降本率
|
||
├── 策略对比卡:
|
||
│ ├── A方案:单一供应商(含供应商总价排名表)
|
||
│ └── B方案:拆分采购(含K家供应商选择器 K=2/3/4)
|
||
│ ├── 动态条形图:供应商中标SKU占比
|
||
│ └── 明细表:每SKU分配供应商/单价/数量/总价
|
||
├── SKU明细表:推荐供应商/单价/总价/次优总价/节省/降本率
|
||
├── 采购建议列表(自然语言)
|
||
└── 导出 CSV 报告
|
||
```
|
||
|
||
### 6.2 关键交互细节
|
||
|
||
| 交互 | 实现细节 |
|
||
|------|----------|
|
||
| 文件上传 | 拖拽区域 + 点击选择,支持 .xlsx/.xls/.csv,显示上传进度 |
|
||
| 供应商名称 | 必填校验,作为报价归属标识 |
|
||
| K值选择器 | 2/3/4 三按钮,单选互斥,支持取消选中回退无限制方案 |
|
||
| 表格高亮 | 最优价格绿色背景+粗体,价差红色显示 |
|
||
| 空状态处理 | 无数据/无对比数据分别展示占位提示 |
|
||
| 导出CSV | BOM头防乱码,含完整SKU明细列 |
|
||
|
||
---
|
||
|
||
## 7. 文件解析规范
|
||
|
||
### 7.1 支持格式
|
||
- `.xlsx` / `.xls` (Excel)
|
||
- `.csv` (逗号分隔)
|
||
|
||
### 7.2 必需列映射 (parse_products.py)
|
||
| 标准列名 | 别名/兼容 | 说明 |
|
||
|----------|-----------|------|
|
||
| `title` / `name` | 商品名称、产品名称 | 必填,作为去重主键 |
|
||
| `spec` | 规格、型号 | 可选 |
|
||
| `unit` | 单位 | 默认 "个" |
|
||
| `price` | 单价、价格 | 必填,数值型 |
|
||
| `quantity` | 数量、采购量 | 默认 1 |
|
||
| `category` | 品类、分类 | 默认 "未分类" |
|
||
| `brand` | 品牌 | 可选 |
|
||
| `minOrder` | 起订量 | 可选 |
|
||
|
||
### 7.3 解析脚本接口
|
||
```python
|
||
# scripts/parse_products.py
|
||
# 输入: 文件路径
|
||
# 输出: JSON 数组到 stdout
|
||
# 格式: [{"title": "...", "spec": "...", "price": 100, ...}, ...]
|
||
# 错误: 输出 {"error": "错误信息"} 到 stdout
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 部署与运维
|
||
|
||
### 8.1 环境变量
|
||
```env
|
||
DATABASE_URL=file:./dev.db # SQLite文件路径
|
||
# 或 PostgreSQL: postgresql://user:pass@host:5432/db
|
||
```
|
||
|
||
### 8.2 启动命令
|
||
```bash
|
||
# 开发
|
||
npm run dev # 同时启动前端(3001) + 后端(4000)
|
||
npm run dev:api # 仅后端
|
||
npm run dev:web # 仅前端
|
||
|
||
# 生产部署
|
||
npm run build
|
||
bash scripts/deploy.sh # 一键构建+rsync+PM2重启
|
||
```
|
||
|
||
### 8.3 生产架构
|
||
```
|
||
Nginx (80/443)
|
||
├── /api/* → NestJS:4000
|
||
├── /uploads/* → 静态文件 /data/pingtai/uploads/
|
||
└── /* → Next.js:3001
|
||
PM2 管理: pingtai-api, pingtai-web
|
||
```
|
||
|
||
---
|
||
|
||
## 9. 扩展指南
|
||
|
||
### 9.1 新增策略维度
|
||
在 `ComparisonService.getSuggestion()` 中扩展:
|
||
1. 新增 `splitByCount[K]` 的 K 值
|
||
2. 或新增约束维度(如:按品牌集中、按地区集中、按最小起订量过滤)
|
||
3. 在 `recommendations` 中增加对应建议文案
|
||
|
||
### 9.2 多租户隔离
|
||
当前单租户设计,扩展多租户需:
|
||
- `Product` 增加 `tenantId` 字段 + 复合索引
|
||
- API 增加租户上下文中间件
|
||
- `getComparisonData` / `getSuggestion` 加 `where: { tenantId }`
|
||
|
||
### 9.3 报价有效期管理
|
||
- `Product` 增加 `validFrom` / `validTo` 字段
|
||
- 查询时过滤 `validTo >= now()`
|
||
- 前端增加"报价有效期"筛选器
|
||
|
||
### 9.4 供应商画像扩展
|
||
- 新增 `Supplier` 实体:资质、交付周期、历史履约率、质量评分
|
||
- 建议算法引入权重:`综合得分 = 价格权重*0.6 + 交付权重*0.2 + 质量权重*0.2`
|
||
|
||
---
|
||
|
||
## 10. 附录:关键代码路径速查
|
||
|
||
| 功能 | 文件路径 |
|
||
|------|----------|
|
||
| 比价数据聚合 | `apps/api/src/comparison/comparison.service.ts:12-69` |
|
||
| 采购建议核心算法 | `apps/api/src/comparison/comparison.service.ts:131-314` |
|
||
| K家供应商组合优化 | `apps/api/src/comparison/comparison.service.ts:254-314` |
|
||
| 报价文件上传解析 | `apps/api/src/comparison/comparison.service.ts:71-129` |
|
||
| Python解析脚本 | `scripts/parse_products.py` |
|
||
| 前端三步向导主页 | `apps/web/src/components/ComparisonPage.tsx` |
|
||
| 比价表格渲染 | `ComparisonPage.tsx:256-320` |
|
||
| 采购建议报告UI | `ComparisonPage.tsx:327-623` |
|
||
| 导出CSV逻辑 | `ComparisonPage.tsx:606-617` |
|
||
|
||
---
|
||
|
||
## 11. 迁移清单 (给目标平台)
|
||
|
||
- [ ] 复刻 `Product` 数据模型到目标数据库
|
||
- [ ] 移植 `parse_products.py` 适配目标平台的文件存储
|
||
- [ ] 实现 `ComparisonService` 核心类(去重、聚合、三大策略算法)
|
||
- [ ] 暴露 3 个 REST API 端点
|
||
- [ ] 实现前端三步向导页面(上传/比价/建议)
|
||
- [ ] 集成文件上传组件 + 进度反馈
|
||
- [ ] 实现 CSV 导出功能
|
||
- [ ] 补充单元测试:去重逻辑、K组合枚举、边界情况(单供应商、无报价SKU)
|
||
- [ ] 压力测试:1000 SKU × 10 供应商 × K=4 组合枚举性能
|
||
|
||
---
|
||
|
||
**文档结束** — 如有疑问可参考源码实现细节。建议先跑通核心算法单元测试,再集成前后端。 |