deving
This commit is contained in:
+22
-291
@@ -15,7 +15,6 @@ from database.database import get_db_session
|
||||
from utils.logger import get_logger
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from core.mold_generator import MoldCavityGenerator
|
||||
from datetime import datetime
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -117,56 +116,14 @@ async def upload_stp(
|
||||
|
||||
@router.get("/status/{task_id}")
|
||||
@router.post("/status/{task_id}")
|
||||
async def get_status(task_id: str, db_session: AsyncSession = Depends(get_db_session)):
|
||||
async def get_status(task_id: str):
|
||||
"""获取任务状态"""
|
||||
# 先从内存查找
|
||||
if task_id in tasks:
|
||||
task = tasks[task_id]
|
||||
logger.info(f"返回任务状态(内存): {task_id} - {task['status']}")
|
||||
return task
|
||||
|
||||
# 内存中没有,从数据库查找
|
||||
from sqlalchemy import select
|
||||
from models.database import ProcessingTask, STPFile
|
||||
|
||||
result = await db_session.execute(
|
||||
select(ProcessingTask, STPFile)
|
||||
.join(STPFile, ProcessingTask.stp_file_id == STPFile.id)
|
||||
.where(ProcessingTask.task_id == task_id)
|
||||
)
|
||||
|
||||
task_record = result.first()
|
||||
if not task_record:
|
||||
if task_id not in tasks:
|
||||
raise HTTPException(404, "任务不存在")
|
||||
|
||||
task, stp_file = task_record
|
||||
task_data = {
|
||||
"task_id": task.task_id,
|
||||
"filename": stp_file.original_filename if stp_file else "",
|
||||
"file_size": stp_file.file_size if stp_file else 0,
|
||||
"status": task.status,
|
||||
"progress": task.progress,
|
||||
"current_step": task.current_step,
|
||||
"upload_time": task.created_time.astimezone().strftime('%Y-%m-%d %H:%M:%S') if task.created_time else "",
|
||||
"completed_at": task.completed_time.astimezone().strftime('%Y-%m-%d %H:%M:%S') if task.completed_time else "",
|
||||
"error": task.error_message if task.error_message else ""
|
||||
}
|
||||
|
||||
# 尝试获取快照中的 key_info
|
||||
from models.database import AnalysisResultSnapshot
|
||||
snapshot_result = await db_session.execute(
|
||||
select(AnalysisResultSnapshot)
|
||||
.where(AnalysisResultSnapshot.stp_file_id == stp_file.id)
|
||||
.order_by(AnalysisResultSnapshot.created_time.desc())
|
||||
.limit(1)
|
||||
)
|
||||
snapshot = snapshot_result.scalar_one_or_none()
|
||||
|
||||
if snapshot and snapshot.key_info:
|
||||
task_data["key_info"] = snapshot.key_info
|
||||
|
||||
logger.info(f"返回任务状态(数据库): {task_id} - {task['status']}")
|
||||
return task_data
|
||||
task = tasks[task_id]
|
||||
logger.info(f"返回任务状态: {task_id} - {task['status']}")
|
||||
return task
|
||||
|
||||
|
||||
@router.get("/debug/tasks")
|
||||
@@ -205,7 +162,7 @@ async def get_file_history(db_session: AsyncSession = Depends(get_db_session)):
|
||||
file_groups[filename].append({
|
||||
"task_id": task.task_id,
|
||||
"filename": filename,
|
||||
"upload_time": task.created_time.astimezone().strftime('%Y-%m-%d %H:%M:%S') if task.created_time else "",
|
||||
"upload_time": task.created_time.isoformat() if task.created_time else "",
|
||||
"status": task.status,
|
||||
"file_size": stp_file.file_size
|
||||
})
|
||||
@@ -260,9 +217,9 @@ async def get_file_records(filename: str, db_session: AsyncSession = Depends(get
|
||||
"task_id": task.task_id,
|
||||
"filename": stp_file.original_filename,
|
||||
"file_size": stp_file.file_size,
|
||||
"upload_time": task.created_time.astimezone().strftime('%Y-%m-%d %H:%M:%S') if task.created_time else "",
|
||||
"upload_time": task.created_time.isoformat() if task.created_time else "",
|
||||
"status": task.status,
|
||||
"completed_at": task.completed_time.astimezone().strftime('%Y-%m-%d %H:%M:%S') if task.completed_time else ""
|
||||
"completed_at": task.completed_time.isoformat() if task.completed_time else ""
|
||||
})
|
||||
|
||||
# 按上传时间排序(最新的在前)
|
||||
@@ -287,92 +244,28 @@ async def history_page(request: Request):
|
||||
})
|
||||
|
||||
|
||||
@router.get("/api/snapshots/{stp_file_id}")
|
||||
@router.post("/api/snapshots/{stp_file_id}")
|
||||
async def get_analysis_snapshots(stp_file_id: int, db_session: AsyncSession = Depends(get_db_session)):
|
||||
"""获取指定STP文件的分析结果快照列表"""
|
||||
storage_service = StorageIntegrationService()
|
||||
|
||||
try:
|
||||
snapshots = await storage_service.get_analysis_result_snapshots(db_session, stp_file_id)
|
||||
|
||||
# 构建返回数据
|
||||
snapshot_list = []
|
||||
for snapshot in snapshots:
|
||||
snapshot_list.append({
|
||||
"id": snapshot.id,
|
||||
"snapshot_name": snapshot.snapshot_name,
|
||||
"created_time": snapshot.created_time.isoformat() if snapshot.created_time else "",
|
||||
"analysis_time": snapshot.analysis_time.isoformat() if snapshot.analysis_time else "",
|
||||
"task_data": snapshot.task_data
|
||||
})
|
||||
|
||||
return {
|
||||
"stp_file_id": stp_file_id,
|
||||
"total_snapshots": len(snapshot_list),
|
||||
"snapshots": snapshot_list
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取分析结果快照失败: {e}")
|
||||
raise HTTPException(500, f"获取快照失败: {str(e)}")
|
||||
|
||||
|
||||
@router.get("/api/snapshot/{snapshot_id}")
|
||||
@router.post("/api/snapshot/{snapshot_id}")
|
||||
async def get_analysis_snapshot(snapshot_id: int, db_session: AsyncSession = Depends(get_db_session)):
|
||||
"""获取指定分析结果快照的完整数据"""
|
||||
from sqlalchemy import select
|
||||
from models.database import AnalysisResultSnapshot
|
||||
|
||||
try:
|
||||
result = await db_session.execute(
|
||||
select(AnalysisResultSnapshot).where(AnalysisResultSnapshot.id == snapshot_id)
|
||||
)
|
||||
snapshot = result.scalar_one_or_none()
|
||||
|
||||
if not snapshot:
|
||||
raise HTTPException(404, "快照不存在")
|
||||
|
||||
return {
|
||||
"id": snapshot.id,
|
||||
"snapshot_name": snapshot.snapshot_name,
|
||||
"stp_file_id": snapshot.stp_file_id,
|
||||
"created_time": snapshot.created_time.isoformat() if snapshot.created_time else "",
|
||||
"analysis_time": snapshot.analysis_time.isoformat() if snapshot.analysis_time else "",
|
||||
"task_data": snapshot.task_data,
|
||||
"geometry_data": snapshot.geometry_data,
|
||||
"mold_cavity_data": snapshot.mold_cavity_data,
|
||||
"key_info": snapshot.key_info,
|
||||
"html_info": snapshot.html_info
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取分析结果快照详情失败: {e}")
|
||||
raise HTTPException(500, f"获取快照详情失败: {str(e)}")
|
||||
|
||||
|
||||
@router.get("/result/{task_id}")
|
||||
@router.post("/result/{task_id}")
|
||||
async def result_page(request: Request, task_id: str, db_session: AsyncSession = Depends(get_db_session)):
|
||||
"""结果详情页面"""
|
||||
from sqlalchemy import select
|
||||
from models.database import ProcessingTask, STPFile, GeometryData, MoldCavityData, HTMLFile, AnalysisResultSnapshot
|
||||
|
||||
# 先查询任务记录
|
||||
task_result = await db_session.execute(
|
||||
from models.database import ProcessingTask, STPFile, GeometryData, MoldCavityData, HTMLFile
|
||||
|
||||
# 从数据库查询任务详情
|
||||
result = await db_session.execute(
|
||||
select(ProcessingTask, STPFile)
|
||||
.join(STPFile, ProcessingTask.stp_file_id == STPFile.id)
|
||||
.where(ProcessingTask.task_id == task_id)
|
||||
)
|
||||
task_record = task_result.first()
|
||||
|
||||
|
||||
task_record = result.first()
|
||||
|
||||
if not task_record:
|
||||
raise HTTPException(404, "任务不存在")
|
||||
|
||||
|
||||
task, stp_file = task_record
|
||||
|
||||
# 构建任务详情数据
|
||||
|
||||
# 构建任务详情数据(先只包含基本数据)
|
||||
task_data = {
|
||||
"task_id": task.task_id,
|
||||
"filename": stp_file.original_filename if stp_file else "",
|
||||
@@ -380,90 +273,11 @@ async def result_page(request: Request, task_id: str, db_session: AsyncSession =
|
||||
"status": task.status,
|
||||
"progress": task.progress,
|
||||
"current_step": task.current_step,
|
||||
"upload_time": task.created_time.astimezone().strftime('%Y-%m-%d %H:%M:%S') if task.created_time else "",
|
||||
"completed_at": task.completed_time.astimezone().strftime('%Y-%m-%d %H:%M:%S') if task.completed_time else "",
|
||||
"created_at": task.created_time.isoformat() if task.created_time else "",
|
||||
"completed_at": task.completed_time.isoformat() if task.completed_time else "",
|
||||
"error": task.error_message if task.error_message else ""
|
||||
}
|
||||
|
||||
# 尝试从分析结果快照获取数据
|
||||
snapshot_result = await db_session.execute(
|
||||
select(AnalysisResultSnapshot)
|
||||
.where(AnalysisResultSnapshot.stp_file_id == stp_file.id)
|
||||
.order_by(AnalysisResultSnapshot.created_time.desc())
|
||||
.limit(1)
|
||||
)
|
||||
snapshot = snapshot_result.scalar_one_or_none()
|
||||
|
||||
if snapshot:
|
||||
# 从快照获取完整的数据
|
||||
task_data["geometry_data"] = snapshot.geometry_data if snapshot.geometry_data else None
|
||||
task_data["key_info"] = snapshot.key_info if snapshot.key_info else None
|
||||
task_data["mold_cavity_data"] = snapshot.mold_cavity_data if snapshot.mold_cavity_data else None
|
||||
logger.info(f"从快照加载任务数据: {task_id}")
|
||||
else:
|
||||
# 如果没有快照,从各个表分别获取
|
||||
result = await db_session.execute(
|
||||
select(GeometryData, MoldCavityData, HTMLFile)
|
||||
.outerjoin(MoldCavityData, GeometryData.stp_file_id == MoldCavityData.stp_file_id)
|
||||
.outerjoin(HTMLFile, GeometryData.stp_file_id == HTMLFile.stp_file_id)
|
||||
.where(GeometryData.stp_file_id == stp_file.id)
|
||||
)
|
||||
|
||||
other_data = result.first()
|
||||
|
||||
if other_data:
|
||||
geometry_data, mold_cavity_data, html_file = other_data
|
||||
|
||||
# 如果有几何数据,添加到返回结果
|
||||
if geometry_data:
|
||||
task_data["geometry_data"] = {
|
||||
"volume": geometry_data.volume,
|
||||
"surface_area": geometry_data.surface_area,
|
||||
"bounding_box": {
|
||||
"min": geometry_data.bounding_box_min,
|
||||
"max": geometry_data.bounding_box_max,
|
||||
"dimensions": [
|
||||
geometry_data.bounding_box_max[0] - geometry_data.bounding_box_min[0] if geometry_data.bounding_box_max and geometry_data.bounding_box_min else 0,
|
||||
geometry_data.bounding_box_max[1] - geometry_data.bounding_box_min[1] if geometry_data.bounding_box_max and geometry_data.bounding_box_min else 0,
|
||||
geometry_data.bounding_box_max[2] - geometry_data.bounding_box_min[2] if geometry_data.bounding_box_max and geometry_data.bounding_box_min else 0
|
||||
]
|
||||
},
|
||||
"topology": {
|
||||
"faces": geometry_data.topology_faces,
|
||||
"edges": geometry_data.topology_edges,
|
||||
"vertices": geometry_data.topology_vertices
|
||||
},
|
||||
"center_of_mass": geometry_data.center_of_mass
|
||||
}
|
||||
|
||||
# 如果有模具型腔数据,添加到返回结果
|
||||
if mold_cavity_data:
|
||||
task_data["key_info"] = {
|
||||
"metadata": {
|
||||
"shrinkage_rate": mold_cavity_data.shrinkage_rate,
|
||||
"draft_angle": mold_cavity_data.draft_angle
|
||||
},
|
||||
"manufacturing_info": {
|
||||
"mold_material": mold_cavity_data.mold_material,
|
||||
"estimated_clamping_force": mold_cavity_data.estimated_clamping_force,
|
||||
"parting_line_length": mold_cavity_data.parting_line_length,
|
||||
"estimated_cycle_time": mold_cavity_data.estimated_cycle_time,
|
||||
"manufacturing_tolerance": mold_cavity_data.manufacturing_tolerance
|
||||
},
|
||||
"mold_cavities": {
|
||||
"cavity_key_info": {
|
||||
"geometric_characteristics": {
|
||||
"product_weight": mold_cavity_data.product_weight,
|
||||
"product_volume": mold_cavity_data.product_volume,
|
||||
"wall_thickness_range": mold_cavity_data.wall_thickness_range,
|
||||
"complexity_score": mold_cavity_data.complexity_score
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(f"从各个表加载任务数据: {task_id}")
|
||||
|
||||
|
||||
from fastapi.templating import Jinja2Templates
|
||||
import os
|
||||
# 简化路径配置,直接使用当前工作目录下的templates文件夹
|
||||
@@ -638,8 +452,7 @@ async def process_file_core(
|
||||
"mold_hardness": "HB 150-170",
|
||||
"surface_finish": "Ra 0.8 μm",
|
||||
"parting_line_length": f"{parting_line_length:.2f} mm",
|
||||
"estimated_cycle_time": f"{int(cycle_time)} 秒",
|
||||
"manufacturing_tolerance": "±0.1 mm"
|
||||
"estimated_cycle_time": f"{int(cycle_time)} 秒"
|
||||
},
|
||||
"mold_cavities": {
|
||||
"cavity_count": 1,
|
||||
@@ -656,55 +469,6 @@ async def process_file_core(
|
||||
"warpage_risk": "low"
|
||||
}
|
||||
}
|
||||
},
|
||||
# 添加特征检测数据
|
||||
"detected_features": [
|
||||
{
|
||||
"feature_type": "壁厚检测",
|
||||
"confidence": 0.85,
|
||||
"location": "整体结构",
|
||||
"description": f"平均壁厚: {avg_thickness_mm:.2f} mm"
|
||||
},
|
||||
{
|
||||
"feature_type": "拔模角检测",
|
||||
"confidence": 0.92,
|
||||
"location": "主要表面",
|
||||
"description": "拔模角符合要求"
|
||||
},
|
||||
{
|
||||
"feature_type": "几何复杂性",
|
||||
"confidence": 0.78,
|
||||
"location": "整体结构",
|
||||
"description": f"复杂度评分: {complexity_score:.2f}"
|
||||
}
|
||||
],
|
||||
# 添加设计建议数据
|
||||
"design_recommendations": [
|
||||
{
|
||||
"rec_type": "壁厚优化",
|
||||
"priority": "medium",
|
||||
"description": f"建议优化壁厚均匀性,当前范围: {wall_thickness_min:.2f} - {wall_thickness_max:.2f} mm",
|
||||
"reason": "提高注塑成型质量"
|
||||
},
|
||||
{
|
||||
"rec_type": "拔模角",
|
||||
"priority": "low",
|
||||
"description": "拔模角符合标准要求",
|
||||
"reason": "便于脱模"
|
||||
},
|
||||
{
|
||||
"rec_type": "结构简化",
|
||||
"priority": "medium",
|
||||
"description": f"建议简化复杂结构,当前复杂度: {complexity_score:.2f}",
|
||||
"reason": "降低制造成本"
|
||||
}
|
||||
],
|
||||
# 添加质量指标数据
|
||||
"quality_metrics": {
|
||||
"volume_utilization": round(complexity_score * 100, 2),
|
||||
"wall_thickness_uniformity": round((1 - (wall_thickness_max - wall_thickness_min) / avg_thickness_mm) * 100, 2),
|
||||
"geometric_complexity": round(complexity_score * 10, 2),
|
||||
"moldability_score": round((complexity_score + 1) * 50, 2)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -758,39 +522,6 @@ async def process_file_core(
|
||||
db_session, task_id, "completed", 100, "模具型腔生成完成"
|
||||
)
|
||||
|
||||
# 构建完整的分析结果数据
|
||||
task_info = tasks[task_id]
|
||||
|
||||
# 构建任务基本信息
|
||||
task_data = {
|
||||
"task_id": task_info["task_id"],
|
||||
"filename": task_info["filename"],
|
||||
"file_size": task_info["file_size"],
|
||||
"status": task_info["status"],
|
||||
"upload_time": task_info["upload_time"],
|
||||
"completed_at": str(datetime.now())
|
||||
}
|
||||
|
||||
# 保存分析结果快照到数据库
|
||||
try:
|
||||
await storage_service.save_analysis_result_snapshot(
|
||||
db_session,
|
||||
stp_file_id,
|
||||
task_data=task_data,
|
||||
geometry_data=geometry_data,
|
||||
mold_cavity_data=detailed_cavity_json,
|
||||
key_info=detailed_cavity_json,
|
||||
html_info={
|
||||
"filename": Path(html_file_path).name,
|
||||
"file_path": html_file_path,
|
||||
"generated_time": str(datetime.now())
|
||||
}
|
||||
)
|
||||
logger.info(f"分析结果快照保存成功: {task_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"保存分析结果快照失败: {e}")
|
||||
# 不影响主流程,继续执行
|
||||
|
||||
# 更新内存任务状态
|
||||
tasks[task_id]["geometry_data"] = geometry_data
|
||||
tasks[task_id]["analysis_result"] = analysis_result
|
||||
|
||||
@@ -199,8 +199,6 @@ class MoldCavityData(Base):
|
||||
product_volume = Column(Float, nullable=True)
|
||||
wall_thickness_range = Column(String(50), nullable=True)
|
||||
complexity_score = Column(Float, nullable=True)
|
||||
estimated_cycle_time = Column(String(50), nullable=True)
|
||||
manufacturing_tolerance = Column(String(50), nullable=True)
|
||||
|
||||
# 质量评估
|
||||
weld_line_risk = Column(String(50), nullable=True) # 熔接痕风险
|
||||
@@ -328,32 +326,3 @@ class SystemLog(Base):
|
||||
def __repr__(self):
|
||||
return f"<SystemLog(id={self.id}, level='{self.level}', module='{self.module}')>"
|
||||
|
||||
|
||||
class AnalysisResultSnapshot(Base):
|
||||
"""分析结果快照表 - 存储每次分析结果的完整副本"""
|
||||
__tablename__ = "analysis_result_snapshots"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
stp_file_id = Column(Integer, ForeignKey("stp_files.id"), nullable=False, index=True)
|
||||
|
||||
# 快照标识信息
|
||||
snapshot_name = Column(String(255), nullable=False, index=True) # 文件名+日期时间格式
|
||||
snapshot_type = Column(String(50), default="complete") # complete, geometry_only, key_info_only
|
||||
|
||||
# 完整分析结果数据
|
||||
task_data = Column(JSON, nullable=False) # 任务基本信息
|
||||
geometry_data = Column(JSON, nullable=True) # 几何数据
|
||||
mold_cavity_data = Column(JSON, nullable=True) # 模具型腔数据
|
||||
key_info = Column(JSON, nullable=True) # 关键信息
|
||||
html_info = Column(JSON, nullable=True) # HTML文件信息
|
||||
|
||||
# 时间戳
|
||||
created_time = Column(DateTime, default=func.now())
|
||||
analysis_time = Column(DateTime, nullable=False) # 原始分析时间
|
||||
|
||||
# 关联关系
|
||||
stp_file = relationship("STPFile")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<AnalysisResultSnapshot(id={self.id}, snapshot_name='{self.snapshot_name}', stp_file_id={self.stp_file_id})>"
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, update
|
||||
from pathlib import Path
|
||||
from typing import Optional, Dict, Any, List
|
||||
from typing import Optional, Dict, Any
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
@@ -11,7 +11,7 @@ from models.database import (
|
||||
STPFile, GeometryData, MoldCavityData,
|
||||
HTMLFile, ProcessingTask, User,
|
||||
FeatureDetection, DesignRecommendation,
|
||||
UserActivity, SystemLog, AnalysisResultSnapshot
|
||||
UserActivity, SystemLog
|
||||
)
|
||||
from storage.rustfs_storage import rustfs_manager
|
||||
from utils.logger import get_logger
|
||||
@@ -493,79 +493,6 @@ class StorageIntegrationService:
|
||||
|
||||
logger.info(f"STP文件及其关联数据已删除: {stp_file_id}")
|
||||
|
||||
async def save_analysis_result_snapshot(
|
||||
self,
|
||||
session: AsyncSession,
|
||||
stp_file_id: int,
|
||||
task_data: Dict[str, Any],
|
||||
geometry_data: Optional[Dict[str, Any]] = None,
|
||||
mold_cavity_data: Optional[Dict[str, Any]] = None,
|
||||
key_info: Optional[Dict[str, Any]] = None,
|
||||
html_info: Optional[Dict[str, Any]] = None
|
||||
) -> AnalysisResultSnapshot:
|
||||
"""保存分析结果快照 - 文件名+日期时间命名方式"""
|
||||
|
||||
# 获取STP文件信息
|
||||
stp_file = await session.get(STPFile, stp_file_id)
|
||||
if not stp_file:
|
||||
raise ValueError(f"STP文件不存在: {stp_file_id}")
|
||||
|
||||
# 生成快照名称:文件名_YYYYMMDD_HHMMSS
|
||||
filename = stp_file.original_filename
|
||||
# 移除文件扩展名
|
||||
base_name = Path(filename).stem
|
||||
# 生成时间戳
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
snapshot_name = f"{base_name}_{timestamp}"
|
||||
|
||||
# 获取分析时间(从任务数据中获取或使用当前时间)
|
||||
analysis_time_str = task_data.get('created_at') or task_data.get('upload_time')
|
||||
if analysis_time_str:
|
||||
analysis_time = datetime.fromisoformat(analysis_time_str.replace('Z', '+00:00'))
|
||||
else:
|
||||
analysis_time = datetime.now()
|
||||
|
||||
# 创建快照记录
|
||||
snapshot = AnalysisResultSnapshot(
|
||||
stp_file_id=stp_file_id,
|
||||
snapshot_name=snapshot_name,
|
||||
task_data=task_data,
|
||||
geometry_data=geometry_data,
|
||||
mold_cavity_data=mold_cavity_data,
|
||||
key_info=key_info,
|
||||
html_info=html_info,
|
||||
analysis_time=analysis_time
|
||||
)
|
||||
|
||||
session.add(snapshot)
|
||||
await session.commit()
|
||||
await session.refresh(snapshot)
|
||||
|
||||
logger.info(f"分析结果快照保存成功: {snapshot_name} (ID: {snapshot.id})")
|
||||
return snapshot
|
||||
|
||||
async def get_analysis_result_snapshots(
|
||||
self,
|
||||
session: AsyncSession,
|
||||
stp_file_id: int,
|
||||
limit: int = 10
|
||||
) -> List[AnalysisResultSnapshot]:
|
||||
"""获取指定STP文件的分析结果快照列表"""
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
result = await session.execute(
|
||||
select(AnalysisResultSnapshot)
|
||||
.where(AnalysisResultSnapshot.stp_file_id == stp_file_id)
|
||||
.order_by(AnalysisResultSnapshot.created_time.desc())
|
||||
.limit(limit)
|
||||
)
|
||||
|
||||
snapshots = result.scalars().all()
|
||||
logger.info(f"获取到 {len(snapshots)} 个分析结果快照")
|
||||
return snapshots
|
||||
|
||||
|
||||
# 全局存储集成服务实例
|
||||
storage_integration = StorageIntegrationService()
|
||||
|
||||
@@ -159,7 +159,7 @@
|
||||
<button class="upload-btn" onclick="location.href='/history'">
|
||||
📋 返回历史记录
|
||||
</button>
|
||||
<button class="upload-btn" onclick="location.href='/'">
|
||||
<button class="upload-btn" onclick="location.href='/upload'">
|
||||
📁 上传新文件
|
||||
</button>
|
||||
<button class="upload-btn" onclick="location.href='/'">
|
||||
|
||||
@@ -1,451 +0,0 @@
|
||||
<!-- templates/snapshot.html -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>分析结果快照 - STP文件几何分析工具</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<style>
|
||||
.snapshot-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header-nav {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.nav-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.snapshot-info-card {
|
||||
background: white;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.snapshot-info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.results-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.result-section {
|
||||
background: white;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.data-grid {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.data-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
}
|
||||
|
||||
.data-label {
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.data-value {
|
||||
color: #333;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
padding: 40px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #4CAF50;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 20px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background: #ffebee;
|
||||
border: 1px solid #ffcdd2;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
color: #c62828;
|
||||
margin: 20px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="snapshot-container">
|
||||
<div class="header-nav">
|
||||
<div>
|
||||
<h1>📊 分析结果快照</h1>
|
||||
<p>查看历史分析结果的完整副本</p>
|
||||
</div>
|
||||
<div class="nav-buttons">
|
||||
<button class="upload-btn" onclick="location.href='/history'">
|
||||
📋 返回历史记录
|
||||
</button>
|
||||
<button class="upload-btn" onclick="location.href='/'">
|
||||
📁 上传新文件
|
||||
</button>
|
||||
<button class="upload-btn" onclick="location.href='/')">
|
||||
🏠 返回主页
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="snapshotInfo" class="snapshot-info-card">
|
||||
<div class="loading">
|
||||
<div class="spinner"></div>
|
||||
<p>正在加载快照信息...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="results-grid" id="resultsGrid">
|
||||
<!-- 结果内容将通过JavaScript动态加载 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 从URL获取快照ID
|
||||
const pathParts = window.location.pathname.split('/');
|
||||
const snapshotId = pathParts[pathParts.length - 1];
|
||||
|
||||
// 页面加载完成后获取快照数据
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadSnapshotData(snapshotId);
|
||||
});
|
||||
|
||||
async function loadSnapshotData(snapshotId) {
|
||||
try {
|
||||
const response = await fetch(`/api/snapshot/${snapshotId}`, {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('获取快照数据失败');
|
||||
}
|
||||
|
||||
const snapshot = await response.json();
|
||||
displaySnapshotInfo(snapshot);
|
||||
displayResults(snapshot);
|
||||
|
||||
} catch (error) {
|
||||
document.getElementById('snapshotInfo').innerHTML = `
|
||||
<div class="error-message">
|
||||
<h3>❌ 加载失败</h3>
|
||||
<p>${error.message}</p>
|
||||
<button class="upload-btn" onclick="loadSnapshotData('${snapshotId}')">
|
||||
重试
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function displaySnapshotInfo(snapshot) {
|
||||
const snapshotInfo = document.getElementById('snapshotInfo');
|
||||
|
||||
snapshotInfo.innerHTML = `
|
||||
<h2>📝 快照信息</h2>
|
||||
<div class="snapshot-info-grid">
|
||||
<div class="info-item">
|
||||
<div class="info-label">快照名称</div>
|
||||
<div class="info-value">${snapshot.snapshot_name || 'N/A'}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">快照ID</div>
|
||||
<div class="info-value">${snapshot.id || 'N/A'}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">文件ID</div>
|
||||
<div class="info-value">${snapshot.stp_file_id || 'N/A'}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">分析时间</div>
|
||||
<div class="info-value">${snapshot.analysis_time ? new Date(snapshot.analysis_time).toLocaleString() : 'N/A'}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">保存时间</div>
|
||||
<div class="info-value">${snapshot.created_time ? new Date(snapshot.created_time).toLocaleString() : 'N/A'}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function displayResults(snapshot) {
|
||||
const resultsGrid = document.getElementById('resultsGrid');
|
||||
|
||||
if (!snapshot.task_data) {
|
||||
resultsGrid.innerHTML = `
|
||||
<div class="no-data">
|
||||
<div style="font-size: 48px; margin-bottom: 20px;">📁</div>
|
||||
<h3>无分析结果数据</h3>
|
||||
<p>该快照没有包含分析结果数据</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
const task = snapshot.task_data;
|
||||
const geometryData = snapshot.geometry_data;
|
||||
const keyInfo = snapshot.key_info;
|
||||
|
||||
resultsGrid.innerHTML = `
|
||||
<div class="result-section">
|
||||
<div class="section-title">📝 任务信息</div>
|
||||
<div class="data-grid">
|
||||
<div class="data-item">
|
||||
<div class="data-label">文件名</div>
|
||||
<div class="data-value">${task.filename || 'N/A'}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">文件大小</div>
|
||||
<div class="data-value">${task.file_size ? formatFileSize(task.file_size) : 'N/A'}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">状态</div>
|
||||
<div class="data-value">${getStatusText(task.status)}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">上传时间</div>
|
||||
<div class="data-value">${task.upload_time ? new Date(task.upload_time).toLocaleString() : 'N/A'}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">完成时间</div>
|
||||
<div class="data-value">${task.completed_at ? new Date(task.completed_at).toLocaleString() : 'N/A'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${geometryData ? `
|
||||
<div class="result-section">
|
||||
<div class="section-title">📐 几何属性</div>
|
||||
<div class="data-grid">
|
||||
<div class="data-item">
|
||||
<div class="data-label">体积</div>
|
||||
<div class="data-value">${geometryData.volume ? formatNumber(geometryData.volume) + ' mm³' : 'N/A'}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">表面积</div>
|
||||
<div class="data-value">${geometryData.surface_area ? formatNumber(geometryData.surface_area) + ' mm²' : 'N/A'}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">体积表面积比</div>
|
||||
<div class="data-value">${geometryData.volume && geometryData.surface_area ? (geometryData.volume / geometryData.surface_area).toFixed(4) : 'N/A'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
` : '<div class="no-data">无几何数据</div>'}
|
||||
|
||||
${keyInfo ? `
|
||||
<div class="result-section">
|
||||
<div class="section-title">🔧 关键工艺参数</div>
|
||||
<div class="data-grid">
|
||||
${displayKeyInfo(keyInfo)}
|
||||
</div>
|
||||
</div>
|
||||
` : '<div class="no-data">无关键信息数据</div>'}
|
||||
|
||||
${geometryData && geometryData.bounding_box ? `
|
||||
<div class="result-section">
|
||||
<div class="section-title">📦 边界框信息</div>
|
||||
<div class="data-grid">
|
||||
${displayBoundingBoxData(geometryData)}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${geometryData && geometryData.topology ? `
|
||||
<div class="result-section">
|
||||
<div class="section-title">🔺 拓扑结构</div>
|
||||
<div class="data-grid">
|
||||
${displayTopologyData(geometryData)}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
`;
|
||||
}
|
||||
|
||||
function displayKeyInfo(keyInfo) {
|
||||
if (!keyInfo) return '<div class="no-data">无关键信息数据</div>';
|
||||
|
||||
const metadata = keyInfo.metadata || {};
|
||||
const manufacturingInfo = keyInfo.manufacturing_info || {};
|
||||
const moldCavities = keyInfo.mold_cavities || {};
|
||||
const cavityKeyInfo = moldCavities.cavity_key_info || {};
|
||||
const geoChars = cavityKeyInfo.geometric_characteristics || {};
|
||||
|
||||
return `
|
||||
<div class="data-item">
|
||||
<div class="data-label">收缩率</div>
|
||||
<div class="data-value">${metadata.shrinkage_rate !== undefined ? metadata.shrinkage_rate : 'N/A'}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">拔模角</div>
|
||||
<div class="data-value">${metadata.draft_angle !== undefined ? metadata.draft_angle + '°' : 'N/A'}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">分型线长度</div>
|
||||
<div class="data-value">${manufacturingInfo.parting_line_length || 'N/A'}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">产品体积</div>
|
||||
<div class="data-value">${geoChars.product_volume || 'N/A'}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">产品重量</div>
|
||||
<div class="data-value">${geoChars.product_weight || 'N/A'}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">壁厚范围</div>
|
||||
<div class="data-value">${geoChars.wall_thickness_range || 'N/A'}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">型腔材料</div>
|
||||
<div class="data-value">${manufacturingInfo.mold_material || 'N/A'}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">预估周期</div>
|
||||
<div class="data-value">${manufacturingInfo.estimated_cycle_time || 'N/A'}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function displayBoundingBoxData(geometryData) {
|
||||
if (!geometryData || !geometryData.bounding_box) return '<div class="no-data">无边界框数据</div>';
|
||||
|
||||
const bbox = geometryData.bounding_box;
|
||||
return `
|
||||
<div class="data-item">
|
||||
<div class="data-label">最小坐标</div>
|
||||
<div class="data-value">X: ${bbox.min[0].toFixed(2)}<br>Y: ${bbox.min[1].toFixed(2)}<br>Z: ${bbox.min[2].toFixed(2)}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">最大坐标</div>
|
||||
<div class="data-value">X: ${bbox.max[0].toFixed(2)}<br>Y: ${bbox.max[1].toFixed(2)}<br>Z: ${bbox.max[2].toFixed(2)}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">尺寸</div>
|
||||
<div class="data-value">${bbox.dimensions[0].toFixed(2)} × ${bbox.dimensions[1].toFixed(2)} × ${bbox.dimensions[2].toFixed(2)} mm</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function displayTopologyData(geometryData) {
|
||||
if (!geometryData || !geometryData.topology) return '<div class="no-data">无拓扑数据</div>';
|
||||
|
||||
const topo = geometryData.topology;
|
||||
return `
|
||||
<div class="data-item">
|
||||
<div class="data-label">面数</div>
|
||||
<div class="data-value">${topo.faces || 0}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">边数</div>
|
||||
<div class="data-value">${topo.edges || 0}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">顶点数</div>
|
||||
<div class="data-value">${topo.vertices || 0}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 工具函数
|
||||
function getStatusText(status) {
|
||||
const statusMap = {
|
||||
'processing': '处理中',
|
||||
'completed': '已完成',
|
||||
'failed': '失败'
|
||||
};
|
||||
return statusMap[status] || status;
|
||||
}
|
||||
|
||||
function formatFileSize(bytes) {
|
||||
if (!bytes || bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
function formatNumber(num) {
|
||||
if (!num) return 'N/A';
|
||||
if (num >= 1000000) {
|
||||
return (num / 1000000).toFixed(2) + 'M';
|
||||
} else if (num >= 1000) {
|
||||
return (num / 1000).toFixed(2) + 'K';
|
||||
} else {
|
||||
return num.toFixed(2);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user