# 客户分析与复购提醒实现方案

## 需求背景

| 模块 | 功能 | 需求说明 |
| --- | --- | --- |
| 客户分析 | 客户分析 | 将定期的度数、散光等客户档案数据做统计分析，根据客户消费历史时间提示二次复购 |

该需求面向眼镜业务客户经营，核心不是单纯做报表，而是把订单中的眼镜定制参数沉淀为客户长期档案，再基于客户历史消费周期和处方变化生成复购提醒。

## 设计原则

1. 客户主数据仍以 `customer` 为核心，不在 `customer` 表直接堆叠度数、散光等眼镜专业字段。
2. 历史订单中的定制参数必须保留快照，不因客户档案后续修改而改变。
3. 客户长期处方档案应标准化保存，便于查询、统计、趋势分析和提醒。
4. 复购提醒优先复用现有 `CustomerDynamic`、`SystemSuggestion` 能力，不新增一套独立提醒系统。
5. 第一阶段先覆盖眼镜行业，后续可扩展到其他行业客户档案分析。

## 现有基础

当前系统已有以下可复用能力：

| 能力 | 现有位置 | 用途 |
| --- | --- | --- |
| 客户主数据 | `customer` | 客户基础资料、负责人、联系方式、客户类型 |
| 客户动态数据 | `customer_dynamic` / `CustomerDynamic` | 下单次数、最近下单时间、平均下单周期、复购提醒 |
| 销售订单 | `sale_order` / `sale_order_detail` | 客户消费历史、购买时间、商品明细 |
| 定制履约单元 | `custom_order_item` | 每副眼镜或定制单元 |
| 定制参数快照 | `custom_order_item.params_snapshot` | 下单时完整眼镜参数 |
| 销售明细原始参数 | `sale_order_detail.extra` | Shopify line item 原始参数 |
| 系统建议 | `system_suggestion` | 客户跟进提醒、异常提示、业务建议 |

因此客户分析应建立在：

```text
sale_order / sale_order_detail
  -> custom_order_item.params_snapshot
  -> optical_prescription
  -> customer optical profile
  -> system_suggestion
```

## 核心数据模型

### optical_prescription

新增客户验光/处方档案表：`hg_optical_prescription`。

用于保存客户历史处方，支持从 Shopify 参数、定制履约参数、手工录入、导入等来源生成。

```sql
CREATE TABLE `hg_optical_prescription` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `company_id` char(18) NOT NULL COMMENT '公司ID',
  `customer_id` int(11) unsigned NOT NULL COMMENT '客户ID',
  `sale_order_id` int(11) unsigned DEFAULT NULL COMMENT '销售订单ID',
  `sale_order_detail_id` int(11) unsigned DEFAULT NULL COMMENT '销售订单明细ID',
  `custom_order_item_id` int(11) unsigned DEFAULT NULL COMMENT '定制履约单元ID',
  `source` varchar(32) NOT NULL DEFAULT 'manual' COMMENT '来源: shopify/manual/import/order',
  `prescription_time` datetime DEFAULT NULL COMMENT '处方时间',
  `od_sph` decimal(5,2) DEFAULT NULL COMMENT '右眼球镜',
  `od_cyl` decimal(5,2) DEFAULT NULL COMMENT '右眼柱镜/散光',
  `od_axis` smallint(3) unsigned DEFAULT NULL COMMENT '右眼轴位',
  `od_add` decimal(5,2) DEFAULT NULL COMMENT '右眼ADD',
  `os_sph` decimal(5,2) DEFAULT NULL COMMENT '左眼球镜',
  `os_cyl` decimal(5,2) DEFAULT NULL COMMENT '左眼柱镜/散光',
  `os_axis` smallint(3) unsigned DEFAULT NULL COMMENT '左眼轴位',
  `os_add` decimal(5,2) DEFAULT NULL COMMENT '左眼ADD',
  `pd` decimal(5,2) DEFAULT NULL COMMENT '瞳距',
  `pd_right` decimal(5,2) DEFAULT NULL COMMENT '右眼瞳距',
  `pd_left` decimal(5,2) DEFAULT NULL COMMENT '左眼瞳距',
  `prescription_type` varchar(64) DEFAULT NULL COMMENT '处方类型',
  `lens_group` varchar(100) DEFAULT NULL COMMENT '镜片组',
  `lens_option` varchar(100) DEFAULT NULL COMMENT '镜片选项',
  `snapshot` json DEFAULT NULL COMMENT '原始参数快照',
  `is_latest` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否最新处方',
  `status` tinyint(2) NOT NULL DEFAULT '1' COMMENT '状态',
  `create_admin_id` int(11) unsigned DEFAULT NULL,
  `edit_admin_id` int(11) unsigned DEFAULT NULL,
  `create_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_customer_latest` (`company_id`, `customer_id`, `is_latest`),
  KEY `idx_order` (`sale_order_id`, `sale_order_detail_id`),
  KEY `idx_custom_item` (`custom_order_item_id`),
  KEY `idx_prescription_time` (`prescription_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户眼镜处方档案';
