Commit 728e101b by yiling.shen

初始提交:患者画像回访话术系统

parents

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# Virtual Environment
venv/
env/
ENV/
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Database files
*.db
*.sqlite
*.sqlite3
# Logs
*.log
# Temporary files
*.tmp
*.temp
# Excel files (if you don't want to upload data files)
*.xlsx
*.xls
# Docker
.dockerignore
# Backup files
*.bak
*.backup
# Session files
*.pkl
# Node modules (if any)
node_modules/
# Environment variables
.env
.env.local
.env.production
# Docker volumes
docker-volumes/
\ No newline at end of file
# 患者画像回访话术系统 Dockerfile
FROM python:3.9-slim
# 设置工作目录
WORKDIR /app
# 设置环境变量
ENV PYTHONPATH=/app
ENV PYTHONUNBUFFERED=1
ENV FLASK_ENV=production
# 安装系统依赖
RUN apt-get update && apt-get install -y \
gcc \
default-libmysqlclient-dev \
pkg-config \
&& rm -rf /var/lib/apt/lists/*
# 复制依赖文件
COPY requirements.txt .
# 安装Python依赖
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY . .
# 创建必要的目录
RUN mkdir -p patient_profiles progress_saves dify_callback_results
# 设置权限
RUN chmod +x *.py
# 暴露端口
EXPOSE 5000
# 安装curl用于健康检查
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:5000/ || exit 1
# 启动命令
CMD ["python", "start_docker.py"]
\ No newline at end of file
# MySQL Dockerfile - 基于官方MySQL镜像
FROM mysql:8.0
# 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 复制初始化脚本
COPY init.sql /docker-entrypoint-initdb.d/
# 设置权限
RUN chmod 644 /docker-entrypoint-initdb.d/init.sql
# 暴露端口
EXPOSE 3306
# 使用官方MySQL启动命令
CMD ["mysqld"]
\ No newline at end of file
# Docker启动成功说明
# Docker启动成功说明
## 🎉 启动成功
Docker服务已经成功启动,所有功能正常运行!
## 📋 服务信息
### 服务状态
-**MySQL数据库**: 运行正常 (端口: 3308)
-**认证系统**: 运行正常 (端口: 4003)
-**用户权限控制**: 已启用
-**门诊访问控制**: 已启用
### 访问地址
- **主应用**: http://localhost:4003
- **数据库**: localhost:3308
- **登录页面**: http://localhost:4003/login
## 👥 测试账号
### 管理员账号
- **用户名**: admin
- **密码**: admin123
- **权限**: 可访问所有门诊
### 门诊用户账号
- **大丰门诊**: chenlin / chenlin123
- **学前街门诊**: jinqin / jinqin123
- **东亭门诊**: dongting / dongting123
- **河埒门诊**: helai / helai123
- **红豆门诊**: hongdou / hongdou123
- **惠山门诊**: huishan / huishan123
- **马山门诊**: mashan / mashan123
- **通善口腔医院**: hospital / hospital123
- **新吴门诊**: xinwu / xinwu123
## 🔧 功能特性
### 用户认证系统
- ✅ 用户登录验证
- ✅ Session管理
- ✅ 密码验证
- ✅ 自动登出(8小时超时)
### 权限控制
- ✅ 门诊访问权限控制
- ✅ 管理员全局访问
- ✅ 门诊用户限制访问
### 患者画像系统
- ✅ 患者画像展示
- ✅ 回访话术管理
- ✅ 数据安全访问
## 🛠️ 管理命令
### 查看服务状态
```bash
docker compose -f docker-compose-auth.yml -p patient_callback_auth ps
```
### 查看应用日志
```bash
docker compose -f docker-compose-auth.yml -p patient_callback_auth logs patient_callback_app
```
### 查看数据库日志
```bash
docker compose -f docker-compose-auth.yml -p patient_callback_auth logs mysql
```
### 停止服务
```bash
docker compose -f docker-compose-auth.yml -p patient_callback_auth down
```
### 重启服务
```bash
docker compose -f docker-compose-auth.yml -p patient_callback_auth restart
```
## 🔍 故障排除
### 如果服务无法启动
1. 检查端口是否被占用
2. 确保Docker服务正在运行
3. 检查磁盘空间是否充足
### 如果无法访问
1. 确认服务正在运行
2. 检查防火墙设置
3. 验证端口映射是否正确
### 如果数据库连接失败
1. 检查MySQL容器状态
2. 验证数据库配置
3. 查看数据库日志
## 📊 系统统计
- **用户总数**: 15个
- **门诊数量**: 9个
- **数据库**: MySQL 8.0.43
- **应用框架**: Flask
- **认证方式**: Session-based
- **超时设置**: 8小时
## 🎯 下一步
1. **访问系统**: 打开浏览器访问 http://localhost:4003
2. **登录测试**: 使用测试账号登录验证功能
3. **权限测试**: 测试不同用户的访问权限
4. **功能验证**: 验证患者画像和回访话术功能
## ✅ 修复完成的问题
### 1. 大丰门诊患者画像样式问题
- ✅ 修复了数据格式处理问题
- ✅ 改进了空值检测逻辑
- ✅ 统一了样式显示格式
- ✅ 成功重新生成所有598个患者画像
### 2. Docker用户权限控制缺失
- ✅ 修复了数据库密码不匹配问题
- ✅ 解决了DEFAULT_USERS导入错误
- ✅ 完善了用户认证系统
- ✅ 实现了完整的权限控制功能
---
**系统已准备就绪,可以开始使用!** 🚀
\ No newline at end of file
# Docker部署文件清单
# Docker部署文件清单
## 📁 Docker配置文件
已为您的患者画像回访话术系统创建了完整的Docker部署方案,包含以下文件:
### 核心Docker文件
1. **`Dockerfile`** - 应用容器镜像构建文件
- 基于Python 3.9-slim
- 安装必要的系统依赖和Python库
- 配置健康检查
- 设置启动命令
2. **`docker-compose.yml`** - 主要的Docker Compose配置文件
- MySQL 8.0 数据库服务
- 患者画像应用服务
- 网络和数据卷配置
- 环境变量设置
3. **`docker-compose.override.yml`** - 开发环境覆盖配置
- 开发模式设置
- 代码热重载支持
- 简化的开发密码
4. **`requirements.txt`** - Python依赖文件
- Flask Web框架
- PyMySQL数据库驱动
- 其他必要的库
5. **`.dockerignore`** - Docker构建忽略文件
- 排除不必要的文件和目录
- 优化镜像大小
### 数据库配置文件
6. **`init.sql`** - MySQL数据库初始化脚本
- 创建所需的数据表
- 插入默认数据
- 设置字符集和索引
7. **`docker_database_config.py`** - Docker环境数据库配置
- 支持环境变量配置
- 向下兼容现有配置文件
- 自动检测Docker环境
### 启动和测试文件
8. **`start_docker.py`** - Docker环境启动脚本
- 环境检查
- 数据库等待和初始化
- Flask应用启动
9. **`test_docker_deployment.py`** - 部署测试脚本
- 自动测试Docker服务
- Web服务连接测试
- 数据库连接验证
### 快速部署脚本
10. **`快速部署Docker.bat`** - Windows快速部署脚本
- Windows环境一键部署
- 自动检查和启动服务
11. **`deploy_docker.sh`** - Linux/Mac快速部署脚本
- Unix环境一键部署
- 自动测试和验证
### 文档文件
12. **`README_DOCKER.md`** - Docker部署完整指南
- 详细的部署说明
- 常用命令参考
- 故障排除指南
## 🚀 快速开始
### 方式1:使用快速部署脚本
**Windows用户:**
```cmd
双击运行 "快速部署Docker.bat"
```
**Linux/Mac用户:**
```bash
chmod +x deploy_docker.sh
./deploy_docker.sh
```
### 方式2:手动部署
```bash
# 1. 构建并启动服务
docker-compose up -d
# 2. 查看服务状态
docker-compose ps
# 3. 查看日志
docker-compose logs -f
# 4. 测试部署
python test_docker_deployment.py
```
## 🔧 配置说明
### 默认配置
- **应用端口**: 5000
- **数据库端口**: 3306 (仅开发环境暴露)
- **数据库用户**: callback_user
- **数据库密码**: callback_pass_2024
- **数据库名**: callback_system
### 环境变量
系统支持以下环境变量配置:
| 变量名 | 默认值 | 说明 |
|-------|--------|------|
| DB_HOST | mysql | 数据库主机 |
| DB_PORT | 3306 | 数据库端口 |
| DB_USER | callback_user | 数据库用户 |
| DB_PASSWORD | callback_pass_2024 | 数据库密码 |
| DB_NAME | callback_system | 数据库名 |
| FLASK_ENV | production | Flask环境 |
## 📊 访问地址
部署成功后,可通过以下地址访问:
- **主应用**: http://localhost:5000
- **登录页面**: http://localhost:5000/login
- **患者画像**: http://localhost:5000/patient_profiles/
- **API接口**: http://localhost:5000/api/
### 默认登录信息
- **用户名**: admin
- **密码**: admin123
## 🛠️ 常用操作
```bash
# 启动服务
docker-compose up -d
# 停止服务
docker-compose down
# 重建并启动
docker-compose up -d --build
# 查看日志
docker-compose logs -f patient_callback_app
# 进入应用容器
docker-compose exec patient_callback_app bash
# 进入数据库
docker-compose exec mysql mysql -u callback_user -p callback_system
# 备份数据库
docker-compose exec mysql mysqldump -u root -p callback_system > backup.sql
# 清理数据重新初始化
docker-compose down -v
docker-compose up -d
```
## 📝 注意事项
1. **首次启动**: 需要时间下载镜像和初始化数据库,请耐心等待
2. **端口冲突**: 如果5000或3306端口被占用,请修改docker-compose.yml中的端口映射
3. **数据持久化**: 数据库数据存储在Docker卷中,删除容器不会丢失数据
4. **安全设置**: 生产环境请修改默认密码
5. **资源要求**: 建议至少2GB内存和10GB磁盘空间
## 🆘 常见问题
1. **容器启动失败**: 检查Docker是否正常运行,端口是否被占用
2. **数据库连接失败**: 等待MySQL完全启动(通常需要30-60秒)
3. **应用无法访问**: 检查防火墙设置,确保5000端口可访问
4. **权限问题**: Windows用户请以管理员身份运行,Linux用户确保Docker权限
## 📞 技术支持
如需技术支持,请提供:
1. 操作系统信息
2. Docker版本信息
3. 错误日志内容
4. 服务状态信息
详细的故障排除指南请参考 `README_DOCKER.md` 文件。
\ No newline at end of file
# MySQL回访记录系统使用指南
# MySQL回访记录系统使用指南
## 系统概述
本系统将患者回访记录存储到MySQL数据库中,提供完整的回访记录管理功能。
## 🚀 快速开始
### 方式一:自动安装(推荐)
1. **运行安装向导**
```bash
python setup_mysql.py
```
2. **按提示完成配置**
- 系统会自动安装依赖库
- 引导您配置MySQL连接信息
- 测试数据库连接
- 创建启动脚本
3. **启动服务器**
- 双击 `启动服务器.bat`
- 或运行 `python callback_api_server.py`
### 方式二:手动安装
1. **安装依赖库**
```bash
pip install pymysql flask flask-cors
```
2. **配置数据库**
```bash
python database_config.py
```
3. **测试连接**
```bash
python callback_record_mysql.py
```
4. **启动服务器**
```bash
python callback_api_server.py
```
## 📋 系统要求
### 软件要求
- Python 3.6 或更高版本
- MySQL 5.7 或更高版本
- 现代浏览器(Chrome、Firefox、Edge等)
### Python库依赖
- `pymysql` - MySQL数据库连接
- `flask` - Web API框架
- `flask-cors` - 跨域请求支持
## 🔧 配置说明
### 数据库配置文件 (`database_config.ini`)
```ini
[mysql]
host = localhost
port = 3306
user = root
password = your_password
database = callback_system
charset = utf8mb4
```
### 配置项说明
- `host`: MySQL服务器地址
- `port`: MySQL服务器端口(默认3306)
- `user`: 数据库用户名
- `password`: 数据库密码
- `database`: 数据库名称(系统会自动创建)
- `charset`: 字符集(建议使用utf8mb4)
## 🗄️ 数据库结构
### 数据表:`callback_records`
| 字段名 | 类型 | 说明 |
|--------|------|------|
| record_id | INT | 记录ID(主键,自增) |
| case_number | VARCHAR(50) | 病历号 |
| callback_methods | JSON | 回访方式(JSON格式) |
| callback_record | TEXT | 回访记录内容 |
| operator | VARCHAR(100) | 操作员 |
| create_time | TIMESTAMP | 创建时间 |
| update_time | TIMESTAMP | 更新时间 |
### 索引
- `idx_case_number`: 病历号索引
- `idx_create_time`: 创建时间索引
- `idx_operator`: 操作员索引
## 🌐 API接口文档
### 基础URL
```
http://localhost:5000/api/
```
### 1. 保存回访记录
```http
POST /api/callback-records
Content-Type: application/json
{
"caseNumber": "TS0B000249",
"callbackMethods": ["打电话", "发微信"],
"callbackRecord": "[2025-01-28 15:30] 客户知道了\n已安排下次复查",
"operator": "张三"
}
```
**响应示例:**
```json
{
"success": true,
"id": 123,
"message": "保存成功",
"timestamp": "2025-01-28T15:30:00"
}
```
### 2. 获取回访记录
```http
GET /api/callback-records/{case_number}
```
**响应示例:**
```json
{
"success": true,
"data": [
{
"record_id": 123,
"case_number": "TS0B000249",
"callback_methods": ["打电话", "发微信"],
"callback_record": "[2025-01-28 15:30] 客户知道了",
"operator": "张三",
"create_time": "2025-01-28T15:30:00"
}
],
"count": 1
}
```
### 3. 获取统计信息
```http
GET /api/callback-records/statistics
```
### 4. 删除回访记录
```http
DELETE /api/callback-records/{record_id}
```
### 5. 健康检查
```http
GET /api/health
```
## 💻 使用流程
### 1. 启动系统
1. 确保MySQL服务正在运行
2. 启动API服务器:`python callback_api_server.py`
3. 浏览器访问:`http://localhost:5000/patient_profiles/`
### 2. 使用回访记录功能
1. 打开任意患者画像页面
2. 滚动到页面底部的"回访记录"区域
3. 选择回访方式(打电话、发微信、发信息)
4. 输入回访记录或使用快捷输入按钮
5. 点击"保存"按钮
### 3. 快捷输入功能
系统提供以下快捷输入按钮:
- **客户知道了**
- **客户考虑下**
- **客户未接电话**
- **已发信息跟进**
点击按钮会自动添加时间戳和内容到回访记录中。
## 🔍 功能特性
### ✅ 已实现功能
1. **回访方式选择**:支持多选(打电话、发微信、发信息)
2. **回访记录输入**:500字符限制,实时字符计数
3. **快捷输入按钮**:自动添加时间戳
4. **数据验证**:前端和后端双重验证
5. **MySQL存储**:完整的数据库操作
6. **API接口**:RESTful API设计
7. **错误处理**:友好的错误提示
8. **统计功能**:回访记录统计分析
### 🔄 预留功能
1. **设置下次回访**:计划中的功能
2. **新建预约**:计划中的功能
## 🐛 故障排除
### 常见问题
#### 1. 数据库连接失败
**症状**:API返回"数据库未初始化"错误
**解决方案**:
- 检查MySQL服务是否启动
- 验证数据库配置信息
- 确认数据库用户权限
#### 2. 依赖库缺失
**症状**:ImportError或ModuleNotFoundError
**解决方案**:
```bash
pip install pymysql flask flask-cors
```
#### 3. 端口被占用
**症状**:服务器启动失败,提示端口占用
**解决方案**:
- 更改API服务器端口
- 或停止占用5000端口的其他程序
#### 4. 跨域请求被阻止
**症状**:浏览器控制台显示CORS错误
**解决方案**:
- 确保flask-cors已安装
- 检查API服务器是否正常运行
### 日志查看
- API服务器日志会显示在控制台
- 数据库操作日志包含详细的错误信息
## 📊 性能优化
### 数据库优化
1. **索引优化**:已为常用查询字段创建索引
2. **连接池**:考虑使用连接池提高并发性能
3. **查询优化**:避免全表扫描
### 前端优化
1. **请求缓存**:避免重复请求
2. **输入防抖**:减少不必要的验证请求
3. **错误重试**:网络错误时自动重试
## 🔐 安全考虑
### 数据库安全
1. **密码保护**:不要使用弱密码
2. **权限控制**:为应用创建专用数据库用户
3. **网络安全**:生产环境中限制数据库访问
### API安全
1. **输入验证**:所有输入都经过验证
2. **SQL注入防护**:使用参数化查询
3. **访问控制**:考虑添加身份验证
## 📈 监控和维护
### 定期维护
1. **数据备份**:定期备份回访记录数据
2. **日志清理**:清理过期的日志文件
3. **性能监控**:监控数据库和API性能
### 数据备份示例
```bash
mysqldump -u root -p callback_system > backup_$(date +%Y%m%d).sql
```
## 🆘 技术支持
如遇到问题,请按以下顺序排查:
1. **检查系统状态**
```bash
python -c "import pymysql, flask; print('依赖OK')"
```
2. **测试数据库连接**
```bash
python callback_record_mysql.py
```
3. **检查API健康状态**
访问:http://localhost:5000/api/health
4. **查看详细日志**
在API服务器控制台查看错误信息
---
*最后更新时间:2025-01-28*
\ No newline at end of file
# Dify平台回访话术生成系统使用指南
本项目已成功从阿里云百炼平台迁移到Dify平台,提供更加灵活和强大的AI回访话术生成功能。
## 📋 项目概述
本系统使用Dify平台的API来生成个性化的患者回访话术,支持多种API类型和响应模式,能够根据患者的基本信息、就诊历史和漏诊情况生成专业的回访内容。
## 🔧 环境准备
### 1. 安装依赖
确保已安装必要的Python包:
```bash
pip install requests
```
### 2. 获取Dify API密钥
1. 访问您的Dify平台:http://dify.lumiidental.com
2. 登录您的账户
3. 创建或选择一个应用
4. 在应用设置中找到API密钥
5. 复制API密钥备用
### 3. 配置API密钥
有两种方式配置API密钥:
#### 方式一:环境变量(推荐)
```bash
# Windows
set DIFY_API_KEY=your_actual_api_key_here
# Linux/Mac
export DIFY_API_KEY=your_actual_api_key_here
```
#### 方式二:直接修改配置文件
编辑 `dify_config.py` 文件,将 `your_dify_api_key_here` 替换为您的实际API密钥:
```python
self.API_KEY = "your_actual_api_key_here"
```
## 📁 文件说明
### 核心文件
- `dify_callback_api.py` - Dify平台API调用核心模块
- `dify_config.py` - Dify平台配置管理
- `batch_generate_dify_callback.py` - 批量生成回访话术的主程序
### 数据文件
- `漏诊客户画像.json` - 患者基本信息
- `病历漏诊信息.json` - 漏诊相关信息(可选)
- `合并结果.json` - 合并后的患者数据
### 输出目录
- `dify_callback_results/` - 生成的回访话术结果
## 🚀 快速开始
### 1. 基本使用
运行批量生成脚本:
```bash
python batch_generate_dify_callback.py
```
### 2. 选择API类型
系统支持三种API类型:
1. **Chat API(聊天完成)** - 推荐使用
- 适用于对话式应用
- 支持上下文记忆
- 响应质量高
2. **Completion API(文本完成)**
- 适用于文本生成应用
- 简单直接
- 适合单次生成
3. **Workflow API(工作流)**
- 适用于复杂的工作流应用
- 支持多步骤处理
- 可定制性强
### 3. 处理流程
1. 系统自动加载患者数据
2. 选择API类型(1-3)
3. 选择处理数量
4. 自动生成回访话术
5. 保存结果到文件
## 📊 输出文件
每次运行会生成三个文件:
1. **完整版JSON文件**
- 包含所有生成结果和元数据
- 文件名:`Dify回访话术_完整版_{api_type}_{timestamp}.json`
2. **文本版话术文件**
- 纯文本格式的回访话术
- 文件名:`Dify回访话术_文本版_{api_type}_{timestamp}.txt`
3. **统计报告**
- 生成统计和分析数据
- 文件名:`Dify回访话术_统计报告_{api_type}_{timestamp}.json`
## ⚙️ 高级配置
### 自定义配置
编辑 `dify_config.py` 可以自定义:
```python
# 请求配置
self.REQUEST_TIMEOUT = 60 # 请求超时时间(秒)
self.REQUEST_DELAY = 1.0 # 批量请求间隔时间(秒)
self.MAX_RETRIES = 3 # 最大重试次数
# API配置
self.DEFAULT_API_TYPE = "chat" # 默认API类型
self.RESPONSE_MODE = "blocking" # 响应模式
self.USER_ID = "callback_system" # 用户ID
```
### 服务器地址配置
如果您的Dify服务器地址不同,可以通过环境变量设置:
```bash
export DIFY_BASE_URL=http://your-dify-server.com/v1
```
## 🔍 API调用示例
### 单个患者回访示例
```python
from dify_callback_api import DifyCallbackAPI
from dify_config import get_dify_config
# 获取配置
config = get_dify_config()
# 初始化API客户端
api_client = DifyCallbackAPI(config.API_KEY, config.BASE_URL)
# 患者数据
patient_data = {
"客户": "张三",
"年龄": 35,
"性别": "男",
"病历号": "TS001",
"主诉": "牙痛",
"本次治疗": "根管治疗",
# ... 其他患者信息
}
# 调用Chat API
result = api_client.call_chat_completion(patient_data)
print(result.get('answer', ''))
# 调用Completion API
callback_text = api_client.call_completion_api(patient_data)
print(callback_text)
```
### 批量处理示例
```python
# 批量处理多个患者
patients_data = [patient1, patient2, patient3] # 患者数据列表
# 批量调用
results = api_client.batch_callback(
patients_data,
delay=1.0, # 请求间隔
api_type="chat" # API类型
)
# 保存结果
api_client.save_callback_results(results, "batch_results.json")
```
## 🛠️ 故障排除
### 常见问题
1. **"API密钥未设置"错误**
```
错误: DIFY_API_KEY 未设置或使用默认值
```
**解决方案**:按照上述方法配置API密钥
2. **连接失败**
```
无法连接到Dify服务器
```
**解决方案**:
- 检查服务器地址是否正确
- 确认网络连接正常
- 验证服务器是否运行
3. **API调用失败**
```
API调用失败: 401 Unauthorized
```
**解决方案**:
- 验证API密钥是否正确
- 检查API密钥是否有权限访问该应用
4. **数据格式错误**
```
患者数据格式异常
```
**解决方案**:
- 检查JSON文件格式是否正确
- 确认必要字段是否存在
### 调试模式
启用详细日志输出:
```python
import logging
logging.basicConfig(level=logging.DEBUG)
```
### 测试连接
运行配置测试:
```bash
python dify_config.py
```
## 📈 性能优化
### 批量处理优化
1. **调整请求间隔**:根据服务器性能调整 `REQUEST_DELAY`
2. **设置超时时间**:根据网络情况调整 `REQUEST_TIMEOUT`
3. **重试机制**:合理设置 `MAX_RETRIES`
### 数据处理优化
1. **分批处理**:对于大量数据,建议分批处理
2. **错误恢复**:失败的请求会自动重试
3. **结果缓存**:避免重复处理相同数据
## 🔐 安全注意事项
1. **API密钥安全**:
- 不要在代码中硬编码API密钥
- 使用环境变量存储敏感信息
- 定期更换API密钥
2. **数据隐私**:
- 确保患者数据的安全性
- 遵守相关的隐私保护法规
- 及时清理临时文件
## 📚 API参考
### DifyCallbackAPI类
#### 初始化
```python
api_client = DifyCallbackAPI(api_key, base_url)
```
#### 主要方法
- `call_chat_completion()` - 聊天完成API
- `call_completion_api()` - 文本完成API
- `call_workflow()` - 工作流API
- `batch_callback()` - 批量处理
- `test_connection()` - 测试连接
## 🆚 与百炼平台的差异
| 功能 | 百炼平台 | Dify平台 |
|------|----------|----------|
| API类型 | 单一完成API | 多种API类型 |
| 响应模式 | blocking/streaming | blocking/streaming |
| 工作流支持 | 无 | 支持 |
| 自定义程度 | 低 | 高 |
| 部署方式 | 云服务 | 云服务/自部署 |
## 📞 技术支持
如有问题,请:
1. 查看本文档的故障排除部分
2. 检查Dify平台官方文档
3. 查看系统日志获取详细错误信息
## 🔄 更新日志
- **v2.0.0**: 迁移到Dify平台
- 支持多种API类型
- 增强的配置管理
- 改进的错误处理
- 更好的批量处理性能
- **v1.0.0**: 百炼平台版本(已废弃)
---
**注意**:本系统已完全迁移到Dify平台,不再支持百炼平台。如需使用百炼平台,请使用v1.0.0版本的代码。
\ No newline at end of file
# 患者画像回访话术系统 - Docker部署指南
## 📋 系统概述
本系统采用Docker Compose进行容器化部署,包含以下服务:
- **patient_callback_app**: 患者画像回访话术系统主应用
- **mysql**: MySQL 8.0 数据库服务
- **callback_api**: 回访API服务(可选)
## 🚀 快速开始
### 前置要求
1. **安装Docker**
```bash
# Windows/Mac: 下载Docker Desktop
# Linux (Ubuntu/Debian):
sudo apt-get update
sudo apt-get install docker.io docker-compose
# 验证安装
docker --version
docker-compose --version
```
2. **克隆代码**
```bash
git clone <your-repository>
cd 患者画像-回访话术
```
### 一键部署(生产环境)
```bash
# 启动所有服务
docker-compose up -d
# 查看服务状态
docker-compose ps
# 查看日志
docker-compose logs -f
```
### 开发环境部署
```bash
# 使用开发环境配置启动
docker-compose -f docker-compose.yml -f docker-compose.override.yml up -d
# 或者使用简化命令(默认会加载override文件)
docker-compose up -d
```
## 🔧 配置说明
### 环境变量配置
系统支持通过环境变量配置数据库连接:
| 环境变量 | 默认值 | 说明 |
|---------|--------|------|
| `DB_HOST` | mysql | 数据库主机地址 |
| `DB_PORT` | 3306 | 数据库端口 |
| `DB_USER` | callback_user | 数据库用户名 |
| `DB_PASSWORD` | callback_pass_2024 | 数据库密码 |
| `DB_NAME` | callback_system | 数据库名称 |
| `FLASK_ENV` | production | Flask运行环境 |
| `FLASK_DEBUG` | 0 | Flask调试模式 |
### 自定义配置
1. **修改数据库密码**
```yaml
# 在docker-compose.yml中修改
environment:
MYSQL_ROOT_PASSWORD: your_secure_password
MYSQL_PASSWORD: your_secure_password
DB_PASSWORD: your_secure_password
```
2. **修改端口映射**
```yaml
# 将应用端口改为8080
ports:
- "8080:5000"
```
## 📊 服务访问
启动成功后,可通过以下地址访问:
- **主应用**: http://localhost:5000
- **登录页面**: http://localhost:5000/login
- **患者画像**: http://localhost:5000/patient_profiles/
- **API接口**: http://localhost:5000/api/
- **MySQL数据库**: localhost:3306 (仅开发环境)
### 默认登录信息
- **用户名**: admin
- **密码**: admin123
## 🛠️ 常用命令
### Docker Compose 基础命令
```bash
# 启动服务(后台运行)
docker-compose up -d
# 启动服务(前台运行,查看日志)
docker-compose up
# 停止服务
docker-compose down
# 重启服务
docker-compose restart
# 查看服务状态
docker-compose ps
# 查看日志
docker-compose logs
docker-compose logs -f patient_callback_app # 查看特定服务日志
# 进入容器
docker-compose exec patient_callback_app bash
docker-compose exec mysql mysql -u root -p
# 更新镜像
docker-compose pull
docker-compose up -d --force-recreate
```
### 数据管理命令
```bash
# 备份数据库
docker-compose exec mysql mysqldump -u root -p callback_system > backup.sql
# 恢复数据库
docker-compose exec -T mysql mysql -u root -p callback_system < backup.sql
# 查看数据库
docker-compose exec mysql mysql -u callback_user -p callback_system
```
### 开发调试命令
```bash
# 查看应用日志
docker-compose logs -f patient_callback_app
# 重建应用镜像
docker-compose build patient_callback_app
# 强制重新创建容器
docker-compose up -d --force-recreate patient_callback_app
# 清理未使用的镜像和容器
docker system prune -f
```
## 🔍 故障排除
### 常见问题
1. **数据库连接失败**
```bash
# 检查MySQL服务状态
docker-compose logs mysql
# 等待MySQL完全启动(通常需要30-60秒)
docker-compose exec mysql mysqladmin ping -h localhost
```
2. **端口冲突**
```bash
# 检查端口占用
netstat -tlnp | grep 5000
# 修改docker-compose.yml中的端口映射
ports:
- "5001:5000" # 使用其他端口
```
3. **应用启动失败**
```bash
# 查看详细错误日志
docker-compose logs patient_callback_app
# 进入容器调试
docker-compose exec patient_callback_app python start_docker.py
```
4. **数据库初始化失败**
```bash
# 删除数据卷重新初始化
docker-compose down -v
docker-compose up -d
```
### 日志级别调试
```bash
# 设置详细日志
docker-compose -f docker-compose.yml -f docker-compose.override.yml up
# 或者临时修改环境变量
docker-compose exec patient_callback_app env FLASK_DEBUG=1 python start_docker.py
```
## 📦 数据持久化
系统使用Docker卷保存数据:
```yaml
volumes:
mysql_data: # MySQL数据库文件
./patient_profiles: # 患者画像HTML文件
./progress_saves: # 进度保存文件
./dify_callback_results: # Dify回访结果
```
### 备份重要数据
```bash
# 备份患者画像文件
tar -czf patient_profiles_backup.tar.gz patient_profiles/
# 备份数据库
docker-compose exec mysql mysqldump -u root -p --all-databases > full_backup.sql
```
## 🔒 安全配置
### 生产环境安全建议
1. **修改默认密码**
- 数据库root密码
- 应用管理员密码
- 数据库用户密码
2. **网络安全**
```yaml
# 不暴露MySQL端口到外网
ports: [] # 删除MySQL的ports配置
```
3. **环境变量**
```bash
# 使用.env文件管理敏感配置
echo "DB_PASSWORD=your_secure_password" > .env
```
## 📈 性能优化
### 资源限制
```yaml
# 在docker-compose.yml中添加资源限制
deploy:
resources:
limits:
memory: 512M
cpus: '0.5'
reservations:
memory: 256M
cpus: '0.25'
```
### MySQL优化
```yaml
# MySQL配置优化
command: >
--default-authentication-plugin=mysql_native_password
--innodb-buffer-pool-size=256M
--max-connections=100
```
## 🆕 更新部署
```bash
# 1. 停止服务
docker-compose down
# 2. 拉取最新代码
git pull origin main
# 3. 重建镜像
docker-compose build
# 4. 启动服务
docker-compose up -d
# 5. 检查状态
docker-compose ps
docker-compose logs -f
```
## 📞 技术支持
如遇到问题,请提供以下信息:
1. **系统信息**
```bash
docker --version
docker-compose --version
uname -a
```
2. **服务状态**
```bash
docker-compose ps
```
3. **错误日志**
```bash
docker-compose logs > debug.log
```
---
**注意**: 首次启动可能需要较长时间下载镜像和初始化数据库,请耐心等待。
\ No newline at end of file
# 登录系统使用说明
## 概述
本系统为回访记录管理系统提供了完整的用户认证和授权功能,包括用户登录、会话管理、权限控制等。
## 功能特性
### 🔐 用户认证
- 安全的密码哈希存储(SHA-256 + 盐值 + 多轮哈希)
- 会话管理和自动过期
- 记住登录状态功能
- 密码长度和复杂度验证
### 👥 用户管理
- 多角色支持(管理员/普通用户)
- 用户创建、删除、密码修改
- 活跃会话监控
- 用户权限控制
### 🛡️ 安全特性
- CSRF防护
- 会话超时保护
- 密码策略强制执行
- API访问控制
## 快速开始
### 1. 安装依赖
```bash
pip install pymysql flask flask-cors
```
### 2. 配置数据库
首次运行时系统会自动创建配置文件:
```bash
python database_config.py
```
编辑 `database_config.ini` 文件,配置MySQL连接信息:
```ini
[mysql]
host = localhost
port = 3306
user = root
password = your_password
database = callback_system
charset = utf8mb4
```
### 3. 启动服务器
使用自动启动脚本:
```bash
python start_auth_server.py
```
或直接启动认证服务器:
```bash
python auth_server.py
```
### 4. 访问系统
- **登录页面**: http://localhost:5000/login
- **系统仪表盘**: http://localhost:5000/dashboard.html
- **用户管理**: http://localhost:5000/user_management.html (仅管理员)
## 默认账户
系统会自动创建默认管理员账户:
- **用户名**: admin
- **密码**: admin123
⚠️ **重要**: 请登录后立即修改默认密码!
## 文件结构
```
├── auth_server.py # 认证服务器主程序
├── user_manager.py # 用户管理模块
├── session_manager.py # 会话管理模块
├── login.html # 登录页面
├── dashboard.html # 系统仪表盘
├── user_management.html # 用户管理页面
├── auth_client.js # 前端认证客户端库
├── database_config.py # 数据库配置管理
├── start_auth_server.py # 自动启动脚本
└── README_LOGIN.md # 本说明文档
```
## API接口
### 认证相关
| 接口 | 方法 | 说明 |
|------|------|------|
| `/api/auth/login` | POST | 用户登录 |
| `/api/auth/logout` | POST | 用户注销 |
| `/api/auth/validate` | POST | 验证会话 |
| `/api/auth/user/profile` | GET | 获取用户信息 |
| `/api/auth/user/change-password` | POST | 修改密码 |
### 管理员接口
| 接口 | 方法 | 说明 |
|------|------|------|
| `/api/auth/admin/users` | GET | 获取用户列表 |
| `/api/auth/admin/users` | POST | 创建用户 |
| `/api/auth/sessions` | GET | 获取活跃会话 |
### 系统接口
| 接口 | 方法 | 说明 |
|------|------|------|
| `/api/health` | GET | 系统健康检查 |
## 前端集成
### 引入认证客户端
```html
<script src="/auth_client.js"></script>
```
### 基本使用
```javascript
// 检查登录状态
if (!authClient.isLoggedIn()) {
authClient.redirectToLogin();
}
// 获取当前用户
const user = authClient.getCurrentUser();
// 发送认证请求
const response = await authClient.apiRequest('/api/some-endpoint', {
method: 'POST',
authenticated: true,
body: JSON.stringify(data)
});
// 用户注销
await authClient.logout();
```
### 权限控制
```javascript
// 要求用户登录
if (!authClient.requireLogin()) {
return; // 会自动跳转到登录页面
}
// 要求管理员权限
if (!authClient.requireAdmin()) {
alert('需要管理员权限');
return;
}
```
## 部署配置
### 开发环境
默认配置适用于开发环境,使用 `debug=True` 模式。
### 生产环境
1. **修改Flask配置**:
```python
app.run(
host='0.0.0.0',
port=5000,
debug=False, # 关闭调试模式
threaded=True
)
```
2. **使用HTTPS**:
- 配置SSL证书
- 强制HTTPS重定向
3. **数据库优化**:
- 使用连接池
- 配置数据库索引
- 定期备份数据
4. **安全加固**:
- 修改默认密码
- 配置防火墙
- 启用访问日志
- 定期更新依赖
## 数据库表结构
### users 表
| 字段 | 类型 | 说明 |
|------|------|------|
| user_id | INT | 用户ID(主键) |
| username | VARCHAR(50) | 用户名(唯一) |
| password_hash | VARCHAR(255) | 密码哈希 |
| salt | VARCHAR(255) | 盐值 |
| role | ENUM | 用户角色(admin/user |
| created_at | TIMESTAMP | 创建时间 |
| last_login | TIMESTAMP | 最后登录时间 |
| is_active | BOOLEAN | 是否激活 |
## 故障排除
### 常见问题
1. **数据库连接失败**
- 检查MySQL服务是否运行
- 验证数据库配置信息
- 确认用户权限
2. **依赖包缺失**
```bash
pip install pymysql flask flask-cors
```
3. **会话过期**
- 会话默认24小时过期
- 勾选"记住登录状态"可延长到7
4. **权限不足**
- 确保用户有相应权限
- 管理员功能需要admin角色
### 日志调试
启用调试模式查看详细日志:
```python
app.run(debug=True)
```
## 安全建议
1. **密码策略**:
- 最小长度6位
- 建议包含大小写字母、数字、特殊字符
2. **会话管理**:
- 定期清理过期会话
- 监控异常登录活动
3. **数据保护**:
- 定期备份用户数据
- 加密敏感信息传输
4. **访问控制**:
- 最小权限原则
- 定期审核用户权限
## 技术支持
如有问题,请检查:
1. 系统日志输出
2. 数据库连接状态
3. 网络配置
4. 防火墙设置
更多技术问题请参考项目文档或联系开发团队。
\ No newline at end of file
# SQL脚本执行完成说明
# SQL脚本执行完成说明
## ✅ 执行成功
**执行时间**: 2025-08-07 10:54:48
**执行方式**: Docker MySQL容器
**数据库**: callback_system
## 📊 创建结果统计
- **管理员用户**: 1个
- **普通用户**: 14个
- **总用户数**: 15个
## 👥 创建的普通用户详情
### 按诊所分组:
| 诊所 | 用户名 | 真实姓名 | 诊所权限 | 状态 |
|------|--------|----------|----------|------|
| **学前街门诊** | jinqin | 金沁 | clinic_xuexian | 已激活 |
| | renshanshan | 任珊珊 | clinic_xuexian | 已激活 |
| | shaojun | 邵君 | clinic_xuexian | 已激活 |
| **新吴门诊** | litingting | 李婷婷 | clinic_xinwu | 已激活 |
| **红豆门诊** | maqiuyi | 马秋怡 | clinic_hongdou | 已激活 |
| | tangqimin | 唐其敏 | clinic_hongdou | 已激活 |
| **东亭门诊** | yueling | 岳玲 | clinic_dongting | 已激活 |
| **马山门诊** | jijunlin | 季君林 | clinic_mashan | 已激活 |
| | zhouliping | 周丽萍 | clinic_mashan | 已激活 |
| **总院** | feimiaomiao | 费苗妙 | clinic_hospital | 已激活 |
| | chenxinyu | 陈心语 | clinic_hospital | 已激活 |
| **惠山门诊** | yanghong | 杨红 | clinic_huishan | 已激活 |
| | panjinli | 潘金丽 | clinic_huishan | 已激活 |
| **大丰门诊** | chenlin | 陈琳 | clinic_dafeng | 已激活 |
## 🔑 登录信息
所有用户的密码格式为:**用户名 + "123"**
例如:
- jinqin → 密码:jinqin123
- litingting → 密码:litingting123
- maqiuyi → 密码:maqiuyi123
## 🛡️ 安全特性
1. **密码加密**: 使用SHA256哈希算法存储
2. **权限控制**: 每个用户只能访问指定诊所数据
3. **账户状态**: 所有用户已激活,可立即使用
4. **数据完整性**: 使用ON DUPLICATE KEY UPDATE避免重复创建
## 📍 系统访问
用户可以通过以下方式登录:
1. 访问系统地址:http://localhost:4003
2. 使用对应的用户名和密码登录
3. 系统会自动跳转到对应诊所的患者画像页面
## 🔍 验证命令
如需验证用户创建情况,可执行:
```bash
docker exec -i patient_callback_mysql_auth mysql -u callback_user -pcallback_pass_2024 callback_system -e "SELECT username, user_type, clinic_access FROM users WHERE user_type = 'user';"
```
## 📝 数据库信息
- **容器名**: patient_callback_mysql_auth
- **数据库**: callback_system
- **用户**: callback_user
- **端口**: 3308 (外部) → 3306 (内部)
## ✨ 总结
🎉 **14个诊所普通用户创建成功!**
- 覆盖8个诊所
- 每个用户都有独立的诊所访问权限
- 所有账户已激活,可立即使用
- 密码格式统一,便于管理
用户现在可以使用提供的账户信息登录系统,访问对应诊所的患者画像数据!
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
为所有患者页面添加回访话术模块
在患者画像页面中添加口腔回访话术模块的占位符
"""
import os
import re
from typing import List
class CallbackModuleAdder:
"""回访话术模块添加器"""
def __init__(self):
"""初始化"""
self.patient_profiles_dir = "patient_profiles"
# 诊所目录列表
self.clinic_dirs = [
"clinic_dafeng",
"clinic_dongting",
"clinic_helai",
"clinic_hongdou",
"clinic_hospital",
"clinic_huishan",
"clinic_mashan",
"clinic_xinwu",
"clinic_xuexian"
]
def add_callback_module_to_patient(self, patient_file: str) -> bool:
"""为单个患者页面添加回访话术模块"""
try:
# 读取患者页面
with open(patient_file, 'r', encoding='utf-8') as f:
content = f.read()
# 检查是否已有回访话术模块
if "<!-- 口腔回访话术模块 -->" in content:
return True # 已存在,跳过
# 查找插入位置(在回访记录模块之前)
callback_records_pattern = r'(<!-- 回访记录模块 -->)'
match = re.search(callback_records_pattern, content)
if not match:
print(f"⚠️ 患者页面 {patient_file} 未找到回访记录模块")
return False
# 构建回访话术模块HTML
callback_module_html = """
<!-- 口腔回访话术模块 -->
<div id="callback-content" class="mt-8">
<section class="scroll-trigger">
<div class="bg-white rounded-xl shadow-lg overflow-hidden border-l-4 border-green-500">
<div class="bg-green-600 px-6 py-4 text-white">
<h2 class="text-xl font-bold flex items-center">
<i class="fas fa-phone mr-3"></i>
口腔回访话术 <span class="text-sm ml-2 font-normal">Oral Callback Script</span>
</h2>
</div>
<div class="p-6">
<div class="mb-6 grid grid-cols-1 md:grid-cols-2 gap-4 bg-gray-50 p-4 rounded-lg">
<div>
<span class="font-medium text-gray-600">患者姓名:</span>
<span class="text-gray-800">待更新</span>
</div>
<div>
<span class="font-medium text-gray-600">年龄性别:</span>
<span class="text-gray-800">待更新</span>
</div>
<div>
<span class="font-medium text-gray-600">优先诊断:</span>
<span class="text-blue-600 font-semibold">待更新</span>
</div>
<div>
<span class="font-medium text-gray-600">生成时间:</span>
<span class="text-gray-800">待更新</span>
</div>
</div>
<!-- 结构化回访话术内容 -->
<div class="callback-sections">
<div class="callback-script-content">
<div class="bg-gray-50 p-4 rounded-lg mb-4">
<h4 class="font-medium text-gray-800 mb-3">AI生成的回访话术</h4>
<div class="text-sm text-gray-700 leading-relaxed whitespace-pre-line">
回访话术内容将在这里显示...
</div>
</div>
</div>
</div>
<div class="mt-6 p-4 bg-blue-50 border border-blue-200 rounded-lg">
<p class="text-sm text-blue-700">
<i class="fas fa-info-circle mr-2"></i>
<strong>使用提示:</strong>此话术为Dify AI智能生成,请根据实际情况灵活调整使用。建议按照四个模块的顺序进行回访。
</p>
</div>
</div>
</div>
</section>
</div>
"""
# 在回访记录模块之前插入回访话术模块
new_content = re.sub(
callback_records_pattern,
callback_module_html + r'\1',
content
)
# 写回文件
with open(patient_file, 'w', encoding='utf-8') as f:
f.write(new_content)
return True
except Exception as e:
print(f"❌ 为患者页面 {patient_file} 添加回访话术模块时出错: {e}")
return False
def add_callback_module_to_clinic(self, clinic_dir: str) -> tuple:
"""为诊所的所有患者页面添加回访话术模块"""
clinic_path = os.path.join(self.patient_profiles_dir, clinic_dir)
patients_dir = os.path.join(clinic_path, "patients")
if not os.path.exists(patients_dir):
print(f"❌ 诊所目录不存在: {patients_dir}")
return 0, 0
# 获取所有患者HTML文件
patient_files = [f for f in os.listdir(patients_dir) if f.endswith('.html')]
print(f"📁 处理诊所 {clinic_dir}: {len(patient_files)} 个患者文件")
success_count = 0
total_count = len(patient_files)
for patient_file in patient_files:
patient_path = os.path.join(patients_dir, patient_file)
if self.add_callback_module_to_patient(patient_path):
success_count += 1
return success_count, total_count
def add_callback_module_to_all_clinics(self):
"""为所有诊所的患者页面添加回访话术模块"""
print("=" * 60)
print("为所有患者页面添加回访话术模块")
print("=" * 60)
total_success = 0
total_files = 0
for clinic_dir in self.clinic_dirs:
success, total = self.add_callback_module_to_clinic(clinic_dir)
total_success += success
total_files += total
print(f"✅ {clinic_dir}: {success}/{total} 个文件处理成功")
print("\n" + "=" * 60)
print("添加完成!统计结果:")
print(f" 成功添加: {total_success} 个患者页面")
print(f" 总文件数: {total_files} 个")
print(f" 成功率: {total_success/total_files*100:.1f}%" if total_files > 0 else " 成功率: 0%")
print("=" * 60)
def main():
"""主函数"""
adder = CallbackModuleAdder()
adder.add_callback_module_to_all_clinics()
if __name__ == "__main__":
main()
\ No newline at end of file
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pandas as pd
import sys
def analyze_excel_data():
try:
# 读取Excel文件
df = pd.read_excel('合并结果.xlsx')
print('=== 数据基本信息 ===')
print(f'总行数: {len(df)}')
print(f'总列数: {len(df.columns)}')
print()
print('=== 所有字段列表 ===')
for i, col in enumerate(df.columns, 1):
print(f'{i:2d}. {col}')
print()
print('=== 各字段非空值统计 ===')
for col in df.columns:
non_null = df[col].notna().sum()
print(f'{col}: {non_null}/{len(df)} ({non_null/len(df)*100:.1f}%)')
print()
print('=== 前5行数据示例 ===')
pd.set_option('display.max_columns', None)
pd.set_option('display.width', 1000)
pd.set_option('display.max_colwidth', 50)
# 分批显示数据,避免输出过宽
cols_per_batch = 5
for i in range(0, len(df.columns), cols_per_batch):
batch_cols = df.columns[i:i+cols_per_batch]
print(f'\n--- 字段 {i+1}-{min(i+cols_per_batch, len(df.columns))} ---')
print(df[batch_cols].head(5))
print('\n=== 数据类型信息 ===')
print(df.dtypes)
# 检查关键字段的数据分布
print('\n=== 关键字段数据分布 ===')
key_fields = ['姓名', '病历号', '性别', '年龄', '诊所']
for field in key_fields:
if field in df.columns:
unique_count = df[field].nunique()
print(f'{field}: {unique_count} 个不同值')
if field == '性别':
print(f' 性别分布: {df[field].value_counts().to_dict()}')
elif field == '诊所':
print(f' 诊所分布: {df[field].value_counts().head(5).to_dict()}')
except Exception as e:
print(f'读取Excel文件时出错: {e}')
sys.exit(1)
if __name__ == "__main__":
analyze_excel_data()
\ No newline at end of file
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
回访记录API服务器
提供MySQL数据库的HTTP API接口
"""
import os
import sys
from datetime import datetime
from flask import Flask, request, jsonify, send_from_directory
from flask_cors import CORS
# 添加当前目录到Python路径
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
try:
from callback_record_mysql import MySQLCallbackRecordManager, CallbackRecord
from database_config import DatabaseConfig
DEPENDENCIES_AVAILABLE = True
except ImportError as e:
DEPENDENCIES_AVAILABLE = False
IMPORT_ERROR = str(e)
app = Flask(__name__)
CORS(app) # 允许跨域请求
# 全局数据库管理器
db_manager = None
def initialize_database():
"""初始化数据库连接"""
global db_manager
if not DEPENDENCIES_AVAILABLE:
print(f"依赖库未安装: {IMPORT_ERROR}")
print("请运行: pip install pymysql flask flask-cors")
return False
try:
# 加载数据库配置
config_manager = DatabaseConfig()
if not config_manager.validate_config():
print("数据库配置无效,请运行 python database_config.py 进行配置")
return False
mysql_config = config_manager.get_mysql_config()
# 创建数据库管理器
db_manager = MySQLCallbackRecordManager(**mysql_config)
# 测试连接
if db_manager.test_connection():
print("✓ MySQL数据库连接成功")
return True
else:
print("✗ MySQL数据库连接失败")
return False
except Exception as e:
print(f"数据库初始化失败: {e}")
return False
@app.route('/api/callback-records', methods=['POST'])
def save_callback_record():
"""保存回访记录API"""
if not db_manager:
return jsonify({
'success': False,
'message': '数据库未初始化'
}), 500
try:
data = request.get_json()
# 验证必需字段
required_fields = ['caseNumber', 'callbackMethods', 'callbackRecord', 'operator']
for field in required_fields:
if field not in data or not data[field]:
return jsonify({
'success': False,
'message': f'缺少必需字段: {field}'
}), 400
# 创建记录对象
record = CallbackRecord(
case_number=data['caseNumber'],
callback_methods=data['callbackMethods'],
callback_record=data['callbackRecord'],
operator=data['operator']
)
# 保存到数据库
record_id = db_manager.save_record(record)
return jsonify({
'success': True,
'id': record_id,
'message': '保存成功',
'timestamp': datetime.now().isoformat()
})
except Exception as e:
print(f"保存回访记录失败: {e}")
return jsonify({
'success': False,
'message': f'保存失败: {str(e)}'
}), 500
@app.route('/api/callback-records/<case_number>', methods=['GET'])
def get_callback_records(case_number):
"""获取回访记录API"""
if not db_manager:
return jsonify({
'success': False,
'message': '数据库未初始化'
}), 500
try:
records = db_manager.get_records_by_case_number(case_number)
return jsonify({
'success': True,
'data': [record.to_dict() for record in records],
'count': len(records)
})
except Exception as e:
print(f"获取回访记录失败: {e}")
return jsonify({
'success': False,
'message': f'获取失败: {str(e)}'
}), 500
@app.route('/api/callback-records/statistics', methods=['GET'])
def get_statistics():
"""获取统计信息API"""
if not db_manager:
return jsonify({
'success': False,
'message': '数据库未初始化'
}), 500
try:
stats = db_manager.get_statistics()
return jsonify({
'success': True,
'data': stats
})
except Exception as e:
print(f"获取统计信息失败: {e}")
return jsonify({
'success': False,
'message': f'获取失败: {str(e)}'
}), 500
@app.route('/api/callback-records/<int:record_id>', methods=['DELETE'])
def delete_callback_record(record_id):
"""删除回访记录API"""
if not db_manager:
return jsonify({
'success': False,
'message': '数据库未初始化'
}), 500
try:
success = db_manager.delete_record(record_id)
if success:
return jsonify({
'success': True,
'message': '删除成功'
})
else:
return jsonify({
'success': False,
'message': '记录不存在'
}), 404
except Exception as e:
print(f"删除回访记录失败: {e}")
return jsonify({
'success': False,
'message': f'删除失败: {str(e)}'
}), 500
@app.route('/api/health', methods=['GET'])
def health_check():
"""健康检查API"""
status = {
'status': 'ok',
'timestamp': datetime.now().isoformat(),
'database': 'disconnected'
}
if db_manager and db_manager.test_connection():
status['database'] = 'connected'
return jsonify(status)
@app.route('/')
def index():
"""首页 - 显示API文档"""
return """
<h1>回访记录API服务器</h1>
<h2>可用接口:</h2>
<ul>
<li><strong>POST</strong> /api/callback-records - 保存回访记录</li>
<li><strong>GET</strong> /api/callback-records/&lt;case_number&gt; - 获取指定病历号的回访记录</li>
<li><strong>GET</strong> /api/callback-records/statistics - 获取统计信息</li>
<li><strong>DELETE</strong> /api/callback-records/&lt;record_id&gt; - 删除回访记录</li>
<li><strong>GET</strong> /api/health - 健康检查</li>
</ul>
<h2>使用说明:</h2>
<ol>
<li>确保MySQL数据库正在运行</li>
<li>配置数据库连接信息(运行 python database_config.py)</li>
<li>启动API服务器(运行 python callback_api_server.py)</li>
<li>在患者画像页面中使用回访记录功能</li>
</ol>
"""
# 静态文件服务 - 为患者画像页面提供服务
@app.route('/patient_profiles/<path:filename>')
def patient_profiles(filename):
"""提供患者画像页面文件"""
return send_from_directory('patient_profiles', filename)
@app.route('/callback_record.js')
def callback_js():
"""提供JavaScript文件"""
return send_from_directory('.', 'callback_record.js')
def main():
"""主函数"""
print("=== 回访记录API服务器 ===")
# 检查依赖
if not DEPENDENCIES_AVAILABLE:
print(f"错误: {IMPORT_ERROR}")
print("\n请安装所需依赖:")
print("pip install pymysql flask flask-cors")
return
# 初始化数据库
if not initialize_database():
print("数据库初始化失败,服务器无法启动")
print("\n请检查:")
print("1. MySQL服务是否正在运行")
print("2. 数据库配置是否正确(运行 python database_config.py 检查)")
print("3. 数据库用户是否有足够权限")
return
print("\n服务器启动中...")
print("API地址: http://localhost:5000/api/")
print("患者画像: http://localhost:5000/patient_profiles/")
print("按 Ctrl+C 停止服务器")
try:
# 启动Flask服务器
app.run(
host='0.0.0.0', # 允许外部访问
port=5000,
debug=True, # 开发模式
threaded=True # 多线程支持
)
except KeyboardInterrupt:
print("\n服务器已停止")
except Exception as e:
print(f"服务器启动失败: {e}")
if __name__ == "__main__":
main()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
回访记录相关常量定义
包含回访不成功原因和AI反馈类型
"""
# 回访不成功的原因(简化版,控制在10个以内)
CALLBACK_FAILURE_REASONS = [
# 联系不上类(合并)
"患者电话无人接听/关机/停机",
"患者联系方式有误或已变更",
"患者微信/短信无回复",
# 拒绝回访类(合并)
"患者拒绝接受回访或表示不需要",
"患者对诊疗结果不满意",
# 时间不合适(合并)
"患者时间不便(工作/休息/外出)",
# 健康和治疗状况(合并)
"患者身体不适或已在其他医院治疗",
# 经济原因(合并)
"患者经济条件不允许或费用问题",
# 信任度问题(合并)
"患者对诊断结果有疑虑或不信任",
# 其他原因
"其他特殊情况"
]
# 放弃回访的原因(新增)
ABANDON_CALLBACK_REASONS = [
"有下次预约",
"漏诊项不存在",
"漏诊项已治疗",
"其他"
]
# AI错误反馈类型(简化版,控制在10个以内)
AI_FEEDBACK_TYPES = [
# 漏诊检测问题(合并)
"AI漏诊检测错误(检测不准确或遗漏)",
# 话术内容问题(合并)
"AI话术内容不合适(过于专业或过于简单)",
# 语言表达问题(合并)
"AI语言表达不自然或用词不当",
# 个性化问题(合并)
"AI内容缺乏个性化或与患者情况不符",
# 治疗建议问题(合并)
"AI治疗方案或复查时间建议不合理",
# 患者信息分析问题(合并)
"AI对患者年龄/性别/病史分析错误",
# 技术和格式问题(合并)
"AI生成内容格式错误或信息缺失",
# 数据解析问题(合并)
"AI未能正确解析患者数据或病历",
# 系统稳定性问题(合并)
"AI功能不稳定或响应速度慢",
# 其他问题
"其他AI相关问题"
]
# 回访状态
CALLBACK_STATUS_OPTIONS = [
"已回访",
"未回访"
]
# 回访方式选项
CALLBACK_METHODS = [
"打电话",
"发微信",
"发短信",
"视频通话",
"面谈"
]
# 回访结果选项(新增放弃回访)
CALLBACK_RESULT_OPTIONS = [
"成功",
"不成功",
"放弃回访"
]
\ No newline at end of file
/**
* 回访记录功能JavaScript
* 处理字符计数、保存等功能
*/
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
initializeCallbackRecord();
});
/**
* 初始化回访记录功能
*/
function initializeCallbackRecord() {
// 初始化字符计数
initCharacterCount();
// 初始化表单验证
initFormValidation();
console.log('回访记录功能初始化完成');
}
/**
* 初始化字符计数功能
*/
function initCharacterCount() {
const textareas = document.querySelectorAll('textarea[name="callback-record"]');
textareas.forEach(textarea => {
const caseNumber = textarea.id.replace('callback-record-', '');
const countElement = document.getElementById(`char-count-${caseNumber}`);
if (countElement) {
// 初始化计数
updateCharacterCount(textarea.id);
// 监听输入事件
textarea.addEventListener('input', function() {
updateCharacterCount(this.id);
});
// 监听粘贴事件
textarea.addEventListener('paste', function() {
setTimeout(() => {
updateCharacterCount(this.id);
}, 0);
});
}
});
}
/**
* 更新字符计数
* @param {string} textareaId - 文本域ID
*/
function updateCharacterCount(textareaId) {
const textarea = document.getElementById(textareaId);
const caseNumber = textareaId.replace('callback-record-', '');
const countElement = document.getElementById(`char-count-${caseNumber}`);
if (textarea && countElement) {
const currentLength = textarea.value.length;
const maxLength = textarea.getAttribute('maxlength') || 500;
countElement.textContent = currentLength;
// 根据字符数改变颜色
if (currentLength > maxLength * 0.9) {
countElement.className = 'text-red-500 font-medium';
} else if (currentLength > maxLength * 0.7) {
countElement.className = 'text-yellow-600';
} else {
countElement.className = 'text-gray-500';
}
}
}
/**
* 初始化表单验证
*/
function initFormValidation() {
const textareas = document.querySelectorAll('textarea[name="callback-record"]');
textareas.forEach(textarea => {
textarea.addEventListener('blur', function() {
validateCallbackRecord(this);
});
});
}
/**
* 验证回访记录
* @param {HTMLElement} textarea - 文本域元素
*/
function validateCallbackRecord(textarea) {
const value = textarea.value.trim();
const minLength = 5; // 最少5个字符
// 移除之前的错误样式
textarea.classList.remove('border-red-500', 'border-green-500');
if (value.length > 0 && value.length < minLength) {
textarea.classList.add('border-red-500');
showToast('回访记录至少需要5个字符', 'warning');
} else if (value.length >= minLength) {
textarea.classList.add('border-green-500');
}
}
/**
* 保存回访记录
* @param {string} caseNumber - 病历号
*/
function saveCallbackRecord(caseNumber) {
const textarea = document.getElementById(`callback-record-${caseNumber}`);
const methodCheckboxes = document.querySelectorAll(`input[name="callback-method-${caseNumber}"]:checked`);
if (!textarea) {
showToast('找不到回访记录输入框', 'error');
return;
}
const record = textarea.value.trim();
if (!record) {
showToast('请输入回访记录内容', 'warning');
textarea.focus();
return;
}
if (record.length < 5) {
showToast('回访记录至少需要5个字符', 'warning');
textarea.focus();
return;
}
// 获取选中的回访方式
const methods = Array.from(methodCheckboxes).map(cb => cb.value);
if (methods.length === 0) {
showToast('请选择至少一种回访方式', 'warning');
return;
}
// 构建保存数据
const saveData = {
caseNumber: caseNumber,
callbackMethods: methods,
callbackRecord: record,
operator: '系统用户', // 可以根据实际情况修改
timestamp: new Date().toISOString()
};
// 显示保存中状态
const saveBtn = document.querySelector(`button[onclick="saveCallbackRecord('${caseNumber}')"]`);
const originalText = saveBtn.innerHTML;
saveBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-1"></i>保存中...';
saveBtn.disabled = true;
// 调用保存API
saveCallbackRecordToDatabase(saveData)
.then(result => {
showToast('回访记录保存成功', 'success');
console.log('保存成功:', result);
// 可以在这里添加保存成功后的处理逻辑
// 比如清空表单、显示保存时间等
})
.catch(error => {
console.error('保存失败:', error);
showToast(`保存失败: ${error.message}`, 'error');
})
.finally(() => {
// 恢复按钮状态
saveBtn.innerHTML = originalText;
saveBtn.disabled = false;
});
}
/**
* 保存回访记录到MySQL数据库
* @param {Object} data - 保存数据
* @returns {Promise} - 保存结果
*/
async function saveCallbackRecordToDatabase(data) {
console.log('准备保存到MySQL数据库的数据:', data);
try {
const response = await fetch('/api/callback-records', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(data)
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`HTTP ${response.status}: ${errorText}`);
}
const result = await response.json();
if (result.success) {
return result;
} else {
throw new Error(result.message || '保存失败');
}
} catch (error) {
console.error('保存回访记录到MySQL失败:', error);
// 如果是网络错误或API不可用,显示友好提示
if (error.name === 'TypeError' || error.message.includes('fetch')) {
throw new Error('无法连接到服务器,请检查网络连接或联系管理员');
}
throw error;
}
}
/**
* 设置下次回访(预留功能)
* @param {string} caseNumber - 病历号
*/
function scheduleNextCallback(caseNumber) {
showToast('此功能正在开发中...', 'info');
console.log('设置下次回访:', caseNumber);
}
/**
* 新建预约(预留功能)
* @param {string} caseNumber - 病历号
*/
function createAppointment(caseNumber) {
showToast('此功能正在开发中...', 'info');
console.log('新建预约:', caseNumber);
}
/**
* 显示提示消息
* @param {string} message - 消息内容
* @param {string} type - 消息类型 (success, error, warning, info)
*/
function showToast(message, type = 'info') {
// 创建提示元素
const toast = document.createElement('div');
toast.className = `fixed top-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 transition-all duration-300 transform translate-x-full`;
// 根据类型设置样式
switch (type) {
case 'success':
toast.classList.add('bg-green-500', 'text-white');
break;
case 'error':
toast.classList.add('bg-red-500', 'text-white');
break;
case 'warning':
toast.classList.add('bg-yellow-500', 'text-white');
break;
default:
toast.classList.add('bg-blue-500', 'text-white');
}
toast.textContent = message;
document.body.appendChild(toast);
// 显示动画
setTimeout(() => {
toast.classList.remove('translate-x-full');
}, 100);
// 自动隐藏
setTimeout(() => {
toast.classList.add('translate-x-full');
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 300);
}, 3000);
}
/**
* 获取认证令牌(如果需要的话)
* @returns {string} - 认证令牌
*/
function getAuthToken() {
// 这里可以实现获取认证令牌的逻辑
// 比如从localStorage、sessionStorage或cookie中获取
return '';
}
\ No newline at end of file
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
回访话术重复检查工具
检查所有患者画像页面中是否存在重复的回访话术记录
"""
import os
import re
import sys
# 确保输出为UTF-8编码
if sys.stdout.encoding != 'utf-8':
sys.stdout.reconfigure(encoding='utf-8')
def check_callback_duplicates(profiles_dir='patient_profiles'):
"""检查患者画像页面中的回访话术重复情况"""
if not os.path.exists(profiles_dir):
print(f"患者画像目录不存在: {profiles_dir}")
return
print("=" * 60)
print("回访话术重复检查工具")
print("=" * 60)
html_files = [f for f in os.listdir(profiles_dir) if f.endswith('.html')]
total_files = len(html_files)
duplicate_files = []
for html_file in html_files:
file_path = os.path.join(profiles_dir, html_file)
patient_id = html_file.replace('.html', '')
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# 查找所有的 callback-content 块
callback_content_matches = re.findall(r'<div id="callback-content"', content)
count = len(callback_content_matches)
if count > 1:
duplicate_files.append((patient_id, count))
print(f"❌ {patient_id}: 发现 {count} 个重复的回访话术块")
elif count == 1:
print(f"✅ {patient_id}: 回访话术正常 (1个)")
else:
print(f"⚠️ {patient_id}: 没有回访话术")
except Exception as e:
print(f"❌ {patient_id}: 读取文件时出错 - {e}")
print("\n" + "=" * 60)
print("检查结果汇总")
print("=" * 60)
print(f"总计检查文件: {total_files}")
print(f"发现重复问题: {len(duplicate_files)} 个")
if duplicate_files:
print("\n重复问题详情:")
for patient_id, count in duplicate_files:
print(f" - {patient_id}: {count} 个重复块")
else:
print("🎉 所有患者页面的回访话术都正常,没有重复问题!")
print("=" * 60)
def main():
"""主函数"""
check_callback_duplicates()
if __name__ == "__main__":
main()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json
# 加载患者数据
with open('合并结果.json', encoding='utf-8') as f:
data = json.load(f)
# 查找TS0K064355患者
found = False
for patient in data:
if patient.get('病历号') == 'TS0K064355':
print(f'✅ 找到患者: {patient.get("姓名", "未知")} - {patient.get("病历号")}')
found = True
break
if not found:
print('❌ 未找到病历号为TS0K064355的患者')
print('前10个患者的病历号:')
for i, patient in enumerate(data[:10]):
print(f' {i+1}. {patient.get("病历号", "未知")}')
# 查找包含064355的病历号
print('\n包含064355的病历号:')
for patient in data:
case_num = patient.get('病历号', '')
if '064355' in case_num:
print(f' - {case_num} ({patient.get("姓名", "未知")})')
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
检查回访话术生成进度
显示当前会话的详细状态信息
"""
import os
import pickle
from datetime import datetime
def check_progress():
"""检查当前进度"""
print("📊 检查回访话术生成进度...")
progress_dir = "progress_saves"
if not os.path.exists(progress_dir):
print("❌ 未找到进度保存目录")
return
session_files = []
for file_name in os.listdir(progress_dir):
if file_name.startswith('session_') and file_name.endswith('.pkl'):
file_path = os.path.join(progress_dir, file_name)
session_files.append((file_name, file_path, os.path.getmtime(file_path)))
if not session_files:
print("❌ 未找到任何保存的会话")
return
# 按修改时间排序
session_files.sort(key=lambda x: x[2], reverse=True)
print(f"\n📋 找到 {len(session_files)} 个保存的会话:")
print("=" * 60)
for i, (file_name, file_path, mtime) in enumerate(session_files, 1):
session_id = file_name[8:-4]
try:
with open(file_path, 'rb') as f:
state_data = pickle.load(f)
save_time = datetime.fromtimestamp(mtime).strftime('%Y-%m-%d %H:%M:%S')
print(f"🔹 会话 {i}: {session_id}")
print(f" 保存时间: {save_time}")
print(f" 当前诊所: {state_data.get('current_clinic', '未知')}")
print(f" 当前患者索引: {state_data.get('current_patient_index', 0) + 1}")
completed = state_data.get('completed_clinics', [])
print(f" 已完成诊所 ({len(completed)}): {', '.join(completed) if completed else '无'}")
config = state_data.get('generation_config', {})
print(f" API类型: {config.get('api_type', '未知')}")
print(f" 处理模式: {config.get('mode', '未知')}")
selected_clinics = config.get('selected_clinics', [])
remaining_clinics = [c for c in selected_clinics if c not in completed]
print(f" 剩余诊所 ({len(remaining_clinics)}): {', '.join(remaining_clinics) if remaining_clinics else '无'}")
# 计算进度百分比
total_clinics = len(selected_clinics)
completed_clinics = len(completed)
if total_clinics > 0:
progress_percent = (completed_clinics / total_clinics) * 100
print(f" 整体进度: {completed_clinics}/{total_clinics} ({progress_percent:.1f}%)")
print("-" * 60)
except Exception as e:
print(f"❌ 读取会话 {session_id} 失败: {e}")
print("-" * 60)
# 显示最新会话的详细信息
if session_files:
latest_file = session_files[0][1]
print(f"\n🔍 最新会话详细信息:")
try:
with open(latest_file, 'rb') as f:
state_data = pickle.load(f)
print(f"📅 开始时间: {state_data.get('start_time', '未知')}")
print(f"💾 最后保存: {state_data.get('save_timestamp', '未知')}")
# 显示当前诊所的处理结果
current_callbacks = state_data.get('current_clinic_callbacks', [])
if current_callbacks:
success_count = len([cb for cb in current_callbacks if cb.get('callback_type') == 'success'])
error_count = len([cb for cb in current_callbacks if cb.get('callback_type') == 'error'])
print(f"📊 当前诊所已处理: {len(current_callbacks)} 个患者")
print(f" ✅ 成功: {success_count} 个")
print(f" ❌ 失败: {error_count} 个")
# 显示所有已完成诊所的统计
all_results = state_data.get('all_results', {})
if all_results:
print(f"\n📈 已完成诊所统计:")
total_processed = 0
total_success = 0
total_failed = 0
for clinic_name, callbacks in all_results.items():
success = len([cb for cb in callbacks if cb.get('callback_type') == 'success'])
failed = len([cb for cb in callbacks if cb.get('callback_type') == 'error'])
total_processed += len(callbacks)
total_success += success
total_failed += failed
print(f" 🏥 {clinic_name}: {success}✅ / {failed}❌ (共{len(callbacks)}个)")
print(f"\n📊 总计统计:")
print(f" 总处理患者: {total_processed} 个")
print(f" 总成功: {total_success} 个")
print(f" 总失败: {total_failed} 个")
if total_processed > 0:
success_rate = (total_success / total_processed) * 100
print(f" 成功率: {success_rate:.1f}%")
except Exception as e:
print(f"❌ 读取详细信息失败: {e}")
if __name__ == "__main__":
check_progress()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
清除杜秋萍患者的测试回访记录数据
"""
import sqlite3
import json
from datetime import datetime
import os
def view_and_clear_test_data():
"""查看并清除测试数据"""
db_path = "callback_records.db"
if not os.path.exists(db_path):
print(f"❌ 数据库文件不存在: {db_path}")
return
try:
with sqlite3.connect(db_path) as conn:
cursor = conn.cursor()
# 检查表结构
cursor.execute("PRAGMA table_info(callback_records)")
columns = [row[1] for row in cursor.fetchall()]
print(f"📋 数据库表结构: {columns}")
# 查找杜秋萍相关的记录
print("\n🔍 查找杜秋萍患者的回访记录...")
# 尝试不同的查询方式
queries = [
"SELECT * FROM callback_records WHERE case_number LIKE '%杜秋萍%'",
"SELECT * FROM callback_records WHERE case_number LIKE '%TS0%'",
"SELECT * FROM callback_records WHERE case_number LIKE '%TEST%'",
"SELECT * FROM callback_records LIMIT 10"
]
for i, query in enumerate(queries, 1):
try:
cursor.execute(query)
records = cursor.fetchall()
print(f"\n📊 查询 {i}: {query}")
print(f"找到 {len(records)} 条记录")
for record in records:
print(f" - {record}")
except Exception as e:
print(f"查询 {i} 失败: {e}")
# 查找所有记录
print("\n📋 查看所有回访记录...")
try:
cursor.execute("SELECT * FROM callback_records")
all_records = cursor.fetchall()
print(f"总共有 {len(all_records)} 条记录")
if all_records:
print("\n前5条记录:")
for i, record in enumerate(all_records[:5], 1):
print(f" {i}. {record}")
# 询问是否清除所有测试数据
print(f"\n⚠️ 发现 {len(all_records)} 条记录,可能包含测试数据")
response = input("是否清除所有回访记录?(y/N): ")
if response.lower() == 'y':
cursor.execute("DELETE FROM callback_records")
conn.commit()
print("✅ 已清除所有回访记录")
# 验证清除结果
cursor.execute("SELECT COUNT(*) FROM callback_records")
count = cursor.fetchone()[0]
print(f"📊 清除后剩余记录数: {count}")
else:
print("❌ 取消清除操作")
except Exception as e:
print(f"查询所有记录失败: {e}")
except Exception as e:
print(f"❌ 数据库操作失败: {e}")
def clear_specific_patient_data(patient_name="杜秋萍"):
"""清除特定患者的回访记录"""
db_path = "callback_records.db"
if not os.path.exists(db_path):
print(f"❌ 数据库文件不存在: {db_path}")
return
try:
with sqlite3.connect(db_path) as conn:
cursor = conn.cursor()
# 查找包含患者姓名的记录
cursor.execute("SELECT * FROM callback_records WHERE case_number LIKE ?", (f'%{patient_name}%',))
records = cursor.fetchall()
if records:
print(f"🔍 找到 {len(records)} 条 {patient_name} 的记录:")
for record in records:
print(f" - {record}")
response = input(f"\n是否删除 {patient_name} 的所有回访记录?(y/N): ")
if response.lower() == 'y':
cursor.execute("DELETE FROM callback_records WHERE case_number LIKE ?", (f'%{patient_name}%',))
conn.commit()
print(f"✅ 已删除 {patient_name} 的所有回访记录")
else:
print("❌ 取消删除操作")
else:
print(f"📝 未找到 {patient_name} 的回访记录")
except Exception as e:
print(f"❌ 操作失败: {e}")
if __name__ == "__main__":
print("🧹 清除测试回访记录数据")
print("=" * 50)
print("选择操作:")
print("1. 查看并清除所有测试数据")
print("2. 清除杜秋萍患者的记录")
print("3. 清除所有回访记录")
choice = input("\n请选择 (1-3): ")
if choice == "1":
view_and_clear_test_data()
elif choice == "2":
clear_specific_patient_data("杜秋萍")
elif choice == "3":
db_path = "callback_records.db"
if os.path.exists(db_path):
try:
with sqlite3.connect(db_path) as conn:
cursor = conn.cursor()
cursor.execute("DELETE FROM callback_records")
conn.commit()
print("✅ 已清除所有回访记录")
except Exception as e:
print(f"❌ 清除失败: {e}")
else:
print("❌ 数据库文件不存在")
else:
print("❌ 无效选择")
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
门诊配置文件
定义所有门诊的基本信息和映射关系
"""
# 门诊映射配置 (基于实际数据分析结果)
CLINIC_MAPPING = {
'学前街门诊': {
'clinic_id': 'clinic_xuexian',
'clinic_name': '学前街门诊',
'json_file': '学前街门诊.json',
'folder_name': 'clinic_xuexian',
'description': '通善学前街门诊',
'expected_patients': 765
},
'大丰门诊': {
'clinic_id': 'clinic_dafeng',
'clinic_name': '大丰门诊',
'json_file': '诊所患者json/大丰门诊.json',
'folder_name': 'clinic_dafeng',
'description': '通善大丰门诊',
'expected_patients': 598
},
'东亭门诊': {
'clinic_id': 'clinic_dongting',
'clinic_name': '东亭门诊',
'json_file': '诊所患者json/东亭门诊.json',
'folder_name': 'clinic_dongting',
'description': '通善东亭门诊',
'expected_patients': 479
},
'河埒门诊': {
'clinic_id': 'clinic_helai',
'clinic_name': '河埒门诊',
'json_file': '诊所患者json/河埒门诊.json',
'folder_name': 'clinic_helai',
'description': '通善河埒门诊',
'expected_patients': 108
},
'红豆门诊': {
'clinic_id': 'clinic_hongdou',
'clinic_name': '红豆门诊',
'json_file': '诊所患者json/红豆门诊.json',
'folder_name': 'clinic_hongdou',
'description': '通善红豆门诊',
'expected_patients': 500
},
'惠山门诊': {
'clinic_id': 'clinic_huishan',
'clinic_name': '惠山门诊',
'json_file': '诊所患者json/惠山门诊.json',
'folder_name': 'clinic_huishan',
'description': '通善惠山门诊',
'expected_patients': 323
},
'马山门诊': {
'clinic_id': 'clinic_mashan',
'clinic_name': '马山门诊',
'json_file': '诊所患者json/马山门诊.json',
'folder_name': 'clinic_mashan',
'description': '通善马山门诊',
'expected_patients': 527
},
'通善口腔医院': {
'clinic_id': 'clinic_hospital',
'clinic_name': '通善口腔医院',
'json_file': '诊所患者json/通善口腔医院.json',
'folder_name': 'clinic_hospital',
'description': '通善口腔医院总院',
'expected_patients': 536
},
'新吴门诊': {
'clinic_id': 'clinic_xinwu',
'clinic_name': '新吴门诊',
'json_file': '诊所患者json/新吴门诊.json',
'folder_name': 'clinic_xinwu',
'description': '通善新吴门诊',
'expected_patients': 297
}
}
# 快速访问映射
CLINIC_ID_TO_NAME = {info['clinic_id']: info['clinic_name'] for info in CLINIC_MAPPING.values()}
CLINIC_ID_TO_JSON = {info['clinic_id']: info['json_file'] for info in CLINIC_MAPPING.values()}
CLINIC_ID_TO_FOLDER = {info['clinic_id']: info['folder_name'] for info in CLINIC_MAPPING.values()}
# 默认用户配置 (基于实际门诊用户)
DEFAULT_USERS = [
# 学前街门诊 (3名用户)
{
'username': 'jinqin',
'password': 'jinqin123',
'role': 'clinic_user',
'clinic_id': 'clinic_xuexian',
'real_name': '金沁',
'clinic_name': '学前街门诊'
},
{
'username': 'renshanshan',
'password': 'renshanshan123',
'role': 'clinic_user',
'clinic_id': 'clinic_xuexian',
'real_name': '任珊珊',
'clinic_name': '学前街门诊'
},
{
'username': 'shaojun',
'password': 'shaojun123',
'role': 'clinic_user',
'clinic_id': 'clinic_xuexian',
'real_name': '邵君',
'clinic_name': '学前街门诊'
},
# 新吴门诊 (1名用户)
{
'username': 'litingting',
'password': 'litingting123',
'role': 'clinic_user',
'clinic_id': 'clinic_xinwu',
'real_name': '李婷婷',
'clinic_name': '新吴门诊'
},
# 红豆门诊 (2名用户)
{
'username': 'maqiuyi',
'password': 'maqiuyi123',
'role': 'clinic_user',
'clinic_id': 'clinic_hongdou',
'real_name': '马秋怡',
'clinic_name': '红豆门诊'
},
{
'username': 'tangqimin',
'password': 'tangqimin123',
'role': 'clinic_user',
'clinic_id': 'clinic_hongdou',
'real_name': '唐其敏',
'clinic_name': '红豆门诊'
},
# 东亭门诊 (1名用户)
{
'username': 'yueling',
'password': 'yueling123',
'role': 'clinic_user',
'clinic_id': 'clinic_dongting',
'real_name': '岳玲',
'clinic_name': '东亭门诊'
},
# 马山门诊 (2名用户)
{
'username': 'jijunlin',
'password': 'jijunlin123',
'role': 'clinic_user',
'clinic_id': 'clinic_mashan',
'real_name': '季君林',
'clinic_name': '马山门诊'
},
{
'username': 'zhouliping',
'password': 'zhouliping123',
'role': 'clinic_user',
'clinic_id': 'clinic_mashan',
'real_name': '周丽萍',
'clinic_name': '马山门诊'
},
# 通善口腔医院总院 (2名用户)
{
'username': 'feimiaomiao',
'password': 'feimiaomiao123',
'role': 'clinic_user',
'clinic_id': 'clinic_hospital',
'real_name': '费苗妙',
'clinic_name': '通善口腔医院'
},
{
'username': 'chenxinyu',
'password': 'chenxinyu123',
'role': 'clinic_user',
'clinic_id': 'clinic_hospital',
'real_name': '陈心语',
'clinic_name': '通善口腔医院'
},
# 惠山门诊 (2名用户)
{
'username': 'yanghong',
'password': 'yanghong123',
'role': 'clinic_user',
'clinic_id': 'clinic_huishan',
'real_name': '杨红',
'clinic_name': '惠山门诊'
},
{
'username': 'panjinli',
'password': 'panjinli123',
'role': 'clinic_user',
'clinic_id': 'clinic_huishan',
'real_name': '潘金丽',
'clinic_name': '惠山门诊'
},
# 大丰门诊 (1名用户)
{
'username': 'chenlin',
'password': 'chenlin123',
'role': 'clinic_user',
'clinic_id': 'clinic_dafeng',
'real_name': '陈琳',
'clinic_name': '大丰门诊'
},
# 总部管理员 (最高权限)
{
'username': 'admin',
'password': 'admin123',
'role': 'admin',
'clinic_id': None,
'real_name': '系统管理员',
'clinic_name': '总部管理'
}
]
# 系统配置
SYSTEM_CONFIG = {
'total_clinics': len(CLINIC_MAPPING),
'total_expected_patients': sum(info['expected_patients'] for info in CLINIC_MAPPING.values()),
'base_url': '/patient_profiles',
'shared_resources_path': '/shared',
'admin_path': '/admin'
}
def get_clinic_info(clinic_id):
"""根据clinic_id获取门诊信息"""
for info in CLINIC_MAPPING.values():
if info['clinic_id'] == clinic_id:
return info
return None
def get_clinic_by_name(clinic_name):
"""根据门诊名称获取门诊信息"""
return CLINIC_MAPPING.get(clinic_name)
def get_all_clinic_ids():
"""获取所有门诊ID列表"""
return [info['clinic_id'] for info in CLINIC_MAPPING.values()]
def get_user_by_username(username):
"""根据用户名获取用户信息"""
for user in DEFAULT_USERS:
if user['username'] == username:
return user
return None
if __name__ == "__main__":
print("🏥 门诊配置信息:")
print(f"门诊总数: {SYSTEM_CONFIG['total_clinics']}")
print(f"预期患者总数: {SYSTEM_CONFIG['total_expected_patients']}")
print(f"用户总数: {len(DEFAULT_USERS)}")
print("\n📋 门诊列表:")
for clinic_id in get_all_clinic_ids():
info = get_clinic_info(clinic_id)
print(f" {info['clinic_name']} ({clinic_id}) - {info['expected_patients']} 人")
print("\n👥 用户列表:")
for user in DEFAULT_USERS:
clinic_name = user.get('clinic_name', '总部')
print(f" {user['username']} ({user['real_name']}) - {user['role']} - {clinic_name}")
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
完整修复方案
确保处理所有诊所的所有患者
"""
import os
import pickle
import shutil
from datetime import datetime
def complete_fix():
"""完整修复方案"""
print("🎯 完整修复方案")
print("="*60)
session_file = "progress_saves/session_20250805_232912.pkl"
if not os.path.exists(session_file):
print("❌ 未找到会话文件")
return
try:
# 1. 备份当前文件
backup_file = f"progress_saves/complete_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pkl"
shutil.copy2(session_file, backup_file)
print(f"✅ 已创建备份: {os.path.basename(backup_file)}")
# 2. 读取当前状态
with open(session_file, 'rb') as f:
state_data = pickle.load(f)
print("\n📊 当前状态分析:")
print(f" - 当前诊所: {state_data.get('current_clinic', '未知')}")
print(f" - 当前患者索引: {state_data.get('current_patient_index', 0)}")
print(f" - 已完成诊所: {state_data.get('completed_clinics', [])}")
current_callbacks = state_data.get('current_clinic_callbacks', [])
if current_callbacks:
print(f" - 当前诊所已处理: {len(current_callbacks)} 个患者")
# 3. 分析问题
print("\n🔍 问题分析:")
print(" 1. 红豆门诊: 500个患者完全丢失")
print(" 2. 通善口腔医院: 前89个缺失,已处理114个,后422个未处理")
print(" 3. 马山门诊: 527个患者未处理")
# 4. 提供修复方案
print("\n🔧 修复方案选择:")
print("1. 保守方案: 保存当前数据,按顺序重新处理")
print("2. 激进方案: 完全重新开始,确保数据完整性")
print("3. 智能方案: 保存已处理数据,补充缺失部分")
while True:
try:
choice = input("请选择方案 (1-3): ").strip()
if choice == "1":
conservative_fix(state_data)
break
elif choice == "2":
aggressive_fix(state_data)
break
elif choice == "3":
smart_complete_fix(state_data)
break
else:
print("请输入1-3")
except KeyboardInterrupt:
print("\n取消操作")
return
# 5. 保存修复后的状态
with open(session_file, 'wb') as f:
pickle.dump(state_data, f)
print("\n✅ 完整修复完成!")
except Exception as e:
print(f"❌ 修复失败: {e}")
import traceback
traceback.print_exc()
def conservative_fix(state_data):
"""保守方案"""
print("\n🛡️ 保守方案...")
# 保存当前通善口腔医院数据
current_callbacks = state_data.get('current_clinic_callbacks', [])
if current_callbacks:
if 'all_results' not in state_data:
state_data['all_results'] = {}
state_data['all_results']['通善口腔医院_部分'] = current_callbacks
print(f" ✅ 保存了通善口腔医院 {len(current_callbacks)} 个患者的处理结果")
# 设置回红豆门诊
state_data['current_clinic'] = '红豆门诊'
state_data['current_patient_index'] = 0
state_data['current_clinic_callbacks'] = []
# 确保红豆门诊不在已完成列表中
if '红豆门诊' in state_data.get('completed_clinics', []):
state_data['completed_clinics'].remove('红豆门诊')
# 确保通善口腔医院不在已完成列表中
if '通善口腔医院' in state_data.get('completed_clinics', []):
state_data['completed_clinics'].remove('通善口腔医院')
print(" ✅ 设置当前诊所为红豆门诊")
print(" 📋 处理顺序: 红豆门诊 → 通善口腔医院 → 马山门诊")
print(" 💡 通善口腔医院已处理的数据已保存,稍后可合并")
def aggressive_fix(state_data):
"""激进方案"""
print("\n⚡ 激进方案...")
# 清空所有数据,重新开始
state_data['current_clinic'] = '红豆门诊'
state_data['current_patient_index'] = 0
state_data['current_clinic_callbacks'] = []
state_data['completed_clinics'] = ['东亭门诊', '大丰门诊', '惠山门诊', '新吴门诊', '河埒门诊']
# 保留已完成诊所的数据
print(" ✅ 保留已完成诊所的数据")
print(" ✅ 重新开始处理剩余诊所")
print(" 📋 处理顺序: 红豆门诊 → 通善口腔医院 → 马山门诊")
def smart_complete_fix(state_data):
"""智能完整修复方案"""
print("\n🧠 智能完整修复方案...")
# 保存当前通善口腔医院数据
current_callbacks = state_data.get('current_clinic_callbacks', [])
if current_callbacks:
if 'all_results' not in state_data:
state_data['all_results'] = {}
state_data['all_results']['通善口腔医院_已处理'] = current_callbacks
print(f" ✅ 保存了通善口腔医院 {len(current_callbacks)} 个患者的处理结果")
# 设置回红豆门诊
state_data['current_clinic'] = '红豆门诊'
state_data['current_patient_index'] = 0
state_data['current_clinic_callbacks'] = []
# 确保红豆门诊不在已完成列表中
if '红豆门诊' in state_data.get('completed_clinics', []):
state_data['completed_clinics'].remove('红豆门诊')
# 确保通善口腔医院不在已完成列表中
if '通善口腔医院' in state_data.get('completed_clinics', []):
state_data['completed_clinics'].remove('通善口腔医院')
print(" ✅ 设置当前诊所为红豆门诊")
print(" 📋 处理顺序:")
print(" 1. 红豆门诊 (500个患者)")
print(" 2. 通善口腔医院 (前89个 + 后422个 = 511个患者)")
print(" 3. 马山门诊 (527个患者)")
print(" 💡 通善口腔医院已处理的数据已保存,稍后可合并")
def show_detailed_status():
"""显示详细状态"""
print("\n📊 详细状态分析:")
session_file = "progress_saves/session_20250805_232912.pkl"
if not os.path.exists(session_file):
print("❌ 未找到会话文件")
return
try:
with open(session_file, 'rb') as f:
state_data = pickle.load(f)
# 诊所患者数量
clinic_patients = {
'东亭门诊': 479,
'大丰门诊': 598,
'惠山门诊': 323,
'新吴门诊': 297,
'河埒门诊': 108,
'红豆门诊': 500,
'通善口腔医院': 536,
'马山门诊': 527
}
print("📋 各诊所状态:")
completed_clinics = state_data.get('completed_clinics', [])
current_clinic = state_data.get('current_clinic', '未知')
for clinic, patient_count in clinic_patients.items():
if clinic in completed_clinics:
print(f" ✅ {clinic}: {patient_count} 个患者 (已完成)")
elif clinic == current_clinic:
current_callbacks = state_data.get('current_clinic_callbacks', [])
print(f" 🔄 {clinic}: {patient_count} 个患者 (处理中: {len(current_callbacks)} 个)")
else:
print(f" ⏳ {clinic}: {patient_count} 个患者 (待处理)")
# 计算总体进度
total_patients = sum(clinic_patients.values())
completed_patients = sum(clinic_patients[clinic] for clinic in completed_clinics)
current_processed = len(state_data.get('current_clinic_callbacks', []))
print(f"\n📊 总体进度:")
print(f" - 总患者数: {total_patients} 个")
print(f" - 已完成: {completed_patients} 个")
print(f" - 当前处理: {current_processed} 个")
print(f" - 剩余: {total_patients - completed_patients - current_processed} 个")
if total_patients > 0:
progress_percent = ((completed_patients + current_processed) / total_patients) * 100
print(f" - 进度: {progress_percent:.1f}%")
except Exception as e:
print(f"❌ 读取状态失败: {e}")
if __name__ == "__main__":
print("="*60)
print("完整修复方案")
print("="*60)
# 显示详细状态
show_detailed_status()
# 询问是否修复
print("\n🔧 是否要执行完整修复?")
confirm = input("确认修复?(Y/n): ").strip().lower()
if confirm in ['', 'y', 'yes']:
complete_fix()
else:
print("取消修复")
\ No newline at end of file
-- 批量创建诊所用户SQL脚本
USE callback_system;
-- 学前街门诊用户
INSERT INTO users (username, password_hash, user_type, clinic_access, created_at, is_active)
VALUES
('jinqin', SHA2('jinqin123', 256), 'user', '["clinic_xuexian"]', NOW(), 1),
('renshanshan', SHA2('renshanshan123', 256), 'user', '["clinic_xuexian"]', NOW(), 1),
('shaojun', SHA2('shaojun123', 256), 'user', '["clinic_xuexian"]', NOW(), 1)
ON DUPLICATE KEY UPDATE
password_hash = VALUES(password_hash),
clinic_access = VALUES(clinic_access),
updated_at = NOW();
-- 新吴门诊用户
INSERT INTO users (username, password_hash, user_type, clinic_access, created_at, is_active)
VALUES
('litingting', SHA2('litingting123', 256), 'user', '["clinic_xinwu"]', NOW(), 1)
ON DUPLICATE KEY UPDATE
password_hash = VALUES(password_hash),
clinic_access = VALUES(clinic_access),
updated_at = NOW();
-- 红豆门诊用户
INSERT INTO users (username, password_hash, user_type, clinic_access, created_at, is_active)
VALUES
('maqiuyi', SHA2('maqiuyi123', 256), 'user', '["clinic_hongdou"]', NOW(), 1),
('tangqimin', SHA2('tangqimin123', 256), 'user', '["clinic_hongdou"]', NOW(), 1)
ON DUPLICATE KEY UPDATE
password_hash = VALUES(password_hash),
clinic_access = VALUES(clinic_access),
updated_at = NOW();
-- 东亭门诊用户
INSERT INTO users (username, password_hash, user_type, clinic_access, created_at, is_active)
VALUES
('yueling', SHA2('yueling123', 256), 'user', '["clinic_dongting"]', NOW(), 1)
ON DUPLICATE KEY UPDATE
password_hash = VALUES(password_hash),
clinic_access = VALUES(clinic_access),
updated_at = NOW();
-- 马山门诊用户
INSERT INTO users (username, password_hash, user_type, clinic_access, created_at, is_active)
VALUES
('jijunlin', SHA2('jijunlin123', 256), 'user', '["clinic_mashan"]', NOW(), 1),
('zhouliping', SHA2('zhouliping123', 256), 'user', '["clinic_mashan"]', NOW(), 1)
ON DUPLICATE KEY UPDATE
password_hash = VALUES(password_hash),
clinic_access = VALUES(clinic_access),
updated_at = NOW();
-- 总院用户
INSERT INTO users (username, password_hash, user_type, clinic_access, created_at, is_active)
VALUES
('feimiaomiao', SHA2('feimiaomiao123', 256), 'user', '["clinic_hospital"]', NOW(), 1),
('chenxinyu', SHA2('chenxinyu123', 256), 'user', '["clinic_hospital"]', NOW(), 1)
ON DUPLICATE KEY UPDATE
password_hash = VALUES(password_hash),
clinic_access = VALUES(clinic_access),
updated_at = NOW();
-- 惠山门诊用户
INSERT INTO users (username, password_hash, user_type, clinic_access, created_at, is_active)
VALUES
('yanghong', SHA2('yanghong123', 256), 'user', '["clinic_huishan"]', NOW(), 1),
('panjinli', SHA2('panjinli123', 256), 'user', '["clinic_huishan"]', NOW(), 1)
ON DUPLICATE KEY UPDATE
password_hash = VALUES(password_hash),
clinic_access = VALUES(clinic_access),
updated_at = NOW();
-- 大丰门诊用户
INSERT INTO users (username, password_hash, user_type, clinic_access, created_at, is_active)
VALUES
('chenlin', SHA2('chenlin123', 256), 'user', '["clinic_dafeng"]', NOW(), 1)
ON DUPLICATE KEY UPDATE
password_hash = VALUES(password_hash),
clinic_access = VALUES(clinic_access),
updated_at = NOW();
-- 查看创建结果
SELECT
username,
user_type,
clinic_access,
created_at,
is_active
FROM users
WHERE user_type = 'user'
ORDER BY created_at DESC;
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from callback_record_model import CallbackRecordManager, CallbackRecord
def create_test_callback_for_existing():
"""为现有患者TS0K030750创建测试回访记录"""
manager = CallbackRecordManager()
# 测试回访记录数据
test_records = [
{
'case_number': 'TS0K030750',
'callback_methods': ['打电话', '发微信'],
'callback_success': True,
'next_appointment_time': '2025-08-15 10:00',
'failure_reason': None,
'ai_feedback_type': None,
'callback_status': '已回访',
'operator': '张护士'
},
{
'case_number': 'TS0K030750',
'callback_methods': ['打电话'],
'callback_success': False,
'next_appointment_time': None,
'failure_reason': '患者电话无人接听/关机/停机',
'ai_feedback_type': 'AI话术内容不合适(过于专业或过于简单)',
'callback_status': '已回访',
'operator': '李医生'
},
{
'case_number': 'TS0K030750',
'callback_methods': ['发短信', '视频通话'],
'callback_success': True,
'next_appointment_time': '2025-08-20 14:30',
'failure_reason': None,
'ai_feedback_type': None,
'callback_status': '已回访',
'operator': '王助理'
}
]
print('📝 为TS0K030750创建测试回访记录...')
for i, data in enumerate(test_records, 1):
record = CallbackRecord(
case_number=data['case_number'],
callback_methods=data['callback_methods'],
callback_success=data['callback_success'],
operator=data['operator'],
next_appointment_time=data['next_appointment_time'],
failure_reason=data['failure_reason'],
ai_feedback_type=data['ai_feedback_type'],
callback_status=data['callback_status']
)
result = manager.save_record(record)
print(f'✅ 测试记录 {i}: 保存成功,ID: {result.record_id}')
print('🎉 测试数据创建完成!')
if __name__ == "__main__":
create_test_callback_for_existing()
\ No newline at end of file
USE callback_system;
-- 清空现有用户表
TRUNCATE TABLE users;
-- 创建管理员用户 (admin/admin123)
INSERT INTO users (username, password_hash, user_type, clinic_access, created_at, is_active)
VALUES ('admin', 'admin123', 'admin', '["all"]', NOW(), 1);
-- 学前街门诊用户
INSERT INTO users (username, password_hash, user_type, clinic_access, created_at, is_active) VALUES
('jinqin', 'jinqin123', 'user', '["clinic_xuexian"]', NOW(), 1),
('renshanshan', 'renshanshan123', 'user', '["clinic_xuexian"]', NOW(), 1),
('shaojun', 'shaojun123', 'user', '["clinic_xuexian"]', NOW(), 1);
-- 新吴门诊用户
INSERT INTO users (username, password_hash, user_type, clinic_access, created_at, is_active) VALUES
('litingting', 'litingting123', 'user', '["clinic_xinwu"]', NOW(), 1);
-- 红豆门诊用户
INSERT INTO users (username, password_hash, user_type, clinic_access, created_at, is_active) VALUES
('maqiuyi', 'maqiuyi123', 'user', '["clinic_hongdou"]', NOW(), 1),
('tangqimin', 'tangqimin123', 'user', '["clinic_hongdou"]', NOW(), 1);
-- 东亭门诊用户
INSERT INTO users (username, password_hash, user_type, clinic_access, created_at, is_active) VALUES
('yueling', 'yueling123', 'user', '["clinic_dongting"]', NOW(), 1);
-- 马山门诊用户
INSERT INTO users (username, password_hash, user_type, clinic_access, created_at, is_active) VALUES
('jijunlin', 'jijunlin123', 'user', '["clinic_mashan"]', NOW(), 1),
('zhouliping', 'zhouliping123', 'user', '["clinic_mashan"]', NOW(), 1);
-- 总院用户
INSERT INTO users (username, password_hash, user_type, clinic_access, created_at, is_active) VALUES
('feimiaomiao', 'feimiaomiao123', 'user', '["clinic_hospital"]', NOW(), 1),
('chenxinyu', 'chenxinyu123', 'user', '["clinic_hospital"]', NOW(), 1);
-- 惠山门诊用户
INSERT INTO users (username, password_hash, user_type, clinic_access, created_at, is_active) VALUES
('yanghong', 'yanghong123', 'user', '["clinic_huishan"]', NOW(), 1),
('panjinli', 'panjinli123', 'user', '["clinic_huishan"]', NOW(), 1);
-- 大丰门诊用户
INSERT INTO users (username, password_hash, user_type, clinic_access, created_at, is_active) VALUES
('chenlin', 'chenlin123', 'user', '["clinic_dafeng"]', NOW(), 1);
\ No newline at end of file
This diff is collapsed. Click to expand it.
[mysql]
host = localhost
port = 3306
user = root
password = 123456
database = callback_system
charset = utf8mb4
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
数据库配置管理
用于管理MySQL数据库连接配置
"""
import configparser
import os
from typing import Dict, Any
class DatabaseConfig:
"""数据库配置管理类"""
def __init__(self, config_file: str = "database_config.ini"):
"""
初始化配置管理器
Args:
config_file: 配置文件路径
"""
self.config_file = config_file
self.config = configparser.ConfigParser()
# 如果配置文件不存在,创建默认配置
if not os.path.exists(config_file):
self.create_default_config()
self.load_config()
def create_default_config(self):
"""创建默认配置文件"""
self.config['mysql'] = {
'host': 'localhost',
'port': '3306',
'user': 'root',
'password': '', # 用户需要填入实际密码
'database': 'callback_system',
'charset': 'utf8mb4'
}
with open(self.config_file, 'w', encoding='utf-8') as f:
f.write("""# 回访记录系统数据库配置文件
# 请根据您的MySQL数据库实际情况修改以下配置
""")
self.config.write(f)
print(f"已创建默认配置文件: {self.config_file}")
print("请编辑此文件,填入您的MySQL数据库连接信息")
def load_config(self):
"""加载配置文件"""
try:
self.config.read(self.config_file, encoding='utf-8')
except Exception as e:
print(f"加载配置文件失败: {e}")
raise
def get_mysql_config(self) -> Dict[str, Any]:
"""
获取MySQL配置
优先使用环境变量,其次使用配置文件
Returns:
MySQL连接配置字典
"""
# 优先使用环境变量
if os.getenv('DB_HOST'):
mysql_config = {
'host': os.getenv('DB_HOST'),
'port': int(os.getenv('DB_PORT', '3306')),
'user': os.getenv('DB_USER'),
'password': os.getenv('DB_PASSWORD', ''),
'database': os.getenv('DB_NAME'),
'charset': os.getenv('DB_CHARSET', 'utf8mb4')
}
print(f"使用Docker环境变量配置: {mysql_config['host']}:{mysql_config['port']}")
return mysql_config
# 使用配置文件
if 'mysql' not in self.config:
raise ValueError("配置文件中未找到mysql配置段")
mysql_config = dict(self.config['mysql'])
# 转换端口为整数
mysql_config['port'] = int(mysql_config.get('port', 3306))
return mysql_config
def validate_config(self) -> bool:
"""
验证配置是否完整
Returns:
配置有效返回True
"""
try:
config = self.get_mysql_config()
required_fields = ['host', 'port', 'user', 'database']
for field in required_fields:
if not config.get(field):
print(f"配置项 {field} 缺失或为空")
return False
if not config.get('password'):
print("警告:数据库密码为空,如果数据库需要密码,请在配置文件中设置")
return True
except Exception as e:
print(f"配置验证失败: {e}")
return False
def update_config(self, section: str, key: str, value: str):
"""
更新配置项
Args:
section: 配置段名
key: 配置项名
value: 配置值
"""
if section not in self.config:
self.config.add_section(section)
self.config[section][key] = value
with open(self.config_file, 'w', encoding='utf-8') as f:
self.config.write(f)
print(f"配置已更新: [{section}] {key} = {value}")
def main():
"""主函数 - 配置向导"""
print("=== 回访记录系统数据库配置向导 ===\n")
config_manager = DatabaseConfig()
if config_manager.validate_config():
print("✓ 配置文件验证通过")
config = config_manager.get_mysql_config()
print(f"当前配置:")
print(f" 主机: {config['host']}")
print(f" 端口: {config['port']}")
print(f" 用户: {config['user']}")
print(f" 数据库: {config['database']}")
print(f" 字符集: {config['charset']}")
else:
print("✗ 配置文件验证失败")
print(f"请编辑配置文件: {config_manager.config_file}")
# 提供交互式配置
print("\n是否要现在配置数据库连接信息?(y/n): ", end="")
if input().lower() == 'y':
print("\n请输入MySQL数据库连接信息:")
host = input("主机地址 (默认: localhost): ").strip() or "localhost"
port = input("端口 (默认: 3306): ").strip() or "3306"
user = input("用户名 (默认: root): ").strip() or "root"
password = input("密码: ").strip()
database = input("数据库名 (默认: callback_system): ").strip() or "callback_system"
# 更新配置
config_manager.update_config('mysql', 'host', host)
config_manager.update_config('mysql', 'port', port)
config_manager.update_config('mysql', 'user', user)
config_manager.update_config('mysql', 'password', password)
config_manager.update_config('mysql', 'database', database)
print("\n配置已保存!")
if __name__ == "__main__":
main()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
调试回访话术数据加载和匹配
"""
from generate_html import load_oral_callback_data
def test_callback_data():
"""测试回访话术数据加载"""
print("测试大丰门诊回访话术数据加载...")
# 加载大丰门诊的回访话术数据
oral_callback_data = load_oral_callback_data('大丰门诊')
print(f"加载的数据数量: {len(oral_callback_data)}")
# 测试特定患者ID
patient_id = 'TS0G040927'
patient_data = oral_callback_data.get(patient_id)
if patient_data:
print(f"找到患者 {patient_id} 的回访话术数据:")
print(f" 患者姓名: {patient_data.get('patient_name')}")
print(f" 年龄性别: {patient_data.get('age')}岁 {patient_data.get('gender')}")
print(f" 生成时间: {patient_data.get('generation_time')}")
print(f" 回访类型: {patient_data.get('callback_type')}")
print(f" 回访话术长度: {len(patient_data.get('callback_script', ''))}")
# 检查是否有回访话术内容
callback_script = patient_data.get('callback_script', '')
if callback_script:
print("✓ 回访话术内容存在")
else:
print("✗ 回访话术内容为空")
else:
print(f"未找到患者 {patient_id} 的回访话术数据")
print(f"可用的患者ID前5个: {list(oral_callback_data.keys())[:5]}")
if __name__ == "__main__":
test_callback_data()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from generate_html import generate_html
import os
def debug_generate():
print('当前工作目录:', os.getcwd())
print('patient_profiles目录是否存在:', os.path.exists('patient_profiles'))
try:
# 尝试生成页面
print('开始生成TS0K030750页面...')
generate_html('TS0K030750', {})
print('生成函数执行完毕')
# 检查文件是否存在
file_path = 'patient_profiles/TS0K030750.html'
if os.path.exists(file_path):
print(f'✅ 文件已生成: {file_path}')
print(f'文件大小: {os.path.getsize(file_path)} 字节')
else:
print(f'❌ 文件未生成: {file_path}')
except Exception as e:
print(f'❌ 生成过程出错: {e}')
import traceback
traceback.print_exc()
if __name__ == "__main__":
debug_generate()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json
import os
def debug_generate_detailed():
print('=== 调试generate_html函数 ===')
# 1. 检查患者数据
print('1. 加载患者数据...')
with open('合并结果.json', encoding='utf-8') as f:
data = json.load(f)
# 查找TS0K030750
patient_records = []
for record in data:
if record.get('病历号') == 'TS0K030750':
patient_records.append(record)
print(f'找到 {len(patient_records)} 条TS0K030750的记录')
if patient_records:
latest = patient_records[0]
print(f'患者姓名: {latest.get("姓名", "未知")}')
print(f'病历号: {latest.get("病历号", "未知")}')
else:
print('❌ 未找到TS0K030750的记录!')
return
# 2. 检查回访记录数据
print('\n2. 检查回访记录...')
from callback_record_model import CallbackRecordManager
manager = CallbackRecordManager()
callback_records = manager.get_records_by_case_number('TS0K030750')
print(f'找到 {len(callback_records)} 条回访记录')
# 3. 调用generate_html函数
print('\n3. 调用generate_html函数...')
from generate_html import generate_html
try:
generate_html('TS0K030750', patient_records)
print('✅ generate_html函数执行完毕')
# 检查文件是否生成
if os.path.exists('TS0K030750.html'):
print('✅ 文件已生成: TS0K030750.html')
file_size = os.path.getsize('TS0K030750.html')
print(f'文件大小: {file_size} 字节')
# 移动到正确位置
import shutil
if not os.path.exists('patient_profiles'):
os.makedirs('patient_profiles')
shutil.move('TS0K030750.html', 'patient_profiles/TS0K030750.html')
print('✅ 文件已移动到patient_profiles目录')
else:
print('❌ 文件未生成')
except Exception as e:
print(f'❌ 生成过程出错: {e}')
import traceback
traceback.print_exc()
if __name__ == "__main__":
debug_generate_detailed()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
调试进度文件
详细分析当前会话的状态信息
"""
import os
import pickle
import json
from datetime import datetime
def debug_progress():
"""详细调试进度文件"""
print("🔍 详细调试进度文件...")
progress_dir = "progress_saves"
session_file = os.path.join(progress_dir, "session_20250805_232912.pkl")
if not os.path.exists(session_file):
print("❌ 未找到会话文件")
return
try:
with open(session_file, 'rb') as f:
state_data = pickle.load(f)
print("="*60)
print("🔍 会话详细信息:")
print("="*60)
print(f"📅 会话ID: {state_data.get('session_id', '未知')}")
print(f"📅 开始时间: {state_data.get('start_time', '未知')}")
print(f"💾 最后保存: {state_data.get('save_timestamp', '未知')}")
print(f"🏥 当前诊所: {state_data.get('current_clinic', '未知')}")
print(f"👤 当前患者索引: {state_data.get('current_patient_index', 0)}")
print("\n📋 配置信息:")
config = state_data.get('generation_config', {})
print(f" - API类型: {config.get('api_type', '未知')}")
print(f" - 处理模式: {config.get('mode', '未知')}")
selected_clinics = config.get('selected_clinics', [])
print(f" - 选择的诊所 ({len(selected_clinics)}): {selected_clinics}")
completed_clinics = state_data.get('completed_clinics', [])
print(f"\n✅ 已完成诊所 ({len(completed_clinics)}): {completed_clinics}")
remaining_clinics = [c for c in selected_clinics if c not in completed_clinics]
print(f"⏳ 剩余诊所 ({len(remaining_clinics)}): {remaining_clinics}")
print("\n📊 当前诊所处理状态:")
current_callbacks = state_data.get('current_clinic_callbacks', [])
if current_callbacks:
success_count = len([cb for cb in current_callbacks if cb.get('callback_type') == 'success'])
error_count = len([cb for cb in current_callbacks if cb.get('callback_type') == 'error'])
print(f" - 当前诊所已处理: {len(current_callbacks)} 个患者")
print(f" - ✅ 成功: {success_count} 个")
print(f" - ❌ 失败: {error_count} 个")
# 显示最后几个处理的患者
if len(current_callbacks) > 0:
print(f"\n📋 最后5个处理的患者:")
last_5 = current_callbacks[-5:] if len(current_callbacks) >= 5 else current_callbacks
for i, cb in enumerate(last_5):
status = "✅" if cb.get('callback_type') == 'success' else "❌"
print(f" {len(current_callbacks) - len(last_5) + i + 1}. {cb.get('patient_name', '未知')} ({cb.get('patient_id', '未知')}) {status}")
else:
print(" - 当前诊所无处理记录")
print("\n📈 所有已完成诊所统计:")
all_results = state_data.get('all_results', {})
total_processed = 0
total_success = 0
total_failed = 0
for clinic_name, callbacks in all_results.items():
success = len([cb for cb in callbacks if cb.get('callback_type') == 'success'])
failed = len([cb for cb in callbacks if cb.get('callback_type') == 'error'])
total_processed += len(callbacks)
total_success += success
total_failed += failed
print(f" 🏥 {clinic_name}: {success}✅ / {failed}❌ (共{len(callbacks)}个)")
print(f"\n📊 总计统计:")
print(f" - 总处理患者: {total_processed} 个")
print(f" - 总成功: {total_success} 个")
print(f" - 总失败: {total_failed} 个")
if total_processed > 0:
success_rate = (total_success / total_processed) * 100
print(f" - 成功率: {success_rate:.1f}%")
# 检查红豆门诊的状态
print("\n🔍 红豆门诊状态分析:")
if "红豆门诊" in completed_clinics:
print(" ✅ 红豆门诊在已完成列表中")
else:
print(" ❌ 红豆门诊不在已完成列表中")
if "红豆门诊" in all_results:
red_callbacks = all_results["红豆门诊"]
red_success = len([cb for cb in red_callbacks if cb.get('callback_type') == 'success'])
red_failed = len([cb for cb in red_callbacks if cb.get('callback_type') == 'error'])
print(f" 📊 红豆门诊结果: {red_success}✅ / {red_failed}❌ (共{len(red_callbacks)}个)")
else:
print(" ❌ 红豆门诊不在结果列表中")
if state_data.get('current_clinic') == "红豆门诊":
print(" 🔄 当前正在处理红豆门诊")
else:
print(f" ℹ️ 当前处理诊所: {state_data.get('current_clinic', '未知')}")
# 检查是否有红豆门诊的中间结果文件
output_dir = "dify_callback_results"
red_files = []
if os.path.exists(output_dir):
for file_name in os.listdir(output_dir):
if "红豆门诊" in file_name:
red_files.append(file_name)
if red_files:
print(f" 📁 找到红豆门诊文件: {red_files}")
else:
print(" 📁 未找到红豆门诊中间结果文件")
except Exception as e:
print(f"❌ 读取进度文件失败: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
debug_progress()
\ No newline at end of file
#!/bin/bash
# 患者画像回访话术系统 - Docker快速部署脚本
set -e # 遇到错误立即退出
echo "🚀 患者画像回访话术系统 - Docker快速部署"
echo "================================================"
# 检查Docker环境
echo ""
echo "📋 检查Docker环境..."
if ! command -v docker &> /dev/null; then
echo "❌ 错误:未检测到Docker,请先安装Docker"
echo "安装指南:https://docs.docker.com/get-docker/"
exit 1
fi
if ! command -v docker-compose &> /dev/null; then
echo "❌ 错误:未检测到Docker Compose"
echo "安装指南:https://docs.docker.com/compose/install/"
exit 1
fi
echo "✅ Docker环境检查通过"
docker --version
docker-compose --version
# 停止现有服务
echo ""
echo "🔧 停止现有服务..."
docker-compose down || true
# 构建镜像
echo ""
echo "🏗️ 构建镜像..."
docker-compose build
# 启动服务
echo ""
echo "🚀 启动服务..."
docker-compose up -d
# 等待服务启动
echo ""
echo "⏳ 等待服务启动完成..."
sleep 10
# 检查服务状态
echo ""
echo "📊 检查服务状态..."
docker-compose ps
# 运行测试
echo ""
echo "🧪 运行部署测试..."
if command -v python3 &> /dev/null; then
python3 test_docker_deployment.py
elif command -v python &> /dev/null; then
python test_docker_deployment.py
else
echo "⚠️ 未找到Python,跳过自动测试"
echo "请手动访问 http://localhost:5000 验证部署"
fi
echo ""
echo "🎉 部署完成!"
echo ""
echo "🌐 访问地址:"
echo " 主应用:http://localhost:5000"
echo " 登录页面:http://localhost:5000/login"
echo " 患者画像:http://localhost:5000/patient_profiles/"
echo ""
echo "📖 更多信息请查看 README_DOCKER.md"
echo ""
# 询问是否打开浏览器
read -p "是否打开浏览器访问系统?(y/n): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
if command -v xdg-open &> /dev/null; then
xdg-open http://localhost:5000
elif command -v open &> /dev/null; then
open http://localhost:5000
else
echo "请手动打开浏览器访问:http://localhost:5000"
fi
fi
\ No newline at end of file
{
{
"generation_info": {
"platform": "Dify",
"api_type": "agent_chat",
"generation_time": "2025-07-31 16:10:56",
"total_count": 1,
"success_count": 1,
"error_count": 0
},
"callbacks": [
{
"patient_id": "TS0K064355",
"patient_name": "迟鹏领",
"age": 54,
"gender": "男",
"generation_time": "2025-07-31 16:10:56",
"callback_script": "好的,正在为您生成回访话术...\n\n**逻辑判断与流程选择:**\n1. **年龄判断**:患者年龄为54岁,大于13岁,执行“成人漏诊话术模板”。\n2. **漏诊项优先级判断**:患者存在“缺失牙”和“牙槽骨吸收”两项漏诊。“缺失牙”(优先级③)高于“牙槽骨吸收”(优先级④)。\n3. **最终方案**:本次话术将严格按照“成人漏诊话术模板”(4模块结构),并仅针对“缺失牙”这一项进行回访。\n\n---\n\n═══ 第一部分:开场白 ═══\n• 您好,我是江苏瑞泰通善口腔学前街医院的回访专员。\n• 孙吉卿医生特意交代我来关注您的后续情况。\n• (如果是熟悉患者可说:孙吉卿医生上次还和我提起您呢)\n• 您自从6月30号检查后,口腔情况怎么样?\n\n═══ 第二部分:告知漏诊项目 ═══\n• 上次来检查的时候,孙吉卿医生注意到您有缺失牙的情况。\n• 缺牙的地方如果不处理,旁边的牙齿可能会慢慢歪掉,时间一长,吃东西也会不太舒服。\n• 趁现在牙槽骨条件还不错,早点处理效果更好,也能更好地恢复咀嚼功能。\n• 孙吉卿医生说,这个问题早点看看会比较安心。\n\n═══ 第三部分:复查建议 ═══\n• 建议您方便的时候来院复查一下。\n• 让孙吉卿医生帮您再仔细看看。\n• 复查检查大概需要【30分钟左右】的时间,主要是了解一下您缺牙位置目前的情况。\n• 孙吉卿医生【时间段1】和【时间段2】这两个时间段有空,您看哪个时间比较方便?\n\n═══ 第四部分:结束回访语 ═══\n**预约成功:**\n• 好的,那我们【具体预约时间】见。\n• 那不打扰您了,祝您生活愉快。\n\n**预约不成功:**\n• 好的,那我下个星期再跟您联系。\n• 那不打扰您了,祝您生活愉快。",
"api_type": "agent_chat",
"has_missed_diagnosis": false,
"patient_data_summary": {
"last_visit_time": "2025-06-30 00:00:00",
"last_clinic": "江苏瑞泰通善口腔学前街医院",
"last_doctor": "孙吉卿",
"visit_count": 0
},
"callback_type": "success"
}
]
}
\ No newline at end of file
{
{
"generation_info": {
"platform": "Dify",
"api_type": "agent_chat",
"generation_time": "2025-07-31 16:24:11",
"total_count": 1,
"success_count": 1,
"error_count": 0
},
"callbacks": [
{
"patient_id": "TS0K064355",
"patient_name": "迟鹏领",
"age": 54,
"gender": "男",
"generation_time": "2025-07-31 16:24:11",
"callback_script": "好的,已收到您的指令。正在根据患者信息和漏诊项优先级进行逻辑判断。\n\n* **年龄判断**: 患者年龄为54岁,大于13岁,执行“成人漏诊话术模板”。\n* **漏诊项优先级判断**: 患者存在“缺失牙”和“牙槽骨吸收”两个漏诊项。根据优先级排序(缺失牙 > 牙槽骨吸收),本次话术将**仅针对“缺失牙”项目**进行,忽略“牙槽骨吸收”。\n* **模板确认**: 使用“成人漏诊话术模板”(4模块结构)。\n\n正在生成话术,请稍候。\n\n---\n\n═══ 第一部分:开场白 ═══\n* 您好,我是江苏瑞泰通善口腔学前街医院的回访专员,请问是迟先生吗?\n* 孙吉卿医生特意交代我来关注您的后续情况。\n* (如果是熟悉患者可说:孙吉卿医生上次还和我提起您呢)\n* 您自从6月30号检查后,口腔情况怎么样?\n\n═══ 第二部分:告知漏诊项目 ═══\n* 上次来检查的时候,孙吉卿医生注意到您有缺失牙的情况。\n* 缺牙的地方如果不处理,旁边的牙齿可能会慢慢歪掉,时间久了还会影响吃东西。\n* 其实早一点处理,比以后问题变得复杂了要省事省心很多。\n* 这个情况,孙吉卿医生也特别嘱咐我们提醒您一下,别忽略了。\n\n═══ 第三部分:复查建议 ═══\n* 建议您方便的时候来院复查一下。\n* 让孙吉卿医生帮您再仔细看看,了解一下缺牙位置目前的状况。\n* 复查检查大概需要【30分钟】的时间。\n* 孙吉卿医生【时间段1】和【时间段2】这两个时间段有空,您看哪个时间比较方便?\n\n═══ 第四部分:结束回访语 ═══\n**预约成功:**\n* 好的,那我们【具体预约时间】见。\n* 那不打扰您了,祝您生活愉快。\n\n**预约不成功:**\n* 好的,那我下个星期再跟您联系。\n* 那不打扰您了,祝您生活愉快。",
"api_type": "agent_chat",
"has_missed_diagnosis": false,
"patient_data_summary": {
"last_visit_time": "2025-06-30 00:00:00",
"last_clinic": "江苏瑞泰通善口腔学前街医院",
"last_doctor": "孙吉卿",
"visit_count": 0
},
"callback_type": "success"
}
]
}
\ No newline at end of file
{
{
"generation_info": {
"platform": "Dify",
"api_type": "agent_chat",
"generation_time": "2025-08-05 14:28:01",
"total_count": 3,
"success_count": 3,
"error_count": 0
},
"callbacks": [
{
"patient_id": "TS0K064355",
"patient_name": "迟鹏领",
"age": 54,
"gender": "男",
"generation_time": "2025-08-05 14:26:55",
"callback_script": "```json\n{\n \"selected_diagnosis\": \"缺失牙\"\n}\n```\n═══ 第一部分:开场白 ═══\n• 您好,我是瑞泰通善口腔的护士长小王。\n• 迟先生,孙吉卿医生特意交代我来关注您的后续情况。\n• 您自从2025年6月检查后,口腔情况怎么样?\n\n═══ 第二部分:告知漏诊项目 ═══\n• 上次来检查的时候,孙吉卿医生注意到您有缺失牙的情况。\n• 缺牙的地方如果不处理,旁边的牙齿可能会慢慢歪掉,影响您吃东西。\n• 趁现在早一点处理,比以后问题变得复杂时要省事也省心。\n• 这个情况,孙吉卿医生也特别嘱咐我们提醒您一下。\n\n═══ 第三部分:复查建议 ═══\n• 如果方便的话您看最近有没有时间来院复查一下。\n• 让孙吉卿医生帮您再仔细看看。\n• 【复查检查约30分钟,了解缺失牙位目前状况】\n• 孙吉卿医生【时间段1】和【时间段2】这两个时间段有空,您看哪个时间比较方便?\n\n═══ 第四部分:结束回访语 ═══\n[预约成功]\n• 好的,那我们【具体预约时间】见。\n• 那不打扰您了,祝您生活愉快。\n\n[预约不成功]\n• 好的,那我下个星期再跟您联系。\n• 好的那不打扰您了,祝您生活愉快。",
"api_type": "agent_chat",
"has_missed_diagnosis": false,
"patient_data_summary": {
"last_visit_time": "2025-06-30 00:00:00",
"last_clinic": "江苏瑞泰通善口腔学前街医院",
"last_doctor": "孙吉卿",
"visit_count": 0
},
"callback_type": "success"
},
{
"patient_id": "TS0M008666",
"patient_name": "钱明艳",
"age": 52,
"gender": "女",
"generation_time": "2025-08-05 14:27:26",
"callback_script": "```json\n{\n \"selected_diagnosis\": \"缺失牙\"\n}\n```\n═══ 第一部分:开场白 ═══\n• 您好,钱女士,我是瑞泰通善口腔的护士长小王。\n• 胡航医生特意交代我来关注您的后续情况。\n• 您自从去年6月30号检查后,口腔情况怎么样?\n\n═══ 第二部分:告知漏诊项目 ═══\n• 上次来检查的时候,胡航医生注意到您有缺失牙的情况。\n• 缺牙的地方如果不处理,旁边的牙齿可能会慢慢歪掉,影响咬合,吃东西也会不太舒服。\n• 趁现在牙槽骨条件还不错,早点处理效果更好,也避免将来多花功夫。\n• 这个情况,胡航医生也特别嘱咐我们提醒您一下,别忽略了,早点关注会更好。\n\n═══ 第三部分:复查建议 ═══\n• 如果方便的话您看最近有没有时间来院复查一下。\n• 让胡航医生帮您再仔细看看。\n• 复查检查约30分钟,了解缺失牙位目前状况。\n• 胡航医生【时间段1】和【时间段2】这两个时间段有空,您看哪个时间比较方便?\n\n═══ 第四部分:结束回访语 ═══\n**预约成功:**\n• 好的,那我们【具体预约时间】见。\n• 那不打扰您了,祝您生活愉快。\n\n**预约不成功:**\n• 好的,那我下个星期再跟您联系。\n• 好的那不打扰您了,祝您生活愉快。",
"api_type": "agent_chat",
"has_missed_diagnosis": false,
"patient_data_summary": {
"last_visit_time": "2025-06-30 00:00:00",
"last_clinic": "江苏瑞泰通善口腔学前街医院",
"last_doctor": "胡航",
"visit_count": 0
},
"callback_type": "success"
},
{
"patient_id": "TS0M008652",
"patient_name": "杜秋萍",
"age": 66,
"gender": "女",
"generation_time": "2025-08-05 14:28:01",
"callback_script": "```json\n{\n \"selected_diagnosis\": \"缺失牙\"\n}\n```\n═══ 第一部分:开场白 ═══\n• 您好,杜女士,我是瑞泰通善口腔的护士长小张。\n• 王程文医生特意交代我来关注您后续的情况。\n• 您自从去年6月检查后,口腔情况怎么样?\n\n═══ 第二部分:告知漏诊项目 ═══\n• 上次来检查的时候,王程文医生注意到您有缺失牙的情况。\n• 缺牙的地方如果不处理,旁边的牙齿可能会慢慢歪掉,时间一长,吃东西也会不太舒服。\n• 趁现在牙槽骨条件还不错,早点处理效果更好,也能保障咱们晚年的生活质量。\n• 王程文医生说,这个问题早点看看会比较安心。\n\n═══ 第三部分:复查建议 ═══\n• 如果方便的话您看最近有没有时间来院复查一下。\n• 让王程文医生帮您再仔细看看。\n• 复查检查约30分钟,了解缺失牙位目前状况。\n• 王程文医生【时间段1】和【时间段2】这两个时间段有空,您看哪个时间比较方便?\n\n═══ 第四部分:结束回访语 ═══\n预约成功:\n• 好的,那我们【具体预约时间】见。\n• 那不打扰您了,祝您生活愉快。\n预约不成功:\n• 好的,那我下个星期再跟您联系。\n• 好的那不打扰您了,祝您生活愉快。",
"api_type": "agent_chat",
"has_missed_diagnosis": false,
"patient_data_summary": {
"last_visit_time": "2025-06-30 00:00:00",
"last_clinic": "江苏瑞泰通善口腔学前街医院",
"last_doctor": "王程文",
"visit_count": 0
},
"callback_type": "success"
}
]
}
\ No newline at end of file
============================================================
============================================================
Dify平台回访话术批量生成结果
API类型: AGENT_CHAT
生成时间: 2025-07-31 16:10:56
总计处理: 1 个患者
成功生成: 1 个话术
生成失败: 0 个
============================================================
【1】患者: 迟鹏领 (TS0K064355)
年龄: 54岁, 性别: 男
最后就诊: 2025-06-30 00:00:00
就诊医生: 孙吉卿
就诊次数: 0次
----------------------------------------
回访话术:
好的,正在为您生成回访话术...
**逻辑判断与流程选择:**
1. **年龄判断**:患者年龄为54岁,大于13岁,执行“成人漏诊话术模板”。
2. **漏诊项优先级判断**:患者存在“缺失牙”和“牙槽骨吸收”两项漏诊。“缺失牙”(优先级③)高于“牙槽骨吸收”(优先级④)。
3. **最终方案**:本次话术将严格按照“成人漏诊话术模板”(4模块结构),并仅针对“缺失牙”这一项进行回访。
---
═══ 第一部分:开场白 ═══
• 您好,我是江苏瑞泰通善口腔学前街医院的回访专员。
• 孙吉卿医生特意交代我来关注您的后续情况。
• (如果是熟悉患者可说:孙吉卿医生上次还和我提起您呢)
• 您自从6月30号检查后,口腔情况怎么样?
═══ 第二部分:告知漏诊项目 ═══
• 上次来检查的时候,孙吉卿医生注意到您有缺失牙的情况。
• 缺牙的地方如果不处理,旁边的牙齿可能会慢慢歪掉,时间一长,吃东西也会不太舒服。
• 趁现在牙槽骨条件还不错,早点处理效果更好,也能更好地恢复咀嚼功能。
• 孙吉卿医生说,这个问题早点看看会比较安心。
═══ 第三部分:复查建议 ═══
• 建议您方便的时候来院复查一下。
• 让孙吉卿医生帮您再仔细看看。
• 复查检查大概需要【30分钟左右】的时间,主要是了解一下您缺牙位置目前的情况。
• 孙吉卿医生【时间段1】和【时间段2】这两个时间段有空,您看哪个时间比较方便?
═══ 第四部分:结束回访语 ═══
**预约成功:**
• 好的,那我们【具体预约时间】见。
• 那不打扰您了,祝您生活愉快。
**预约不成功:**
• 好的,那我下个星期再跟您联系。
• 那不打扰您了,祝您生活愉快。
============================================================
============================================================
============================================================
Dify平台回访话术批量生成结果
API类型: AGENT_CHAT
生成时间: 2025-07-31 16:24:11
总计处理: 1 个患者
成功生成: 1 个话术
生成失败: 0 个
============================================================
【1】患者: 迟鹏领 (TS0K064355)
年龄: 54岁, 性别: 男
最后就诊: 2025-06-30 00:00:00
就诊医生: 孙吉卿
就诊次数: 0次
----------------------------------------
回访话术:
好的,已收到您的指令。正在根据患者信息和漏诊项优先级进行逻辑判断。
* **年龄判断**: 患者年龄为54岁,大于13岁,执行“成人漏诊话术模板”。
* **漏诊项优先级判断**: 患者存在“缺失牙”和“牙槽骨吸收”两个漏诊项。根据优先级排序(缺失牙 > 牙槽骨吸收),本次话术将**仅针对“缺失牙”项目**进行,忽略“牙槽骨吸收”。
* **模板确认**: 使用“成人漏诊话术模板”(4模块结构)。
正在生成话术,请稍候。
---
═══ 第一部分:开场白 ═══
* 您好,我是江苏瑞泰通善口腔学前街医院的回访专员,请问是迟先生吗?
* 孙吉卿医生特意交代我来关注您的后续情况。
* (如果是熟悉患者可说:孙吉卿医生上次还和我提起您呢)
* 您自从6月30号检查后,口腔情况怎么样?
═══ 第二部分:告知漏诊项目 ═══
* 上次来检查的时候,孙吉卿医生注意到您有缺失牙的情况。
* 缺牙的地方如果不处理,旁边的牙齿可能会慢慢歪掉,时间久了还会影响吃东西。
* 其实早一点处理,比以后问题变得复杂了要省事省心很多。
* 这个情况,孙吉卿医生也特别嘱咐我们提醒您一下,别忽略了。
═══ 第三部分:复查建议 ═══
* 建议您方便的时候来院复查一下。
* 让孙吉卿医生帮您再仔细看看,了解一下缺牙位置目前的状况。
* 复查检查大概需要【30分钟】的时间。
* 孙吉卿医生【时间段1】和【时间段2】这两个时间段有空,您看哪个时间比较方便?
═══ 第四部分:结束回访语 ═══
**预约成功:**
* 好的,那我们【具体预约时间】见。
* 那不打扰您了,祝您生活愉快。
**预约不成功:**
* 好的,那我下个星期再跟您联系。
* 那不打扰您了,祝您生活愉快。
============================================================
============================================================
============================================================
Dify平台回访话术批量生成结果
API类型: AGENT_CHAT
生成时间: 2025-08-05 14:28:01
总计处理: 3 个患者
成功生成: 3 个话术
生成失败: 0 个
============================================================
【1】患者: 迟鹏领 (TS0K064355)
年龄: 54岁, 性别: 男
最后就诊: 2025-06-30 00:00:00
就诊医生: 孙吉卿
就诊次数: 0次
----------------------------------------
回访话术:
```json
{
"selected_diagnosis": "缺失牙"
}
```
═══ 第一部分:开场白 ═══
• 您好,我是瑞泰通善口腔的护士长小王。
• 迟先生,孙吉卿医生特意交代我来关注您的后续情况。
• 您自从2025年6月检查后,口腔情况怎么样?
═══ 第二部分:告知漏诊项目 ═══
• 上次来检查的时候,孙吉卿医生注意到您有缺失牙的情况。
• 缺牙的地方如果不处理,旁边的牙齿可能会慢慢歪掉,影响您吃东西。
• 趁现在早一点处理,比以后问题变得复杂时要省事也省心。
• 这个情况,孙吉卿医生也特别嘱咐我们提醒您一下。
═══ 第三部分:复查建议 ═══
• 如果方便的话您看最近有没有时间来院复查一下。
• 让孙吉卿医生帮您再仔细看看。
• 【复查检查约30分钟,了解缺失牙位目前状况】
• 孙吉卿医生【时间段1】和【时间段2】这两个时间段有空,您看哪个时间比较方便?
═══ 第四部分:结束回访语 ═══
[预约成功]
• 好的,那我们【具体预约时间】见。
• 那不打扰您了,祝您生活愉快。
[预约不成功]
• 好的,那我下个星期再跟您联系。
• 好的那不打扰您了,祝您生活愉快。
============================================================
【2】患者: 钱明艳 (TS0M008666)
年龄: 52岁, 性别: 女
最后就诊: 2025-06-30 00:00:00
就诊医生: 胡航
就诊次数: 0次
----------------------------------------
回访话术:
```json
{
"selected_diagnosis": "缺失牙"
}
```
═══ 第一部分:开场白 ═══
• 您好,钱女士,我是瑞泰通善口腔的护士长小王。
• 胡航医生特意交代我来关注您的后续情况。
• 您自从去年6月30号检查后,口腔情况怎么样?
═══ 第二部分:告知漏诊项目 ═══
• 上次来检查的时候,胡航医生注意到您有缺失牙的情况。
• 缺牙的地方如果不处理,旁边的牙齿可能会慢慢歪掉,影响咬合,吃东西也会不太舒服。
• 趁现在牙槽骨条件还不错,早点处理效果更好,也避免将来多花功夫。
• 这个情况,胡航医生也特别嘱咐我们提醒您一下,别忽略了,早点关注会更好。
═══ 第三部分:复查建议 ═══
• 如果方便的话您看最近有没有时间来院复查一下。
• 让胡航医生帮您再仔细看看。
• 复查检查约30分钟,了解缺失牙位目前状况。
• 胡航医生【时间段1】和【时间段2】这两个时间段有空,您看哪个时间比较方便?
═══ 第四部分:结束回访语 ═══
**预约成功:**
• 好的,那我们【具体预约时间】见。
• 那不打扰您了,祝您生活愉快。
**预约不成功:**
• 好的,那我下个星期再跟您联系。
• 好的那不打扰您了,祝您生活愉快。
============================================================
【3】患者: 杜秋萍 (TS0M008652)
年龄: 66岁, 性别: 女
最后就诊: 2025-06-30 00:00:00
就诊医生: 王程文
就诊次数: 0次
----------------------------------------
回访话术:
```json
{
"selected_diagnosis": "缺失牙"
}
```
═══ 第一部分:开场白 ═══
• 您好,杜女士,我是瑞泰通善口腔的护士长小张。
• 王程文医生特意交代我来关注您后续的情况。
• 您自从去年6月检查后,口腔情况怎么样?
═══ 第二部分:告知漏诊项目 ═══
• 上次来检查的时候,王程文医生注意到您有缺失牙的情况。
• 缺牙的地方如果不处理,旁边的牙齿可能会慢慢歪掉,时间一长,吃东西也会不太舒服。
• 趁现在牙槽骨条件还不错,早点处理效果更好,也能保障咱们晚年的生活质量。
• 王程文医生说,这个问题早点看看会比较安心。
═══ 第三部分:复查建议 ═══
• 如果方便的话您看最近有没有时间来院复查一下。
• 让王程文医生帮您再仔细看看。
• 复查检查约30分钟,了解缺失牙位目前状况。
• 王程文医生【时间段1】和【时间段2】这两个时间段有空,您看哪个时间比较方便?
═══ 第四部分:结束回访语 ═══
预约成功:
• 好的,那我们【具体预约时间】见。
• 那不打扰您了,祝您生活愉快。
预约不成功:
• 好的,那我下个星期再跟您联系。
• 好的那不打扰您了,祝您生活愉快。
============================================================
============================================================
============================================================
Dify平台回访话术批量生成结果
API类型: AGENT_CHAT
生成时间: 2025-08-05 14:32:22
总计处理: 5 个患者
成功生成: 5 个话术
生成失败: 0 个
============================================================
【1】患者: 迟鹏领 (TS0K064355)
年龄: 54岁, 性别: 男
最后就诊: 2025-06-30 00:00:00
就诊医生: 孙吉卿
就诊次数: 0次
----------------------------------------
回访话术:
```json
{
"selected_diagnosis": "缺失牙"
}
```
═══ 第一部分:开场白 ═══
• 您好,我是瑞泰通善口腔的护士长小张。
• 迟先生,您好。
• 孙吉卿医生特意交代我来关注您的后续情况。
• 您自从2025年6月30号检查后,口腔情况怎么样?
═══ 第二部分:告知漏诊项目 ═══
• 上次来检查的时候,孙吉卿医生注意到您有缺失牙的情况。
• 缺牙的地方如果不处理,旁边的牙齿可能会慢慢歪掉,对面的牙齿还可能伸长出来。
• 其实早一点处理,比以后复杂时省事也省心。
• 这个情况,孙吉卿医生也特别嘱咐我们提醒您一下。
═══ 第三部分:复查建议 ═══
• 如果方便的话您看最近有没有时间来院复查一下。
• 让孙吉卿医生帮您再仔细看看。
• 【复查检查约30分钟,了解缺失牙位目前状况】
• 孙吉卿医生【时间段1】和【时间段2】这两个时间段有空,您看哪个时间比较方便?
═══ 第四部分:结束回访语 ═══
**预约成功:**
• 好的,那我们【具体预约时间】见。
• 那不打扰您了,祝您生活愉快。
**预约不成功:**
• 好的,那我下个星期再跟您联系。
• 好的那不打扰您了,祝您生活愉快。
============================================================
【2】患者: 钱明艳 (TS0M008666)
年龄: 52岁, 性别: 女
最后就诊: 2025-06-30 00:00:00
就诊医生: 胡航
就诊次数: 0次
----------------------------------------
回访话术:
```json
{
"selected_diagnosis": "缺失牙"
}
```
═══ 第一部分:开场白 ═══
• 您好,我是瑞泰通善口腔的医生助理小王。
• 请问是钱女士吗?胡航医生特意交代我来关注您的后续情况。
• 您自从6月30号检查后,口腔情况怎么样?
═══ 第二部分:告知漏诊项目 ═══
• 上次来检查的时候,胡航医生注意到您有缺失牙的情况。
• 缺牙的地方如果不处理,旁边的牙齿可能会慢慢歪掉,对面的牙也会伸长,影响到正常的咬合。
• 趁现在牙槽骨条件还不错,早点关注,处理起来效果也会更好。
• 所以胡航医生也特别嘱咐我们提醒您一下,这个问题早点看看会比较安心。
═══ 第三部分:复查建议 ═══
• 如果方便的话您看最近有没有时间来院复查一下。
• 让胡航医生帮您再仔细看看。
• 【复查检查约30分钟,了解缺失牙位目前状况】
• 胡航医生【时间段1】和【时间段2】这两个时间段有空,您看哪个时间比较方便?
═══ 第四部分:结束回访语 ═══
预约成功:
• 好的,那我们【具体预约时间】见。
• 那不打扰您了,祝您生活愉快。
预约不成功:
• 好的,那我下个星期再跟您联系。
• 好的那不打扰您了,祝您生活愉快。
============================================================
【3】患者: 杜秋萍 (TS0M008652)
年龄: 66岁, 性别: 女
最后就诊: 2025-06-30 00:00:00
就诊医生: 王程文
就诊次数: 0次
----------------------------------------
回访话术:
```json
{
"selected_diagnosis": "缺失牙"
}
```
═══ 第一部分:开场白 ═══
• 您好,杜女士,我是瑞泰通善口腔的护士长小王。
• 王程文医生特意交代我来关注您后续的口腔情况。
• 您自从2025年6月那次检查后,口腔情况怎么样?
═══ 第二部分:告知漏诊项目 ═══
• 上次来检查的时候,王程文医生注意到您有缺失牙的情况。
• 缺牙的地方如果不处理,旁边的牙齿可能会慢慢歪掉,对面的牙齿也可能伸长出来,影响咬合。
• 趁现在牙槽骨条件还不错,早点关注这个问题,不仅能保护好邻牙,吃东西也能更踏实。
• 所以王程文医生也特别嘱咐我们提醒您一下,这个问题别忽略了,早点关注会更好。
═══ 第三部分:复查建议 ═══
• 如果方便的话您看最近有没有时间来院复查一下。
• 让王程文医生帮您再仔细看看。
• 【复查检查约30分钟,了解缺失牙位目前状况】
• 王程文医生【时间段1】和【时间段2】这两个时间段有空,您看哪个时间比较方便?
═══ 第四部分:结束回访语 ═══
预约成功:
• 好的,那我们【具体预约时间】见。
• 那不打扰您了,祝您生活愉快。
预约不成功:
• 好的,那我下个星期再跟您联系。
• 好的那不打扰您了,祝您生活愉快。
============================================================
【4】患者: 周蓉 (TS0K065136)
年龄: 38岁, 性别: 女
最后就诊: 2025-06-30 00:00:00
就诊医生: 沈佳丽
就诊次数: 0次
----------------------------------------
回访话术:
```json
{
"selected_diagnosis": "牙槽骨吸收"
}
```
═══ 第一部分:开场白 ═══
• 您好,我是瑞泰通善口腔的护士长小李。
• 周女士您好,沈佳丽医生特意交代我来关注您的后续情况。
• 您自从去年6月30号检查后,口腔情况怎么样?
═══ 第二部分:告知漏诊项目 ═══
• 上次来检查的时候,沈佳丽医生注意到您有牙槽骨吸收的情况。
• 这个问题如果一直拖着,可能会出现牙齿松动、牙缝变大的情况,影响您吃东西。
• 趁现在问题还不算严重,早点关注,把情况稳住,比以后复杂了再处理要省心很多。
• 这个情况,沈佳丽医生也特别嘱咐我们提醒您一下,别忽略了。
═══ 第三部分:复查建议 ═══
• 如果方便的话您看最近有没有时间来院复查一下。
• 让沈佳丽医生帮您再仔细看看,评估一下现状。
• 复查检查约30-45分钟,需要仔细检查牙周健康状况。
• 沈佳丽医生【时间段1】和【时间段2】这两个时间段有空,您看哪个时间比较方便?
═══ 第四部分:结束回访语 ═══
**预约成功:**
• 好的,那我们【具体预约时间】见。
• 那不打扰您了,祝您生活愉快。
**预约不成功:**
• 好的,那我下个星期再跟您联系。
• 好的那不打扰您了,祝您生活愉快。
============================================================
【5】患者: 陆志毅 (TS0M008662)
年龄: 64岁, 性别: 男
最后就诊: 2025-06-30 00:00:00
就诊医生: 蒋亚萍
就诊次数: 0次
----------------------------------------
回访话术:
```json
{
"selected_diagnosis": "缺失牙"
}
```
═══ 第一部分:开场白 ═══
• 您好,陆先生,我是瑞泰通善口腔的健康助理小王。
• 蒋亚萍医生特意交代我来关注您的后续情况。
• 您自从2025年6月检查后,口腔情况怎么样?
═══ 第二部分:告知漏诊项目 ═══
• 上次来检查的时候,蒋亚萍医生注意到您有缺失牙的情况。
• 缺牙的地方如果不处理,旁边的牙齿可能会慢慢歪掉,时间一长,吃东西也会不太舒服。
• 趁现在早点关注,及时修复,既能保护邻牙,也能让您以后吃东西更省心。
• 这个情况,蒋亚萍医生也特别嘱咐我们提醒您一下,早点看看会比较安心。
═══ 第三部分:复查建议 ═══
• 如果方便的话您看最近有没有时间来院复查一下。
• 让蒋亚萍医生帮您再仔细看看。
• 复查检查约30分钟,了解缺失牙位目前状况。
• 蒋亚萍医生【时间段1】和【时间段2】这两个时间段有空,您看哪个时间比较方便?
═══ 第四部分:结束回访语 ═══
预约成功:
• 好的,那我们【具体预约时间】见。
• 那不打扰您了,祝您生活愉快。
预约不成功:
• 好的,那我下个星期再跟您联系。
• 好的那不打扰您了,祝您生活愉快。
============================================================
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment