Compare commits

4 Commits

Author SHA1 Message Date
zydi 66f85bc74a update 2025-01-22 08:03:11 +00:00
zydi 16ce5d2591 update 2025-01-22 08:00:24 +00:00
zydi 4efe6fa6ca update v1.0 2025-01-22 07:46:05 +00:00
zydi 04fee9fc48 update 2025-01-22 07:34:41 +00:00
52 changed files with 1885 additions and 4387 deletions
+64
View File
@@ -0,0 +1,64 @@
# 依赖目录
node_modules/
vendor/
# 编译输出
dist/
build/
out/
# IDE 和编辑器文件
.idea/
.vscode/
*.sublime-project
*.sublime-workspace
.vs/
# 操作系统文件
.DS_Store
Thumbs.db
# 环境文件
.env
.env.local
.env.*.local
# 日志文件
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# 临时文件
*.tmp
*.temp
*.swp
*~
# 测试覆盖率报告
coverage/
# 缓存目录
.cache/
.npm/
.yarn/
# 其他
*.bak
*.zip
*.tar.gz
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
.pytest_cache/
.coverage
htmlcov/
.tox/
.nox/
.venv/
venv/
ENV/
+34 -92
View File
@@ -5,6 +5,8 @@
## 更新日志
### 2025-01-21更新
0. 更新代码结构
### 2025-01-17更新
0. 本次主要更新paper页面;
1. 接入了memarid.js, 后端分析报告添加了对流程图的输出,前端渲染流程图;
@@ -35,29 +37,32 @@ Initial commit
## 系统架构
### 1. 采集 (boot.py)
### 1. 数据采集 (boot/)
- 基于MicroPython实现的高速数据采集器
- 支持多通道数据采集
- 使用UDP协议传输数据
- 支持设备序列号注册机制
### 2. 服务器端 (data.py)
### 2. 数据处理 (data/)
- 基于Python asyncio的异步UDP服务器
- 支持数据解析和转发
- 与MongoDB集成存储实验数据
- 实现WebSocket实时数据推送
- 设备在线状态管理
### 3. 设备管理 (device.py)
### 3. 设备管理 (device-manager/)
- 设备注册和管理API
- 序列号生成和验证
- 设备状态监控
- Redis集成用于在线状态追踪
- device-register.html: 设备注册界面
### 4. Web界面
- lab.html: 主界面
- device.html: 设备管理界面
- device-monitor.html: 单个设备数据监控界面
### 4. 主程序 (app/)
- 主程序 app/main.py
- web页面 app/frontend/
- 项目管理
- 实验管理
- 报告生成
## 主要功能
1. 高速数据采集
@@ -67,112 +72,49 @@ Initial commit
5. 在线状态追踪
## 使用说明
### 文件夹
1. history 保存了历史版本代码,可以忽略
2. lab 项目历史版本,可以忽略
### 文件结构
lab/
├── #后端
├── boot.py # 采集端程序
├── data.py # 数据处理服务器
├── device.py # 设备管理API
├── main.py # 主程序
├── #前端
├── device.html # 设备管理
├── device-monitor.html # 单个设备数据监控界面
├── device-register.html # 设备注册
── paper.html # 文献分析
├── reports.html # 报告管理
├── lab.html # 前端主界面
└── login.html # 登录界面
|
└── README.md # 项目说明
#### 主程序 main.py https://dev.obscura.work/lab
├── app/ #主程序文件夹
| ├── app/ # app主程序
| ├── docs/ # app文档
| ├── frontend/ # app前端页面
| ├── main.py # 主程序
| └── README.md # 项目说明
├── boot/ # 采集端
├── data/ # 数据处理服务器
├── device-manager/ # 设备管理
── README.md # 项目说明
### 项目部署
#### 主程序 app/main.py
1. 服务器:222.186.10.253
2. PORT = 6000
3. 数据库:
mongodb: 222.186.10.253:27017/lab
Redis222.186.10.253:6379; db199、201-208
4. 访问地址:[https://dev.obscura.work/lab]
#### 设备管理 device.py https://dev.obscura.work/devices
#### 前端页面 app/frontend/
1. 访问地址:[https://beta.obscura.work/lab]
#### 设备管理 device-manager/device-register.py
1. 服务器:222.186.10.253
2. PORT = 6001
3. 数据库:
mongodb: 222.186.10.253:27017/lab
Redis222.186.10.253:6379; db200
#### 采集端配置 boot.py
4. 访问地址:[https://dev.obscura.work/devices]
#### 采集端配置 boot/
SSID = "Obscura"
UDP_HOST = "222.186.10.253"
UDP_PORT = 6002
#### 数据处理 data.py
#### 数据处理 data/
1. 服务器:222.186.10.253
2. PORT = 6002
3. 数据库:
mongodb: 222.186.10.253:27017/lab
Redis222.186.10.253:6379; db200; data type:stream
#### 前端页面 https://beta.obscura.work/lab/
1. lab.html: 主界面
2. device-register.html: 设备注册
3. device-monitor.html: 单个设备数据监控
4. device.html: 设备管理
5. paper.html: 文献分析
6. reports.html: 报告管理
7. login.html: 登录界面
#### Redis 数据库使用情况
1. DB 199: 实验状态数据
- 键格式: `experiment_status:{experiment_id}:{serial_number}`
- 值类型: String
- 存储内容: 实验设备的活动状态 ("active" 或 "inactive")
2. DB 200: 实验原始数据
- 键格式: `experiment:{experiment_id}:{serial_number}`
- 值类型: Redis Stream
- 存储内容: 设备采集的原始数据点
3. DB 201: 实验分析报告
- 键格式: `experiment_report:{experiment_id}`
- 值类型: String (JSON)
- 存储内容: 单个实验的分析报告
- 使用位置: 实验数据分析相关路由
4. DB 202: 项目分析报告
- 键格式: `project_report:{project_id}`
- 值类型: String (JSON)
- 存储内容: 整个项目的汇总分析报告
5. DB 203: 文献分析报告
- 键格式: `reference_report:{reference_id}`
- 值类型: String (JSON)
- 存储内容: 单个文献的分析报告
6. DB 204: 文献汇总报告
- 键格式: `reference_summary_report:{project_id}`
- 值类型: String (JSON)
- 存储内容: 项目所有文献的汇总分析报告
7. DB 205: 项目备忘录
- 键格式: `memo:{project_id}`
- 值类型: String (JSON)
- 存储内容: 项目的备忘录内容和创建时间
8. DB 206: 实验备忘录
- 键格式: `memo:{experiment_id}`
- 值类型: String (JSON)
- 存储内容: 实验的备忘录内容和创建时间
9. DB 207: 文献AI对话记录
- 键格式: `reference_chat:{reference_id}`
- 值类型: String (JSON)
- 存储内容: 单个文献的AI对话记录
10. DB 208: 存储文献AI对话的任务状态
- 键格式: `task:{task_id}`
- 值类型: String (JSON)
- 存储内容: 单个文献的AI对话的任务状态跟踪
+116
View File
@@ -0,0 +1,116 @@
# Lab数据分析系统
## 项目概述
这是一个基于FastAPI开发的实验室数据分析平台,提供实验数据采集、分析、项目管理和文献管理等功能。
系统使用MongoDB作为主数据库,Redis作为缓存和消息队列,并集成了DeepSeek API进行智能分析。
## 主要功能
### 1. 用户管理
- 用户注册和登录
- JWT token认证
- 用户权限管理
### 2. 项目管理
- 创建和管理研究项目
- 项目数据分析和报告生成
- 项目级别的问答系统
- 项目备忘录功能
### 3. 实验管理
- 实验创建和配置
- 实验设备关联
- 实验数据实时采集
- 实验数据分析和可视化
- 实验报告自动生成
- 实验问答功能
- 传感器数据采集
- 实时数据传输(WebSocket)
### 4. 设备管理
- 设备注册和激活
### 5. 文献管理
- 文献上传和管理
- 文献智能分析
- 文献总结报告
- 文献问答系统
### 6. web界面
## 技术架构
### 后端框架
- FastAPI: 主要Web框架
- Pydantic: 数据验证和序列化
- Motor: 异步MongoDB驱动
- aioredis: 异步Redis客户端
- PyJWT: JWT认证
- WebSocket: 实时数据传输
### 数据库
- MongoDB: 主数据库
- Redis: 缓存和消息队列
### AI集成
- DeepSeek API: 智能分析和问答系统
## 项目结构
frontend/ # web界面
docs/ # 说明文档
app/
├── init.py
├── cores/ # 核心配置和工具
│ ├── config.py # 配置文件
│ └── db.py # 数据库工具
├── models/ # 数据模型
│ ├── basemodel.py # 基础模型
│ ├── project.py # 项目报告
│ ├── experiment.py # 实验报告
│ ├── paper.py # 文献报告
│ └── paper_summary.py # 文献总结报告
└── routers/ # API路由
├── device.py # 设备管理
├── project.py # 项目管理
├── project_report.py # 项目报告
├── experiment.py # 实验管理
├── experiment_report.py # 实验报告
├── experiment_device.py # 实验设备
├── login.py # 用户认证
├── paper.py # 文献报告
├── paper_summary.py # 文献总结报告
├── memo.py # 备忘录
└── websocket.py # WebSocket处理
## Redis数据库映射
- db199: 实验状态
- db200: 原始数据
- db201: 实验分析报告
- db202: 项目分析报告
- db203: 文献分析报告
- db204: 文献汇总报告
- db205: 项目备忘录
- db206: 实验备忘录
- db207: 问答历史记录
- db208: 问答任务状态
## 安装和部署
1. 环境要求
```bash
Python 3.8+
MongoDB 4.0+
Redis 6.0+
```
2. 启动服务
```bash
uvicorn app.main:app --host 0.0.0.0 --port 8000
```
## API文档
启动服务后访问:
- Swagger UI: http://localhost:8000/docs
- ReDoc: http://localhost:8000/redoc
+3
View File
@@ -0,0 +1,3 @@
from .main import app
__all__ = ['app']
+4
View File
@@ -0,0 +1,4 @@
from .cores import *
from .routers import *
__all__ = ['cores', 'routers']
+21
View File
@@ -0,0 +1,21 @@
from .config import *
from .db import *
__all__ = [
'MONGODB_URL',
'REDIS_URL',
'DEEPSEEK_API_CONFIG',
'client',
'UPLOAD_PATH',
'TaskStatus',
'THREAD_POOL_CONFIG',
'format_response',
'format_error',
'ALLOWED_FILE_TYPES',
'Database',
'get_database',
'connect_to_mongo',
'close_mongo_connection',
'get_redis',
'get_redis_key'
]
+13 -1
View File
@@ -140,4 +140,16 @@ async def get_redis_key(redis_type: str, key_pattern: str, **kwargs) -> tuple[in
pattern = db_info["key_pattern"]
key = pattern.format(**kwargs)
return db_number, key
return db_number, key
async def get_redis_db_number(redis_type: str) -> int:
"""
只获取Redis数据库编号
参数:
redis_type: REDIS_DB_MAPPING中的键名
返回:
int: 数据库编号
"""
return REDIS_DB_MAPPING[redis_type]["db"]
+28 -10
View File
@@ -1,6 +1,6 @@
from ..cores.config import DEEPSEEK_API_CONFIG, TaskStatus, client
from ..cores.db import get_redis, get_redis_key
from ..cores.config import MONGODB_URL, REDIS_URL, REDIS_DB_MAP
from ..cores.config import TaskStatus, client
from ..cores.db import get_redis, get_redis_key, get_redis_db_number
from ..cores.config import MONGODB_URL, REDIS_URL
from datetime import datetime, timezone
import json
import asyncio
@@ -10,11 +10,9 @@ from redis import asyncio as aioredis
from concurrent.futures import ThreadPoolExecutor
from ..cores.config import THREAD_POOL_CONFIG
# 创建项目分析线程池
exp_analysis_executor = ThreadPoolExecutor(
max_workers=THREAD_POOL_CONFIG["exp_analysis_workers"],
max_workers=THREAD_POOL_CONFIG["analysis_workers"],
thread_name_prefix="exp_analysis"
)
@@ -112,10 +110,13 @@ async def process_experiment_analysis(experiment_id: str):
redis = await aioredis.from_url(REDIS_URL, encoding="utf-8", decode_responses=True)
try:
await redis.select(REDIS_DB_MAP["experiment_analysis"])
status_key = f"experiment_analysis_status:{experiment_id}"
# 更新状态为进行中
db_number, status_key = await get_redis_key(
"experiment_analysis",
"status",
experiment_id=experiment_id
)
await redis.select(db_number)
status_data = {
"status": "processing",
"start_time": datetime.now(timezone.utc).isoformat()
@@ -156,7 +157,8 @@ async def process_experiment_analysis(experiment_id: str):
session_points = 0
for device in session_devices:
stream_key = f"experiment:{experiment_id}:{device['serial_number']}"
await redis.select(200)
db_number = await get_redis_db_number("raw_data")
await redis.select(db_number)
data_points = await redis.xrange(
stream_key,
min=str(start_ms),
@@ -214,6 +216,12 @@ async def process_experiment_analysis(experiment_id: str):
print("分析报告已保存")
else:
print("分析失败")
db_number, status_key = await get_redis_key(
"experiment_analysis",
"status",
experiment_id=experiment_id
)
await redis.select(db_number)
status_data = {
"status": "failed",
"error": "Failed to generate analysis result",
@@ -326,6 +334,11 @@ async def process_experiment_question(task_id: str, experiment_id: str, question
await redis.set(chat_history_key, json.dumps(history))
# 更新任务状态为完成
db_number, task_key = await get_redis_key(
"qa_task",
"key_pattern",
task_id=task_id
)
await redis.select(db_number)
await redis.hset(
task_key,
@@ -339,6 +352,11 @@ async def process_experiment_question(task_id: str, experiment_id: str, question
except Exception as e:
print(f"Error processing question: {e}")
# 更新任务状态为失败
db_number, task_key = await get_redis_key(
"qa_task",
"key_pattern",
task_id=task_id
)
await redis.select(db_number)
await redis.hset(
task_key,
+264 -4
View File
@@ -5,9 +5,9 @@ from typing import List, Dict
import PyPDF2
import aiohttp
from concurrent.futures import ThreadPoolExecutor
from ..cores.config import client
from ..cores.config import client,TaskStatus
from ..cores.db import get_redis, get_redis_key
import os
# 在全局范围创建线程池
pdf_thread_pool = ThreadPoolExecutor(max_workers=3) # 限制并发PDF处理数量
@@ -18,6 +18,133 @@ async def get_aiohttp_session():
headers={"Authorization": f"Bearer sk-3027fb3c810b4e17985fa397d41250b9"}
)
async def process_batch_analysis(references: List[dict]):
"""批量处理文献分析的后台任务"""
redis = await get_redis()
# 限制并发数量
semaphore = asyncio.Semaphore(3)
async def process_single_reference(ref: dict):
async with semaphore:
try:
reference_id = ref["reference_id"]
file_path = ref["file_path"]
if not os.path.exists(file_path):
print(f"Reference file not found: {file_path}")
return
print(f"\n开始处理文献 {ref.get('reference_title', '未知标题')}")
print(f"文献ID: {reference_id}")
print(f"文件路径: {file_path}")
# 异步读取PDF
print("\n=== 步骤1: 读取PDF文件 ===")
pdf_content = await read_pdf_async(file_path)
if not pdf_content:
raise Exception("Failed to read PDF content")
# 打印字符数
content_length = len(pdf_content)
print(f"\n=== 步骤2: 内容长度检查 ===")
print(f"PDF内容总字符数: {content_length}")
# 根据内容长度选择不同的处理方式
if content_length <= 200000:
print(f"\n=== 步骤3A: 使用直接分析方式 ===")
print(f"文档长度在处理范围内 ({content_length} <= 200000)")
# 直接分析文档内容
print("开始分析文档内容...")
document_analysis = await analyze_reference_document_async(pdf_content[:180000])
if not document_analysis:
raise Exception("Failed to analyze document")
print("文档分析完成")
else:
print(f"\n=== 步骤3B: 使用分段分析方式 ===")
print(f"文档超过200000字符 ({content_length} > 200000)")
# 分段分析长文档
print("\n--- 开始分段分析 ---")
print("正在调用 analyze_long_document_async...")
try:
analysis_results = await analyze_long_document_async(pdf_content)
except Exception as e:
print(f"分段分析过程中出错: {str(e)}")
raise
if not analysis_results:
raise Exception("Failed to analyze document in segments")
print(f"分段分析完成,共分析了 {len(analysis_results)} 个段落")
# 合并分析结果
try:
document_analysis = await merge_analysis_results(analysis_results)
except Exception as e:
raise
if not document_analysis:
raise Exception("Failed to merge analysis results")
# 等待一小段时间避免API限制
await asyncio.sleep(1)
# 异步分析文献价值
try:
value_evaluation = await analyze_reference_value_async(document_analysis)
print(f"analyze_reference_value_async 返回结果类型: {type(value_evaluation)}")
except Exception as e:
raise
if not value_evaluation:
raise Exception("Failed to evaluate value")
print("文献价值分析完成")
# 合并结果
print("\n=== 步骤5: 保存最终结果 ===")
analysis_result = {
**document_analysis,
**value_evaluation,
"status": "completed"
}
# 保存结果
db_number, report_key = await get_redis_key(
"reference_analysis",
"report",
reference_id=reference_id
)
await redis.select(db_number)
await redis.set(report_key, json.dumps(analysis_result))
except Exception as e:
try:
# 保存错误状态
db_number, report_key = await get_redis_key(
"reference_analysis",
"report",
reference_id=ref['reference_id']
)
await redis.select(db_number)
error_status = {
"status": "failed",
"message": str(e)
}
await redis.set(report_key, json.dumps(error_status))
except Exception as redis_error:
print(f"Error updating Redis status: {redis_error}")
try:
# 并发处理所有引用
await asyncio.gather(
*(process_single_reference(ref) for ref in references)
)
finally:
try:
await redis.aclose()
except Exception as e:
print(f"Error closing Redis connection: {e}")
async def read_pdf_async(file_path: str) -> str:
"""在线程池中异步读取PDF"""
def read_pdf():
@@ -316,4 +443,137 @@ async def analyze_reference_value_async(content_analysis: dict):
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
])
async def process_reference_question(task_id: str, reference_id: str, question: str, reference: dict):
"""处理文献问答的后台任务"""
redis = await get_redis()
try:
# 更新任务状态为处理中
db_number, task_key = await get_redis_key(
"qa_task",
"key_pattern",
task_id=task_id
)
await redis.select(db_number)
await redis.hset(
task_key,
mapping={
"status": TaskStatus.PROCESSING,
"reference_id": reference_id,
"question": question
}
)
# 从Redis获取分析报告
db_number, report_key = await get_redis_key(
"reference_analysis",
"report",
reference_id=reference_id
)
await redis.select(db_number)
report_data = await redis.get(report_key)
if not report_data:
raise Exception("文献分析报告不存在,请先进行分析")
# 读取PDF文件内容
file_path = reference.get("reference_link")
if not file_path or not os.path.exists(file_path):
raise Exception("文献文件不存在")
# 读取PDF内容
pdf_content = ""
with open(file_path, 'rb') as file:
pdf_reader = PyPDF2.PdfReader(file)
for page in pdf_reader.pages:
content = page.extract_text()
pdf_content += content
# 限制内容长度
MAX_CHARS = 180000
pdf_content = pdf_content[:MAX_CHARS]
# 构建系统提示和用户提示
system_prompt = """
你是一个专业的学术助手,负责回答关于学术文献的问题。
你应该基于文献内容和分析报告提供准确、专业的回答。
回答应当简洁明了,并尽可能引用文献中的具体内容。
"""
# 构建上下文
context = {
"文献内容": pdf_content,
"分析报告": json.loads(report_data)
}
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": f"基于以下文献内容和分析报告回答问题:\n\n文献信息:{json.dumps(context, ensure_ascii=False)}\n\n问题:{question}"}
]
# 调用DeepSeek API获取回答
response = client.chat.completions.create(
model="deepseek-chat",
messages=messages
)
answer = response.choices[0].message.content
# 保存对话历史
db_number, chat_history_key = await get_redis_key(
"chat_history",
"reference",
reference_id=reference_id
)
await redis.select(db_number)
# 获取现有历史记录
existing_history = await redis.get(chat_history_key)
history = json.loads(existing_history) if existing_history else []
# 添加新的对话
history.append({
"question": question,
"answer": answer,
"timestamp": datetime.now(timezone.utc).isoformat()
})
# 保存更新后的历史记录
await redis.set(chat_history_key, json.dumps(history))
# 更新任务状态为完成
db_number, task_key = await get_redis_key(
"qa_task",
"key_pattern",
task_id=task_id
)
await redis.select(db_number)
await redis.hset(
task_key,
mapping={
"status": TaskStatus.COMPLETED,
"answer": answer,
"reference_title": reference.get("reference_title")
}
)
except Exception as e:
print(f"Error processing question: {e}")
# 更新任务状态为失败
db_number, task_key = await get_redis_key(
"qa_task",
"key_pattern",
task_id=task_id
)
await redis.select(db_number)
await redis.hset(
task_key,
mapping={
"status": TaskStatus.FAILED,
"error": str(e)
}
)
finally:
try:
await redis.aclose()
except Exception as e:
print(f"Error closing Redis connection: {e}")
+25
View File
@@ -0,0 +1,25 @@
from .login import router as login_router
from .project import router as project_router
from .device import router as device_router
from .experiment_device import router as experiment_device_router
from .experiment import router as experiment_router
from .experiment_report import router as experiment_report_router
from .websocket import router as websocket_router
from .project_report import router as project_report_router
from .memo import router as memo_router
from .paper import router as paper_router
from .paper_summary import router as paper_summary_router
__all__ = [
'login_router',
'project_router',
'device_router',
'experiment_device_router',
'experiment_router',
'experiment_report_router',
'websocket_router',
'project_report_router',
'memo_router',
'paper_router',
'paper_summary_router'
]
+7 -4
View File
@@ -5,9 +5,12 @@ from bson import ObjectId
from ..cores.db import get_database
from .login import get_current_user, UserModel
router = APIRouter()
router = APIRouter(
prefix="/lab",
tags=["device"]
)
@router.post("/lab/user/devices/{serial_number}")
@router.post("/user/devices/{serial_number}")
async def add_device_to_user(
serial_number: str,
current_user: UserModel = Depends(get_current_user)
@@ -122,7 +125,7 @@ async def add_device_to_user(
print(f"Error adding device to user: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to add device: {str(e)}")
@router.get("/lab/userdevices")
@router.get("/user/devices")
async def get_user_devices(current_user: UserModel = Depends(get_current_user)):
"""
获取当前用户的所有传感器列表
@@ -152,7 +155,7 @@ async def get_user_devices(current_user: UserModel = Depends(get_current_user)):
print(f"Error getting user devices: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to get user devices: {str(e)}")
@router.delete("/lab/user/devices/{device_id}")
@router.delete("/user/devices/{device_id}")
async def remove_user_device(
device_id: str,
current_user: UserModel = Depends(get_current_user)
+16 -12
View File
@@ -1,7 +1,7 @@
from datetime import datetime, timezone
from bson import ObjectId
import json
from ..cores.db import get_database, get_redis, get_redis_key
from ..cores.db import get_database, get_redis, get_redis_key, get_redis_db_number
from ..models.basemodel import ExperimentModel, ExperimentCreate, UserModel, ExperimentStatus,FormulaCreate
from .login import get_current_user
from io import BytesIO, StringIO
@@ -101,11 +101,10 @@ async def start_experiment_session(
if not experiment_devices:
raise HTTPException(status_code=400, detail="No devices added to the experiment")
# 更新实验状态到Redis (新增)
await redis.select(199)
# 更新实验状态到Redis
for device in experiment_devices:
db_number, status_key = await get_redis_key(
"experiment_status",
"experiment_status",
"key_pattern",
experiment_id=experiment_id,
device_serial=device['serial_number']
@@ -163,11 +162,10 @@ async def stop_experiment_session(
if not session:
raise HTTPException(status_code=404, detail="Active experiment session not found")
# 更新实验状态到Redis (新增)
await redis.select(199) # 使用同一个db
# 更新实验状态到Redis
for device in session.get("devices", []):
db_number, status_key = await get_redis_key(
"experiment_status",
"experiment_status",
"key_pattern",
experiment_id=experiment_id,
device_serial=device['serial_number']
@@ -291,8 +289,9 @@ async def export_experiment_data(
zip_file.writestr('README.txt', readme_content)
# 选择db200获取原始数据
await redis.select(200)
# 获取原始数据
db_number = await get_redis_db_number("raw_data")
await redis.select(db_number)
# 按设备处理数据
for session in sessions:
@@ -375,7 +374,7 @@ async def export_experiment_data(
experiment = await db.experiments.find_one({"_id": ObjectId(experiment_id)})
zip_filename = f"experiment_{experiment['experiment_name']}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip"
# 准备ZIP文<50><E69687>下载
# 准备ZIP文下载
zip_buffer.seek(0)
return StreamingResponse(
@@ -508,8 +507,13 @@ async def delete_experiment(
await db.experiments.delete_one({"_id": ObjectId(experiment_id)})
# 删除Redis中的实验报告
await redis.select(201)
await redis.delete(f"experiment_report:{experiment_id}")
db_number, report_key = await get_redis_key(
"experiment_analysis",
"report",
experiment_id=experiment_id
)
await redis.select(db_number)
await redis.delete(report_key)
return {"message": "Experiment successfully deleted"}
+18 -11
View File
@@ -4,13 +4,12 @@ from datetime import datetime, timezone
import asyncio
from bson import ObjectId
from ..cores.config import (
REDIS_DB_MAP, TaskStatus, format_response,
exp_analysis_thread_pool
TaskStatus, format_response
)
from ..cores.db import get_database, get_redis, get_redis_key
from .login import get_current_user
from ..models.basemodel import QuestionModel,UserModel
from ..models.experiment import run_experiment_in_thread,process_experiment_question
from ..models.experiment import run_experiment_in_thread,process_experiment_question, exp_analysis_executor
router = APIRouter(
prefix="/lab",
@@ -24,12 +23,16 @@ async def analyze_data(
current_user: UserModel = Depends(get_current_user)
):
"""启动实验数据分析"""
db = await get_database()
redis = await get_redis()
try:
await redis.select(REDIS_DB_MAP["experiment_analysis"])
status_key = f"experiment_analysis_status:{experiment_id}"
# 获取分析状态
db_number, status_key = await get_redis_key(
"experiment_analysis",
"status",
experiment_id=experiment_id
)
await redis.select(db_number)
current_status = await redis.get(status_key)
if current_status:
@@ -47,8 +50,8 @@ async def analyze_data(
}
await redis.set(status_key, json.dumps(status_data))
# 使用线程池提交任务
exp_analysis_thread_pool.submit(run_experiment_in_thread, experiment_id)
# 使用从 experiment.py 导入的线程池执行器
exp_analysis_executor.submit(run_experiment_in_thread, experiment_id)
return format_response({
"experiment_id": experiment_id,
@@ -182,9 +185,13 @@ async def get_experiment_qa_history(
redis = await get_redis()
try:
# 从Redis db207获取对话历史
await redis.select(207)
chat_history_key = f"experiment_chat_history:{experiment_id}"
# 获取对话历史
db_number, chat_history_key = await get_redis_key(
"chat_history",
"experiment",
experiment_id=experiment_id
)
await redis.select(db_number)
history_data = await redis.get(chat_history_key)
if not history_data:
+2 -133
View File
@@ -10,10 +10,9 @@ from ..cores.db import get_database, get_redis, get_redis_key
from ..cores.config import UPLOAD_PATH, TaskStatus
from ..routers.login import get_current_user
from ..models.paper import (
process_batch_analysis, process_reference_question,
run_analysis_in_thread
process_batch_analysis, process_reference_question
)
from ..models.basemodel import ReferenceModel, QuestionModel, UserModel
from ..models.basemodel import QuestionModel, UserModel
router = APIRouter(
prefix="/lab",
@@ -115,136 +114,6 @@ async def get_reference_report(
except Exception as e:
print(f"Error closing Redis connection: {e}")
@router.get("/references/{project_id}/analyze_report")
async def analyze_reference_summary_report(
project_id: str,
current_user: UserModel = Depends(get_current_user)
):
"""分析文献数据"""
db = await get_database()
redis = await get_redis()
try:
db_number, status_key = await get_redis_key(
"reference_summary", # 使用 REDIS_DB_MAPPING 中定义的类型
"status", # 使用 key_patterns 中定义的模式
project_id=project_id
)
await redis.select(db_number)
current_status = await redis.get(status_key)
if current_status:
status_data = json.loads(current_status)
if status_data.get("status") == "processing":
return {
"message": "文献分析任务正在进行中",
"status": "processing",
"project_id": project_id,
"start_time": status_data.get("start_time")
}
reference_cursor = db.references.find({
"project_id": ObjectId(project_id)
})
reference_ids = []
async for reference in reference_cursor:
reference_ids.append(str(reference["_id"]))
if not reference_ids:
raise HTTPException(status_code=404, detail="No references found for this project")
status_data = {
"status": "processing",
"start_time": datetime.now(timezone.utc).isoformat(),
"total_references": len(reference_ids),
"completed_references": 0
}
await redis.set(status_key, json.dumps(status_data))
asyncio.create_task(run_analysis_in_thread(project_id, reference_ids))
return {
"message": "文献分析任务已启动",
"status": "processing",
"project_id": project_id,
"start_time": status_data["start_time"],
"total_references": len(reference_ids)
}
except Exception as e:
print(f"Error starting reference analysis: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
finally:
try:
await redis.aclose()
except Exception as e:
print(f"Error closing Redis connection: {e}")
@router.get("/references/{project_id}/analysis_status")
async def get_reference_analysis_status(
project_id: str,
current_user: UserModel = Depends(get_current_user)
):
"""获取文献分析任务的状态"""
redis = await get_redis()
try:
db_number, status_key = await get_redis_key(
"reference_summary",
"status", # 使用 status 键模式
project_id=project_id
)
await redis.select(db_number)
status_data = await redis.get(status_key)
if not status_data:
return {
"status": "not_started",
"project_id": project_id
}
return json.loads(status_data)
except Exception as e:
print(f"Error getting analysis status: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
finally:
try:
await redis.aclose()
except Exception as e:
print(f"Error closing Redis connection: {e}")
@router.get("/references/{project_id}/summary_report")
async def get_reference_summary_report(
project_id: str,
current_user: UserModel = Depends(get_current_user)
):
"""从 Redis 读取已保存的文献汇总报告"""
redis = await get_redis()
try:
db_number, report_key = await get_redis_key(
"reference_summary", # 使用 REDIS_DB_MAPPING 中定义的类型
"report", # 使用 key_patterns 中定义的模式
project_id=project_id
)
await redis.select(db_number)
existing_report = await redis.get(report_key)
if not existing_report:
raise HTTPException(status_code=404, detail="No saved reference report found")
return json.loads(existing_report)
except Exception as e:
print(f"Error getting reference report: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
finally:
try:
await redis.aclose()
except Exception as e:
print(f"Error closing Redis connection: {e}")
@router.post("/references/{reference_id}/qa")
async def ask_reference_question(
reference_id: str,
+1 -1
View File
@@ -144,4 +144,4 @@ async def get_reference_summary_report(
try:
await redis.aclose()
except Exception as e:
print(f"Error closing Redis connection: {e}")
print(f"Error closing Redis connection: {e}")
+2 -2
View File
@@ -6,8 +6,8 @@ from bson import ObjectId
from ..cores.config import TaskStatus
from ..cores.db import get_database, get_redis, get_redis_key
from .login import get_current_user
from ..models.project import pro_analysis_executor, run_project_in_thread, QuestionModel, process_project_question
from ..models.basemodel import UserModel
from ..models.project import pro_analysis_executor, run_project_in_thread, process_project_question
from ..models.basemodel import UserModel, QuestionModel
router = APIRouter(
prefix="/lab",
+27
View File
@@ -0,0 +1,27 @@
# cores/config.py
## 文件说明
该文件包含了应用程序的核心配置信息,包括数据库连接、API配置、文件上传设置等。
## 主要组件
### 数据库配置
- `MONGODB_URL`: MongoDB连接字符串
- `DATABASE_NAME`: 数据库名称
- `REDIS_URL`: Redis连接字符串
### API配置
- `DEEPSEEK_API_CONFIG`: DeepSeek API的配置信息
- `client`: OpenAI客户端实例
### 文件处理配置
- `UPLOAD_PATH`: 文件上传目录
- `ALLOWED_FILE_TYPES`: 允许上传的文件类型集合
### 任务管理
- `TaskStatus`: 任务状态枚举类(pending/processing/completed/failed
- `THREAD_POOL_CONFIG`: 不同类型任务的线程池配置
### 响应格式化工具
- `format_response()`: 格式化成功响应的函数
- `format_error()`: 格式化错误响应的函数
+31
View File
@@ -0,0 +1,31 @@
# cores/db.py
## 文件说明
该文件包含数据库相关的工具和配置,主要处理MongoDB和Redis的连接和操作。
## 主要组件
### Redis数据库映射
`REDIS_DB_MAPPING`: 详细的Redis数据库映射配置,包含:
- 实验状态 (db: 199)
- 原始数据 (db: 200)
- 实验分析 (db: 201)
- 项目分析 (db: 202)
- 文献分析 (db: 203)
- 文献汇总 (db: 204)
- 项目备忘录 (db: 205)
- 实验备忘录 (db: 206)
- 聊天历史 (db: 207)
- 问答任务 (db: 208)
### 工具类
- `PyObjectId`: 用于Pydantic模型中处理MongoDB ObjectId的自定义类
- `Database`: 数据库连接管理类
### 数据库操作函数
- `get_database()`: 获取MongoDB数据库实例
- `connect_to_mongo()`: 建立MongoDB连接
- `close_mongo_connection()`: 关闭MongoDB连接
- `get_redis()`: 获取Redis连接
- `get_redis_key()`: 获取Redis键名和数据库编号
- `get_redis_db_number()`: 获取Redis数据库编号
+80
View File
@@ -0,0 +1,80 @@
# basemodel.py
## 文件说明
该文件定义了应用程序中使用的基础数据模型,使用Pydantic进行数据验证和序列化。
## 主要模型
### ExperimentDeviceModel
实验-设备关联模型,用于关联实验和设备。
- `id`: ObjectId,主键
- `experiment_id`: 关联的实验ID
- `user_device_id`: 关联的用户设备ID
### QuestionModel
问题模型,用于处理用户提问。
- `question`: 问题内容
### UserModel
用户模型,存储用户信息。
- `id`: ObjectId,主键
- `username`: 用户名
- `password`: 密码
- `email`: 电子邮件
- `name`: 姓名
- `institution`: 所属机构
### ProjectModel
项目模型,存储项目信息。
- `id`: ObjectId,主键
- `user_id`: 创建者ID
- `project_name`: 项目名称
- `create_time`: 创建时间
- `description`: 项目描述
### ExperimentModel
实验模型,存储实验信息。
- `id`: ObjectId,主键
- `project_id`: 所属项目ID
- `experiment_name`: 实验名称
- `create_time`: 创建时间
- `description`: 实验描述
- `status`: 实验状态
### ExperimentSession
实验会话模型,记录实验会话信息。
- `id`: ObjectId,主键
- `experiment_id`: 实验ID
- `user_id`: 用户ID
- `start_time`: 开始时间
- `end_time`: 结束时间
- `duration`: 持续时间
### ExperimentData
实验数据模型,存储实验数据。
- `id`: ObjectId,主键
- `experiment_id`: 实验ID
- `session_ids`: 会话ID列表
- `user_id`: 用户ID
- `device_id`: 设备ID
- `sensor_name`: 传感器名称
- `last_update`: 最后更新时间
### MemoModel
备忘录模型,用于存储备忘信息。
- `content`: 备忘内容
- `create_time`: 创建时间
### FormulaModel & FormulaCreate
公式相关模型,用于存储和创建公式。
- `data_name`: 数据名称
- `data_unit`: 数据单位
- `formula`: 公式内容
### ReferenceModel
文献引用模型,存储文献信息。
- `id`: ObjectId,主键
- `project_id`: 项目ID
- `reference_link`: 文献链接
- `reference_title`: 文献标题
- `upload_time`: 上传时间
+48
View File
@@ -0,0 +1,48 @@
# experiment.py
## 文件说明
该文件包含了实验分析相关的功能实现,主要处理实验数据的分析和问答功能。
## 主要组件
### 线程池配置
- `exp_analysis_executor`: 实验分析线程池,用于并发处理实验分析任务
### 核心函数
#### analyze_experiment_data
异步函数,负责分析实验数据。
- 输入:实验信息
- 输出:JSON格式的分析报告
- 功能:使用DeepSeek API生成实验分析报告
#### run_experiment_in_thread
在独立线程中运行分析任务的函数。
- 创建新的事件循环
- 执行异步分析任务
- 处理异常情况
#### process_experiment_analysis
后台处理实验分析的主要函数。
- 更新任务状态
- 收集实验数据
- 生成分析报告
- 保存分析结果
#### process_experiment_question
处理实验相关问答的后台任务。
- 更新任务状态
- 获取分析报告
- 生成回答
- 保存对话历史
## 主要流程
1. 任务初始化和状态更新
2. 数据收集和预处理
3. 调用AI模型进行分析
4. 结果保存和状态更新
5. 异常处理和资源清理
## 数据存储
- MongoDB: 存储实验基础信息
- Redis: 存储分析报告和任务状态
+63
View File
@@ -0,0 +1,63 @@
# paper.py
## 文件说明
该文件实现了文献分析相关的功能,包括PDF处理、文献分析和问答功能。
## 主要组件
### 工具函数
#### read_pdf_async
异步读取PDF文件内容。
- 使用线程池处理PDF读取
- 根据内容长度进行处理
- 返回处理后的文本内容
#### call_deepseek_api_async
异步调用DeepSeek API。
- 处理API请求
- 返回JSON格式的响应
### 分析函数
#### analyze_long_document_async
处理长文档的分析功能。
- 文档分段处理
- 每段内容分析
- 合并分析结果
#### analyze_reference_document_async
分析单个文献文档。
- 提取基本信息
- 内容分析
- 生成流程图
#### analyze_reference_value_async
评估文献的价值。
- 学术贡献分析
- 实践意义评估
- 局限性分析
### 任务处理
#### process_batch_analysis
批量处理文献分析任务。
- 并发控制
- 状态管理
- 结果保存
#### process_reference_question
处理文献相关的问答功能。
- 问题处理
- 生成回答
- 保存对话历史
## 数据流程
1. PDF文件读取和预处理
2. 文本分析和价值评估
3. 结果整合和存储
4. 问答处理和历史记录
## 存储使用
- Redis: 存储分析报告和状态
- 文件系统: 存储PDF文件
+75
View File
@@ -0,0 +1,75 @@
# paper_summary.py
## 文件说明
该文件实现了文献汇总分析的功能,用于对多篇文献进行综合分析和总结。
## 主要组件
### 线程池配置
- `summary_executor`: 文献汇总分析线程池
### 核心函数
#### analyze_reference_summary
分析文献汇总报告的主要函数。
- 输入:多篇文献的分析数据
- 输出:JSON格式的汇总报告
- 内容包括:
- 文献概览
- 研究趋势
- 关键发现
- 研究空白
- 未来方向
- 影响评估
#### run_analysis_in_thread
在独立线程中运行汇总分析任务。
- 使用summary_executor执行任务
- 异步处理分析流程
#### process_reference_analysis
后台处理文献分析任务的主要函数。
- 收集所有文献报告
- 更新处理进度
- 生成汇总分析
- 保存分析结果
## 数据结构
### 汇总报告格式
```json
{
"Paper Summary Report": {
"overview": {
"total_papers": "数量",
"time_range": {"start_year": "开始年份", "end_year": "结束年份"},
"main_research_areas": "主要研究领域"
},
"research_trends": {
"major_themes": "主要主题",
"common_methodologies": "常用方法",
"emerging_topics": "新兴话题"
},
"key_findings": {
"theoretical_advances": "理论进展",
"experimental_results": "实验结果",
"common_conclusions": "共同结论"
},
"research_gaps": {
"current_limitations": "当前限制",
"unexplored_areas": "未探索领域",
"technical_challenges": "技术挑战"
},
"future_directions": {
"potential_applications": "潜在应用",
"methodological_suggestions": "方法建议"
},
"impact_assessment": {
"academic_influence": "学术影响",
"practical_value": "实践价值"
}
}
}
```
## 存储使用
- Redis: 存储分析报告和状态信息
+91
View File
@@ -0,0 +1,91 @@
# project.py
## 文件说明
该文件实现了项目分析相关的功能,包括项目数据分析和问答功能。
## 主要组件
### 线程池配置
- `pro_analysis_executor`: 项目分析线程池
### 核心函数
#### analyze_project_data
分析项目数据的主要函数。
- 输入:项目数据和统计信息
- 输出:JSON格式的分析报告
- 报告内容:
- 项目概览
- 统计数据
- 性能分析
- 共同发现
- 建议
#### run_project_in_thread
在独立线程中运行项目分析任务。
- 创建新的事件循环
- 执行异步分析任务
- 处理异常情况
#### process_project_analysis
后台处理项目分析的主要函数。
- 收集实验数据
- 计算统计信息
- 生成分析报告
- 更新任务状态
#### process_project_question
处理项目相关问答的后台任务。
- 获取项目报告
- 生成回答
- 保存对话历史
## 数据结构
### 项目分析报告格式
```json
{
"Project Analysis Report": {
"1. Project Overview": {
"Project Name": "项目名称",
"Total Experiments": "实验总数",
"Total Data Points": "数据点总数"
},
"2. Aggregated Statistics": {
"Total Sessions": "会话总数",
"Total Duration": "总持续时间",
"Average Session Duration": "平均会话时长",
"Average Data Points per Session": "每会话平均数据点"
},
"3. Performance Analysis": {
"Best Performing Sessions": [
{
"Session ID": "会话ID",
"experiment_id": "实验ID",
"Success Factors": "成功因素"
}
],
"Problematic Sessions": [
{
"Session ID": "会话ID",
"Issues": "问题",
"Possible Causes": "可能原因"
}
]
},
"4. Common Findings": {
"Recurring Issues": "常见问题",
"Equipment performance": "设备性能",
"Sensor reliability": "传感器可靠性"
},
"5. Recommendations": {
"Equipment optimization": "设备优化建议",
"Process improvement": "流程改进建议",
"Data collection": "数据收集策略"
}
}
}
```
## 存储使用
- MongoDB: 存储项目和实验基础信息
- Redis: 存储分析报告和任务状态
+28
View File
@@ -0,0 +1,28 @@
# device.py
## 文件说明
该文件实现了设备管理相关的路由处理,包括设备添加、查询和移除功能。
## API端点
### POST /lab/user/devices/{serial_number}
添加设备到用户设备列表
- 参数:设备序列号
- 功能:通过序列号验证并添加设备
- 返回:设备信息和添加状态
### GET /lab/userdevices
获取用户的所有设备列表
- 功能:返回当前用户关联的所有设备信息
- 返回:设备列表,包含详细信息
### DELETE /lab/user/devices/{device_id}
将设备标记为非活动状态
- 参数:设备ID
- 功能:停用指定设备
- 返回:停用状态确认
## 数据处理
- 设备状态管理(active/inactive
- 设备-用户关联关系维护
- 序列号验证和状态更新
+41
View File
@@ -0,0 +1,41 @@
# experiment.py
## 文件说明
实验管理相关的路由处理,包括实验的创建、查询、数据导出等功能。
## API端点
### POST /lab/experiments
创建新实验
- 参数:实验创建请求模型
- 功能:创建新的实验记录
- 返回:实验ID和创建状态
### GET /lab/experiments
获取项目下的所有实验
- 参数:项目ID
- 功能:返回指定项目的实验列表
- 返回:实验列表
### POST /lab/experiments/{experiment_id}/start
开始新的实验会话
- 参数:实验ID
- 功能:创建新的实验会话
- 返回:会话信息和设备列表
### GET /lab/experiments/{experiment_id}/export
导出实验数据
- 参数:实验ID
- 功能:将实验数据导出为ZIP文件
- 返回:包含CSV文件的ZIP压缩包
### POST /lab/experiments/{experiment_id}/complete
完成实验
- 参数:实验ID
- 功能:将实验状态标记为已完成
- 返回:完成状态确认
## 数据处理
- 实验会话管理
- 数据导出格式化
- 实验状态跟踪
+35
View File
@@ -0,0 +1,35 @@
# experiment_device.py
## 文件说明
实验设备关联管理的路由处理,处理实验和设备之间的关联关系。
## API端点
### POST /lab/experiments/{experiment_id}/devices/{user_device_id}
添加设备到实验
- 参数:实验ID和用户设备ID
- 功能:建立实验和设备的关联关系
- 返回:关联状态确认
### GET /lab/experiments/{experiment_id}/devices
获取实验中的所有设备
- 参数:实验ID
- 功能:返回实验关联的设备列表
- 返回:设备详细信息列表
### GET /lab/experiments/{experiment_id}/devices/public
公开接口获取实验设备
- 参数:实验ID
- 功能:无需认证获取设备信息
- 返回:设备基本信息列表
### DELETE /lab/experiments/{experiment_id}/devices/{user_device_id}
从实验中移除设备
- 参数:实验ID和设备ID
- 功能:解除实验和设备的关联
- 返回:移除状态确认
## 数据处理
- 设备关联关系管理
- 设备信息格式化
- 权限验证
+41
View File
@@ -0,0 +1,41 @@
# experiment_report.py
## 文件说明
实验报告相关的路由处理,包括数据分析、状态查询和问答功能。
## API端点
### GET /lab/experiments/{experiment_id}/analyze
启动实验数据分析
- 参数:实验ID
- 功能:异步启动数据分析任务
- 返回:分析任务状态
### GET /lab/experiments/{experiment_id}/analysis_status
获取分析任务状态
- 参数:实验ID
- 功能:查询分析进度
- 返回:当前分析状态
### GET /lab/experiments/{experiment_id}/report
获取已保存的报告
- 参数:实验ID
- 功能:获取分析报告内容
- 返回:完整的分析报告
### POST /lab/experiments/{experiment_id}/qa
向实验报告提问
- 参数:实验ID和问题内容
- 功能:创建问答任务
- 返回:任务ID和状态
### GET /lab/experiments/{experiment_id}/qa/history
获取问答历史
- 参数:实验ID
- 功能:获取历史问答记录
- 返回:问答历史列表
## 数据处理
- 异步任务管理
- 报告存储和检索
- 问答历史记录
+40
View File
@@ -0,0 +1,40 @@
# login.py
## 文件说明
用户认证和授权相关的路由处理,包括用户注册、登录和Token验证。
## 配置
- SECRET_KEY: JWT密钥
- ALGORITHM: JWT算法(HS256)
- ACCESS_TOKEN_EXPIRE_MINUTES: Token过期时间(30天)
## API端点
### POST /lab/register
用户注册
- 参数:用户注册信息(UserModel)
- 功能:创建新用户账户
- 返回:用户ID和注册状态
### POST /lab/token
用户登录
- 参数:用户名和密码
- 功能:验证用户身份并生成访问令牌
- 返回:JWT访问令牌
## 工具函数
### create_access_token
- 功能:生成JWT访问令牌
- 参数:用户数据
- 返回:编码后的JWT令牌
### get_current_user
- 功能:从JWT令牌获取当前用户
- 参数:JWT令牌
- 返回:当前用户模型
## 安全特性
- JWT令牌认证
- 密码验证
- 用户会话管理
+35
View File
@@ -0,0 +1,35 @@
# memo.py
## 文件说明
备忘录功能相关的路由处理,包括项目和实验备忘录的管理。
## API端点
### POST /lab/projects/{project_id}/memo
保存项目备忘录
- 参数:项目ID和备忘内容
- 功能:保存或更新项目备忘录
- 返回:保存状态确认
### GET /lab/projects/{project_id}/memo
获取项目备忘录
- 参数:项目ID
- 功能:获取项目的备忘录内容
- 返回:备忘录内容和创建时间
### POST /lab/experiments/{experiment_id}/memo
保存实验备忘录
- 参数:实验ID和备忘内容
- 功能:保存或更新实验备忘录
- 返回:保存状态确认
### GET /lab/experiments/{experiment_id}/memo
获取实验备忘录
- 参数:实验ID
- 功能:获取实验的备忘录内容
- 返回:备忘录内容和创建时间
## 数据存储
- Redis数据库存储
- 备忘录内容格式化
- 时间戳管理
+42
View File
@@ -0,0 +1,42 @@
# paper.py
## 文件说明
文献管理相关的路由处理,包括文献上传、分析和问答功能。
## API端点
### POST /lab/projects/{project_id}/references/batch
批量上传文献
- 参数:项目ID和文件列表
- 功能:上传并处理多个文献文件
- 返回:上传状态和文献信息
### GET /lab/projects/{project_id}/references
获取项目文献列表
- 参数:项目ID
- 功能:获取项目关联的所有文献
- 返回:文献列表
### GET /lab/references/{reference_id}/report
获取文献分析报告
- 参数:文献ID
- 功能:获取已保存的分析报告
- 返回:完整的分析报告
### POST /lab/references/{reference_id}/qa
向文献提问
- 参数:文献ID和问题内容
- 功能:创建文献问答任务
- 返回:任务ID和状态
### GET /lab/references/{reference_id}/qa/history
获取文献问答历史
- 参数:文献ID
- 功能:获取历史问答记录
- 返回:问答历史列表
## 功能特性
- 文件上传处理
- 异步分析任务
- 问答历史管理
- 报告存储和检索
+30
View File
@@ -0,0 +1,30 @@
# paper_summary.py
## 文件说明
文献汇总分析相关的路由处理,提供项目级别的文献分析功能。
## API端点
### GET /lab/references/{project_id}/analyze_report
启动文献汇总分析
- 参数:项目ID
- 功能:对项目下所有文献进行汇总分析
- 返回:分析任务状态
### GET /lab/references/{project_id}/analysis_status
获取汇总分析状态
- 参数:项目ID
- 功能:查询汇总分析进度
- 返回:当前分析状态和进度
### GET /lab/references/{project_id}/summary_report
获取文献汇总报告
- 参数:项目ID
- 功能:获取已完成的汇总分析报告
- 返回:完整的汇总报告
## 功能特性
- 批量文献分析
- 进度追踪
- 结果汇总
- 异步任务处理
+29
View File
@@ -0,0 +1,29 @@
# project.py
## 文件说明
项目管理相关的路由处理,包括项目的创建、查询和删除功能。
## API端点
### POST /lab/projects
创建新项目
- 参数:项目信息(ProjectModel)
- 功能:创建新的项目记录
- 返回:项目ID和创建状态
### GET /lab/projects
获取用户的所有项目
- 功能:返回当前用户的所有项目列表
- 返回:项目列表,包含完整项目信息
### DELETE /lab/projects/{project_id}
删除项目
- 参数:项目ID
- 功能:删除项目及其所有相关数据
- 返回:删除状态确认
## 数据处理
- 项目数据管理
- 关联数据清理
- 用户权限验证
- Redis缓存清理
+42
View File
@@ -0,0 +1,42 @@
# project_report.py
## 文件说明
项目报告相关的路由处理,包括项目数据分析和问答功能。
## API端点
### GET /lab/projects/{project_id}/analyze
启动项目数据分析
- 参数:项目ID
- 功能:异步启动项目分析任务
- 返回:分析任务状态
### GET /lab/projects/{project_id}/analysis_status
获取项目分析状态
- 参数:项目ID
- 功能:查询分析进度
- 返回:当前分析状态
### GET /lab/projects/{project_id}/report
获取项目分析报告
- 参数:项目ID
- 功能:获取已保存的分析报告
- 返回:完整的分析报告
### POST /lab/projects/{project_id}/qa
向项目报告提问
- 参数:项目ID和问题内容
- 功能:创建项目问答任务
- 返回:任务ID和状态
### GET /lab/projects/{project_id}/qa/history
获取项目问答历史
- 参数:项目ID
- 功能:获取历史问答记录
- 返回:问答历史列表
## 功能特性
- 异步分析处理
- 状态追踪
- 问答历史管理
- 报告存储和检索
+32
View File
@@ -0,0 +1,32 @@
# websocket.py
## 文件说明
WebSocket连接管理相关的路由处理,用于实时数据传输。
## 主要组件
### ConnectionManager类
WebSocket连接管理器
- 管理活动连接
- 处理连接/断开
- 消息广播
- 连接状态追踪
## API端点
### WebSocket /lab/ws/{experiment_id}/{serial_number}
WebSocket连接端点
- 参数:实验ID和设备序列号
- 功能:建立WebSocket连接
- 返回:连接确认
### GET /lab/status
获取WebSocket服务状态
- 功能:返回服务运行状态
- 返回:状态信息和活动连接数
## 功能特性
- 实时数据传输
- 连接生命周期管理
- 消息队列处理
- 错误处理和恢复
+91
View File
@@ -0,0 +1,91 @@
# Lab 界面
这是一个用于实验室管理的web界面, 作为app/的web界面
## 项目结构
## 页面说明
### login.html
- 用户登录界面
- 用户注册功能
- 使用 JWT token 认证
- 响应式设计
### lab.html
- lab主页面
- 项目管理功能
- 实验管理功能
- - 支持实验数据记录
- - 支持实验备忘录
- - 实时设备数据监控,公式计算
- - 生成实验报告及AI问答
### paper.html
- 论文分析功能
- 支持论文上传
- 论文分析报告生成
- 支持流程图可视化
- 集成了论文问答功能
### reports.html
- 实验报告管理
- 项目报告查看
- 论文分析报告查看
- 支持报告下载
- 支持报告分类和排序
### device.html
- 设备管理界面
- 显示设备列表
- 支持添加/删除设备
- 显示设备详细信息(传感器等)
- 支持设备状态监控
### device-monitor.html
- 实时监控单一设备数据
- 显示设备基本信息
- 支持图表可视化展示数据
- 支持自定义数据计算和显示
- 使用 WebSocket 实现实时数据更新
## 技术栈
- Bootstrap 5.1.3 - UI 框架
- Chart.js - 图表可视化
- Mermaid - 流程图渲染
- WebSocket - 实时数据传输
- JWT - 用户认证
## API 接口
所有页面通过统一的 API 基础 URL 访问后端服务:
```javascript
const API_BASE_URL = 'https://dev.obscura.work/lab'
```
## 样式特点
- 现代化的 UI 设计
- 响应式布局
- 统一的色彩系统
- 平滑的动画过渡
- 良好的交互反馈
## 使用说明
1. 用户需要先登录/注册才能访问系统
2. 登录后可以进行:
- 项目管理
- 设备管理
- 实验记录
- 论文分析
- 报告生成
3. 支持实时数据监控和可视化
4. 可以生成和下载分析报告
## 注意事项
- 需要现代浏览器支持
- 需要保持网络连接以进行实时数据更新
- 部分功能需要等待后端处理
- 建议使用最新版本的 Chrome 或 Firefox 浏览器
View File
@@ -663,14 +663,6 @@
<i class="bi bi-sort-down"></i>
<span>Sort</span>
</div>
<div class="sidebar-item" onclick="generateSummaryReport()">
<i class="bi bi-file-earmark-text"></i>
<span>Summary</span>
</div>
<div class="sidebar-item" onclick="exportAllReports()">
<i class="bi bi-download"></i>
<span>Download</span>
</div>
</div>
</div>
</div>
@@ -1078,76 +1070,6 @@
bootstrap.Modal.getInstance(document.getElementById('reportsSortModal')).hide();
}
// Export all reports
async function exportAllReports() {
try {
const response = await fetch(`${API_BASE_URL}/projects`, {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
const projects = await response.json();
const reports = projects.map(project => ({
project_name: project.project_name,
description: project.description,
project_reports: project.project_reports,
paper_analysis: project.paper_analysis
}));
const reportText = JSON.stringify(reports, null, 2);
const blob = new Blob([reportText], { type: 'application/json' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'all_reports.json';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
}
} catch (error) {
console.error('Failed to export all reports:', error);
alert('Failed to export all reports, please try again');
}
}
// Generate summary report
async function generateSummaryReport() {
try {
const response = await fetch(`${API_BASE_URL}/projects`, {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
const projects = await response.json();
const summary = projects.map(project => ({
project_name: project.project_name,
description: project.description,
project_reports: project.project_reports,
paper_analysis: project.paper_analysis
}));
const summaryText = JSON.stringify(summary, null, 2);
const blob = new Blob([summaryText], { type: 'application/json' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'summary_report.json';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
}
} catch (error) {
console.error('Failed to generate summary report:', error);
alert('Failed to generate summary report, please try again');
}
}
// Switch between project report and paper analysis
function switchReportSection(section) {
// Remove active class from all sections
-3930
View File
File diff suppressed because it is too large Load Diff
+13 -13
View File
@@ -7,22 +7,22 @@ from fastapi.middleware.cors import CORSMiddleware
from openai import OpenAI
# Local application imports
from .cores.config import client
from .cores.db import (
from app.cores.config import client
from app.cores.db import (
connect_to_mongo,
close_mongo_connection,
)
from .routers.login import router as login_router
from .routers.project import router as project_router
from .routers.device import router as device_router
from .routers.experiment_device import router as experiment_device_router
from .routers.experiment import router as experiment_router
from .routers.experiment_report import router as experiment_report_router
from .routers.websocket import router as websocket_router
from .routers.project_report import router as project_report_router
from .routers.memo import router as memo_router
from .routers.paper import router as paper_router
from .routers.paper_summary import router as paper_summary_router
from app.routers.login import router as login_router
from app.routers.project import router as project_router
from app.routers.device import router as device_router
from app.routers.experiment_device import router as experiment_device_router
from app.routers.experiment import router as experiment_router
from app.routers.experiment_report import router as experiment_report_router
from app.routers.websocket import router as websocket_router
from app.routers.project_report import router as project_report_router
from app.routers.memo import router as memo_router
from app.routers.paper import router as paper_router
from app.routers.paper_summary import router as paper_summary_router
@asynccontextmanager
+79
View File
@@ -0,0 +1,79 @@
# 高速数据采集模块
这是一个基于MicroPython的高速数据采集模块,专门设计用于ESP32平台,可以进行高频率模拟信号采样并通过UDP网络传输。
## 主要功能
- 高速ADC采样(12位精度)
- WiFi网络连接
- UDP数据传输
- 看门狗保护机制
- 自动重试机制
## 技术规格
- 采样频率:20Hz
- 每次采样包含50个数据点
- ADC分辨率:12位
- 网络协议:UDP
- 看门狗超时时间:20秒
## 配置参数
```python
SSID = "Obscura" # WiFi名称
PASSWORD = "Obscura2024" # WiFi密码
UDP_HOST = "222.186.10.253" # UDP服务器地址
UDP_PORT = 6002 # UDP端口
SERIAL_NUMBER = "ES01-208742cf95-efe5fb" # 设备序列号
```
## 使用方法
1. 导入模块:
```python
from boot import HighSpeedCollector
```
2. 创建采集器实例:
```python
collector = HighSpeedCollector()
```
3. 连接WiFi并启动采集:
```python
collector.connect_wifi()
collector.setup_udp()
collector.start()
```
4. 停止采集:
```python
collector.stop()
```
## 数据格式
每个UDP数据包的格式如下:
- 时间戳(2字节)
- 分片索引(2字节)
- 50个采样数据点(每个2字节)
## 错误处理
- WiFi连接超时:30秒后触发异常
- 自动重试机制:最多重试3次
- 看门狗保护:防止程序死机
## 注意事项
1. 确保WiFi配置正确
2. 确保UDP服务器地址可访问
3. 程序运行时会自动进行内存回收(GC)
4. 设备会定期发送序列号用于身份识别
## 依赖
- MicroPython
- ESP32开发板
- 需要ADC功能支持
+3
View File
@@ -0,0 +1,3 @@
from .boot import HighSpeedCollector
__all__ = ['HighSpeedCollector']
+103
View File
@@ -0,0 +1,103 @@
# UDP数据采集服务器
这是一个基于Python异步IO的UDP数据采集服务器,用于接收、处理和转发实验设备的传感器数据。
## 主要功能
- 接收并解析设备传感器数据
- 自动识别和管理设备序列号
- 实时监控活跃实验状态
- 数据存储到Redis流
- 通过WebSocket实时转发数据
- 支持数据降采样处理
- 自动重连和错误恢复机制
## 系统要求
- Python 3.7+
- MongoDB
- Redis
- 相关Python包依赖:
- motor
- websockets
- redis
- asyncio
## 配置说明
### MongoDB配置
```python
mongodb_url = "mongodb://lab:password@host:27017/lab"
```
### Redis配置
```python
redis_url = 'redis://host:6379'
redis_password = 'password'
redis_db = 200
```
### 服务器配置
```python
host = '0.0.0.0'
port = 6002
```
## 数据流程
1. 设备通过UDP发送数据包到服务器
2. 服务器解析数据包,识别设备序列号
3. 查询设备对应的活跃实验
4. 将数据存储到Redis流
5. 通过WebSocket实时转发处理后的数据
## 主要类和方法
### UDPServer
- `init_mongodb()`: 初始化MongoDB连接
- `init_redis()`: 初始化Redis连接
- `update_active_experiments()`: 更新活跃实验信息
- `process_and_forward_data()`: 处理和转发数据
- `handle_sensor_data()`: 处理传感器数据
- `batch_redis_write()`: 批量写入Redis
## 运行方式
```bash
python data.py
```
## 数据格式
### 传感器数据包格式
### WebSocket消息格式
```json
{
"type": "data_batch",
"data_points": [
{
"sensor_data": {
"sensor_name": value
},
"timestamp": timestamp
}
],
"serial_number": "device_serial",
"total_points": count
}
```
## 错误处理
- 自动重连机制
- 数据校验
- 异常捕获和日志记录
- 优雅关闭处理
## 注意事项
1. 确保MongoDB和Redis服务正常运行
2. 检查网络防火墙设置,确保UDP端口开放
3. 监控服务器资源使用情况
4. 定期检查日志文件
View File
+64
View File
@@ -0,0 +1,64 @@
# 设备管理系统 (Device Management System)
用于管理实验设备的Web应用系统,提供设备注册、序列号管理和在线状态监控等功能。
## 功能特点
### 1. 设备管理
- 创建、编辑和删除设备
- 支持多通道设备配置
- 自定义传感器参数设置
### 2. 序列号管理
- 批量生成设备序列号(每次10个)
- 序列号状态跟踪
- 序列号格式:`XX00-UUID(10位)-设备ID后6位`
- XX: 设备类型前两位大写
- 00: 批次号
- UUID: 随机生成的10位字符
- 设备ID后6位: 用作校验码
### 3. 在线设备监控
- 实时设备状态监控
- 设备活跃度追踪(5分钟超时)
- 通道数据流监控
## 技术栈
## 配置说明
### 文件夹结构
device-manager/
├── config/ # 配置文件
│ ├── database.py # 数据库配置
│ └── models.py # 数据模型
├── device-register.py # 主程序
├── device-register.html # web界面
└── README.md
### 数据库配置
- MongoDB
- URL: mongodb://lab:y6aHwySAhzrbibLD@222.186.10.253:27017/lab
- 数据库: lab
- 集合: devices
- Redis
- URL: redis://:Obscura@2024@222.186.10.253:6379
- 数据库: db200
- 用途: 存储设备实时状态和数据流
## API 接口
### 设备管理
- `POST /devices/devices` - 创建新设备
- `GET /devices/devices` - 获取所有设备
- `PUT /devices/devices/{device_id}` - 更新设备信息
- `DELETE /devices/devices/{device_id}` - 删除设备
### 序列号管理
- `POST /devices/devices/{device_id}/serials` - 生成设备序列号
- `GET /devices/devices/{device_id}/serials` - 获取设备序列号列表
### 在线设备
- `GET /devices/devices/online` - 获取在线设备列表
+40
View File
@@ -0,0 +1,40 @@
from motor.motor_asyncio import AsyncIOMotorClient
from redis import asyncio as aioredis
# 数据库配置
MONGODB_URL = "mongodb://lab:y6aHwySAhzrbibLD@222.186.10.253:27017/lab"
REDIS_URL = "redis://:Obscura@2024@222.186.10.253:6379"
# 数据库连接类
class Database:
"""数据库连接管理类"""
client: AsyncIOMotorClient = None
db = Database()
redis_client = None
async def get_database():
"""获取数据库连接"""
if db.client is None:
await connect_to_mongo()
return db.client["lab"]
async def connect_to_mongo():
try:
db.client = AsyncIOMotorClient(MONGODB_URL)
# 验证连接
await db.client.admin.command('ping')
print("Successfully connected to MongoDB")
except Exception as e:
print(f"Could not connect to MongoDB: {e}")
raise
async def close_mongo_connection():
if db.client:
db.client.close()
async def init_redis():
global redis_client
if redis_client is None:
redis_client = await aioredis.from_url(REDIS_URL, db=200)
return redis_client
+59
View File
@@ -0,0 +1,59 @@
from typing import Optional, List
from pydantic import BaseModel, Field
from bson import ObjectId
class PyObjectId(ObjectId):
"""
自定义ObjectId类,用于在Pydantic模型中处理MongoDB的ObjectId
"""
@classmethod
def __get_validators__(cls):
yield cls.validate
@classmethod
def validate(cls, v, handler):
if not ObjectId.is_valid(v):
raise ValueError("Invalid ObjectId")
return ObjectId(v)
@classmethod
def __get_pydantic_json_schema__(cls, _schema_cache, **_kwargs):
return {
'type': 'string',
'description': 'ObjectId',
'pattern': r'^[0-9a-fA-F]{24}$'
}
@classmethod
def __modify_schema__(cls, field_schema):
field_schema.update(
type='string',
description='ObjectId',
pattern=r'^[0-9a-fA-F]{24}$'
)
class SensorModel(BaseModel):
"""传感器模型"""
index: str
sensor_name: str
sensor_type: str
unit: str
class DeviceModel(BaseModel):
id: Optional[PyObjectId] = Field(alias="_id", default=None)
device_name: str
device_type: str
device_number: int
serial_numbers: list = Field(default_factory=list)
sensors: List[SensorModel] = Field(default_factory=list)
class Config:
populate_by_name = True
arbitrary_types_allowed = True
json_encoders = {ObjectId: str}
def dict(self, *args, **kwargs):
"""确保返回的字典包含 _id 字段"""
kwargs["by_alias"] = True
return super().dict(*args, **kwargs)
@@ -1,17 +1,17 @@
# Device routes
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from typing import Optional
from motor.motor_asyncio import AsyncIOMotorClient
from bson import ObjectId
from pydantic import BaseModel, Field
from contextlib import asynccontextmanager
from datetime import datetime, timedelta
import uuid
from redis import asyncio as aioredis
import asyncio
# Database Configuration
MONGODB_URL = "mongodb://lab:y6aHwySAhzrbibLD@222.186.10.253:27017/lab"
from config.database import (
get_database, connect_to_mongo, close_mongo_connection,
init_redis
)
from config.models import DeviceModel
# 更新 FastAPI 实例化
@asynccontextmanager
@@ -35,87 +35,7 @@ app.add_middleware(
expose_headers=["*"]
)
# MongoDB setup
class PyObjectId(ObjectId):
"""
自定义ObjectId类用于在Pydantic模型中处理MongoDB的ObjectId
"""
@classmethod
def __get_validators__(cls):
yield cls.validate
@classmethod
def validate(cls, v, handler):
if not ObjectId.is_valid(v):
raise ValueError("Invalid ObjectId")
return ObjectId(v)
@classmethod
def __get_pydantic_json_schema__(cls, _schema_cache, **_kwargs):
return {
'type': 'string',
'description': 'ObjectId',
'pattern': r'^[0-9a-fA-F]{24}$'
}
@classmethod
def __modify_schema__(cls, field_schema):
field_schema.update(
type='string',
description='ObjectId',
pattern=r'^[0-9a-fA-F]{24}$'
)
class SensorModel(BaseModel):
"""传感器模型"""
index: str
sensor_name: str
sensor_type: str
unit: str
class DeviceModel(BaseModel):
id: Optional[PyObjectId] = Field(alias="_id", default=None)
device_name: str
device_type: str
device_number: int
serial_numbers: list = Field(default_factory=list)
sensors: list[SensorModel] = Field(default_factory=list)
class Config:
populate_by_name = True
arbitrary_types_allowed = True
json_encoders = {ObjectId: str}
def dict(self, *args, **kwargs):
"""确保返回的字典包含 _id 字段"""
kwargs["by_alias"] = True
return super().dict(*args, **kwargs)
# Database connection
class Database:
"""数据库连接管理类"""
client: AsyncIOMotorClient = None
db = Database()
async def get_database():
"""获取数据库连接"""
if db.client is None:
await connect_to_mongo()
return db.client["lab"]
async def connect_to_mongo():
try:
db.client = AsyncIOMotorClient(MONGODB_URL)
# 验证连接
await db.client.admin.command('ping')
print("Successfully connected to MongoDB")
except Exception as e:
print(f"Could not connect to MongoDB: {e}")
raise
async def close_mongo_connection():
db.client.close()
@app.post("/devices/devices")
async def create_device(device: DeviceModel):
@@ -299,23 +219,12 @@ async def update_device(device_id: str, device: DeviceModel):
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
# 添加Redis连接配置
REDIS_URL = "redis://:Obscura@2024@222.186.10.253:6379"
redis_client = None
# 用于存储上一次检查时的stream长度
last_stream_lengths = {}
# 在全局变量中添加设备活跃时间记录
device_last_active = {}
# 初始化Redis连接
async def init_redis():
global redis_client
if redis_client is None:
redis_client = await aioredis.from_url(REDIS_URL, db=200)
return redis_client
@app.get("/devices/devices/online")
async def get_online_devices():
"""获取在线设备列表"""