Commit 5ff33ce4 by luoqi

fix:阶段性清理文件

parent c12e44e8
test_job:
script: "echo 'Hello World'"
only:
- master
\ No newline at end of file
# 最简单的GitLab CI/CD配置
test_job:
script:
- echo "Hello World"
only:
- master
\ No newline at end of file
# 简化版GitLab CI/CD配置 - 用于测试
stages:
- test
simple_test:
stage: test
image: alpine:latest
# 移除标签限制,使用任何可用的Runner
# tags:
# - jarvis
script:
- echo "🚀 开始测试部署..."
- echo "📅 当前时间: $(date)"
- echo "🏥 项目: 患者画像回访话术系统"
- echo "✅ 测试成功!"
only:
- master
when: always
\ No newline at end of file
# 最简单的测试配置
test_job:
script:
- echo "Hello World"
- echo "测试成功"
only:
- master
\ No newline at end of file
# 患者画像回访话术系统 Dockerfile
FROM python:3.9-slim
FROM python:3.11-slim
# 设置工作目录
WORKDIR /app
......@@ -26,7 +26,7 @@ RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# 创建必要的目录
RUN mkdir -p patient_profiles progress_saves dify_callback_results
RUN mkdir -p progress_saves dify_callback_results
# 设置权限
RUN chmod +x *.py
......
# Docker启动成功说明
++ /dev/null
# 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部署文件清单
++ /dev/null
# 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
# Docker部署问题修复完成说明
++ /dev/null
# Docker部署问题修复完成说明
## 问题描述
在将患者画像回访话术系统部署到服务器时,遇到以下问题:
1. **MySQL容器配置文件权限问题**
- 错误信息:`World-writable config file '/etc/mysql/conf.d/mysql.cnf' is ignored.`
- 原因:在Windows系统上,MySQL配置文件挂载到Docker容器时权限设置不正确
2. **数据库字符编码问题**
- 中文字符显示为问号(`3F3F3F`
- API返回的数据中中文内容编码异常
- 数据库表注释显示为问号
## 解决方案
### 1. 修复MySQL配置文件权限问题
**问题原因**
- Windows系统上的文件权限与Linux容器不兼容
- MySQL安全机制拒绝加载权限过宽的配置文件
**解决方案**
- 移除了MySQL配置文件的直接挂载
- 改为通过Docker Compose环境变量和启动命令来设置字符集
**修改内容**
```yaml
# 在docker-compose.yml中
mysql:
environment:
MYSQL_CHARACTER_SET_SERVER: utf8mb4
MYSQL_COLLATION_SERVER: utf8mb4_unicode_ci
command: >
--default-authentication-plugin=mysql_native_password
--character-set-server=utf8mb4
--collation-server=utf8mb4_unicode_ci
--init-connect='SET NAMES utf8mb4'
--skip-character-set-client-handshake
```
### 2. 优化数据库连接配置
**修改内容**
-`callback_record_mysql.py`中增强了数据库连接配置
- 添加了更多字符集相关的参数
- 确保所有连接都使用正确的UTF-8编码
**关键配置**
```python
self.config = {
'host': host,
'port': port,
'user': user,
'password': password,
'database': database,
'charset': charset,
'autocommit': True,
'use_unicode': True,
'init_command': 'SET NAMES utf8mb4',
'sql_mode': 'STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO',
'read_timeout': 60,
'write_timeout': 60,
'connect_timeout': 60
}
```
### 3. 创建修复和验证脚本
**创建的文件**
- `fix_database_encoding_final.py` - 最终数据库编码修复脚本
- `test_encoding.py` - 字符编码测试脚本
**功能**
- 验证数据库字符集设置
- 测试中文数据的存储和读取
- 确保API正确处理中文内容
## 修复效果
### 1. MySQL容器状态
- ✅ 容器启动正常,无配置文件权限警告
- ✅ 所有字符集正确设置为`utf8mb4`
- ✅ 数据库连接稳定
### 2. 数据编码
- ✅ 中文字符正确存储和显示
- ✅ API返回正确的中文数据
- ✅ 数据库表注释正常显示
### 3. 系统功能
- ✅ 回访记录保存功能正常
- ✅ 数据查询和显示功能正常
- ✅ 前端页面正常显示中文内容
## 验证结果
### 数据库字符集设置
```
character_set_client: utf8mb4
character_set_connection: utf8mb4
character_set_database: utf8mb4
character_set_results: utf8mb4
character_set_server: utf8mb4
```
### 测试数据验证
- 测试数据:`TEST_FINAL`
- 回访方式:`["打电话", "发短信"]`
- 回访结果:`成功`
- 操作员:`系统用户`
- 状态:✅ 数据编码正常
### API测试
- 健康检查:✅ 正常
- 数据保存:✅ 正常
- 数据查询:✅ 正常
- 中文编码:✅ 正常
## 部署建议
### 1. 服务器部署
- 确保服务器支持UTF-8编码
- 使用相同的Docker Compose配置
- 定期备份数据库数据
### 2. 监控和维护
- 监控容器健康状态
- 定期检查数据库字符集设置
- 备份重要的回访记录数据
### 3. 故障排除
- 如果遇到编码问题,运行`fix_database_encoding_final.py`脚本
- 检查容器日志:`docker-compose logs mysql`
- 验证数据库连接:`docker-compose exec mysql mysql -u callback_user -pdev_password_123 callback_system`
## 总结
通过以上修复措施,成功解决了Docker部署到服务器时的MySQL容器错误和数据库字符编码问题。系统现在可以:
1. 正确处理中文字符
2. 稳定运行在Docker容器中
3. 提供完整的回访记录功能
4. 支持服务器部署
所有功能已经验证正常,可以安全部署到生产环境。
\ No newline at end of file
# GitLab环境变量配置说明
++ /dev/null
# GitLab环境变量配置说明
## 🚨 重要提醒
**在GitLab中设置这些环境变量后,CI/CD才能正常备份生产环境数据库!**
## 📋 需要在GitLab中设置的环境变量
### 1. SSH连接相关变量
在GitLab项目设置 → CI/CD → Variables 中添加:
| 变量名 | 说明 | 示例值 | 是否保护 |
|--------|------|--------|----------|
| `SSH_PRIVATE_KEY` | SSH私钥内容 | `-----BEGIN OPENSSH PRIVATE KEY-----...` | ✅ 是 |
| `SSH_KNOWN_HOSTS` | 服务器公钥指纹 | `server_ip ssh-rsa AAAAB3...` | ✅ 是 |
| `SSH_HOST` | 生产服务器IP地址 | `192.168.1.100` | ❌ 否 |
| `SSH_PORT` | SSH端口号 | `22` | ❌ 否 |
| `SSH_USER` | SSH用户名 | `root` | ❌ 否 |
### 2. 数据库连接相关变量
| 变量名 | 说明 | 示例值 | 是否保护 |
|--------|------|--------|----------|
| `DB_HOST` | 数据库主机地址 | `localhost``127.0.0.1` | ❌ 否 |
| `DB_PORT` | 数据库端口号 | `3306` | ❌ 否 |
| `DB_USER` | 数据库用户名 | `callback_user` | ❌ 否 |
| `DB_NAME` | 数据库名称 | `callback_system` | ❌ 否 |
| `DB_PASSWORD` | 数据库密码 | `your_password_here` | ✅ 是 |
## 🔧 设置步骤详解
### 步骤1:进入GitLab项目设置
1. 打开你的GitLab项目
2. 点击左侧菜单 `Settings``CI/CD`
3. 找到 `Variables` 部分
### 步骤2:添加SSH私钥
1. 点击 `Add variable`
2. 变量名:`SSH_PRIVATE_KEY`
3. 变量值:粘贴你的SSH私钥内容
4. 勾选 `Protected``Masked`
5. 点击 `Add variable`
### 步骤3:添加服务器公钥指纹
1. 点击 `Add variable`
2. 变量名:`SSH_KNOWN_HOSTS`
3. 变量值:`your_server_ip ssh-rsa your_public_key_fingerprint`
4. 勾选 `Protected`
5. 点击 `Add variable`
### 步骤4:添加服务器连接信息
1. 点击 `Add variable`
2. 变量名:`SSH_HOST`
3. 变量值:你的生产服务器IP地址
4. 点击 `Add variable`
重复上述步骤添加:
- `SSH_PORT`:SSH端口(通常是22)
- `SSH_USER`:SSH用户名
### 步骤5:添加数据库连接信息
1. 点击 `Add variable`
2. 变量名:`DB_HOST`
3. 变量值:数据库主机地址
4. 点击 `Add variable`
重复上述步骤添加:
- `DB_PORT`:数据库端口
- `DB_USER`:数据库用户名
- `DB_NAME`:数据库名称
- `DB_PASSWORD`:数据库密码(勾选Protected和Masked)
## 🔍 如何获取SSH公钥指纹
在生产服务器上执行:
```bash
# 方法1:查看SSH服务公钥
cat /etc/ssh/ssh_host_rsa_key.pub
# 方法2:使用ssh-keyscan获取
ssh-keyscan -H your_server_ip
# 方法3:手动连接获取
ssh -o StrictHostKeyChecking=no your_user@your_server_ip
```
## 📊 环境变量检查清单
- [ ] `SSH_PRIVATE_KEY` - SSH私钥(Protected + Masked)
- [ ] `SSH_KNOWN_HOSTS` - 服务器公钥指纹(Protected)
- [ ] `SSH_HOST` - 服务器IP地址
- [ ] `SSH_PORT` - SSH端口号
- [ ] `SSH_USER` - SSH用户名
- [ ] `DB_HOST` - 数据库主机地址
- [ ] `DB_PORT` - 数据库端口号
- [ ] `DB_USER` - 数据库用户名
- [ ] `DB_NAME` - 数据库名称
- [ ] `DB_PASSWORD` - 数据库密码(Protected + Masked)
## 🚀 测试部署
设置完环境变量后:
1. **推送代码到master分支**
2. **GitLab自动触发CI/CD**
3. **自动备份生产环境数据库**
4. **自动部署新代码**
5. **自动执行安全数据导入**
## 🎯 预期结果
部署成功后,你将看到:
- ✅ 数据库自动备份到 `/backup/database/` 目录
- ✅ 新代码自动部署到生产环境
- ✅ 患者数据安全更新
- ✅ 容器状态检查完成
## 🆘 常见问题
### Q: 为什么SSH连接失败?
A: 检查 `SSH_PRIVATE_KEY``SSH_HOST``SSH_PORT``SSH_USER` 是否正确设置
### Q: 为什么数据库备份失败?
A: 检查 `DB_HOST``DB_PORT``DB_USER``DB_NAME``DB_PASSWORD` 是否正确设置
### Q: 如何查看部署日志?
A: 在GitLab项目页面 → CI/CD → Pipelines 中查看详细日志
## 📞 需要帮助?
如果遇到问题,请检查:
1. 环境变量是否正确设置
2. SSH连接是否正常
3. 数据库连接是否正常
4. 服务器权限是否足够
\ No newline at end of file
# MySQL回访记录系统使用指南
++ /dev/null
# 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脚本执行完成说明
++ /dev/null
# 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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
批量修复所有现有患者详情页面的API_BASE_URL设置
"""
import os
import re
import glob
def fix_single_file(file_path):
"""修复单个HTML文件"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# 检查是否已经有API_BASE_URL设置
if 'window.API_BASE_URL' in content:
return False, "已存在API_BASE_URL"
# 查找插入点
pattern = r'(\s*<script>\s*\n\s*tailwind\.config\s*=\s*{)'
match = re.search(pattern, content)
if match:
# 在tailwind.config之前插入API_BASE_URL设置
api_code = """ // 设置API基础URL - 根据当前页面端口动态配置
const currentPort = window.location.port || (window.location.protocol === 'https:' ? '443' : '80');
const apiPort = currentPort === '5001' ? '5001' : '4002'; // 本地开发用5001,Docker用4002
window.API_BASE_URL = `http://localhost:${apiPort}`;
"""
new_content = content.replace(
match.group(1),
f" <script>\n{api_code}tailwind.config = {{"
)
with open(file_path, 'w', encoding='utf-8') as f:
f.write(new_content)
return True, "修复成功"
else:
return False, "未找到插入点"
except Exception as e:
return False, f"处理失败: {e}"
def batch_fix_api_url():
"""批量修复所有患者详情页面"""
print("🚀 开始批量修复患者详情页面的API_BASE_URL设置")
print("=" * 60)
# 查找所有患者详情页面
pattern = "patient_profiles/*/patients/*.html"
html_files = glob.glob(pattern)
print(f"📊 找到 {len(html_files)} 个患者详情页面")
success_count = 0
skip_count = 0
error_count = 0
for file_path in html_files:
# 获取患者ID和门诊名称
parts = file_path.split('/')
clinic_name = parts[1] if len(parts) > 1 else "未知门诊"
patient_file = parts[-1] if len(parts) > 0 else "未知患者"
patient_id = patient_file.replace('.html', '')
success, message = fix_single_file(file_path)
if success:
success_count += 1
print(f" ✅ {clinic_name}/{patient_id}: {message}")
elif "已存在" in message:
skip_count += 1
print(f" ⏭️ {clinic_name}/{patient_id}: {message}")
else:
error_count += 1
print(f" ❌ {clinic_name}/{patient_id}: {message}")
print("\n" + "=" * 60)
print(f"🎉 批量修复完成!")
print(f"📊 统计结果:")
print(f" ✅ 修复成功: {success_count} 个")
print(f" ⏭️ 跳过(已存在): {skip_count} 个")
print(f" ❌ 修复失败: {error_count} 个")
print(f" 📁 总文件数: {len(html_files)} 个")
def test_specific_files():
"""测试特定文件"""
print("\n🧪 测试特定患者文件...")
test_files = [
"patient_profiles/clinic_xuexian/patients/TS0K064355.html",
"patient_profiles/clinic_dafeng/patients/TS0G000356.html" if os.path.exists("patient_profiles/clinic_dafeng/patients/TS0G000356.html") else None
]
for file_path in test_files:
if file_path and os.path.exists(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
if 'window.API_BASE_URL' in content:
# 提取API_BASE_URL的值
pattern = r"window\.API_BASE_URL\s*=\s*'([^']+)'"
match = re.search(pattern, content)
if match:
api_url = match.group(1)
print(f" ✅ {file_path}: API_BASE_URL = {api_url}")
else:
print(f" ❌ {file_path}: 无法解析API_BASE_URL值")
else:
print(f" ❌ {file_path}: 缺少API_BASE_URL设置")
if __name__ == '__main__':
# 执行批量修复
batch_fix_api_url()
# 测试特定文件
test_specific_files()
print("\n💡 提示:")
print(" - 现在所有患者详情页面都应该包含API_BASE_URL设置")
print(" - 访问患者详情页面时会自动调用回访记录API")
print(" - 测试地址: http://localhost:4002/patient_profiles/clinic_xuexian/patients/TS0K064355.html")
print(" - 打开浏览器开发者工具查看网络请求")
\ 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 -*-
"""
检查陈心语用户配置
"""
from clinic_config import DEFAULT_USERS, get_user_by_username
def main():
print("🔍 检查陈心语用户配置")
print("=" * 50)
# 查找陈心语用户
chenxinyu = get_user_by_username('chenxinyu')
if chenxinyu:
print("✅ 找到陈心语用户配置:")
print(f" 用户名: {chenxinyu['username']}")
print(f" 真实姓名: {chenxinyu['real_name']}")
print(f" 角色: {chenxinyu['role']}")
print(f" 诊所ID: {chenxinyu['clinic_id']}")
print(f" 诊所名称: {chenxinyu['clinic_name']}")
print(f" 密码: {chenxinyu['password']}")
# 检查权限
if chenxinyu['clinic_id'] == 'clinic_helai':
print("\n✅ 权限已正确调整到河埒诊所")
else:
print(f"\n❌ 权限还是 {chenxinyu['clinic_id']},需要调整")
else:
print("❌ 未找到陈心语用户配置")
# 显示所有用户
print(f"\n👥 所有用户列表 ({len(DEFAULT_USERS)} 个):")
for user in DEFAULT_USERS:
clinic_name = user.get('clinic_name', '总部')
print(f" {user['username']} ({user['real_name']}) - {user['role']} - {clinic_name}")
if __name__ == "__main__":
main()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
直接检查数据库中的回访记录
"""
from callback_record_mysql import MySQLCallbackRecordManager
def check_database_records():
"""检查数据库中的回访记录"""
print("🔍 检查MySQL数据库中的回访记录")
print("=" * 50)
try:
# 连接数据库
manager = MySQLCallbackRecordManager()
print(f"✅ 数据库连接成功")
print(f"📡 连接信息: {manager.config['host']}:{manager.config['port']}")
print(f"🗄️ 数据库: {manager.config['database']}")
# 获取所有记录
all_records = manager.get_all_records()
print(f"\n📊 数据库中总记录数: {len(all_records)}")
if len(all_records) == 0:
print("❌ 数据库中没有任何回访记录!")
print("💡 这可能是问题的根源:数据库为空或连接错误")
return
# 分析病历号格式
case_numbers = [record.case_number for record in all_records]
unique_case_numbers = set(case_numbers)
print(f"📋 唯一病历号数量: {len(unique_case_numbers)}")
# 查找TS0M开头的病历号
ts0m_numbers = [cn for cn in unique_case_numbers if cn.startswith('TS0M')]
print(f"🎯 TS0M开头的病历号: {len(ts0m_numbers)} 个")
# 查找特定患者
target_patient = 'TS0M008652'
exact_match = [record for record in all_records if record.case_number == target_patient]
print(f"\n🔍 查找患者 {target_patient}:")
print(f" 精确匹配: {len(exact_match)} 条记录")
if exact_match:
print(f" ✅ 找到记录!")
for i, record in enumerate(exact_match):
print(f" 记录{i+1}: ID={record.record_id}, 结果={record.callback_result}")
print(f" 时间={record.create_time}, 操作员={record.operator}")
else:
print(f" ❌ 未找到精确匹配的记录")
# 模糊匹配
fuzzy_match = [record for record in all_records if target_patient in record.case_number or record.case_number in target_patient]
print(f" 🔍 模糊匹配: {len(fuzzy_match)} 条记录")
if fuzzy_match:
for record in fuzzy_match:
print(f" 病历号: '{record.case_number}', ID={record.record_id}")
# 显示一些TS0M样例
if ts0m_numbers:
print(f"\n📋 TS0M病历号样例 (前10个):")
for cn in sorted(ts0m_numbers)[:10]:
# 查找这个病历号的记录数
count = len([r for r in all_records if r.case_number == cn])
print(f" '{cn}' - {count} 条记录")
# 检查最近的记录
print(f"\n📅 最近的10条记录:")
for i, record in enumerate(all_records[:10]):
print(f" {i+1}. 病历号='{record.case_number}', 时间={record.create_time}")
except Exception as e:
print(f"❌ 检查失败: {e}")
import traceback
traceback.print_exc()
def test_specific_query():
"""测试特定的SQL查询"""
print(f"\n🧪 测试特定SQL查询...")
try:
manager = MySQLCallbackRecordManager()
# 直接测试SQL查询
import pymysql
with manager.get_connection() as conn:
with conn.cursor(pymysql.cursors.DictCursor) as cursor:
# 测试查询
sql = "SELECT case_number, COUNT(*) as count FROM callback_records GROUP BY case_number ORDER BY count DESC LIMIT 10"
cursor.execute(sql)
results = cursor.fetchall()
print(f"📊 回访记录最多的患者:")
for row in results:
print(f" 病历号='{row['case_number']}' - {row['count']} 条记录")
# 测试TS0M008652的具体查询
sql2 = "SELECT * FROM callback_records WHERE case_number = %s"
cursor.execute(sql2, ('TS0M008652',))
target_results = cursor.fetchall()
print(f"\n🎯 患者TS0M008652的SQL查询结果:")
print(f" 找到 {len(target_results)} 条记录")
for row in target_results:
print(f" 记录ID={row['record_id']}, 结果={row['callback_result']}")
print(f" 病历号='{row['case_number']}' (长度: {len(row['case_number'])})")
print(f" 时间={row['create_time']}")
except Exception as e:
print(f"❌ SQL查询测试失败: {e}")
if __name__ == '__main__':
check_database_records()
test_specific_query()
print("\n💡 如果数据库中有记录但索引页面显示'未回访',问题可能是:")
print("1. 病历号格式不匹配(大小写、空格等)")
print("2. 数据库连接配置不一致")
print("3. 索引页面生成时使用了错误的数据库连接")
print("4. 缓存或时间差问题")
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
检查数据库中的回访记录详情
"""
import pymysql
from datetime import datetime
def check_callback_records():
"""检查回访记录详情"""
try:
# 连接数据库
connection = pymysql.connect(
host='localhost',
port=3306,
user='callback_user',
password='dev_password_123',
database='callback_system',
charset='utf8mb4',
use_unicode=True,
init_command='SET NAMES utf8mb4'
)
cursor = connection.cursor()
# 查看表结构
print("=== 回访记录表结构 ===")
cursor.execute("DESCRIBE callback_records")
columns = cursor.fetchall()
for col in columns:
print(f"{col[0]}: {col[1]} {col[2]} {col[3]}")
print("\n=== 最新5条回访记录 ===")
cursor.execute("""
SELECT record_id, case_number, callback_methods, callback_result,
callback_record, operator, create_time
FROM callback_records
ORDER BY create_time DESC
LIMIT 5
""")
records = cursor.fetchall()
for record in records:
print(f"\n记录ID: {record[0]}")
print(f"病例号: {record[1]}")
print(f"回访方式: {record[2]}")
print(f"回访结果: {record[3]}")
print(f"回访记录: {record[4]}")
print(f"操作员: {record[5]}")
print(f"创建时间: {record[6]}")
print("-" * 50)
# 检查是否有失败原因等字段
print("\n=== 检查失败原因字段 ===")
cursor.execute("SHOW COLUMNS FROM callback_records LIKE '%failure%'")
failure_cols = cursor.fetchall()
if failure_cols:
print("失败原因相关字段:")
for col in failure_cols:
print(f" {col[0]}: {col[1]}")
else:
print("没有找到失败原因相关字段")
cursor.close()
connection.close()
except Exception as e:
print(f"数据库连接失败: {e}")
if __name__ == "__main__":
check_callback_records()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
查看数据库表结构
"""
import sqlite3
import os
def check_database_structure():
"""检查数据库表结构"""
db_path = "callback_records.db"
if not os.path.exists(db_path):
print(f"数据库文件 {db_path} 不存在")
return
try:
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# 获取表结构
cursor.execute("PRAGMA table_info(callback_records)")
columns = cursor.fetchall()
print("=== 数据库表结构 ===")
print(f"表名: callback_records")
print(f"字段数量: {len(columns)}")
print("\n字段详情:")
print("-" * 60)
print(f"{'序号':<4} {'字段名':<20} {'类型':<15} {'是否为空':<8} {'默认值':<10}")
print("-" * 60)
for row in columns:
cid, name, type_name, not_null, default_value, pk = row
print(f"{cid:<4} {name:<20} {type_name:<15} {'NOT NULL' if not_null else 'NULL':<8} {str(default_value):<10}")
# 查看放弃回访原因字段的详细信息
print("\n=== 放弃回访原因字段详情 ===")
abandon_reason_info = None
for row in columns:
if row[1] == 'abandon_reason':
abandon_reason_info = row
break
if abandon_reason_info:
print(f"字段名: {abandon_reason_info[1]}")
print(f"数据类型: {abandon_reason_info[2]}")
print(f"是否允许空值: {'否' if abandon_reason_info[3] else '是'}")
print(f"默认值: {abandon_reason_info[4]}")
print(f"是否主键: {'是' if abandon_reason_info[5] else '否'}")
else:
print("未找到 abandon_reason 字段")
# 查看表中的数据示例
print("\n=== 数据示例 ===")
cursor.execute("SELECT * FROM callback_records LIMIT 3")
rows = cursor.fetchall()
if rows:
print(f"找到 {len(rows)} 条记录")
for i, row in enumerate(rows, 1):
print(f"\n记录 {i}:")
for j, value in enumerate(row):
column_name = columns[j][1] if j < len(columns) else f"col_{j}"
print(f" {column_name}: {value}")
else:
print("表中暂无数据")
conn.close()
except Exception as e:
print(f"查看数据库结构时出错: {e}")
if __name__ == "__main__":
check_database_structure()
\ 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 -*-
"""
检查患者TS0M008652的回访记录情况
"""
from callback_record_mysql import MySQLCallbackRecordManager
import json
def check_patient_callback_records():
"""检查患者TS0M008652的回访记录"""
patient_id = 'TS0M008652'
patient_name = '杜秋萍'
print(f"🔍 检查患者 {patient_name}({patient_id}) 的回访记录情况")
print("=" * 60)
try:
# 连接MySQL数据库
print("📡 连接MySQL数据库...")
db_manager = MySQLCallbackRecordManager()
print("✅ 数据库连接成功")
# 查询回访记录
print(f"\n🔍 查询患者 {patient_id} 的回访记录...")
callback_records = db_manager.get_records_by_case_number(patient_id)
if callback_records:
print(f"✅ 找到 {len(callback_records)} 条回访记录:")
print("-" * 40)
for i, record in enumerate(callback_records, 1):
print(f"📋 记录 #{i}:")
print(f" 记录ID: {record.record_id}")
print(f" 病历号: {record.case_number}")
print(f" 回访方式: {', '.join(record.callback_methods) if record.callback_methods else '未知'}")
print(f" 回访结果: {record.callback_result if hasattr(record, 'callback_result') and record.callback_result else ('成功' if record.callback_success else '不成功')}")
print(f" 回访状态: {record.callback_status}")
print(f" 操作员: {record.operator}")
print(f" 创建时间: {record.create_time}")
if hasattr(record, 'next_appointment_time') and record.next_appointment_time:
print(f" 预约时间: {record.next_appointment_time}")
if hasattr(record, 'failure_reason') and record.failure_reason:
print(f" 失败原因: {record.failure_reason}")
if hasattr(record, 'abandon_reason') and record.abandon_reason:
print(f" 放弃原因: {record.abandon_reason}")
print("-" * 40)
else:
print("❌ 未找到回访记录")
# 检查数据库中是否有相似的病历号
print(f"\n🔍 查找相似的病历号...")
all_records = db_manager.get_all_records()
similar_records = [r for r in all_records if patient_id.lower() in r.case_number.lower() or r.case_number.lower() in patient_id.lower()]
if similar_records:
print(f"📋 找到 {len(similar_records)} 条相似的病历号记录:")
for record in similar_records[:5]: # 只显示前5条
print(f" 病历号: {record.case_number}, 记录ID: {record.record_id}, 时间: {record.create_time}")
else:
print("❌ 未找到相似的病历号")
except Exception as e:
print(f"❌ 检查失败: {e}")
import traceback
traceback.print_exc()
def check_index_generation():
"""检查索引页面生成逻辑"""
print(f"\n🔍 检查索引页面生成逻辑...")
try:
# 模拟索引页面生成过程
from callback_record_mysql import MySQLCallbackRecordManager
patient_id = 'TS0M008652'
callback_manager = MySQLCallbackRecordManager()
# 获取回访记录信息(与generate_index_html中的逻辑一致)
callback_records = callback_manager.get_records_by_case_number(patient_id)
# 确定回访状态和成功状态
if callback_records:
# 有回访记录,获取最新的记录状态
latest_callback = callback_records[0] # 记录已按时间排序
callback_status = "已回访"
# 优先使用新的callback_result字段,如果没有则使用旧的callback_success字段
if hasattr(latest_callback, 'callback_result') and latest_callback.callback_result:
callback_success = latest_callback.callback_result
else:
callback_success = "成功" if latest_callback.callback_success else "不成功"
print(f"✅ 根据数据库记录,应该显示:")
print(f" 回访状态: {callback_status}")
print(f" 回访结果: {callback_success}")
else:
# 无回访记录
callback_status = "未回访"
callback_success = "未知"
print(f"❌ 根据数据库记录,应该显示:")
print(f" 回访状态: {callback_status}")
print(f" 回访结果: {callback_success}")
except Exception as e:
print(f"❌ 检查索引生成逻辑失败: {e}")
if __name__ == '__main__':
check_patient_callback_records()
check_index_generation()
print("\n💡 建议:")
print("1. 如果数据库中有回访记录,需要重新生成学前街门诊的索引页面")
print("2. 如果数据库中没有回访记录,需要检查回访记录是否正确保存")
print("3. 访问患者详情页面: http://localhost:4002/patient_profiles/clinic_xuexian/patients/TS0M008652.html")
print("4. 检查浏览器开发者工具中的API调用")
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
检查患者TS0K064355的回访记录
"""
from callback_record_mysql import MySQLCallbackRecordManager
def check_patient_callback(patient_id):
"""检查指定患者的回访记录"""
try:
crm = MySQLCallbackRecordManager()
records = crm.get_callback_records(patient_id)
print(f"🔍 检查患者 {patient_id} 的回访记录")
print("=" * 60)
print(f"📊 回访记录总数: {len(records)}")
if records:
print("\n📋 回访记录详情:")
for i, record in enumerate(records, 1):
print(f" {i}. 记录ID: {record[0]}")
print(f" 患者ID: {record[1]}")
print(f" 回访状态: {record[2]}")
print(f" 回访结果: {record[3]}")
print(f" 创建时间: {record[4]}")
print(f" 备注: {record[5] if len(record) > 5 else 'N/A'}")
print()
else:
print("❌ 数据库中没有找到该患者的回访记录")
return records
except Exception as e:
print(f"❌ 检查失败: {e}")
import traceback
traceback.print_exc()
return None
if __name__ == '__main__':
patient_id = 'TS0K064355'
records = check_patient_callback(patient_id)
\ 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 pymysql
import json
# 数据库配置
DB_CONFIG = {
'host': 'localhost',
'port': 3306,
'user': 'root',
'password': 'dev_password_123',
'database': 'callback_system',
'charset': 'utf8mb4',
'use_unicode': True,
'init_command': 'SET NAMES utf8mb4',
'sql_mode': 'STRICT_TRANS_TABLES',
'read_timeout': 30,
'write_timeout': 30,
'connect_timeout': 30
}
def connect_database():
"""连接数据库"""
try:
connection = pymysql.connect(**DB_CONFIG)
print("✅ 数据库连接成功")
return connection
except Exception as e:
print(f"❌ 数据库连接失败: {e}")
return None
def check_table_structure(connection):
"""检查用户表结构"""
try:
with connection.cursor() as cursor:
# 检查表是否存在
cursor.execute("SHOW TABLES LIKE 'users'")
if not cursor.fetchone():
print("❌ users表不存在")
return False
# 查看表结构
cursor.execute("DESCRIBE users")
columns = cursor.fetchall()
print("\n📋 users表结构:")
for col in columns:
print(f" {col[0]} - {col[1]} - {col[2]}")
return True
except Exception as e:
print(f"❌ 检查表结构失败: {e}")
return False
def check_all_users(connection):
"""检查所有用户"""
try:
with connection.cursor() as cursor:
cursor.execute("SELECT username, user_type, clinic_access, created_at, is_active FROM users")
users = cursor.fetchall()
print(f"\n👥 数据库中的用户 ({len(users)} 个):")
for user in users:
print(f" {user[0]} - {user[1]} - {user[2]} - {user[4]}")
return users
except Exception as e:
print(f"❌ 查询用户失败: {e}")
return []
def check_specific_user(connection, username):
"""检查特定用户"""
try:
with connection.cursor() as cursor:
cursor.execute("SELECT * FROM users WHERE username = %s", (username,))
user = cursor.fetchone()
if user:
print(f"\n✅ 找到用户 {username}:")
cursor.execute("DESCRIBE users")
columns = [col[0] for col in cursor.fetchall()]
for i, col in enumerate(columns):
print(f" {col}: {user[i]}")
return True
else:
print(f"\n❌ 用户 {username} 不存在")
return False
except Exception as e:
print(f"❌ 查询特定用户失败: {e}")
return False
def main():
"""主函数"""
print("🔍 数据库用户检查工具")
print("=" * 50)
# 连接数据库
connection = connect_database()
if not connection:
return
try:
# 检查表结构
if not check_table_structure(connection):
return
# 检查所有用户
users = check_all_users(connection)
# 检查特定用户
check_specific_user(connection, 'chenxinyu')
# 检查其他可能的用户名
print(f"\n🔍 检查可能的用户名变体:")
for username in ['chenxinyu', 'ChenXinyu', 'CHENXINYU', 'chen_xinyu']:
check_specific_user(connection, username)
except Exception as e:
print(f"❌ 执行过程中发生错误: {e}")
finally:
connection.close()
print("\n🔒 数据库连接已关闭")
if __name__ == "__main__":
main()
\ 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 -*-
"""
完整修复方案
确保处理所有诊所的所有患者
"""
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
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
#HttpOnly_localhost FALSE / FALSE 0 session eyJzZXNzaW9uX2lkIjoiSzUyQldvM01CWTBITkhuQlgySWt3cGI4OGxPSlFQS0gza3ZQTmZDbW05VSJ9.aLpH8g.pHwBwzopDYBG6WjacqqUsIOyK6o
#!/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
#!/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 -*-
"""
调试回访记录显示问题
检查API调用和数据处理流程
"""
import requests
import json
def debug_callback_display():
"""调试回访记录显示问题"""
base_url = "http://localhost:4002"
patient_id = "TS0E012520"
print(f"🔍 开始调试患者 {patient_id} 的回访记录显示问题...")
# 1. 检查API健康状态
print("\n📋 步骤1: 检查API健康状态")
try:
response = requests.get(f"{base_url}/api/health")
print(f"健康检查状态码: {response.status_code}")
if response.status_code == 200:
print("✅ API服务正常")
else:
print(f"❌ API服务异常: {response.text}")
return
except Exception as e:
print(f"❌ API健康检查失败: {e}")
return
# 2. 获取回访记录
print("\n📋 步骤2: 获取回访记录")
try:
response = requests.get(f"{base_url}/api/callback-records/{patient_id}")
print(f"获取记录状态码: {response.status_code}")
if response.status_code == 200:
data = response.json()
print(f"API响应: {json.dumps(data, ensure_ascii=False, indent=2)}")
if data.get('success'):
records = data.get('data', [])
print(f"✅ 成功获取 {len(records)} 条回访记录")
# 分析每条记录的数据结构
for i, record in enumerate(records):
print(f"\n📝 记录 {i+1}:")
print(f" 记录ID: {record.get('record_id')}")
print(f" 回访方式: {record.get('callback_methods')}")
print(f" 回访结果: {record.get('callback_result')}")
print(f" 回访状态: {record.get('callback_status')}")
print(f" 操作员: {record.get('operator')}")
print(f" 创建时间: {record.get('create_time')}")
print(f" 回访记录: {record.get('callback_record')}")
# 检查关键字段
if not record.get('callback_methods'):
print(" ⚠️ 缺少callback_methods字段")
if not record.get('callback_result'):
print(" ⚠️ 缺少callback_result字段")
if not record.get('callback_record'):
print(" ⚠️ 缺少callback_record字段")
else:
print(f"❌ API返回失败: {data.get('message')}")
else:
print(f"❌ 获取记录失败: {response.text}")
except Exception as e:
print(f"❌ 获取回访记录失败: {e}")
return
# 3. 检查前端页面
print("\n📋 步骤3: 检查前端页面")
try:
response = requests.get(f"{base_url}/patient_profiles/clinic_huishan/patients/{patient_id}.html")
print(f"页面访问状态码: {response.status_code}")
if response.status_code == 200:
content = response.text
print("✅ 前端页面访问成功")
# 检查关键JavaScript函数是否存在
if 'function updateCallbackRecords' in content:
print("✅ updateCallbackRecords函数存在")
else:
print("❌ updateCallbackRecords函数缺失")
if 'function updateRecordsList' in content:
print("✅ updateRecordsList函数存在")
else:
print("❌ updateRecordsList函数缺失")
if 'function generateRecordHTML' in content:
print("✅ generateRecordHTML函数存在")
else:
print("❌ generateRecordHTML函数缺失")
if 'parse_callbackRecord' in content:
print("✅ parse_callbackRecord函数存在")
else:
print("❌ parse_callbackRecord函数缺失")
else:
print(f"❌ 前端页面访问失败: {response.text}")
except Exception as e:
print(f"❌ 前端页面检查失败: {e}")
if __name__ == "__main__":
debug_callback_display()
\ 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
# 部署触发文件
这个文件用于触发GitLab CI/CD部署流程。
- 创建时间: 2025-08-15
- 目的: 重新触发生产环境部署
- 状态: 准备部署
## 部署内容
1. ✅ 数据库备份工具
2. ✅ 安全数据导入脚本
3. ✅ 更新的导出功能
4. ✅ 患者-诊所对应关系修复
## 预期结果
- 生产环境数据库自动备份
- 新代码自动部署
- 患者数据安全更新
- 导出功能正常工作
\ No newline at end of file
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
诊断东亭门诊患者TS0F029637的回访显示问题
检查数据库记录、详情页面和索引页面的一致性
"""
from callback_record_mysql import MySQLCallbackRecordManager
import os
import re
import json
def diagnose_dongting_patient():
"""诊断东亭门诊患者TS0F029637"""
patient_id = 'TS0F029637'
patient_name = '未知'
clinic_name = '东亭门诊'
clinic_id = 'clinic_dongting'
print(f"🔍 诊断东亭门诊患者: {patient_id}")
print("=" * 60)
# 1. 检查数据库记录
print(f"📊 Step 1: 检查数据库中的回访记录")
try:
manager = MySQLCallbackRecordManager()
records = manager.get_records_by_case_number(patient_id)
print(f" 📋 数据库记录数: {len(records)} 条")
if records:
print(f" ✅ 找到回访记录:")
for i, record in enumerate(records):
print(f" 记录{i+1}: ID={record.record_id}")
print(f" 结果={record.callback_result}")
print(f" 时间={record.create_time}")
print(f" 操作员={record.operator}")
print()
else:
print(f" ❌ 数据库中没有找到回访记录")
except Exception as e:
print(f" ❌ 数据库检查失败: {e}")
records = []
# 2. 检查患者详情页面
print(f"📄 Step 2: 检查患者详情页面")
detail_file = f'patient_profiles/{clinic_id}/patients/{patient_id}.html'
if os.path.exists(detail_file):
print(f" ✅ 详情页面存在: {detail_file}")
with open(detail_file, 'r', encoding='utf-8') as f:
content = f.read()
# 检查是否有API_BASE_URL设置
if 'window.API_BASE_URL' in content:
print(f" ✅ API_BASE_URL已设置")
else:
print(f" ❌ 缺少API_BASE_URL设置 - 这会导致回访记录无法动态加载")
# 检查回访记录容器
if 'callback-records-list' in content:
print(f" ✅ 回访记录容器存在")
else:
print(f" ❌ 缺少回访记录容器")
else:
print(f" ❌ 详情页面不存在: {detail_file}")
# 3. 检查索引页面
print(f"📋 Step 3: 检查索引页面状态")
index_file = f'patient_profiles/{clinic_id}/index.html'
if os.path.exists(index_file):
print(f" ✅ 索引页面存在: {index_file}")
with open(index_file, 'r', encoding='utf-8') as f:
content = f.read()
# 查找患者
patient_pattern = rf'data-id="{patient_id}"'
if re.search(patient_pattern, content):
print(f" ✅ 患者在索引页面中找到")
# 获取当前状态
status_pattern = rf'data-callback-status="([^"]+)"[^>]*data-id="{patient_id}"'
status_match = re.search(status_pattern, content)
if status_match:
current_status = status_match.group(1)
expected_status = "已回访" if records else "未回访"
print(f" 📊 当前索引状态: {current_status}")
print(f" 📊 期望状态: {expected_status}")
if current_status == expected_status:
print(f" ✅ 索引状态正确")
else:
print(f" ❌ 索引状态不正确 - 需要更新")
return {
'patient_id': patient_id,
'clinic_id': clinic_id,
'has_records': len(records) > 0,
'current_status': current_status,
'expected_status': expected_status,
'needs_index_update': True,
'needs_detail_fix': 'window.API_BASE_URL' not in open(detail_file, 'r', encoding='utf-8').read() if os.path.exists(detail_file) else True
}
else:
print(f" ❌ 无法获取索引状态")
else:
print(f" ❌ 患者未在索引页面中找到")
else:
print(f" ❌ 索引页面不存在: {index_file}")
# 4. 检查患者数据文件
print(f"📂 Step 4: 检查患者数据文件")
json_file = f'诊所患者json/东亭门诊.json'
if os.path.exists(json_file):
print(f" ✅ 患者数据文件存在")
with open(json_file, 'r', encoding='utf-8') as f:
data = json.load(f)
# 查找患者
patient_found = False
for patient in data:
if patient.get('病历号') == patient_id:
patient_found = True
patient_name = patient.get('姓名', '未知')
print(f" ✅ 找到患者数据: {patient_name}")
break
if not patient_found:
print(f" ❌ 患者数据不在JSON文件中")
else:
print(f" ❌ 患者数据文件不存在: {json_file}")
return {
'patient_id': patient_id,
'patient_name': patient_name,
'clinic_id': clinic_id,
'has_records': len(records) > 0,
'needs_fix': True
}
def fix_dongting_patient(diagnosis_result):
"""修复东亭门诊患者问题"""
print(f"\n🛠️ 修复东亭门诊患者问题")
print("=" * 60)
patient_id = diagnosis_result['patient_id']
clinic_id = diagnosis_result['clinic_id']
has_records = diagnosis_result['has_records']
fixed_issues = []
# 1. 修复详情页面的API_BASE_URL
detail_file = f'patient_profiles/{clinic_id}/patients/{patient_id}.html'
if os.path.exists(detail_file):
print(f"🔧 修复详情页面: {detail_file}")
with open(detail_file, 'r', encoding='utf-8') as f:
content = f.read()
if 'window.API_BASE_URL' not in content:
# 在<script>标签中添加API_BASE_URL
script_pattern = r'(<script>\s*)'
replacement = r'\1 // 设置API基础URL\n window.API_BASE_URL = \'http://localhost:4002\';\n \n'
new_content = re.sub(script_pattern, replacement, content)
if new_content != content:
with open(detail_file, 'w', encoding='utf-8') as f:
f.write(new_content)
print(f" ✅ 已添加API_BASE_URL设置")
fixed_issues.append("详情页面API_BASE_URL")
else:
print(f" ⚠️ 未找到<script>标签,无法添加API_BASE_URL")
else:
print(f" ✅ API_BASE_URL已存在")
# 2. 更新索引页面状态
index_file = f'patient_profiles/{clinic_id}/index.html'
if os.path.exists(index_file):
print(f"🔧 修复索引页面: {index_file}")
with open(index_file, 'r', encoding='utf-8') as f:
content = f.read()
expected_status = "已回访" if has_records else "未回访"
expected_success = "成功" if has_records else "未知"
# 更新data-callback-status
status_pattern = rf'(data-callback-status=")[^"]+("[^>]*data-id="{patient_id}")'
status_replacement = rf'\g<1>{expected_status}\g<2>'
new_content = re.sub(status_pattern, status_replacement, content)
# 更新data-callback-success
success_pattern = rf'(data-callback-success=")[^"]+("[^>]*data-id="{patient_id}")'
success_replacement = rf'\g<1>{expected_success}\g<2>'
new_content = re.sub(success_pattern, success_replacement, new_content)
if new_content != content:
with open(index_file, 'w', encoding='utf-8') as f:
f.write(new_content)
print(f" ✅ 已更新索引页面状态: {expected_status}")
fixed_issues.append("索引页面状态")
else:
print(f" ✅ 索引页面状态已正确")
return fixed_issues
if __name__ == '__main__':
print("🏥 东亭门诊患者TS0F029637问题诊断和修复")
print("=" * 60)
# 诊断问题
diagnosis = diagnose_dongting_patient()
# 修复问题
if diagnosis.get('needs_fix'):
fixed_issues = fix_dongting_patient(diagnosis)
print(f"\n📊 修复结果:")
if fixed_issues:
print(f" ✅ 已修复的问题:")
for issue in fixed_issues:
print(f" - {issue}")
else:
print(f" ℹ️ 没有需要修复的问题")
print(f"\n💡 建议:")
print(f" 1. 访问详情页面测试回访记录显示: http://localhost:4002/patient_profiles/clinic_dongting/patients/TS0F029637.html")
print(f" 2. 访问索引页面检查状态: http://localhost:4002/patient_profiles/clinic_dongting/")
print(f" 3. 如果问题持续,可能需要重新生成整个东亭门诊的索引页面")
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
直接执行修复脚本 - 不依赖PowerShell或外部命令
完全按照学前街门诊的成功修复方式
"""
print("🏥 开始批量修复所有门诊(直接执行)")
print("=" * 60)
try:
# 导入必要的模块
from generate_html import generate_index_html
import json
import os
# 所有门诊配置
clinics = [
('东亭门诊', 'clinic_dongting', '诊所患者json/东亭门诊.json'),
('大丰门诊', 'clinic_dafeng', '诊所患者json/大丰门诊.json'),
('惠山门诊', 'clinic_huishan', '诊所患者json/惠山门诊.json'),
('新吴门诊', 'clinic_xinwu', '诊所患者json/新吴门诊.json'),
('河埒门诊', 'clinic_helai', '诊所患者json/河埒门诊.json'),
('红豆门诊', 'clinic_hongdou', '诊所患者json/红豆门诊.json'),
('通善口腔医院', 'clinic_hospital', '诊所患者json/通善口腔医院.json'),
('马山门诊', 'clinic_mashan', '诊所患者json/马山门诊.json'),
]
success_count = 0
total_count = len(clinics)
for clinic_name, clinic_id, json_file in clinics:
print(f"\n📋 修复 {clinic_name}...")
try:
# 检查JSON文件是否存在
if not os.path.exists(json_file):
print(f" ❌ 数据文件不存在: {json_file}")
continue
# 加载患者数据
print(f" 📂 加载数据文件: {json_file}")
with open(json_file, 'r', encoding='utf-8') as f:
data = json.load(f)
# 转换数据格式(完全按照学前街门诊的成功方式)
patients_data = {}
for patient in data:
case_number = patient.get('病历号')
if case_number:
patients_data[case_number] = [patient]
print(f" 📊 加载患者数据: {len(patients_data)}个")
# 重新生成索引页面
output_file = f'patient_profiles/{clinic_id}/index.html'
output_dir = os.path.dirname(output_file)
print(f" 📁 确保目录存在: {output_dir}")
os.makedirs(output_dir, exist_ok=True)
print(f" 🔄 生成索引页面...")
generate_index_html(patients_data, output_file, clinic_name)
# 验证生成结果
if os.path.exists(output_file):
file_size = os.path.getsize(output_file)
print(f" ✅ {clinic_name} 索引页面生成成功")
print(f" 文件: {output_file}")
print(f" 大小: {file_size} 字节")
success_count += 1
else:
print(f" ❌ {clinic_name} 索引页面生成失败 - 文件不存在")
except Exception as e:
print(f" ❌ {clinic_name} 修复失败: {e}")
# 显示详细错误信息
import traceback
error_details = traceback.format_exc()
print(f" 详细错误:\n{error_details}")
# 总结结果
print(f"\n" + "=" * 60)
print(f"📊 批量修复完成统计:")
print(f" 总门诊数: {total_count}")
print(f" 修复成功: {success_count}")
print(f" 修复失败: {total_count - success_count}")
print(f" 成功率: {(success_count/total_count*100):.1f}%")
if success_count == total_count:
print(f"\n🎉 所有门诊修复完成!")
print(f"💡 现在所有门诊的索引页面都已按照学前街门诊的成功方式重新生成")
print(f"💡 回访状态将与数据库保持一致")
elif success_count > 0:
print(f"\n✅ 部分门诊修复成功")
print(f"⚠️ {total_count - success_count} 个门诊修复失败,请检查上述错误信息")
else:
print(f"\n❌ 所有门诊修复失败")
print(f"💡 请检查数据文件和权限")
# 提供下一步建议
print(f"\n🔗 修复完成后,可以通过以下方式验证:")
print(f"1. 重新启动Docker: docker compose up -d")
print(f"2. 访问各门诊索引页面:")
for clinic_name, clinic_id, _ in clinics:
print(f" {clinic_name}: http://localhost:4002/patient_profiles/{clinic_id}/")
except ImportError as e:
print(f"❌ 导入模块失败: {e}")
print(f"💡 请确保在正确的Python环境中运行此脚本")
except Exception as e:
print(f"❌ 脚本执行失败: {e}")
import traceback
traceback.print_exc()
print(f"\n" + "=" * 60)
print(f"🏥 修复脚本执行完成")
\ No newline at end of file
......@@ -60,8 +60,7 @@ services:
timeout: 10s
retries: 5
start_period: 40s
volumes:
- ./patient_profiles:/app/patient_profiles:ro
networks:
patient_callback_network:
......
import pandas as pd
import json
import os
import sys
from datetime import datetime
import re
import numpy as np
# 自定义JSON编码器,处理pandas的Timestamp类型和NaN值
class CustomJSONEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, pd.Timestamp):
return obj.strftime('%Y-%m-%d %H:%M:%S')
if isinstance(obj, float) and np.isnan(obj):
return None
return super().default(obj)
def clean_nan_values(data):
"""清理数据中的NaN值,将其转换为None"""
if isinstance(data, list):
return [clean_nan_values(item) for item in data]
elif isinstance(data, dict):
return {key: clean_nan_values(value) for key, value in data.items()}
elif isinstance(data, float) and np.isnan(data):
return None
elif isinstance(data, str) and data.lower() == 'nan':
return None
return data
def format_json_file(json_file, indent=2, encoding='utf-8'):
"""格式化JSON文件,使每个字段单独一行"""
try:
with open(json_file, 'r', encoding=encoding) as f:
data = json.load(f)
# 清理NaN值
data = clean_nan_values(data)
# 使用正则表达式将每个字段放在单独的行上
formatted_json = json.dumps(data, ensure_ascii=False, indent=indent, cls=CustomJSONEncoder)
formatted_json = re.sub(r'(".*?"): (".*?")([,}])', r'\1: \2\3', formatted_json)
with open(json_file, 'w', encoding=encoding) as f:
f.write(formatted_json)
print(f"JSON文件格式化完成: {json_file}")
except Exception as e:
print(f"格式化JSON文件时出错: {e}")
def detect_date_columns(df):
"""检测并转换日期列"""
date_columns = []
for col in df.columns:
# 检查列名是否包含日期相关的关键词
if any(keyword in str(col).lower() for keyword in ['日期', '时间', 'date', 'time']):
date_columns.append(col)
# 尝试将前几行数据转换为日期格式来检测是否为日期列
elif df[col].dtype == 'object':
sample = df[col].dropna().head(5)
if len(sample) > 0:
try:
pd.to_datetime(sample, errors='raise')
date_columns.append(col)
except:
pass
# 转换检测到的日期列
for col in date_columns:
try:
df[col] = pd.to_datetime(df[col], errors='coerce')
print(f"检测到并转换日期列: {col}")
except:
print(f"尝试转换列 {col} 为日期格式失败")
return df
def excel_to_json(excel_file, json_file=None, encoding='utf-8', indent=2, format_output=True):
"""将Excel文件转换为JSON文件
参数:
excel_file: Excel文件路径
json_file: 输出的JSON文件路径,默认为与Excel文件同名但扩展名为.json
encoding: 输出JSON文件的编码,默认为utf-8
indent: JSON文件的缩进空格数,默认为2
format_output: 是否将每个字段放在单独的行上,默认为True
"""
# 如果没有指定JSON文件路径,则使用与Excel文件相同的名称但扩展名为.json
if json_file is None:
base_name = os.path.splitext(excel_file)[0]
json_file = f"{base_name}.json"
try:
# 读取Excel文件
print(f"正在读取Excel文件: {excel_file}")
df = pd.read_excel(excel_file)
# 检测并转换日期列
df = detect_date_columns(df)
# 将NaN值替换为None
df = df.replace({np.nan: None})
# 将DataFrame转换为Python字典列表
records = df.to_dict(orient='records')
# 清理记录中的NaN值
records = clean_nan_values(records)
# 将Python字典列表以美化格式写入JSON文件
with open(json_file, 'w', encoding=encoding) as f:
json.dump(records, f, ensure_ascii=False, indent=indent, cls=CustomJSONEncoder)
print(f"转换完成!JSON文件已保存为: {json_file}")
# 如果需要格式化,则进行格式化
if format_output:
format_json_file(json_file, indent=indent, encoding=encoding)
return json_file
except Exception as e:
print(f"转换过程中出错: {e}")
return None
def display_help():
"""显示帮助信息"""
print("Excel转JSON工具使用说明:")
print("基本用法: python excel_to_json.py [excel文件路径] [可选:json文件路径]")
print("\n参数说明:")
print(" -h, --help 显示此帮助信息")
print(" --no-format 不将每个字段单独一行展示")
print(" --encoding 指定输出文件编码,默认为utf-8")
print(" --indent 指定JSON缩进空格数,默认为2")
print("\n示例:")
print(" python excel_to_json.py data.xlsx")
print(" python excel_to_json.py data.xlsx output.json --encoding=gbk --indent=4")
print(" python excel_to_json.py data.xlsx --no-format")
if __name__ == "__main__":
# 解析命令行参数
args = sys.argv[1:]
# 显示帮助信息
if '-h' in args or '--help' in args:
display_help()
sys.exit(0)
# 默认参数
format_output = '--no-format' not in args
if '--no-format' in args:
args.remove('--no-format')
# 处理编码参数
encoding = 'utf-8'
for arg in args[:]:
if arg.startswith('--encoding='):
encoding = arg.split('=')[1]
args.remove(arg)
# 处理缩进参数
indent = 2
for arg in args[:]:
if arg.startswith('--indent='):
try:
indent = int(arg.split('=')[1])
args.remove(arg)
except ValueError:
print("警告: 缩进参数无效,使用默认值2")
# 处理文件路径
if len(args) > 0:
excel_file = args[0]
json_file = args[1] if len(args) > 1 else None
excel_to_json(excel_file, json_file, encoding=encoding, indent=indent, format_output=format_output)
else:
# 检测当前目录中的Excel文件
files = [f for f in os.listdir('.') if f.endswith(('.xlsx', '.xls'))]
if not files:
print("错误:当前目录中没有找到Excel文件。")
display_help()
sys.exit(1)
if len(files) == 1:
# 如果只有一个Excel文件,直接使用它
excel_file = files[0]
print(f"找到Excel文件: {excel_file}")
excel_to_json(excel_file, encoding=encoding, indent=indent, format_output=format_output)
else:
# 如果有多个Excel文件,让用户选择
print("找到多个Excel文件:")
for i, file in enumerate(files):
print(f"{i+1}. {file}")
try:
choice = int(input("请选择要转换的文件编号: "))
if 1 <= choice <= len(files):
excel_file = files[choice-1]
excel_to_json(excel_file, encoding=encoding, indent=indent, format_output=format_output)
else:
print("无效的选择")
except ValueError:
print("无效的输入")
\ No newline at end of file
exec("""
# 直接在当前环境中执行修复
print('开始修复...')
try:
from generate_html import generate_index_html
import json
import os
# 修复东亭门诊
print('修复东亭门诊...')
with open('诊所患者json/东亭门诊.json', 'r', encoding='utf-8') as f:
data = json.load(f)
patients_data = {}
for patient in data:
case_number = patient.get('病历号')
if case_number:
patients_data[case_number] = [patient]
print(f'加载患者: {len(patients_data)}个')
os.makedirs('patient_profiles/clinic_dongting', exist_ok=True)
generate_index_html(patients_data, 'patient_profiles/clinic_dongting/index.html', '东亭门诊')
print('✅ 东亭门诊修复完成')
# 修复大丰门诊
print('修复大丰门诊...')
with open('诊所患者json/大丰门诊.json', 'r', encoding='utf-8') as f:
data = json.load(f)
patients_data = {}
for patient in data:
case_number = patient.get('病历号')
if case_number:
patients_data[case_number] = [patient]
print(f'加载患者: {len(patients_data)}个')
os.makedirs('patient_profiles/clinic_dafeng', exist_ok=True)
generate_index_html(patients_data, 'patient_profiles/clinic_dafeng/index.html', '大丰门诊')
print('✅ 大丰门诊修复完成')
print('🎉 修复完成!')
except Exception as e:
print(f'❌ 修复失败: {e}')
import traceback
traceback.print_exc()
""")
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
最终验证:检查患者回访状态是否正确显示
"""
import re
import os
from callback_record_mysql import MySQLCallbackRecordManager
def final_verification():
"""最终验证修复结果"""
print("🧪 最终验证:回访状态显示修复结果")
print("=" * 60)
# 测试患者列表
test_patients = [
('TS0M008652', '杜秋萍'),
('TS0K064355', '迟鹏领'),
('TS0K030750', '未知姓名'),
]
success_count = 0
total_count = len(test_patients)
try:
# 1. 检查数据库状态
print("📊 Step 1: 检查数据库状态")
manager = MySQLCallbackRecordManager()
db_status = {}
for patient_id, patient_name in test_patients:
records = manager.get_records_by_case_number(patient_id)
status = "已回访" if records else "未回访"
db_status[patient_id] = status
print(f" {patient_id} ({patient_name}): 数据库状态 = {status} ({len(records)} 条记录)")
# 2. 检查索引页面状态
print(f"\n📄 Step 2: 检查索引页面状态")
index_file = 'patient_profiles/clinic_xuexian/index.html'
if not os.path.exists(index_file):
print(f"❌ 索引页面不存在: {index_file}")
return False
with open(index_file, 'r', encoding='utf-8') as f:
content = f.read()
print(f" 📋 索引页面大小: {len(content)} 字符")
# 3. 验证每个患者
print(f"\n🎯 Step 3: 验证患者状态一致性")
for patient_id, patient_name in test_patients:
print(f"\n👤 患者: {patient_name} ({patient_id})")
# 检查患者是否在页面中
patient_pattern = rf'data-id="{patient_id}"'
if not re.search(patient_pattern, content):
print(f" ❌ 患者未在索引页面中找到")
continue
# 获取索引页面状态
status_pattern = rf'data-callback-status="([^"]+)"[^>]*data-id="{patient_id}"'
status_match = re.search(status_pattern, content)
if status_match:
index_status = status_match.group(1)
print(f" 📄 索引页面状态: {index_status}")
print(f" 📊 数据库状态: {db_status[patient_id]}")
# 检查状态一致性
if index_status == db_status[patient_id]:
print(f" ✅ 状态一致!")
success_count += 1
else:
print(f" ❌ 状态不一致!")
print(f" 期望: {db_status[patient_id]}")
print(f" 实际: {index_status}")
else:
print(f" ❌ 无法从索引页面获取回访状态")
# 4. 总结结果
print(f"\n📊 验证结果总结")
print("=" * 60)
print(f"✅ 成功: {success_count}/{total_count}")
if success_count == total_count:
print(f"🎉 所有患者的回访状态显示正确!")
print(f"💡 问题已彻底解决")
return True
else:
print(f"⚠️ 仍有 {total_count - success_count} 个患者状态不一致")
return False
except Exception as e:
print(f"❌ 验证过程失败: {e}")
import traceback
traceback.print_exc()
return False
def check_web_access():
"""检查Web访问"""
print(f"\n🌐 检查Web访问")
print("=" * 30)
try:
import requests
url = 'http://localhost:4002/patient_profiles/clinic_xuexian/'
print(f"🔗 访问: {url}")
response = requests.get(url, timeout=10)
if response.status_code == 200:
print(f"✅ Web访问成功")
print(f"📄 页面大小: {len(response.text)} 字符")
# 检查关键患者是否可见
content = response.text
test_patients = ['TS0M008652', 'TS0K064355']
for patient_id in test_patients:
if patient_id in content:
# 检查状态
status_pattern = rf'data-callback-status="([^"]+)"[^>]*data-id="{patient_id}"'
match = re.search(status_pattern, content)
status = match.group(1) if match else "未知"
print(f" 👤 {patient_id}: {status}")
else:
print(f" ❌ {patient_id}: 未找到")
return True
else:
print(f"❌ Web访问失败: HTTP {response.status_code}")
return False
except Exception as e:
print(f"❌ Web访问检查失败: {e}")
return False
if __name__ == '__main__':
print("🏥 患者回访状态显示问题最终验证")
print("=" * 60)
# 验证修复结果
verification_success = final_verification()
# 检查Web访问
web_success = check_web_access()
print(f"\n🏆 最终结果")
print("=" * 30)
if verification_success and web_success:
print(f"🎉 修复完全成功!")
print(f"💡 所有患者的回访状态现在都正确显示")
print(f"🔗 可以访问 http://localhost:4002/patient_profiles/clinic_xuexian/ 查看结果")
else:
print(f"⚠️ 修复部分成功,请检查上述详细信息")
print(f"\n📋 问题根源总结:")
print(f" 1. 数据库中确实有患者TS0M008652的回访记录")
print(f" 2. 索引页面生成逻辑正常,患者都在页面中")
print(f" 3. 问题是索引页面中个别患者的回访状态未及时更新")
print(f" 4. 通过手动更新HTML文件中的data-callback-status属性解决")
print(f" 5. 建议:定期重新生成索引页面以保持数据同步")
\ No newline at end of file
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