```

### customer_optical_profile

建议新增客户眼镜画像聚合表：`hg_customer_optical_profile`。

该表不是必须，但建议保留。原因是客户列表、客户详情、复购提醒都需要快速读取分析结果，如果每次实时解析历史订单 JSON，会影响性能，也不利于固化分析结论。

```sql
CREATE TABLE `hg_customer_optical_profile` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `company_id` char(18) NOT NULL COMMENT '公司ID',
  `customer_id` int(11) unsigned NOT NULL COMMENT '客户ID',
  `latest_prescription_id` int(11) unsigned DEFAULT NULL COMMENT '最新处方ID',
  `latest_prescription_time` datetime DEFAULT NULL COMMENT '最新处方时间',
  `last_optical_order_time` datetime DEFAULT NULL COMMENT '最近眼镜订单时间',
  `optical_order_count` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '眼镜订单次数',
  `avg_repurchase_cycle_days` int(11) unsigned DEFAULT NULL COMMENT '平均复购周期',
  `next_repurchase_date` date DEFAULT NULL COMMENT '预计下次复购日期',
  `repurchase_status` tinyint(2) NOT NULL DEFAULT '0' COMMENT '复购状态: 0正常 1临近 2逾期',
  `prescription_change_level` varchar(32) DEFAULT NULL COMMENT '处方变化等级',
  `analysis_result` json DEFAULT NULL COMMENT '分析结果',
  `create_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uni_customer` (`company_id`, `customer_id`),
  KEY `idx_repurchase` (`company_id`, `repurchase_status`, `next_repurchase_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户眼镜画像分析';
```

## 参数抽取服务

新增服务：

```text
App\Service\Common\OpticalPrescriptionExtractService
```

职责：

1. 从 `custom_order_item.params_snapshot` 抽取处方参数。
2. 从 `sale_order_detail.extra` 抽取 Shopify 原始处方参数。
3. 将不同来源字段标准化为统一结构。
4. 写入 `hg_optical_prescription`。
5. 更新同客户最新处方标记。
6. 触发客户眼镜画像刷新。

标准化字段映射：

| Shopify/快照字段 | 标准字段 |
| --- | --- |
| `od.sphere` / `right.sphere` | `od_sph` |
| `od.cylinder` / `right.cylinder` | `od_cyl` |
| `od.axis` / `right.axis` | `od_axis` |
| `od.add` / `right.add` | `od_add` |
| `os.sphere` / `left.sphere` | `os_sph` |
| `os.cylinder` / `left.cylinder` | `os_cyl` |
| `os.axis` / `left.axis` | `os_axis` |
| `os.add` / `left.add` | `os_add` |
| `pd` | `pd` |
| `pdRight` | `pd_right` |
| `pdLeft` | `pd_left` |
| `prescriptionType` | `prescription_type` |
| `lensGroup` | `lens_group` |
| `lensOption` | `lens_option` |

## 触发时机

处方抽取应在以下节点触发：

1. Shopify 订单同步完成，`sale_order_detail.extra` 写入后。
2. `custom_order_item` 新增后。
3. `custom_order_item.params_snapshot` 编辑后。
4. 后台手工新增或编辑客户处方后。
5. 定时补偿任务扫描历史订单。

建议增加补偿命令：

```text
php bin/hyperf.php optical:prescription:extract
```

支持参数：

```text
--company_id=
--customer_id=
--sale_order_id=
--start_date=
--end_date=
```

## 客户眼镜画像分析

新增服务：

```text
App\Service\Common\CustomerOpticalAnalysisService
```

分析维度：

| 指标 | 说明 |
| --- | --- |
| 最新处方 | 客户最近一次处方档案 |
| 历史处方数量 | 客户累计处方记录数 |
| 眼镜订单次数 | 客户历史眼镜订单次数 |
| 最近眼镜订单时间 | 最近一次眼镜订单的 `order_time` |
| 平均复购周期 | 多次购买之间的平均间隔天数 |
| 预计复购日期 | 最近购买时间 + 平均复购周期或默认周期 |
| 球镜变化 | 对比最近两次 SPH |
| 散光变化 | 对比最近两次 CYL |
| ADD 变化 | 对比最近两次 ADD |
| 轴位变化 | 对比最近两次 AXIS |
| 常购镜片类型 | 统计 `lens_group` / `lens_option` |

处方变化等级：

```text
stable         基本稳定
changed        有变化
large_changed 变化较大，需要人工关注
```

建议阈值配置化：

| 配置项 | 默认值 |
| --- | --- |
| `optical_sph_change_threshold` | `0.50` |
| `optical_cyl_change_threshold` | `0.50` |
| `optical_add_change_threshold` | `0.50` |
| `optical_axis_change_threshold` | `10` |
| `optical_default_repurchase_days` | `365` |
| `optical_repurchase_remind_before_days` | `30` |
| `optical_repurchase_overdue_days` | `0` |

## 复购提醒规则

优先复用 `SystemSuggestion`。

提醒事件：

```text
optical_repurchase_reminder
optical_prescription_changed
```

### 复购日期计算

1. 客户有两次及以上眼镜订单：

```text
next_repurchase_date = last_optical_order_time + avg_repurchase_cycle_days
```

2. 客户只有一次眼镜订单：

```text
next_repurchase_date = last_optical_order_time + company_config.optical_default_repurchase_days
```

3. 客户没有眼镜订单但有处方档案：

```text
暂不生成复购提醒，只生成客户档案分析
```

### 提醒状态

```text
0 正常
1 临近复购
2 已逾期
```

判断规则：

```text
临近复购：今天 >= next_repurchase_date - remind_before_days
已逾期：今天 > next_repurchase_date + overdue_days
```

### 提醒内容

示例：

```text
客户距上次购买眼镜已 350 天，预计 15 天后进入复购周期，建议跟进。
```

```text
客户已超过预计复购时间 20 天，上次购买时间为 2025-07-01，建议及时跟进。
```

```text
客户最近处方较上次变化较大，球镜变化 0.75D，建议人工确认并跟进复购。
```

### 去重策略

1. 同一客户同一事件只保留一条有效 `system_suggestion`。
2. 每日定时任务只更新内容和时间，不重复插入。
3. 已处理提醒是否重新打开应由业务配置决定。

## 后台页面设计

### 客户详情页

增加客户分析区块：

```text
眼镜档案
最近处方
历史处方
度数变化趋势
消费周期
预计复购时间
复购提醒状态
```

### 客户列表

增加筛选：

```text
repurchase_due=1
repurchase_overdue=1
prescription_changed=1
latest_prescription_time_start
latest_prescription_time_end
last_optical_order_time_start
last_optical_order_time_end
```

增加展示字段：

```text
最近眼镜订单时间
预计复购日期
复购状态
最新处方时间
处方变化等级
```

### 客户分析页面

可作为客户详情的扩展页：

```text
GET /admin/customer/optical-analysis?id={customer_id}
```

返回：

```json
{
  "profile": {},
  "latest_prescription": {},
  "prescription_history": [],
  "order_history": [],
  "suggestions": []
}
```

## 接口建议

处方档案接口：

```text
GET  /admin/optical-prescription/index?customer_id={customer_id}
GET  /admin/optical-prescription/detail?id={id}
POST /admin/optical-prescription/add
POST /admin/optical-prescription/edit
POST /admin/optical-prescription/delete
```

客户分析接口：

```text
GET  /admin/customer/optical-analysis?id={customer_id}
POST /admin/customer/refresh-optical-analysis
```

客户列表沿用现有客户接口，通过参数筛选：

```text
GET /admin/customer/index?repurchase_due=1
GET /admin/customer/index?prescription_changed=1
```

## 定时任务

### 每日分析任务

每天执行：

```text
1. 扫描有眼镜订单或处方档案的客户
2. 刷新 customer_optical_profile
3. 判断复购状态
4. 写入或更新 system_suggestion
```

建议执行时间：

```text
0 8 * * *
```

### 历史数据补偿任务

用于上线后批量抽取历史订单中的处方数据。

```text
1. 查询 sale_order_detail.extra
2. 查询 custom_order_item.params_snapshot
3. 解析处方
4. 写入 optical_prescription
5. 刷新客户画像
```

## 实施步骤

### 第一阶段：处方档案落地

1. 新增 `hg_optical_prescription` 表。
2. 新增 `OpticalPrescription` Model。
3. 新增处方抽取服务。
4. 从 `custom_order_item.params_snapshot` 抽取处方。
5. 从 `sale_order_detail.extra` 抽取 Shopify 原始处方。
6. 客户详情展示历史处方。

### 第二阶段：客户眼镜画像

1. 新增 `hg_customer_optical_profile` 表。
2. 新增 `CustomerOpticalProfile` Model。
3. 新增 `CustomerOpticalAnalysisService`。
4. 统计最近处方、历史变化、眼镜订单次数、平均复购周期。
5. 客户列表支持复购状态和处方变化筛选。

### 第三阶段：复购提醒

1. 接入 `SystemSuggestion`。
2. 每日定时生成复购提醒。
3. 处方变化较大时生成跟进建议。
4. 客户详情页展示提醒。

### 第四阶段：分析报表

1. 按客户来源统计复购到期客户数。
2. 按镜片类型统计复购周期。
3. 按负责人统计待跟进客户数。
4. 输出客户复购转化效果。

## 风险与注意事项

1. Shopify 参数格式可能不稳定，解析服务必须兼容多种字段命名。
2. 历史订单参数应只作为快照，不应被客户档案修改覆盖。
3. 手工修改处方档案时，需要重新计算客户画像。
4. 提醒不能无限重复生成，应使用事件去重。
5. 复购周期不宜一开始做复杂预测，先用历史平均周期和默认周期即可。
6. 处方属于敏感客户档案，应沿用客户权限控制，普通员工只能查看自己可见客户的处方分析。

## 结论

客户分析需求应拆成两个核心能力：

1. 客户眼镜处方档案标准化。
2. 基于消费周期和处方变化的复购提醒。

第一阶段优先把 `custom_order_item.params_snapshot` 和 `sale_order_detail.extra` 中的度数、散光、轴位、瞳距等数据沉淀到 `optical_prescription`，再通过聚合分析生成客户画像和复购提醒。这样既能复用现有客户、订单、定制履约和系统建议能力，也能避免把客户分析做成难维护的实时 JSON 报表。
