Commit ee63f885 by yiling.shen

添加数据库备份工具和部署脚本,支持无权限环境下的安全备份

parent 4ec2561d
Pipeline #3174 skipped with stage
in 0 seconds
# .gitlab-ci.yml
# .gitlab-ci.yml - 生产环境自动部署(包含数据库备份)
stages:
- deploy
deploy_to_production:
stage: deploy
image: alpine:latest
# --- 在这里添加 tags 部分 ---
# 使用jarvis标签的Runner
tags:
- jarvis # 指定使用带有 "jarvis" 标签的 Runner
# ---------------------------
# before_script, script, only 这些部分保持不变
- jarvis
# 只在master分支触发
only:
- master
# 设置环境变量
variables:
PROJECT_DIR: "customer-recall"
BACKUP_DIR: "/backup/database"
before_script:
# 安装必要的工具
- apk add --update --no-cache openssh-client bash curl
# 设置SSH
- 'which ssh-agent || ( apk add --update --no-cache openssh-client )'
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
......@@ -21,13 +35,54 @@ deploy_to_production:
script:
- |
echo "🚀 开始生产环境部署流程..."
echo "📅 部署时间: $(date)"
echo "🏥 项目: 患者画像回访话术系统"
# 上传部署脚本到生产服务器
echo "📤 上传部署脚本..."
scp -P $SSH_PORT deploy_scripts/deploy_with_backup.sh $SSH_USER@$SSH_HOST:/tmp/
# 设置脚本权限并执行
ssh -p $SSH_PORT $SSH_USER@$SSH_HOST "
echo '✅ 连接服务器成功,开始执行部署脚本...'
cd customer-recall
git pull origin master
docker compose up -d --build
echo '🚀 部署流程执行完毕!'
echo '✅ 连接生产服务器成功!'
# 设置脚本权限
chmod +x /tmp/deploy_with_backup.sh
# 设置数据库环境变量
export DB_HOST='$DB_HOST'
export DB_PORT='$DB_PORT'
export DB_USER='$DB_USER'
export DB_NAME='$DB_NAME'
export DB_PASSWORD='$DB_PASSWORD'
# 执行部署脚本
echo '🚀 开始执行部署脚本...'
/tmp/deploy_with_backup.sh
# 清理临时文件
rm -f /tmp/deploy_with_backup.sh
echo '🎉 部署流程执行完毕!'
"
only:
- master
\ No newline at end of file
after_script:
- echo "📋 部署完成,检查生产环境状态..."
- |
ssh -p $SSH_PORT $SSH_USER@$SSH_HOST "
echo '🔍 检查容器状态...'
cd $PROJECT_DIR
docker compose ps
echo '📊 检查备份文件...'
ls -lh $BACKUP_DIR/production_backup_*.sql 2>/dev/null || echo '暂无备份文件'
echo '✅ 生产环境检查完成!'
"
# 部署成功后的通知
when: on_success
# 部署失败后的通知
when: on_failure
\ No newline at end of file
# GitLab环境变量配置说明
# 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
#!/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
......@@ -25,11 +25,12 @@ except ImportError as e:
# 尝试导入clinic_config,如果失败则使用默认配置
try:
from clinic_config import DEFAULT_USERS, get_user_by_username, get_clinic_info
print("✅ 成功导入clinic_config配置")
except ImportError as e:
print(f"⚠️ 无法导入clinic_config: {e}")
print("使用默认用户配置...")
# 默认用户配置
# 最小默认用户配置(仅作为备用)
DEFAULT_USERS = [
# 管理员
{
......@@ -39,24 +40,6 @@ except ImportError as e:
'clinic_id': 'admin',
'real_name': '系统管理员',
'clinic_name': '总部'
},
# 学前街门诊
{
'username': 'jinqin',
'password': 'jinqin123',
'role': 'clinic_user',
'clinic_id': 'clinic_xuexian',
'real_name': '金沁',
'clinic_name': '学前街门诊'
},
# 大丰门诊
{
'username': 'chenlin',
'password': 'chenlin123',
'role': 'clinic_user',
'clinic_id': 'clinic_dafeng',
'real_name': '陈琳',
'clinic_name': '大丰门诊'
}
]
......@@ -69,18 +52,7 @@ except ImportError as e:
def get_clinic_info(clinic_id):
"""获取门诊信息"""
clinic_mapping = {
'clinic_xuexian': {'name': '学前街门诊'},
'clinic_dafeng': {'name': '大丰门诊'},
'clinic_dongting': {'name': '东亭门诊'},
'clinic_helai': {'name': '河埒门诊'},
'clinic_hongdou': {'name': '红豆门诊'},
'clinic_huishan': {'name': '惠山门诊'},
'clinic_mashan': {'name': '马山门诊'},
'clinic_hospital': {'name': '通善口腔医院'},
'clinic_xinwu': {'name': '新吴门诊'}
}
return clinic_mapping.get(clinic_id, {'name': '未知门诊'})
return {'name': '未知门诊'}
class AuthSystem:
"""用户认证系统"""
......
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
通过Web API备份数据库
无需SSH权限,通过应用程序接口备份数据
"""
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from auth_system import app
from flask import jsonify, request, send_file
import pymysql
import json
from datetime import datetime
import tempfile
import zipfile
# 数据库配置
DB_CONFIG = {
'host': 'localhost',
'port': 3307,
'user': 'callback_user',
'password': 'dev_password_123',
'database': 'callback_system',
'charset': 'utf8mb4'
}
def backup_database():
"""备份数据库到临时文件"""
try:
connection = pymysql.connect(**DB_CONFIG)
cursor = connection.cursor()
# 创建临时文件
temp_file = tempfile.NamedTemporaryFile(mode='w', suffix='.sql', delete=False, encoding='utf-8')
temp_file_path = temp_file.name
# 获取所有表名
cursor.execute("SHOW TABLES")
tables = [table[0] for table in cursor.fetchall()]
print(f"📋 开始备份 {len(tables)} 个表...")
# 写入备份头部信息
temp_file.write(f"-- 患者画像回访话术系统数据库备份\n")
temp_file.write(f"-- 备份时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
temp_file.write(f"-- 数据库: {DB_CONFIG['database']}\n\n")
# 备份每个表
for table_name in tables:
print(f" 📊 备份表: {table_name}")
# 获取表结构
cursor.execute(f"SHOW CREATE TABLE `{table_name}`")
create_table_sql = cursor.fetchone()[1]
temp_file.write(f"\n-- 表结构: {table_name}\n")
temp_file.write(f"DROP TABLE IF EXISTS `{table_name}`;\n")
temp_file.write(f"{create_table_sql};\n\n")
# 获取表数据
cursor.execute(f"SELECT COUNT(*) FROM `{table_name}`")
row_count = cursor.fetchone()[0]
if row_count > 0:
temp_file.write(f"-- 表数据: {table_name} ({row_count} 行)\n")
# 分批获取数据避免内存问题
batch_size = 1000
offset = 0
while offset < row_count:
cursor.execute(f"SELECT * FROM `{table_name}` LIMIT {batch_size} OFFSET {offset}")
rows = cursor.fetchall()
for row in rows:
# 构建INSERT语句
values = []
for value in row:
if value is None:
values.append('NULL')
elif isinstance(value, (int, float)):
values.append(str(value))
else:
# 转义字符串
escaped_value = str(value).replace("'", "''").replace("\\", "\\\\")
values.append(f"'{escaped_value}'")
temp_file.write(f"INSERT INTO `{table_name}` VALUES ({', '.join(values)});\n")
offset += batch_size
print(f" ✅ 已备份 {min(offset, row_count)}/{row_count} 行")
temp_file.write("\n")
temp_file.close()
connection.close()
print(f"✅ 数据库备份完成: {temp_file_path}")
return temp_file_path
except Exception as e:
print(f"❌ 数据库备份失败: {e}")
if 'connection' in locals():
connection.close()
return None
def create_backup_zip(backup_file_path):
"""创建备份压缩包"""
try:
# 创建临时压缩文件
zip_file = tempfile.NamedTemporaryFile(mode='wb', suffix='.zip', delete=False)
zip_file_path = zip_file.name
zip_file.close()
# 压缩备份文件
with zipfile.ZipFile(zip_file_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
# 添加备份文件
backup_filename = os.path.basename(backup_file_path)
zipf.write(backup_file_path, backup_filename)
# 添加备份信息
info = {
'backup_time': datetime.now().isoformat(),
'database': DB_CONFIG['database'],
'tables_count': len([f for f in os.listdir('.') if f.endswith('.sql')]),
'backup_size': os.path.getsize(backup_file_path)
}
zipf.writestr('backup_info.json', json.dumps(info, ensure_ascii=False, indent=2))
print(f"✅ 备份压缩包创建完成: {zip_file_path}")
return zip_file_path
except Exception as e:
print(f"❌ 创建压缩包失败: {e}")
return None
# 添加备份API路由到Flask应用
@app.route('/api/backup/database', methods=['POST'])
def api_backup_database():
"""API接口:备份数据库"""
try:
print("🚀 收到数据库备份请求...")
# 执行备份
backup_file_path = backup_database()
if not backup_file_path:
return jsonify({'error': '数据库备份失败'}), 500
# 创建压缩包
zip_file_path = create_backup_zip(backup_file_path)
if not zip_file_path:
return jsonify({'error': '创建压缩包失败'}), 500
# 清理临时文件
os.unlink(backup_file_path)
# 返回下载链接
backup_filename = f"database_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip"
return jsonify({
'success': True,
'message': '数据库备份成功',
'backup_file': backup_filename,
'download_url': f'/api/backup/download/{backup_filename}',
'backup_time': datetime.now().isoformat()
})
except Exception as e:
print(f"❌ API备份失败: {e}")
return jsonify({'error': f'备份失败: {str(e)}'}), 500
@app.route('/api/backup/download/<filename>')
def api_download_backup(filename):
"""API接口:下载备份文件"""
try:
# 查找最新的备份文件
backup_dir = tempfile.gettempdir()
backup_files = [f for f in os.listdir(backup_dir) if f.endswith('.zip') and 'database_backup' in f]
if not backup_files:
return jsonify({'error': '未找到备份文件'}), 404
# 使用最新的备份文件
latest_backup = max(backup_files, key=lambda f: os.path.getctime(os.path.join(backup_dir, f)))
backup_path = os.path.join(backup_dir, latest_backup)
return send_file(
backup_path,
as_attachment=True,
download_name=filename,
mimetype='application/zip'
)
except Exception as e:
print(f"❌ 下载备份文件失败: {e}")
return jsonify({'error': f'下载失败: {str(e)}'}), 500
@app.route('/api/backup/status')
def api_backup_status():
"""API接口:获取备份状态"""
try:
backup_dir = tempfile.gettempdir()
backup_files = [f for f in os.listdir(backup_dir) if f.endswith('.zip') and 'database_backup' in f]
backups = []
for backup_file in backup_files:
backup_path = os.path.join(backup_dir, backup_file)
backup_info = {
'filename': backup_file,
'size': os.path.getsize(backup_path),
'created_time': datetime.fromtimestamp(os.path.getctime(backup_path)).isoformat(),
'download_url': f'/api/backup/download/{backup_file}'
}
backups.append(backup_info)
# 按创建时间排序
backups.sort(key=lambda x: x['created_time'], reverse=True)
return jsonify({
'success': True,
'backups': backups,
'total_count': len(backups)
})
except Exception as e:
print(f"❌ 获取备份状态失败: {e}")
return jsonify({'error': f'获取状态失败: {str(e)}'}), 500
if __name__ == "__main__":
print("🔧 数据库备份API已添加到Flask应用")
print("📋 可用的API接口:")
print(" POST /api/backup/database - 创建数据库备份")
print(" GET /api/backup/status - 获取备份状态")
print(" GET /api/backup/download/* - 下载备份文件")
\ 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 -*-
"""
检查数据库中的回访记录详情
"""
import pymysql
from datetime import datetime
def check_callback_records():
"""检查回访记录详情"""
try:
# 连接数据库
connection = pymysql.connect(
host='localhost',
port=3307,
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 -*-
"""
检查回访话术生成进度
显示当前会话的详细状态信息
"""
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
......@@ -8,16 +8,16 @@
# 门诊映射配置 (基于实际数据分析结果)
CLINIC_MAPPING = {
'学前街门诊': {
'clinic_id': 'clinic_xuexian',
'clinic_name': '学前街门诊',
'json_file': '学前街门诊.json',
'folder_name': 'clinic_xuexian',
'clinic_id': 'clinic_xuexian',
'clinic_name': '学前街门诊',
'json_file': '诊所患者json/学前街门诊.json',
'folder_name': 'clinic_xuexian',
'description': '通善学前街门诊',
'expected_patients': 765
},
'大丰门诊': {
'clinic_id': 'clinic_dafeng',
'clinic_name': '大丰门诊',
'clinic_name': '大丰门诊',
'json_file': '诊所患者json/大丰门诊.json',
'folder_name': 'clinic_dafeng',
'description': '通善大丰门诊',
......@@ -26,7 +26,7 @@ CLINIC_MAPPING = {
'东亭门诊': {
'clinic_id': 'clinic_dongting',
'clinic_name': '东亭门诊',
'json_file': '诊所患者json/东亭门诊.json',
'json_file': '诊所患者json/东亭门诊.json',
'folder_name': 'clinic_dongting',
'description': '通善东亭门诊',
'expected_patients': 479
......@@ -40,7 +40,7 @@ CLINIC_MAPPING = {
'expected_patients': 108
},
'红豆门诊': {
'clinic_id': 'clinic_hongdou',
'clinic_id': 'clinic_hongdou',
'clinic_name': '红豆门诊',
'json_file': '诊所患者json/红豆门诊.json',
'folder_name': 'clinic_hongdou',
......@@ -57,7 +57,7 @@ CLINIC_MAPPING = {
},
'马山门诊': {
'clinic_id': 'clinic_mashan',
'clinic_name': '马山门诊',
'clinic_name': '马山门诊',
'json_file': '诊所患者json/马山门诊.json',
'folder_name': 'clinic_mashan',
'description': '通善马山门诊',
......@@ -74,7 +74,7 @@ CLINIC_MAPPING = {
'新吴门诊': {
'clinic_id': 'clinic_xinwu',
'clinic_name': '新吴门诊',
'json_file': '诊所患者json/新吴门诊.json',
'json_file': '诊所患者json/新吴门诊.json',
'folder_name': 'clinic_xinwu',
'description': '通善新吴门诊',
'expected_patients': 297
......@@ -86,6 +86,7 @@ CLINIC_ID_TO_NAME = {info['clinic_id']: info['clinic_name'] for info in CLINIC_M
CLINIC_ID_TO_JSON = {info['clinic_id']: info['json_file'] for info in CLINIC_MAPPING.values()}
CLINIC_ID_TO_FOLDER = {info['clinic_id']: info['folder_name'] for info in CLINIC_MAPPING.values()}
# 默认用户配置 (基于实际门诊用户)
DEFAULT_USERS = [
# 学前街门诊 (3名用户)
......@@ -102,7 +103,7 @@ DEFAULT_USERS = [
'password': 'renshanshan123',
'role': 'clinic_user',
'clinic_id': 'clinic_xuexian',
'real_name': '任珊珊',
'real_name': '任姗姗',
'clinic_name': '学前街门诊'
},
{
......@@ -110,10 +111,10 @@ DEFAULT_USERS = [
'password': 'shaojun123',
'role': 'clinic_user',
'clinic_id': 'clinic_xuexian',
'real_name': '邵',
'real_name': '邵',
'clinic_name': '学前街门诊'
},
# 新吴门诊 (1名用户)
{
'username': 'litingting',
......@@ -123,7 +124,7 @@ DEFAULT_USERS = [
'real_name': '李婷婷',
'clinic_name': '新吴门诊'
},
# 红豆门诊 (2名用户)
{
'username': 'maqiuyi',
......@@ -138,10 +139,10 @@ DEFAULT_USERS = [
'password': 'tangqimin123',
'role': 'clinic_user',
'clinic_id': 'clinic_hongdou',
'real_name': '其敏',
'real_name': '其敏',
'clinic_name': '红豆门诊'
},
# 东亭门诊 (1名用户)
{
'username': 'yueling',
......@@ -151,14 +152,14 @@ DEFAULT_USERS = [
'real_name': '岳玲',
'clinic_name': '东亭门诊'
},
# 马山门诊 (2名用户)
{
'username': 'jijunlin',
'password': 'jijunlin123',
'role': 'clinic_user',
'clinic_id': 'clinic_mashan',
'real_name': '季林',
'real_name': '季林',
'clinic_name': '马山门诊'
},
{
......@@ -169,14 +170,14 @@ DEFAULT_USERS = [
'real_name': '周丽萍',
'clinic_name': '马山门诊'
},
# 通善口腔医院总院 (2名用户)
{
'username': 'feimiaomiao',
'password': 'feimiaomiao123',
'role': 'clinic_user',
'clinic_id': 'clinic_hospital',
'real_name': '费苗',
'real_name': '费苗',
'clinic_name': '通善口腔医院'
},
{
......@@ -187,7 +188,7 @@ DEFAULT_USERS = [
'real_name': '陈心语',
'clinic_name': '河埒门诊'
},
# 惠山门诊 (2名用户)
{
'username': 'yanghong',
......@@ -205,7 +206,7 @@ DEFAULT_USERS = [
'real_name': '潘金丽',
'clinic_name': '惠山门诊'
},
# 大丰门诊 (1名用户)
{
'username': 'chenlin',
......@@ -215,7 +216,7 @@ DEFAULT_USERS = [
'real_name': '陈琳',
'clinic_name': '大丰门诊'
},
# 总部管理员 (最高权限)
{
'username': 'admin',
......@@ -231,6 +232,7 @@ DEFAULT_USERS = [
SYSTEM_CONFIG = {
'total_clinics': len(CLINIC_MAPPING),
'total_expected_patients': sum(info['expected_patients'] for info in CLINIC_MAPPING.values()),
'base_url': '/patient_profiles',
'shared_resources_path': '/shared',
'admin_path': '/admin'
......
#!/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
#!/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 -*-
from generate_html import generate_html
import os
def debug_generate():
print('当前工作目录:', os.getcwd())
print('patient_profiles目录是否存在:', os.path.exists('patient_profiles'))
try:
# 尝试生成页面
print('开始生成TS0K030750页面...')
generate_html('TS0K030750', {})
print('生成函数执行完毕')
# 检查文件是否存在
file_path = 'patient_profiles/TS0K030750.html'
if os.path.exists(file_path):
print(f'✅ 文件已生成: {file_path}')
print(f'文件大小: {os.path.getsize(file_path)} 字节')
else:
print(f'❌ 文件未生成: {file_path}')
except Exception as e:
print(f'❌ 生成过程出错: {e}')
import traceback
traceback.print_exc()
if __name__ == "__main__":
debug_generate()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json
import os
def debug_generate_detailed():
print('=== 调试generate_html函数 ===')
# 1. 检查患者数据
print('1. 加载患者数据...')
with open('合并结果.json', encoding='utf-8') as f:
data = json.load(f)
# 查找TS0K030750
patient_records = []
for record in data:
if record.get('病历号') == 'TS0K030750':
patient_records.append(record)
print(f'找到 {len(patient_records)} 条TS0K030750的记录')
if patient_records:
latest = patient_records[0]
print(f'患者姓名: {latest.get("姓名", "未知")}')
print(f'病历号: {latest.get("病历号", "未知")}')
else:
print('❌ 未找到TS0K030750的记录!')
return
# 2. 检查回访记录数据
print('\n2. 检查回访记录...')
from callback_record_model import CallbackRecordManager
manager = CallbackRecordManager()
callback_records = manager.get_records_by_case_number('TS0K030750')
print(f'找到 {len(callback_records)} 条回访记录')
# 3. 调用generate_html函数
print('\n3. 调用generate_html函数...')
from generate_html import generate_html
try:
generate_html('TS0K030750', patient_records)
print('✅ generate_html函数执行完毕')
# 检查文件是否生成
if os.path.exists('TS0K030750.html'):
print('✅ 文件已生成: TS0K030750.html')
file_size = os.path.getsize('TS0K030750.html')
print(f'文件大小: {file_size} 字节')
# 移动到正确位置
import shutil
if not os.path.exists('patient_profiles'):
os.makedirs('patient_profiles')
shutil.move('TS0K030750.html', 'patient_profiles/TS0K030750.html')
print('✅ 文件已移动到patient_profiles目录')
else:
print('❌ 文件未生成')
except Exception as e:
print(f'❌ 生成过程出错: {e}')
import traceback
traceback.print_exc()
if __name__ == "__main__":
debug_generate_detailed()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
调试进度文件
详细分析当前会话的状态信息
"""
import os
import pickle
import json
from datetime import datetime
def debug_progress():
"""详细调试进度文件"""
print("🔍 详细调试进度文件...")
progress_dir = "progress_saves"
session_file = os.path.join(progress_dir, "session_20250805_232912.pkl")
if not os.path.exists(session_file):
print("❌ 未找到会话文件")
return
try:
with open(session_file, 'rb') as f:
state_data = pickle.load(f)
print("="*60)
print("🔍 会话详细信息:")
print("="*60)
print(f"📅 会话ID: {state_data.get('session_id', '未知')}")
print(f"📅 开始时间: {state_data.get('start_time', '未知')}")
print(f"💾 最后保存: {state_data.get('save_timestamp', '未知')}")
print(f"🏥 当前诊所: {state_data.get('current_clinic', '未知')}")
print(f"👤 当前患者索引: {state_data.get('current_patient_index', 0)}")
print("\n📋 配置信息:")
config = state_data.get('generation_config', {})
print(f" - API类型: {config.get('api_type', '未知')}")
print(f" - 处理模式: {config.get('mode', '未知')}")
selected_clinics = config.get('selected_clinics', [])
print(f" - 选择的诊所 ({len(selected_clinics)}): {selected_clinics}")
completed_clinics = state_data.get('completed_clinics', [])
print(f"\n✅ 已完成诊所 ({len(completed_clinics)}): {completed_clinics}")
remaining_clinics = [c for c in selected_clinics if c not in completed_clinics]
print(f"⏳ 剩余诊所 ({len(remaining_clinics)}): {remaining_clinics}")
print("\n📊 当前诊所处理状态:")
current_callbacks = state_data.get('current_clinic_callbacks', [])
if current_callbacks:
success_count = len([cb for cb in current_callbacks if cb.get('callback_type') == 'success'])
error_count = len([cb for cb in current_callbacks if cb.get('callback_type') == 'error'])
print(f" - 当前诊所已处理: {len(current_callbacks)} 个患者")
print(f" - ✅ 成功: {success_count} 个")
print(f" - ❌ 失败: {error_count} 个")
# 显示最后几个处理的患者
if len(current_callbacks) > 0:
print(f"\n📋 最后5个处理的患者:")
last_5 = current_callbacks[-5:] if len(current_callbacks) >= 5 else current_callbacks
for i, cb in enumerate(last_5):
status = "✅" if cb.get('callback_type') == 'success' else "❌"
print(f" {len(current_callbacks) - len(last_5) + i + 1}. {cb.get('patient_name', '未知')} ({cb.get('patient_id', '未知')}) {status}")
else:
print(" - 当前诊所无处理记录")
print("\n📈 所有已完成诊所统计:")
all_results = state_data.get('all_results', {})
total_processed = 0
total_success = 0
total_failed = 0
for clinic_name, callbacks in all_results.items():
success = len([cb for cb in callbacks if cb.get('callback_type') == 'success'])
failed = len([cb for cb in callbacks if cb.get('callback_type') == 'error'])
total_processed += len(callbacks)
total_success += success
total_failed += failed
print(f" 🏥 {clinic_name}: {success}✅ / {failed}❌ (共{len(callbacks)}个)")
print(f"\n📊 总计统计:")
print(f" - 总处理患者: {total_processed} 个")
print(f" - 总成功: {total_success} 个")
print(f" - 总失败: {total_failed} 个")
if total_processed > 0:
success_rate = (total_success / total_processed) * 100
print(f" - 成功率: {success_rate:.1f}%")
# 检查红豆门诊的状态
print("\n🔍 红豆门诊状态分析:")
if "红豆门诊" in completed_clinics:
print(" ✅ 红豆门诊在已完成列表中")
else:
print(" ❌ 红豆门诊不在已完成列表中")
if "红豆门诊" in all_results:
red_callbacks = all_results["红豆门诊"]
red_success = len([cb for cb in red_callbacks if cb.get('callback_type') == 'success'])
red_failed = len([cb for cb in red_callbacks if cb.get('callback_type') == 'error'])
print(f" 📊 红豆门诊结果: {red_success}✅ / {red_failed}❌ (共{len(red_callbacks)}个)")
else:
print(" ❌ 红豆门诊不在结果列表中")
if state_data.get('current_clinic') == "红豆门诊":
print(" 🔄 当前正在处理红豆门诊")
else:
print(f" ℹ️ 当前处理诊所: {state_data.get('current_clinic', '未知')}")
# 检查是否有红豆门诊的中间结果文件
output_dir = "dify_callback_results"
red_files = []
if os.path.exists(output_dir):
for file_name in os.listdir(output_dir):
if "红豆门诊" in file_name:
red_files.append(file_name)
if red_files:
print(f" 📁 找到红豆门诊文件: {red_files}")
else:
print(" 📁 未找到红豆门诊中间结果文件")
except Exception as e:
print(f"❌ 读取进度文件失败: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
debug_progress()
\ No newline at end of file
#!/bin/bash
# 生产环境部署脚本(包含数据库备份)
# 使用方法:./deploy_with_backup.sh
set -e # 遇到错误立即退出
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 日志函数
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# 配置信息
PROJECT_DIR="customer-recall"
BACKUP_DIR="/backup/database"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="production_backup_${TIMESTAMP}.sql"
# 数据库配置(从环境变量获取)
DB_HOST="${DB_HOST:-localhost}"
DB_PORT="${DB_PORT:-3306}"
DB_USER="${DB_USER:-callback_user}"
DB_NAME="${DB_NAME:-callback_system}"
log_info "🚀 开始生产环境部署流程..."
log_info "📅 部署时间: $(date)"
log_info "🏥 项目目录: $PROJECT_DIR"
log_info "🗄️ 数据库: $DB_NAME@$DB_HOST:$DB_PORT"
# 第一步:创建备份目录
log_info "📁 创建备份目录..."
mkdir -p "$BACKUP_DIR"
log_success "备份目录创建成功: $BACKUP_DIR"
# 第二步:数据库备份
log_info "💾 开始数据库备份..."
if command -v mysqldump &> /dev/null; then
# 使用mysqldump备份
log_info "使用mysqldump备份数据库..."
# 检查数据库连接
if mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASSWORD" -e "SELECT 1;" "$DB_NAME" &> /dev/null; then
log_success "数据库连接成功,开始备份..."
# 执行备份
mysqldump -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASSWORD" \
--single-transaction \
--routines \
--triggers \
--events \
--add-drop-database \
--create-options \
"$DB_NAME" > "$BACKUP_DIR/$BACKUP_FILE"
if [ $? -eq 0 ]; then
BACKUP_SIZE=$(du -h "$BACKUP_DIR/$BACKUP_FILE" | cut -f1)
log_success "数据库备份成功!"
log_info "📊 备份文件: $BACKUP_FILE"
log_info "📏 备份大小: $BACKUP_SIZE"
log_info "📍 备份路径: $BACKUP_DIR/$BACKUP_FILE"
else
log_error "数据库备份失败!"
exit 1
fi
else
log_error "无法连接到数据库,请检查配置!"
exit 1
fi
else
log_warning "mysqldump命令不存在,尝试使用Docker容器备份..."
# 使用Docker容器备份
if docker ps | grep -q mysql; then
log_info "使用Docker容器备份数据库..."
docker exec $(docker ps -q --filter "ancestor=mysql") \
mysqldump -u"$DB_USER" -p"$DB_PASSWORD" \
--single-transaction \
--routines \
--triggers \
--events \
--add-drop-database \
--create-options \
"$DB_NAME" > "$BACKUP_DIR/$BACKUP_FILE"
if [ $? -eq 0 ]; then
BACKUP_SIZE=$(du -h "$BACKUP_DIR/$BACKUP_FILE" | cut -f1)
log_success "Docker容器备份成功!"
log_info "📊 备份文件: $BACKUP_FILE"
log_info "📏 备份大小: $BACKUP_SIZE"
else
log_error "Docker容器备份失败!"
exit 1
fi
else
log_error "未找到MySQL容器,无法备份数据库!"
exit 1
fi
fi
# 第三步:验证备份文件
log_info "🔍 验证备份文件..."
if [ -f "$BACKUP_DIR/$BACKUP_FILE" ] && [ -s "$BACKUP_DIR/$BACKUP_FILE" ]; then
log_success "备份文件验证成功!"
else
log_error "备份文件验证失败!"
exit 1
fi
# 第四步:更新代码
log_info "📥 更新项目代码..."
cd "$PROJECT_DIR"
git pull origin master
log_success "代码更新成功!"
# 第五步:部署Docker容器
log_info "🐳 部署Docker容器..."
docker compose down
docker compose up -d --build
# 等待容器启动
log_info "⏳ 等待容器启动..."
sleep 10
# 检查容器状态
if docker compose ps | grep -q "Up"; then
log_success "Docker容器启动成功!"
else
log_error "Docker容器启动失败!"
exit 1
fi
# 第六步:执行安全数据导入
log_info "📋 执行安全数据导入..."
if docker exec $(docker compose ps -q app) python safe_import_patients.py; then
log_success "安全数据导入成功!"
else
log_warning "安全数据导入失败,但部署继续..."
fi
# 第七步:清理旧备份(保留最近7天)
log_info "🧹 清理旧备份文件..."
find "$BACKUP_DIR" -name "production_backup_*.sql" -mtime +7 -delete
log_success "旧备份文件清理完成!"
# 部署完成
log_success "🎉 生产环境部署完成!"
log_info "📊 部署总结:"
log_info " ✅ 数据库备份: $BACKUP_FILE"
log_info " ✅ 代码更新: 完成"
log_info " ✅ 容器部署: 完成"
log_info " ✅ 数据导入: 完成"
log_info " 📍 备份位置: $BACKUP_DIR"
log_info " 🕐 完成时间: $(date)"
# 显示备份文件列表
log_info "📋 当前备份文件列表:"
ls -lh "$BACKUP_DIR"/production_backup_*.sql 2>/dev/null || log_warning "暂无备份文件"
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
修复所有门诊患者画像样式问题
重新生成除了大丰门诊和学前街门诊之外的所有门诊患者画像,确保样式正确
"""
import os
import json
import sys
import traceback
import shutil
# 需要修复的门诊列表(排除大丰门诊和学前街门诊)
CLINICS_TO_FIX = [
'东亭门诊',
'河埒门诊',
'红豆门诊',
'惠山门诊',
'马山门诊',
'通善口腔医院',
'新吴门诊'
]
# 门诊ID映射
CLINIC_ID_MAPPING = {
'东亭门诊': 'clinic_dongting',
'河埒门诊': 'clinic_helai',
'红豆门诊': 'clinic_hongdou',
'惠山门诊': 'clinic_huishan',
'马山门诊': 'clinic_mashan',
'通善口腔医院': 'clinic_hospital',
'新吴门诊': 'clinic_xinwu'
}
def load_clinic_data(clinic_name):
"""加载指定门诊数据"""
try:
print(f"🔍 正在检查 {clinic_name} 数据文件...")
json_file = f'诊所患者json/{clinic_name}.json'
print(f"📁 数据文件路径: {json_file}")
if os.path.exists(json_file):
print(f"✅ 数据文件存在,大小: {os.path.getsize(json_file)} 字节")
with open(json_file, 'r', encoding='utf-8') as f:
print(f"📖 正在读取 {clinic_name} 数据文件...")
data = json.load(f)
print(f"✅ 成功加载 {clinic_name} 数据: {len(data)} 条记录")
return data
else:
print(f"❌ 未找到 {clinic_name} 数据文件: {json_file}")
return []
except Exception as e:
print(f"❌ 加载 {clinic_name} 数据失败: {e}")
print(f"🔍 详细错误信息: {traceback.format_exc()}")
return []
def fix_clinic_patients(clinic_name):
"""修复指定门诊的患者画像"""
print(f"\n🔧 开始修复 {clinic_name} 患者画像...")
# 加载数据
print(f"📊 步骤1: 加载 {clinic_name} 数据...")
data = load_clinic_data(clinic_name)
if not data:
print(f"❌ 无法加载 {clinic_name} 数据,跳过修复")
return False
# 合并患者数据
print(f"📊 步骤2: 合并 {clinic_name} 患者数据...")
try:
from generate_html import merge_patient_data
patients_data = merge_patient_data(data)
print(f"📊 合并后患者数量: {len(patients_data)}")
except Exception as e:
print(f"❌ 合并 {clinic_name} 患者数据失败: {e}")
print(f"🔍 详细错误信息: {traceback.format_exc()}")
return False
# 创建输出目录
print(f"📁 步骤3: 创建 {clinic_name} 输出目录...")
clinic_id = CLINIC_ID_MAPPING.get(clinic_name)
if not clinic_id:
print(f"❌ 未找到 {clinic_name} 的ID映射")
return False
output_dir = f"patient_profiles/{clinic_id}/patients"
os.makedirs(output_dir, exist_ok=True)
print(f"✅ 输出目录: {output_dir}")
# 重新生成每个患者的HTML
print(f"🔄 步骤4: 生成 {clinic_name} 患者画像...")
success_count = 0
error_count = 0
# 处理所有患者
all_patients = list(patients_data.items())
print(f"📊 开始处理 {clinic_name} 的所有 {len(all_patients)} 个患者...")
for i, (patient_id, records) in enumerate(all_patients, 1):
try:
print(f"🔄 正在生成患者 {patient_id} 的画像... ({i}/{len(all_patients)})")
# 生成HTML
from generate_html import generate_html
generate_html(patient_id, records)
# 检查文件是否生成成功
html_file = f"{patient_id}.html"
if os.path.exists(html_file):
# 目标文件路径
target_file = os.path.join(output_dir, html_file)
# 如果目标文件已存在,先删除
if os.path.exists(target_file):
print(f"🗑️ 删除已存在的文件: {target_file}")
os.remove(target_file)
# 移动文件到目标目录
shutil.move(html_file, target_file)
print(f"✅ 患者 {patient_id} 画像生成成功")
success_count += 1
else:
print(f"❌ 患者 {patient_id} 画像生成失败 - 文件未创建")
error_count += 1
except Exception as e:
print(f"❌ 生成患者 {patient_id} 画像时出错: {e}")
print(f"🔍 详细错误信息: {traceback.format_exc()}")
error_count += 1
print(f"\n📈 {clinic_name} 修复完成!")
print(f"✅ 成功: {success_count} 个患者")
print(f"❌ 失败: {error_count} 个患者")
return success_count > 0
def fix_all_clinics():
"""修复所有门诊的患者画像"""
print("🚀 开始修复所有门诊患者画像...")
print("=" * 60)
total_success = 0
total_error = 0
failed_clinics = []
for clinic_name in CLINICS_TO_FIX:
print(f"\n{'='*20} {clinic_name} {'='*20}")
try:
success = fix_clinic_patients(clinic_name)
if success:
total_success += 1
else:
total_error += 1
failed_clinics.append(clinic_name)
except Exception as e:
print(f"❌ 修复 {clinic_name} 时发生异常: {e}")
total_error += 1
failed_clinics.append(clinic_name)
# 输出总结
print(f"\n{'='*60}")
print("📊 修复总结")
print(f"{'='*60}")
print(f"✅ 成功修复的门诊: {total_success}")
print(f"❌ 修复失败的门诊: {total_error}")
if failed_clinics:
print(f"🔍 修复失败的门诊列表:")
for clinic in failed_clinics:
print(f" - {clinic}")
print(f"\n🎯 修复完成!请检查 patient_profiles/ 目录下的各门诊文件夹")
return total_error == 0
def main():
"""主函数"""
print("🚀 所有门诊患者画像修复工具")
print("=" * 60)
print("📋 将修复以下门诊:")
for i, clinic in enumerate(CLINICS_TO_FIX, 1):
clinic_id = CLINIC_ID_MAPPING.get(clinic, '未知')
print(f" {i}. {clinic} -> {clinic_id}")
print(f"\n⏭️ 跳过以下门诊(已修复):")
print(" - 大丰门诊 (clinic_dafeng)")
print(" - 学前街门诊 (clinic_xuexian)")
# 检查依赖
print("\n🔍 检查依赖...")
try:
import pandas as pd
print("✅ pandas 已安装")
except ImportError:
print("❌ pandas 未安装,请运行: pip install pandas")
return False
try:
import flask
print("✅ flask 已安装")
except ImportError:
print("❌ flask 未安装,请运行: pip install flask")
return False
# 确认操作
print(f"\n⚠️ 此操作将重新生成 {len(CLINICS_TO_FIX)} 个门诊的所有患者画像")
print("📁 输出目录: patient_profiles/")
# 执行修复
print("\n🚀 开始执行修复...")
success = fix_all_clinics()
if success:
print("\n🎉 所有门诊修复完成!")
print("📁 请检查以下目录:")
for clinic in CLINICS_TO_FIX:
clinic_id = CLINIC_ID_MAPPING.get(clinic)
if clinic_id:
print(f" - patient_profiles/{clinic_id}/patients/")
else:
print("\n💥 部分门诊修复失败,请检查错误信息")
return success
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\n\n👋 用户中断操作")
sys.exit(0)
except Exception as e:
print(f"\n💥 程序异常: {e}")
print(f"🔍 详细错误信息: {traceback.format_exc()}")
sys.exit(1)
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
批量修复所有患者页面的回访记录容器
为所有患者HTML文件添加缺失的 callback-records-list 容器
"""
import os
import re
import glob
ROOT = os.path.dirname(os.path.abspath(__file__))
PATIENT_PROFILES_DIR = os.path.join(ROOT, 'patient_profiles')
def fix_patient_html(file_path):
"""修复单个患者HTML文件"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# 检查是否已经有 callback-records-list 容器
if 'id="callback-records-list"' in content:
return False # 已经修复过,跳过
# 查找需要修复的位置:<div class="p-6"> 后面的硬编码内容
pattern = r'(<div class="p-6">\s*)\n\s*<div class="mb-6 text-center py-8">\s*<i class="fas fa-phone-slash text-4xl text-gray-300 mb-3"></i>\s*<h3 class="text-lg font-medium text-gray-600 mb-2">暂无回访记录</h3>\s*<p class="text-gray-500 mb-4">该患者尚未进行过回访,请填写首次回访记录。</p>\s*</div>\s*(\n\s*<!-- 新建回访记录表单)'
replacement = r'''\1
<!-- 回访记录列表容器 -->
<div id="callback-records-list">
<div class="mb-6 text-center py-8">
<i class="fas fa-phone-slash text-4xl text-gray-300 mb-3"></i>
<h3 class="text-lg font-medium text-gray-600 mb-2">暂无回访记录</h3>
<p class="text-gray-500 mb-4">该患者尚未进行过回访,请填写首次回访记录。</p>
</div>
</div>
\2'''
# 执行替换
new_content, count = re.subn(pattern, replacement, content, flags=re.MULTILINE | re.DOTALL)
if count > 0:
# 保存修复后的文件
with open(file_path, 'w', encoding='utf-8') as f:
f.write(new_content)
return True
else:
# 如果没有匹配到,尝试更简单的模式
simple_pattern = r'(<div class="p-6">\s*)(.*?)(\s*<!-- 新建回访记录表单)'
def replace_func(match):
prefix = match.group(1)
middle = match.group(2)
suffix = match.group(3)
# 如果中间部分包含"暂无回访记录"但没有callback-records-list容器
if '暂无回访记录' in middle and 'callback-records-list' not in middle:
new_middle = '''
<!-- 回访记录列表容器 -->
<div id="callback-records-list">
<div class="mb-6 text-center py-8">
<i class="fas fa-phone-slash text-4xl text-gray-300 mb-3"></i>
<h3 class="text-lg font-medium text-gray-600 mb-2">暂无回访记录</h3>
<p class="text-gray-500 mb-4">该患者尚未进行过回访,请填写首次回访记录。</p>
</div>
</div>
'''
return prefix + new_middle + suffix
return match.group(0)
new_content, count = re.subn(simple_pattern, replace_func, content, flags=re.MULTILINE | re.DOTALL)
if count > 0:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(new_content)
return True
return False
except Exception as e:
print(f"修复文件失败 {file_path}: {e}")
return False
def main():
"""主函数"""
# 查找所有患者HTML文件
pattern = os.path.join(PATIENT_PROFILES_DIR, '*/patients/*.html')
html_files = glob.glob(pattern)
print(f"找到 {len(html_files)} 个患者HTML文件")
fixed_count = 0
skipped_count = 0
for file_path in html_files:
try:
if fix_patient_html(file_path):
fixed_count += 1
if fixed_count % 100 == 0:
print(f"已修复 {fixed_count} 个文件...")
else:
skipped_count += 1
except Exception as e:
print(f"处理文件失败 {file_path}: {e}")
print(f"\n修复完成:")
print(f"- 修复的文件: {fixed_count}")
print(f"- 跳过的文件: {skipped_count}")
print(f"- 总文件数: {len(html_files)}")
if __name__ == '__main__':
main()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
修复所有患者页面中的硬编码API URL
将 localhost:5000 替换为相对路径
"""
import os
import re
ROOT = os.path.dirname(os.path.abspath(__file__))
TARGET_DIR = os.path.join(ROOT, 'patient_profiles')
def fix_file(file_path):
"""修复单个文件"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
original = content
total_changes = 0
# 检查是否包含需要修复的内容
if 'localhost:5000' in content:
print(f"发现需要修复的文件: {file_path}")
# 使用简单的字符串替换
# 替换 POST 请求 - 单引号格式
content, count1 = re.subn(r"fetch\('http://localhost:5000(/api/[^']*)',", r"fetch('\1',", content)
total_changes += count1
# 替换 GET 请求 - 模板字符串格式
content, count2 = re.subn(r'fetch\(`http://localhost:5000(/api/[^`]+)`\)', r'fetch(`\1`)', content)
total_changes += count2
# 替换 GET 请求 - 单引号格式
content, count3 = re.subn(r"fetch\('http://localhost:5000(/api/[^']*)'\)", r"fetch('\1')", content)
total_changes += count3
if content != original:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)
print(f"✅ 修复 {file_path}: {total_changes} 处更改")
return total_changes
else:
print(f"⚠️ 文件 {file_path} 包含 localhost:5000 但正则表达式未匹配")
return 0
except Exception as e:
print(f"处理文件 {file_path} 时出错: {e}")
return 0
def main():
"""主函数"""
print("🔧 开始修复所有患者页面中的API URL...")
total_files = 0
changed_files = 0
total_changes = 0
for dirpath, dirnames, filenames in os.walk(TARGET_DIR):
for filename in filenames:
if filename.lower().endswith('.html'):
file_path = os.path.join(dirpath, filename)
total_files += 1
changes = fix_file(file_path)
if changes > 0:
changed_files += 1
total_changes += changes
print(f"\n📊 修复完成:")
print(f" - 总文件数: {total_files}")
print(f" - 修改文件数: {changed_files}")
print(f" - 总修改数: {total_changes}")
if changed_files > 0:
print(f"\n🎉 成功修复了 {changed_files} 个文件,共 {total_changes} 处更改")
else:
print("\n✅ 所有文件都是最新的,无需修复")
if __name__ == "__main__":
main()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
修复回访记录初始化问题
在页面加载时调用updateCallbackRecords函数来加载回访记录
"""
import os
import re
import glob
ROOT = os.path.dirname(os.path.abspath(__file__))
PATIENT_PROFILES_DIR = os.path.join(ROOT, 'patient_profiles')
def fix_callback_records_initialization(file_path):
"""修复单个患者页面的回访记录初始化问题"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# 检查是否已经添加了页面加载时初始化回访记录的功能
if 'updateCallbackRecords(patientId)' in content and 'DOMContentLoaded' in content:
# 检查是否在DOMContentLoaded中调用了updateCallbackRecords
if re.search(r'DOMContentLoaded.*updateCallbackRecords', content, re.DOTALL):
return False # 已经修复过了
# 获取病历号
case_number = os.path.basename(file_path).replace('.html', '')
# 查找DOMContentLoaded事件监听器
dom_loaded_pattern = r'(document\.addEventListener\(\'DOMContentLoaded\', function\(\) \{)(.*?)(\}\);)'
match = re.search(dom_loaded_pattern, content, re.DOTALL)
if match:
# 在现有的DOMContentLoaded函数中添加初始化回访记录的代码
existing_code = match.group(2)
new_code = existing_code.rstrip() + f'''
// 页面加载时初始化回访记录
console.log('页面加载完成,开始初始化回访记录...');
updateCallbackRecords('{case_number}');'''
new_content = content.replace(match.group(0),
match.group(1) + new_code + match.group(3))
else:
# 如果没有找到DOMContentLoaded,添加一个新的
# 查找</script>标签的位置
script_end_pattern = r'(\s*</script>\s*</body>)'
script_match = re.search(script_end_pattern, content)
if script_match:
new_script = f'''
<script>
// 页面加载完成后初始化回访记录
document.addEventListener('DOMContentLoaded', function() {{
console.log('页面加载完成,开始初始化回访记录...');
updateCallbackRecords('{case_number}');
}});
</script>
{script_match.group(1)}'''
new_content = content.replace(script_match.group(0), new_script)
else:
# 如果找不到合适的位置,在</body>前添加
body_end_pattern = r'(\s*</body>)'
body_match = re.search(body_end_pattern, content)
if body_match:
new_script = f'''
<script>
// 页面加载完成后初始化回访记录
document.addEventListener('DOMContentLoaded', function() {{
console.log('页面加载完成,开始初始化回访记录...');
updateCallbackRecords('{case_number}');
}});
</script>
{body_match.group(1)}'''
new_content = content.replace(body_match.group(0), new_script)
else:
print(f"无法在文件 {file_path} 中找到合适的位置添加初始化代码")
return False
# 写回文件
with open(file_path, 'w', encoding='utf-8') as f:
f.write(new_content)
print(f"✅ 已修复: {file_path}")
return True
except Exception as e:
print(f"❌ 修复失败 {file_path}: {e}")
return False
def main():
"""主函数:修复所有患者页面的回访记录初始化问题"""
print("🔧 开始修复回访记录初始化问题...")
# 查找所有患者页面
patient_files = []
for clinic_dir in glob.glob(os.path.join(PATIENT_PROFILES_DIR, 'clinic_*')):
patients_dir = os.path.join(clinic_dir, 'patients')
if os.path.exists(patients_dir):
patient_files.extend(glob.glob(os.path.join(patients_dir, '*.html')))
print(f"📁 找到 {len(patient_files)} 个患者页面")
fixed_count = 0
for file_path in patient_files:
if fix_callback_records_initialization(file_path):
fixed_count += 1
print(f"\n🎉 修复完成!")
print(f"📊 总计: {len(patient_files)} 个文件")
print(f"✅ 已修复: {fixed_count} 个文件")
print(f"⏭️ 跳过: {len(patient_files) - fixed_count} 个文件(已修复或无需修复)")
print(f"\n💡 修复说明:")
print(f" - 在页面加载时自动调用 updateCallbackRecords() 函数")
print(f" - 确保刷新页面后回访记录能正常显示")
print(f" - 添加了控制台日志便于调试")
if __name__ == '__main__':
main()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
批量修复门诊页面认证检查代码
"""
import os
import re
def fix_clinic_auth_files():
"""修复所有门诊页面的认证检查代码"""
# 门诊目录列表
clinic_dirs = [
'clinic_xuexian',
'clinic_dafeng',
'clinic_dongting',
'clinic_helai',
'clinic_hongdou',
'clinic_huishan',
'clinic_mashan',
'clinic_hospital',
'clinic_xinwu'
]
# 需要修复的代码模式
old_auth_check = ''' // 检查用户登录状态
async function checkAuth() {
try {
const response = await fetch('/api/user-info');
const result = await response.json();
if (!result.success) {
// 未登录,重定向到登录页
window.location.href = '/login';
return;
}
// 显示用户信息
displayUserInfo(result.user);
// 检查页面访问权限
await checkPageAccess(result.user);
} catch (error) {
console.error('认证检查失败:', error);
window.location.href = '/login';
}
}'''
new_auth_check = ''' // 检查用户登录状态
async function checkAuth() {
try {
// 从localStorage或sessionStorage获取session_id
const sessionId = localStorage.getItem('session_id') || sessionStorage.getItem('session_id');
if (!sessionId) {
window.location.href = '/login';
return;
}
const response = await fetch('/api/user-info', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
session_id: sessionId
})
});
const result = await response.json();
if (!result.success) {
// 清除无效的session
localStorage.removeItem('session_id');
sessionStorage.removeItem('session_id');
window.location.href = '/login';
return;
}
// 显示用户信息
displayUserInfo(result.user);
// 检查页面访问权限
await checkPageAccess(result.user);
} catch (error) {
console.error('认证检查失败:', error);
window.location.href = '/login';
}
}'''
# 需要修复的登出代码模式
old_logout = ''' function logout() {
if (confirm('确定要登出吗?')) {
fetch('/api/logout', {method: 'POST'})
.then(() => window.location.href = '/login')
.catch(() => window.location.href = '/login');
}
}'''
new_logout = ''' function logout() {
if (confirm('确定要登出吗?')) {
fetch('/api/auth/logout', {method: 'POST'})
.then(() => window.location.href = '/login')
.catch(() => window.location.href = '/login');
}
}'''
fixed_count = 0
for clinic_dir in clinic_dirs:
file_path = f'patient_profiles/{clinic_dir}/index.html'
if not os.path.exists(file_path):
print(f"⚠️ 文件不存在: {file_path}")
continue
try:
# 读取文件内容
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
original_content = content
# 修复认证检查代码
if old_auth_check in content:
content = content.replace(old_auth_check, new_auth_check)
print(f"✅ 修复认证检查: {clinic_dir}")
else:
print(f"⚠️ 未找到认证检查代码: {clinic_dir}")
# 修复登出代码
if old_logout in content:
content = content.replace(old_logout, new_logout)
print(f"✅ 修复登出代码: {clinic_dir}")
else:
print(f"⚠️ 未找到登出代码: {clinic_dir}")
# 如果内容有变化,写回文件
if content != original_content:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)
fixed_count += 1
print(f"✅ 已修复: {clinic_dir}")
else:
print(f"ℹ️ 无需修复: {clinic_dir}")
except Exception as e:
print(f"❌ 修复失败 {clinic_dir}: {e}")
print(f"\n🎉 修复完成!共修复了 {fixed_count} 个文件")
if __name__ == "__main__":
print("开始批量修复门诊页面认证检查代码...")
fix_clinic_auth_files()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
修复诊所跳跃问题
解决红豆门诊数据丢失和通善口腔医院跳过前89个患者的问题
"""
import os
import pickle
import shutil
from datetime import datetime
def fix_clinic_jump():
"""修复诊所跳跃问题"""
print("🔧 修复诊所跳跃问题...")
session_file = "progress_saves/session_20250805_232912.pkl"
if not os.path.exists(session_file):
print("❌ 未找到会话文件")
return
try:
# 1. 备份当前文件
backup_file = f"progress_saves/before_fix_{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', [])}")
# 3. 分析问题
print("\n🔍 问题分析:")
print(" 1. 红豆门诊数据完全丢失")
print(" 2. 通善口腔医院从第90个患者开始,跳过了前89个")
print(" 3. 需要重新处理红豆门诊和通善口腔医院的前89个患者")
# 4. 修复策略
print("\n🔧 修复策略:")
# 方案选择
print("请选择修复方案:")
print("1. 重新处理红豆门诊 (推荐)")
print("2. 重新处理通善口腔医院前89个患者")
print("3. 同时修复两个诊所")
while True:
try:
choice = input("请选择方案 (1-3): ").strip()
if choice == "1":
fix_red_clinic(state_data)
break
elif choice == "2":
fix_tongshan_clinic(state_data)
break
elif choice == "3":
fix_both_clinics(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 fix_red_clinic(state_data):
"""修复红豆门诊"""
print("\n🔧 修复红豆门诊...")
# 将当前诊所设置回红豆门诊
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('红豆门诊')
# 如果红豆门诊在all_results中,移除它
if 'all_results' in state_data and '红豆门诊' in state_data['all_results']:
del state_data['all_results']['红豆门诊']
print(" ✅ 设置当前诊所为红豆门诊")
print(" ✅ 重置患者索引为0")
print(" ✅ 清空临时数据")
print(" ✅ 从已完成列表中移除红豆门诊")
def fix_tongshan_clinic(state_data):
"""修复通善口腔医院"""
print("\n🔧 修复通善口腔医院...")
# 将当前诊所设置为通善口腔医院
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('通善口腔医院')
# 如果通善口腔医院在all_results中,移除它
if 'all_results' in state_data and '通善口腔医院' in state_data['all_results']:
del state_data['all_results']['通善口腔医院']
print(" ✅ 设置当前诊所为通善口腔医院")
print(" ✅ 重置患者索引为0")
print(" ✅ 清空临时数据")
print(" ✅ 从已完成列表中移除通善口腔医院")
def fix_both_clinics(state_data):
"""同时修复两个诊所"""
print("\n🔧 同时修复红豆门诊和通善口腔医院...")
# 修复红豆门诊
fix_red_clinic(state_data)
# 注意:这里我们选择先处理红豆门诊
# 因为根据您的描述,红豆门诊应该先处理
print("\n📋 处理顺序:")
print(" 1. 先处理红豆门诊 (从头开始)")
print(" 2. 再处理通善口腔医院 (从头开始)")
print(" 3. 最后处理马山门诊")
def show_current_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)
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:
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(" - 当前诊所无处理记录")
except Exception as e:
print(f"❌ 读取状态失败: {e}")
if __name__ == "__main__":
print("="*60)
print("诊所跳跃问题修复工具")
print("="*60)
# 显示当前状态
show_current_status()
# 询问是否修复
print("\n🔧 是否要修复诊所跳跃问题?")
confirm = input("确认修复?(Y/n): ").strip().lower()
if confirm in ['', 'y', 'yes']:
fix_clinic_jump()
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 fix_clinic_state():
"""修复红豆门诊的处理状态"""
print("🔧 修复红豆门诊处理状态...")
progress_dir = "progress_saves"
session_file = os.path.join(progress_dir, "session_20250805_232912.pkl")
backup_file = os.path.join(progress_dir, f"session_20250805_232912_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pkl")
if not os.path.exists(session_file):
print("❌ 未找到会话文件")
return
try:
# 1. 备份原文件
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('completed_clinics', [])}")
print(f" - 当前患者索引: {state_data.get('current_patient_index', 0)}")
# 3. 修复状态
print("\n🔧 修复操作:")
# 将当前诊所设置回红豆门诊
state_data['current_clinic'] = '红豆门诊'
state_data['current_patient_index'] = 0 # 从头开始处理红豆门诊
# 清空当前诊所的回调数据(如果有的话)
state_data['current_clinic_callbacks'] = []
# 如果红豆门诊在all_results中,移除它(重新处理)
if 'all_results' in state_data and '红豆门诊' in state_data['all_results']:
del state_data['all_results']['红豆门诊']
print(" ✅ 清除红豆门诊的旧结果数据")
# 确保红豆门诊不在已完成列表中
if 'completed_clinics' in state_data and '红豆门诊' in state_data['completed_clinics']:
state_data['completed_clinics'].remove('红豆门诊')
print(" ✅ 从已完成列表中移除红豆门诊")
# 更新保存时间戳
state_data['save_timestamp'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
state_data['last_save_time'] = datetime.now()
# 4. 保存修复后的状态
with open(session_file, 'wb') as f:
pickle.dump(state_data, f)
print(" ✅ 设置当前诊所为红豆门诊")
print(" ✅ 重置患者索引为0")
print(" ✅ 清空临时回调数据")
print("\n📊 修复后状态:")
print(f" - 当前诊所: {state_data.get('current_clinic', '未知')}")
print(f" - 已完成诊所: {state_data.get('completed_clinics', [])}")
print(f" - 当前患者索引: {state_data.get('current_patient_index', 0)}")
# 5. 验证修复结果
print("\n🔍 验证修复结果:")
selected_clinics = state_data.get('generation_config', {}).get('selected_clinics', [])
completed_clinics = state_data.get('completed_clinics', [])
current_clinic = state_data.get('current_clinic')
if current_clinic == '红豆门诊':
print(" ✅ 当前诊所已设置为红豆门诊")
else:
print(f" ❌ 当前诊所设置错误: {current_clinic}")
if '红豆门诊' not in completed_clinics:
print(" ✅ 红豆门诊不在已完成列表中")
else:
print(" ❌ 红豆门诊仍在已完成列表中")
remaining_clinics = [c for c in selected_clinics if c not in completed_clinics]
print(f" 📋 剩余待处理诊所: {remaining_clinics}")
print("\n✅ 修复完成!现在可以重新启动生成器,系统将从红豆门诊开始处理。")
except Exception as e:
print(f"❌ 修复失败: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
fix_clinic_state()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
修复大丰门诊患者画像样式问题
重新生成大丰门诊的患者画像,确保样式正确
"""
import os
import json
import sys
import traceback
import shutil
def load_dafeng_data():
"""加载大丰门诊数据"""
try:
print("🔍 正在检查数据文件...")
# 尝试从诊所患者json目录加载
json_file = '诊所患者json/大丰门诊.json'
print(f"📁 数据文件路径: {json_file}")
if os.path.exists(json_file):
print(f"✅ 数据文件存在,大小: {os.path.getsize(json_file)} 字节")
with open(json_file, 'r', encoding='utf-8') as f:
print("📖 正在读取数据文件...")
data = json.load(f)
print(f"✅ 成功加载大丰门诊数据: {len(data)} 条记录")
return data
else:
print(f"❌ 未找到大丰门诊数据文件: {json_file}")
return []
except Exception as e:
print(f"❌ 加载大丰门诊数据失败: {e}")
print(f"🔍 详细错误信息: {traceback.format_exc()}")
return []
def fix_dafeng_patients():
"""修复大丰门诊患者画像"""
print("🔧 开始修复大丰门诊患者画像...")
# 加载数据
print("📊 步骤1: 加载数据...")
data = load_dafeng_data()
if not data:
print("❌ 无法加载数据,修复失败")
return False
# 合并患者数据
print("📊 步骤2: 合并患者数据...")
try:
from generate_html import merge_patient_data
patients_data = merge_patient_data(data)
print(f"📊 合并后患者数量: {len(patients_data)}")
except Exception as e:
print(f"❌ 合并患者数据失败: {e}")
print(f"🔍 详细错误信息: {traceback.format_exc()}")
return False
# 创建输出目录
print("📁 步骤3: 创建输出目录...")
output_dir = "patient_profiles/clinic_dafeng/patients"
os.makedirs(output_dir, exist_ok=True)
print(f"✅ 输出目录: {output_dir}")
# 重新生成每个患者的HTML
print("🔄 步骤4: 生成患者画像...")
success_count = 0
error_count = 0
# 处理所有患者
all_patients = list(patients_data.items())
print(f"📊 开始处理所有 {len(all_patients)} 个患者...")
for i, (patient_id, records) in enumerate(all_patients, 1):
try:
print(f"🔄 正在生成患者 {patient_id} 的画像... ({i}/{len(all_patients)})")
# 生成HTML
from generate_html import generate_html
generate_html(patient_id, records)
# 检查文件是否生成成功
html_file = f"{patient_id}.html"
if os.path.exists(html_file):
# 目标文件路径
target_file = os.path.join(output_dir, html_file)
# 如果目标文件已存在,先删除
if os.path.exists(target_file):
print(f"🗑️ 删除已存在的文件: {target_file}")
os.remove(target_file)
# 移动文件到目标目录
shutil.move(html_file, target_file)
print(f"✅ 患者 {patient_id} 画像生成成功")
success_count += 1
else:
print(f"❌ 患者 {patient_id} 画像生成失败 - 文件未创建")
error_count += 1
except Exception as e:
print(f"❌ 生成患者 {patient_id} 画像时出错: {e}")
print(f"🔍 详细错误信息: {traceback.format_exc()}")
error_count += 1
print(f"\n📈 修复完成!")
print(f"✅ 成功: {success_count} 个患者")
print(f"❌ 失败: {error_count} 个患者")
return success_count > 0
def main():
"""主函数"""
print("🚀 大丰门诊患者画像修复工具")
print("=" * 50)
# 检查依赖
print("🔍 检查依赖...")
try:
import pandas as pd
print("✅ pandas 已安装")
except ImportError:
print("❌ pandas 未安装,请运行: pip install pandas")
return False
try:
import flask
print("✅ flask 已安装")
except ImportError:
print("❌ flask 未安装,请运行: pip install flask")
return False
# 执行修复
print("\n🚀 开始执行修复...")
success = fix_dafeng_patients()
if success:
print("\n🎉 修复完成!请检查 patient_profiles/clinic_dafeng/patients/ 目录")
else:
print("\n💥 修复失败,请检查错误信息")
return success
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\n\n👋 用户中断操作")
sys.exit(0)
except Exception as e:
print(f"\n💥 程序异常: {e}")
print(f"🔍 详细错误信息: {traceback.format_exc()}")
sys.exit(1)
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
修复数据库编码问题的脚本
"""
import os
import sys
from callback_record_mysql import MySQLCallbackRecordManager
def fix_database_encoding():
"""修复数据库编码问题"""
try:
# 从环境变量获取数据库配置
db_config = {
'host': os.getenv('DB_HOST', 'localhost'),
'port': int(os.getenv('DB_PORT', 3306)),
'user': os.getenv('DB_USER', 'callback_user'),
'password': os.getenv('DB_PASSWORD', 'dev_password_123'),
'database': os.getenv('DB_NAME', 'callback_system'),
'charset': 'utf8mb4'
}
print("正在连接数据库...")
db_manager = MySQLCallbackRecordManager(**db_config)
# 测试连接
if not db_manager.test_connection():
print("数据库连接失败")
return False
print("数据库连接成功")
# 重新初始化数据库(这会删除并重新创建表)
print("正在重新初始化数据库...")
db_manager.init_database()
print("数据库编码问题修复完成")
return True
except Exception as e:
print(f"修复数据库编码失败: {e}")
return False
if __name__ == "__main__":
success = fix_database_encoding()
if success:
print("✅ 数据库编码修复成功")
else:
print("❌ 数据库编码修复失败")
sys.exit(1)
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
最终数据库编码修复脚本
"""
import os
import sys
import pymysql
from callback_record_mysql import MySQLCallbackRecordManager, CallbackRecord
def fix_database_encoding_final():
"""最终修复数据库编码问题"""
try:
# 从环境变量获取数据库配置
db_config = {
'host': os.getenv('DB_HOST', 'localhost'),
'port': int(os.getenv('DB_PORT', 3306)),
'user': os.getenv('DB_USER', 'callback_user'),
'password': os.getenv('DB_PASSWORD', 'dev_password_123'),
'database': os.getenv('DB_NAME', 'callback_system'),
'charset': 'utf8mb4'
}
print("正在连接数据库...")
# 直接测试数据库连接
conn = pymysql.connect(
host=db_config['host'],
port=db_config['port'],
user=db_config['user'],
password=db_config['password'],
database=db_config['database'],
charset='utf8mb4',
use_unicode=True,
init_command='SET NAMES utf8mb4'
)
with conn.cursor() as cursor:
# 设置会话字符集
cursor.execute("SET NAMES utf8mb4")
cursor.execute("SET CHARACTER SET utf8mb4")
cursor.execute("SET character_set_connection=utf8mb4")
# 检查当前字符集设置
cursor.execute("SHOW VARIABLES LIKE 'character_set%'")
charset_vars = cursor.fetchall()
print("当前字符集设置:")
for var, value in charset_vars:
print(f" {var}: {value}")
# 清理测试数据
cursor.execute("DELETE FROM callback_records WHERE case_number LIKE 'TEST%'")
print("已清理测试数据")
# 测试插入中文数据
test_data = {
'case_number': 'TEST_FINAL',
'callback_methods': ['打电话', '发短信'],
'callback_result': '成功',
'callback_record': '回访方式: 打电话, 发短信, 回访结果: 成功',
'operator': '系统用户'
}
sql = """
INSERT INTO callback_records
(case_number, callback_methods, callback_record, callback_result, operator)
VALUES (%s, %s, %s, %s, %s)
"""
import json
cursor.execute(sql, (
test_data['case_number'],
json.dumps(test_data['callback_methods'], ensure_ascii=False),
test_data['callback_record'],
test_data['callback_result'],
test_data['operator']
))
conn.commit()
print("测试数据插入成功")
# 验证数据
cursor.execute("SELECT * FROM callback_records WHERE case_number = 'TEST_FINAL'")
result = cursor.fetchone()
if result:
print("验证数据:")
print(f" 病历号: {result[1]}")
print(f" 回访方式: {result[2]}")
print(f" 回访记录: {result[3]}")
print(f" 回访结果: {result[4]}")
print(f" 操作员: {result[5]}")
# 检查是否有问号
if '?' in str(result):
print("⚠️ 警告: 数据中仍然包含问号字符")
else:
print("✅ 数据编码正常")
else:
print("❌ 未找到测试数据")
conn.close()
# 使用数据库管理器测试
print("\n使用数据库管理器测试...")
db_manager = MySQLCallbackRecordManager(**db_config)
if not db_manager.test_connection():
print("数据库管理器连接失败")
return False
print("数据库管理器连接成功")
# 测试保存记录
record = CallbackRecord(
case_number='TEST_MANAGER',
callback_methods=['打电话'],
callback_record='回访方式: 打电话, 回访结果: 成功',
callback_result='成功',
operator='系统用户'
)
record_id = db_manager.save_record(record)
print(f"记录保存成功,ID: {record_id}")
# 测试获取记录
records = db_manager.get_records_by_case_number('TEST_MANAGER')
if records:
record_dict = records[0].to_dict()
print("获取的记录:")
print(f" 病历号: {record_dict['case_number']}")
print(f" 回访方式: {record_dict['callback_methods']}")
print(f" 回访结果: {record_dict['callback_result']}")
print(f" 操作员: {record_dict['operator']}")
if '?' in str(record_dict):
print("⚠️ 警告: 数据中仍然包含问号字符")
else:
print("✅ 数据库管理器编码正常")
else:
print("❌ 未找到记录")
print("\n✅ 数据库编码修复完成")
return True
except Exception as e:
print(f"❌ 修复失败: {e}")
import traceback
traceback.print_exc()
return False
if __name__ == "__main__":
fix_database_encoding_final()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import re
ROOT = os.path.dirname(os.path.abspath(__file__))
TARGET_DIR = os.path.join(ROOT, 'patient_profiles')
# 两种模式:POST 保存接口 和 GET 查询接口(处理单引号和模板字符串两种写法)
PATTERNS = [
(re.compile(r"fetch\('http://localhost:5000(/api/[^']*)'\)"), r"fetch('\1')"),
(re.compile(r"fetch\(`http://localhost:5000(/api/[^`]+)`\)"), r"fetch(`\1`)")
]
changed_files = 0
changed_spans = 0
for dirpath, _, filenames in os.walk(TARGET_DIR):
for fn in filenames:
if not fn.lower().endswith('.html'):
continue
fp = os.path.join(dirpath, fn)
try:
with open(fp, 'r', encoding='utf-8') as f:
content = f.read()
original = content
for pattern, repl in PATTERNS:
content, n = pattern.subn(repl, content)
changed_spans += n
if content != original:
with open(fp, 'w', encoding='utf-8') as f:
f.write(content)
changed_files += 1
except Exception as e:
print(f"跳过 {fp}: {e}")
print(f"已更新文件: {changed_files}, 替换片段: {changed_spans}")
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
强制修复脚本
直接修改会话文件,强制设置为红豆门诊
"""
import os
import pickle
import shutil
from datetime import datetime
def force_fix():
"""强制修复"""
print("🔧 强制修复 - 直接设置为红豆门诊...")
session_file = "progress_saves/session_20250805_232912.pkl"
if not os.path.exists(session_file):
print("❌ 未找到会话文件")
return
try:
# 1. 备份当前文件
backup_file = f"progress_saves/force_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(f"\n📊 修复前状态:")
print(f" - 当前诊所: {state_data.get('current_clinic', '未知')}")
print(f" - 当前患者索引: {state_data.get('current_patient_index', 0)}")
print(f" - 已完成诊所: {state_data.get('completed_clinics', [])}")
# 3. 保存通善口腔医院数据
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)} 个患者的处理结果")
# 4. 强制设置为红豆门诊
state_data['current_clinic'] = '红豆门诊'
state_data['current_patient_index'] = 0
state_data['current_clinic_callbacks'] = []
# 5. 确保诊所状态正确
if '红豆门诊' in state_data.get('completed_clinics', []):
state_data['completed_clinics'].remove('红豆门诊')
if '通善口腔医院' in state_data.get('completed_clinics', []):
state_data['completed_clinics'].remove('通善口腔医院')
# 6. 更新时间戳
state_data['save_timestamp'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
state_data['last_save_time'] = datetime.now()
# 7. 保存修复后的状态
with open(session_file, 'wb') as f:
pickle.dump(state_data, f)
print(f"\n📊 修复后状态:")
print(f" - 当前诊所: {state_data.get('current_clinic', '未知')}")
print(f" - 当前患者索引: {state_data.get('current_patient_index', 0)}")
print(f" - 已完成诊所: {state_data.get('completed_clinics', [])}")
print("\n✅ 强制修复完成!")
print("📋 现在系统将:")
print(" 1. 从红豆门诊开始处理")
print(" 2. 通善口腔医院已处理的数据已保存")
print(" 3. 后续会按顺序处理所有诊所")
except Exception as e:
print(f"❌ 强制修复失败: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
print("="*60)
print("强制修复工具")
print("="*60)
confirm = input("确认强制修复?这将直接修改会话状态 (Y/n): ").strip().lower()
if confirm in ['', 'y', 'yes']:
force_fix()
else:
print("取消修复")
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
为所有用户生成正确的密码哈希
"""
import hashlib
def hash_password(password: str, salt: str) -> str:
"""生成密码哈希"""
password_bytes = (password + salt).encode('utf-8')
# 进行10000轮哈希增强安全性
for _ in range(10000):
password_bytes = hashlib.sha256(password_bytes).digest()
return password_bytes.hex()
def main():
"""主函数"""
print("=== 生成所有用户的密码哈希 ===")
# 用户列表
users = [
("admin", "admin123", "admin_salt"),
("jinqin", "jinqin123", "jinqin_salt"),
("renshanshan", "renshanshan123", "renshanshan_salt"),
("shaojun", "shaojun123", "shaojun_salt"),
("litingting", "litingting123", "litingting_salt"),
("maqiuyi", "maqiuyi123", "maqiuyi_salt"),
("tangqimin", "tangqimin123", "tangqimin_salt"),
("yueling", "yueling123", "yueling_salt"),
("jijunlin", "jijunlin123", "jijunlin_salt"),
("zhouliping", "zhouliping123", "zhouliping_salt"),
("feimiaomiao", "feimiaomiao123", "feimiaomiao_salt"),
("chenxinyu", "chenxinyu123", "chenxinyu_salt"),
("yanghong", "yanghong123", "yanghong_salt"),
("panjinli", "panjinli123", "panjinli_salt"),
("chenlin", "chenlin123", "chenlin_salt"),
]
print("USE callback_system;")
print()
print("UPDATE users SET password_hash = CASE username")
for username, password, salt in users:
hash_value = hash_password(password, salt)
print(f" WHEN '{username}' THEN '{hash_value}'")
print("END WHERE username IN (")
usernames = [f"'{user[0]}'" for user in users]
print(", ".join(usernames))
print(");")
if __name__ == "__main__":
main()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
诊所患者数据转换脚本
读取诊所患者目录中的Excel文件,为每个诊所生成对应的JSON文件
只处理每个Excel文件的第一个sheet
"""
import pandas as pd
import json
import os
from pathlib import Path
import logging
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
def convert_excel_to_json(excel_file_path, output_dir):
"""
将Excel文件的第一个sheet转换为JSON文件
Args:
excel_file_path (str): Excel文件路径
output_dir (str): 输出目录
Returns:
bool: 转换是否成功
"""
try:
# 获取文件名(不含扩展名)
file_name = Path(excel_file_path).stem
# 跳过临时文件
if file_name.startswith('~$'):
logger.info(f"跳过临时文件: {file_name}")
return True
logger.info(f"正在处理: {file_name}")
# 读取Excel文件的第一个sheet
df = pd.read_excel(excel_file_path, sheet_name=0)
# 处理NaN值,转换为None
df = df.where(pd.notnull(df), None)
# 处理时间戳列,转换为字符串格式
for col in df.columns:
if df[col].dtype == 'datetime64[ns]':
df[col] = df[col].dt.strftime('%Y-%m-%d %H:%M:%S')
elif pd.api.types.is_datetime64_any_dtype(df[col]):
df[col] = df[col].astype(str)
# 转换为字典列表
data = df.to_dict('records')
# 创建输出文件路径
json_file_path = os.path.join(output_dir, f"{file_name}.json")
# 写入JSON文件
with open(json_file_path, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
logger.info(f"成功生成: {json_file_path} (包含 {len(data)} 条记录)")
return True
except Exception as e:
logger.error(f"处理文件 {excel_file_path} 时出错: {str(e)}")
return False
def main():
"""主函数"""
# 定义路径
input_dir = "诊所患者目录"
output_dir = "诊所患者json"
# 创建输出目录
os.makedirs(output_dir, exist_ok=True)
# 获取所有Excel文件
excel_files = []
for file_name in os.listdir(input_dir):
if file_name.endswith('.xlsx') and not file_name.startswith('~$'):
excel_files.append(os.path.join(input_dir, file_name))
if not excel_files:
logger.warning("未找到Excel文件")
return
logger.info(f"找到 {len(excel_files)} 个Excel文件")
# 处理每个Excel文件
success_count = 0
for excel_file in excel_files:
if convert_excel_to_json(excel_file, output_dir):
success_count += 1
logger.info(f"处理完成!成功转换 {success_count}/{len(excel_files)} 个文件")
# 生成汇总信息
summary_info = {
"转换时间": pd.Timestamp.now().strftime("%Y-%m-%d %H:%M:%S"),
"总文件数": len(excel_files),
"成功转换": success_count,
"输出目录": output_dir,
"转换的文件": []
}
# 统计每个文件的记录数
for json_file in os.listdir(output_dir):
if json_file.endswith('.json') and json_file != 'conversion_summary.json':
json_path = os.path.join(output_dir, json_file)
try:
with open(json_path, 'r', encoding='utf-8') as f:
data = json.load(f)
summary_info["转换的文件"].append({
"文件名": json_file,
"记录数": len(data)
})
except Exception as e:
logger.error(f"读取文件 {json_file} 统计信息时出错: {str(e)}")
# 保存汇总信息
summary_path = os.path.join(output_dir, 'conversion_summary.json')
with open(summary_path, 'w', encoding='utf-8') as f:
json.dump(summary_info, f, ensure_ascii=False, indent=2)
logger.info(f"汇总信息已保存到: {summary_path}")
if __name__ == "__main__":
main()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
生成完整的密码哈希SQL
"""
import hashlib
def hash_password(password: str, salt: str) -> str:
"""生成密码哈希"""
password_bytes = (password + salt).encode('utf-8')
# 进行10000轮哈希增强安全性
for _ in range(10000):
password_bytes = hashlib.sha256(password_bytes).digest()
return password_bytes.hex()
def main():
"""主函数"""
print("=== 生成完整的密码哈希SQL ===")
# 用户列表
users = [
("admin", "admin123", "admin_salt"),
("jinqin", "jinqin123", "jinqin_salt"),
("renshanshan", "renshanshan123", "renshanshan_salt"),
("shaojun", "shaojun123", "shaojun_salt"),
("litingting", "litingting123", "litingting_salt"),
("maqiuyi", "maqiuyi123", "maqiuyi_salt"),
("tangqimin", "tangqimin123", "tangqimin_salt"),
("yueling", "yueling123", "yueling_salt"),
("jijunlin", "jijunlin123", "jijunlin_salt"),
("zhouliping", "zhouliping123", "zhouliping_salt"),
("feimiaomiao", "feimiaomiao123", "feimiaomiao_salt"),
("chenxinyu", "chenxinyu123", "chenxinyu_salt"),
("yanghong", "yanghong123", "yanghong_salt"),
("panjinli", "panjinli123", "panjinli_salt"),
("chenlin", "chenlin123", "chenlin_salt"),
]
print("USE callback_system;")
print()
print("UPDATE users SET password_hash = CASE username")
for username, password, salt in users:
hash_value = hash_password(password, salt)
print(f" WHEN '{username}' THEN '{hash_value}'")
print("END WHERE username IN (")
usernames = [f"'{user[0]}'" for user in users]
print(", ".join(usernames))
print(");")
if __name__ == "__main__":
main()
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
生成密码哈希
"""
import hashlib
def hash_password(password: str, salt: str) -> str:
"""生成密码哈希"""
password_bytes = (password + salt).encode('utf-8')
# 进行10000轮哈希增强安全性
for _ in range(10000):
password_bytes = hashlib.sha256(password_bytes).digest()
return password_bytes.hex()
def main():
"""主函数"""
print("=== 生成密码哈希 ===")
# 用户列表
users = [
("admin", "admin123", "admin_salt"),
("jinqin", "jinqin123", "jinqin_salt"),
("renshanshan", "renshanshan123", "renshanshan_salt"),
("shaojun", "shaojun123", "shaojun_salt"),
("litingting", "litingting123", "litingting_salt"),
("maqiuyi", "maqiuyi123", "maqiuyi_salt"),
("tangqimin", "tangqimin123", "tangqimin_salt"),
("yueling", "yueling123", "yueling_salt"),
("jijunlin", "jijunlin123", "jijunlin_salt"),
("zhouliping", "zhouliping123", "zhouliping_salt"),
("feimiaomiao", "feimiaomiao123", "feimiaomiao_salt"),
("chenxinyu", "chenxinyu123", "chenxinyu_salt"),
("yanghong", "yanghong123", "yanghong_salt"),
("panjinli", "panjinli123", "panjinli_salt"),
("chenlin", "chenlin123", "chenlin_salt"),
]
print("UPDATE users SET password_hash = CASE username")
for username, password, salt in users:
hash_value = hash_password(password, salt)
print(f" WHEN '{username}' THEN '{hash_value}'")
print("END WHERE username IN (")
usernames = [f"'{user[0]}'" for user in users]
print(", ".join(usernames))
print(");")
if __name__ == "__main__":
main()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
从诊所患者JSON文件导入数据到数据库
确保患者和诊所的对应关系正确
"""
import os
import json
import pymysql
from datetime import datetime
# 数据库配置
DB_CONFIG = {
'host': 'localhost',
'port': 3307,
'user': 'callback_user',
'password': 'dev_password_123',
'database': 'callback_system',
'charset': 'utf8mb4'
}
def connect_database():
"""连接数据库"""
try:
connection = pymysql.connect(**DB_CONFIG)
print("✅ 数据库连接成功")
return connection
except Exception as e:
print(f"❌ 数据库连接失败: {e}")
return None
def create_patients_table(connection):
"""创建或更新patients表"""
try:
with connection.cursor() as cursor:
# 删除现有表(如果存在)
cursor.execute("DROP TABLE IF EXISTS patients")
# 创建新的patients表
create_table_sql = """
CREATE TABLE patients (
patient_id INT AUTO_INCREMENT PRIMARY KEY,
case_number VARCHAR(50) UNIQUE NOT NULL,
patient_name VARCHAR(100),
patient_phone VARCHAR(20),
gender VARCHAR(10),
age INT,
clinic_name VARCHAR(100) NOT NULL,
diagnosis JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_case_number (case_number),
INDEX idx_clinic_name (clinic_name),
INDEX idx_patient_name (patient_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
"""
cursor.execute(create_table_sql)
connection.commit()
print("✅ patients表创建成功")
except Exception as e:
print(f"❌ 创建patients表失败: {e}")
connection.rollback()
def import_clinic_patients(connection, clinic_name, json_file_path):
"""导入单个门诊的患者数据"""
try:
if not os.path.exists(json_file_path):
print(f"⚠️ JSON文件不存在: {json_file_path}")
return 0
with open(json_file_path, 'r', encoding='utf-8') as f:
patients_data = json.load(f)
if not isinstance(patients_data, list):
print(f"⚠️ JSON文件格式错误: {json_file_path}")
return 0
imported_count = 0
with connection.cursor() as cursor:
for patient in patients_data:
try:
# 提取患者信息(使用中文字段名)
case_number = patient.get('病历号', '')
patient_name = patient.get('姓名', '')
patient_phone = '' # JSON中没有电话字段
gender = patient.get('性别', '')
age = patient.get('年龄')
diagnosis = {
'医生诊断': patient.get('医生诊断', ''),
'上次就诊诊断': patient.get('上次就诊诊断', ''),
'漏诊项': patient.get('漏诊项', ''),
'缺失牙': patient.get('缺失牙', []),
'牙槽骨吸收': patient.get('牙槽骨吸收', ''),
'恒牙萌出空间不足': patient.get('恒牙萌出空间不足', '')
}
if not case_number:
continue
# 插入患者数据
insert_sql = """
INSERT INTO patients (case_number, patient_name, patient_phone, gender, age, clinic_name, diagnosis)
VALUES (%s, %s, %s, %s, %s, %s, %s)
ON DUPLICATE KEY UPDATE
patient_name = VALUES(patient_name),
patient_phone = VALUES(patient_phone),
gender = VALUES(gender),
age = VALUES(age),
clinic_name = VALUES(clinic_name),
diagnosis = VALUES(diagnosis),
updated_at = CURRENT_TIMESTAMP
"""
cursor.execute(insert_sql, (
case_number,
patient_name,
patient_phone,
gender,
age,
clinic_name,
json.dumps(diagnosis, ensure_ascii=False)
))
imported_count += 1
except Exception as e:
print(f"⚠️ 导入患者 {case_number} 失败: {e}")
continue
connection.commit()
print(f"✅ {clinic_name}: 成功导入 {imported_count} 个患者")
return imported_count
except Exception as e:
print(f"❌ 导入 {clinic_name} 患者数据失败: {e}")
connection.rollback()
return 0
def main():
"""主函数"""
print("🚀 开始从JSON文件导入患者数据...")
# 连接数据库
connection = connect_database()
if not connection:
return
try:
# 创建patients表
create_patients_table(connection)
# 定义门诊和对应的JSON文件
clinics = [
('学前街门诊', '学前街门诊.json'), # 根目录文件
('大丰门诊', '诊所患者json/大丰门诊.json'),
('东亭门诊', '诊所患者json/东亭门诊.json'),
('河埒门诊', '诊所患者json/河埒门诊.json'),
('红豆门诊', '诊所患者json/红豆门诊.json'),
('惠山门诊', '诊所患者json/惠山门诊.json'),
('马山门诊', '诊所患者json/马山门诊.json'),
('通善口腔医院', '诊所患者json/通善口腔医院.json'),
('新吴门诊', '诊所患者json/新吴门诊.json')
]
total_imported = 0
# 导入每个门诊的患者数据
for clinic_name, json_file in clinics:
print(f"\n📋 正在导入 {clinic_name}...")
count = import_clinic_patients(connection, clinic_name, json_file)
total_imported += count
# 验证导入结果
with connection.cursor() as cursor:
cursor.execute("SELECT COUNT(*) as total FROM patients")
total_patients = cursor.fetchone()[0]
cursor.execute("SELECT clinic_name, COUNT(*) as count FROM patients GROUP BY clinic_name ORDER BY count DESC")
clinic_counts = cursor.fetchall()
print(f"\n🎉 导入完成!")
print(f"📊 总患者数: {total_patients}")
print(f"📋 各门诊患者分布:")
for clinic_name, count in clinic_counts:
print(f" {clinic_name}: {count} 人")
except Exception as e:
print(f"❌ 导入过程出错: {e}")
connection.rollback()
finally:
connection.close()
print("\n✅ 数据库连接已关闭")
if __name__ == "__main__":
main()
\ No newline at end of file
......@@ -4,6 +4,7 @@ Flask-CORS==4.0.0
# 数据库
PyMySQL==1.1.0
cryptography==41.0.7
# 数据处理
pandas==2.1.4
......
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
简单的数据库备份工具
无需特殊权限,通过应用程序直接备份
"""
import os
import sys
import pymysql
import json
from datetime import datetime
import zipfile
# 数据库配置
DB_CONFIG = {
'host': 'localhost',
'port': 3307,
'user': 'callback_user',
'password': 'dev_password_123',
'database': 'callback_system',
'charset': 'utf8mb4'
}
def create_backup_directory():
"""创建备份目录"""
backup_dir = "database_backups"
if not os.path.exists(backup_dir):
os.makedirs(backup_dir)
print(f"✅ 创建备份目录: {backup_dir}")
return backup_dir
def backup_database(backup_dir):
"""备份数据库"""
try:
print("🔌 连接数据库...")
connection = pymysql.connect(**DB_CONFIG)
cursor = connection.cursor()
# 生成备份文件名
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_file = os.path.join(backup_dir, f"production_backup_{timestamp}.sql")
print(f"📋 开始备份数据库: {DB_CONFIG['database']}")
# 获取所有表名
cursor.execute("SHOW TABLES")
tables = [table[0] for table in cursor.fetchall()]
print(f"📊 发现 {len(tables)} 个表")
with open(backup_file, 'w', encoding='utf-8') as f:
# 写入备份头部信息
f.write(f"-- 患者画像回访话术系统数据库备份\n")
f.write(f"-- 备份时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(f"-- 数据库: {DB_CONFIG['database']}\n")
f.write(f"-- 表数量: {len(tables)}\n\n")
# 备份每个表
for i, table_name in enumerate(tables, 1):
print(f" [{i}/{len(tables)}] 备份表: {table_name}")
# 获取表结构
cursor.execute(f"SHOW CREATE TABLE `{table_name}`")
create_table_sql = cursor.fetchone()[1]
f.write(f"\n-- 表结构: {table_name}\n")
f.write(f"DROP TABLE IF EXISTS `{table_name}`;\n")
f.write(f"{create_table_sql};\n\n")
# 获取表数据
cursor.execute(f"SELECT COUNT(*) FROM `{table_name}`")
row_count = cursor.fetchone()[0]
if row_count > 0:
f.write(f"-- 表数据: {table_name} ({row_count} 行)\n")
# 分批获取数据避免内存问题
batch_size = 1000
offset = 0
while offset < row_count:
cursor.execute(f"SELECT * FROM `{table_name}` LIMIT {batch_size} OFFSET {offset}")
rows = cursor.fetchall()
for row in rows:
# 构建INSERT语句
values = []
for value in row:
if value is None:
values.append('NULL')
elif isinstance(value, (int, float)):
values.append(str(value))
else:
# 转义字符串
escaped_value = str(value).replace("'", "''").replace("\\", "\\\\")
values.append(f"'{escaped_value}'")
f.write(f"INSERT INTO `{table_name}` VALUES ({', '.join(values)});\n")
offset += batch_size
print(f" ✅ 已备份 {min(offset, row_count)}/{row_count} 行")
f.write("\n")
else:
print(f" ⚠️ 表 {table_name} 无数据")
connection.close()
# 获取备份文件大小
file_size = os.path.getsize(backup_file)
size_mb = file_size / (1024 * 1024)
print(f"✅ 数据库备份完成!")
print(f"📁 备份文件: {backup_file}")
print(f"📏 文件大小: {size_mb:.2f} MB")
return backup_file
except Exception as e:
print(f"❌ 数据库备份失败: {e}")
if 'connection' in locals():
connection.close()
return None
def create_backup_zip(backup_file, backup_dir):
"""创建备份压缩包"""
try:
# 生成压缩包文件名
backup_name = os.path.basename(backup_file)
zip_name = backup_name.replace('.sql', '.zip')
zip_path = os.path.join(backup_dir, zip_name)
print(f"📦 创建压缩包: {zip_name}")
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
# 添加备份文件
zipf.write(backup_file, backup_name)
# 添加备份信息
info = {
'backup_time': datetime.now().isoformat(),
'database': DB_CONFIG['database'],
'backup_file': backup_name,
'backup_size': os.path.getsize(backup_file),
'compressed_size': os.path.getsize(zip_path)
}
zipf.writestr('backup_info.json', json.dumps(info, ensure_ascii=False, indent=2))
# 获取压缩包大小
zip_size = os.path.getsize(zip_path)
zip_size_mb = zip_size / (1024 * 1024)
print(f"✅ 压缩包创建完成!")
print(f"📁 压缩包: {zip_path}")
print(f"📏 压缩后大小: {zip_size_mb:.2f} MB")
return zip_path
except Exception as e:
print(f"❌ 创建压缩包失败: {e}")
return None
def cleanup_old_backups(backup_dir, keep_days=7):
"""清理旧备份文件"""
try:
print(f"🧹 清理 {keep_days} 天前的旧备份文件...")
current_time = datetime.now()
deleted_count = 0
for filename in os.listdir(backup_dir):
file_path = os.path.join(backup_dir, filename)
# 检查文件修改时间
file_time = datetime.fromtimestamp(os.path.getmtime(file_path))
days_old = (current_time - file_time).days
if days_old > keep_days:
os.remove(file_path)
print(f" 🗑️ 删除旧文件: {filename}")
deleted_count += 1
if deleted_count > 0:
print(f"✅ 清理完成,删除了 {deleted_count} 个旧文件")
else:
print("✅ 无需清理,所有备份文件都是最新的")
except Exception as e:
print(f"⚠️ 清理旧备份失败: {e}")
def show_backup_list(backup_dir):
"""显示备份文件列表"""
try:
print(f"\n📋 备份文件列表 ({backup_dir}):")
print("-" * 80)
files = []
for filename in os.listdir(backup_dir):
if filename.endswith(('.sql', '.zip')):
file_path = os.path.join(backup_dir, filename)
file_size = os.path.getsize(file_path)
file_time = datetime.fromtimestamp(os.path.getmtime(file_path))
files.append((filename, file_size, file_time))
if not files:
print("暂无备份文件")
return
# 按时间排序
files.sort(key=lambda x: x[2], reverse=True)
for filename, file_size, file_time in files:
size_mb = file_size / (1024 * 1024)
time_str = file_time.strftime("%Y-%m-%d %H:%M:%S")
file_type = "📦" if filename.endswith('.zip') else "🗄️"
print(f"{file_type} {filename}")
print(f" 📅 创建时间: {time_str}")
print(f" 📏 文件大小: {size_mb:.2f} MB")
print()
except Exception as e:
print(f"❌ 显示备份列表失败: {e}")
def main():
"""主函数"""
print("🚀 患者画像回访话术系统 - 数据库备份工具")
print("=" * 60)
try:
# 创建备份目录
backup_dir = create_backup_directory()
# 备份数据库
backup_file = backup_database(backup_dir)
if not backup_file:
print("❌ 备份失败,程序退出")
return
# 创建压缩包
zip_file = create_backup_zip(backup_file, backup_dir)
if zip_file:
# 删除原始SQL文件(保留压缩包)
os.remove(backup_file)
print(f"🗑️ 已删除原始SQL文件,保留压缩包")
# 清理旧备份
cleanup_old_backups(backup_dir)
# 显示备份列表
show_backup_list(backup_dir)
print("\n🎉 备份流程完成!")
print(f"💡 提示: 备份文件保存在 {backup_dir} 目录中")
print(f"💡 提示: 建议将备份文件复制到安全位置保存")
except KeyboardInterrupt:
print("\n\n⚠️ 用户中断操作")
except Exception as e:
print(f"\n❌ 备份过程出错: {e}")
if __name__ == "__main__":
main()
\ No newline at end of file
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