chore: stop tracking frontend node_modules
This commit is contained in:
@@ -0,0 +1,299 @@
|
||||
"""概览数据聚合CRUD操作"""
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
from sqlalchemy import func, select, and_
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.site import Site
|
||||
from app.models.startup_feasibility import StartupFeasibility
|
||||
from app.models.startup_ethics import StartupEthics
|
||||
from app.models.finance_contract import FinanceContract
|
||||
from app.models.kickoff_meeting import KickoffMeeting
|
||||
from app.models.subject import Subject
|
||||
|
||||
|
||||
async def get_project_overview(db: AsyncSession, study_id: uuid.UUID) -> Dict:
|
||||
"""
|
||||
获取项目概览数据,聚合各中心的进度信息
|
||||
|
||||
返回格式:
|
||||
{
|
||||
"study_id": "...",
|
||||
"updated_at": "2026-01-16T16:10:00",
|
||||
"centers": [...],
|
||||
"enrollment_by_month": [...]
|
||||
}
|
||||
"""
|
||||
# 获取所有中心
|
||||
stmt = select(Site).where(Site.study_id == study_id).order_by(Site.created_at)
|
||||
result = await db.execute(stmt)
|
||||
sites = result.scalars().all()
|
||||
|
||||
if not sites:
|
||||
return {
|
||||
"study_id": str(study_id),
|
||||
"updated_at": datetime.now().isoformat(),
|
||||
"centers": [],
|
||||
"enrollment_by_month": []
|
||||
}
|
||||
|
||||
site_ids = [site.id for site in sites]
|
||||
|
||||
# 并行获取各类数据
|
||||
feasibility_data = await _get_feasibility_data(db, study_id, site_ids)
|
||||
ethics_data = await _get_ethics_data(db, study_id, site_ids)
|
||||
contract_data = await _get_contract_data(db, study_id, site_ids)
|
||||
kickoff_data = await _get_kickoff_data(db, study_id, site_ids)
|
||||
enrollment_data = await _get_enrollment_data(db, study_id, site_ids)
|
||||
monthly_enrollment = await _get_monthly_enrollment(db, study_id)
|
||||
|
||||
# 组装中心数据
|
||||
centers = []
|
||||
for site in sites:
|
||||
site_id_str = str(site.id)
|
||||
|
||||
# 机构立项状态
|
||||
feas = feasibility_data.get(site_id_str, {})
|
||||
institution_status = "COMPLETED" if feas.get("approved_date") else "IN_PROGRESS" if feas.get("submit_date") else "NOT_STARTED"
|
||||
institution_completed = feas.get("approved_date")
|
||||
|
||||
# 伦理审批状态 - 依赖机构立项完成
|
||||
eth = ethics_data.get(site_id_str, {})
|
||||
if institution_status == "COMPLETED":
|
||||
ethics_status = "COMPLETED" if eth.get("approved_date") else "IN_PROGRESS" if eth.get("submit_date") else "NOT_STARTED"
|
||||
else:
|
||||
# 机构立项未完成,伦理强制为未开始
|
||||
ethics_status = "NOT_STARTED"
|
||||
ethics_completed = eth.get("approved_date") if institution_status == "COMPLETED" else None
|
||||
|
||||
# 合同签署状态 - 依赖伦理审批完成
|
||||
contract = contract_data.get(site_id_str, {})
|
||||
if ethics_status == "COMPLETED":
|
||||
contract_status = "COMPLETED" if contract.get("signed_date") else "NOT_STARTED"
|
||||
else:
|
||||
# 伦理未完成,合同强制为未开始
|
||||
contract_status = "NOT_STARTED"
|
||||
contract_completed = contract.get("signed_date") if ethics_status == "COMPLETED" else None
|
||||
|
||||
# 启动会状态 - 依赖合同签署完成
|
||||
kickoff = kickoff_data.get(site_id_str, {})
|
||||
if contract_status == "COMPLETED":
|
||||
startup_status = "COMPLETED" if kickoff.get("kickoff_date") else "NOT_STARTED"
|
||||
else:
|
||||
# 合同未完成,启动会强制为未开始
|
||||
startup_status = "NOT_STARTED"
|
||||
startup_completed = kickoff.get("kickoff_date") if contract_status == "COMPLETED" else None
|
||||
|
||||
# 入组状态 - 依赖启动会完成
|
||||
enrollment = enrollment_data.get(site_id_str, {"actual": 0})
|
||||
enrollment_actual = enrollment["actual"]
|
||||
enrollment_target = site.enrollment_target or 0
|
||||
|
||||
if startup_status == "COMPLETED":
|
||||
if enrollment_actual >= enrollment_target and enrollment_target > 0:
|
||||
enrollment_status = "COMPLETED"
|
||||
elif enrollment_actual > 0:
|
||||
enrollment_status = "IN_PROGRESS"
|
||||
else:
|
||||
enrollment_status = "NOT_STARTED"
|
||||
else:
|
||||
# 启动会未完成,入组强制为未开始
|
||||
enrollment_status = "NOT_STARTED"
|
||||
|
||||
# 检查状态 - 依赖入组完成
|
||||
# 暂未实现数据源,固定为NOT_STARTED
|
||||
if enrollment_status == "COMPLETED":
|
||||
inspection_status = "NOT_STARTED" # 未来从inspection表获取
|
||||
else:
|
||||
inspection_status = "NOT_STARTED"
|
||||
|
||||
# 关中心状态 - 依赖检查完成
|
||||
# 暂未实现数据源,固定为NOT_STARTED
|
||||
if inspection_status == "COMPLETED":
|
||||
closeout_status = "NOT_STARTED" # 未来从closeout表获取
|
||||
else:
|
||||
closeout_status = "NOT_STARTED"
|
||||
|
||||
|
||||
centers.append({
|
||||
"center_id": str(site.id),
|
||||
"center_name": site.name,
|
||||
"institution_initiation_status": institution_status,
|
||||
"ethics_status": ethics_status,
|
||||
"contract_sign_status": contract_status,
|
||||
"startup_status": startup_status,
|
||||
"enrollment_status": enrollment_status,
|
||||
"inspection_status": inspection_status,
|
||||
"closeout_status": closeout_status,
|
||||
"institution_initiation_completed_at": institution_completed,
|
||||
"ethics_completed_at": ethics_completed,
|
||||
"contract_sign_completed_at": contract_completed,
|
||||
"startup_completed_at": startup_completed,
|
||||
"enrollment_completed_at": None, # 暂未实现
|
||||
"inspection_completed_at": None,
|
||||
"closeout_completed_at": None,
|
||||
"enrollment_target": enrollment_target,
|
||||
"enrollment_actual": enrollment_actual,
|
||||
})
|
||||
|
||||
return {
|
||||
"study_id": str(study_id),
|
||||
"updated_at": datetime.now().isoformat(),
|
||||
"centers": centers,
|
||||
"enrollment_by_month": monthly_enrollment
|
||||
}
|
||||
|
||||
|
||||
async def _get_feasibility_data(db: AsyncSession, study_id: uuid.UUID, site_ids: List[uuid.UUID]) -> Dict:
|
||||
"""获取各中心的立项数据"""
|
||||
stmt = select(
|
||||
StartupFeasibility.site_id,
|
||||
StartupFeasibility.submit_date,
|
||||
StartupFeasibility.approved_date
|
||||
).where(
|
||||
and_(
|
||||
StartupFeasibility.study_id == study_id,
|
||||
StartupFeasibility.site_id.in_(site_ids)
|
||||
)
|
||||
).order_by(StartupFeasibility.created_at.desc())
|
||||
|
||||
result = await db.execute(stmt)
|
||||
rows = result.all()
|
||||
|
||||
data = {}
|
||||
for row in rows:
|
||||
site_id_str = str(row.site_id)
|
||||
if site_id_str not in data:
|
||||
data[site_id_str] = {
|
||||
"submit_date": row.submit_date.isoformat() if row.submit_date else None,
|
||||
"approved_date": row.approved_date.isoformat() if row.approved_date else None,
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
|
||||
async def _get_ethics_data(db: AsyncSession, study_id: uuid.UUID, site_ids: List[uuid.UUID]) -> Dict:
|
||||
"""获取各中心的伦理数据"""
|
||||
stmt = select(
|
||||
StartupEthics.site_id,
|
||||
StartupEthics.submit_date,
|
||||
StartupEthics.approved_date
|
||||
).where(
|
||||
and_(
|
||||
StartupEthics.study_id == study_id,
|
||||
StartupEthics.site_id.in_(site_ids)
|
||||
)
|
||||
).order_by(StartupEthics.created_at.desc())
|
||||
|
||||
result = await db.execute(stmt)
|
||||
rows = result.all()
|
||||
|
||||
data = {}
|
||||
for row in rows:
|
||||
site_id_str = str(row.site_id)
|
||||
if site_id_str not in data:
|
||||
data[site_id_str] = {
|
||||
"submit_date": row.submit_date.isoformat() if row.submit_date else None,
|
||||
"approved_date": row.approved_date.isoformat() if row.approved_date else None,
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
|
||||
async def _get_contract_data(db: AsyncSession, study_id: uuid.UUID, site_ids: List[uuid.UUID]) -> Dict:
|
||||
"""获取各中心的合同签署数据"""
|
||||
# FinanceContract 通过 site_name 关联,需要先获取site名称映射
|
||||
stmt_sites = select(Site.id, Site.name).where(Site.id.in_(site_ids))
|
||||
result = await db.execute(stmt_sites)
|
||||
site_name_map = {row.name: str(row.id) for row in result.all()}
|
||||
|
||||
stmt = select(
|
||||
FinanceContract.site_name,
|
||||
func.min(FinanceContract.signed_date).label("signed_date")
|
||||
).where(
|
||||
FinanceContract.study_id == study_id
|
||||
).group_by(FinanceContract.site_name)
|
||||
|
||||
result = await db.execute(stmt)
|
||||
rows = result.all()
|
||||
|
||||
data = {}
|
||||
for row in rows:
|
||||
site_id_str = site_name_map.get(row.site_name)
|
||||
if site_id_str:
|
||||
data[site_id_str] = {
|
||||
"signed_date": row.signed_date.isoformat() if row.signed_date else None,
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
|
||||
async def _get_kickoff_data(db: AsyncSession, study_id: uuid.UUID, site_ids: List[uuid.UUID]) -> Dict:
|
||||
"""获取各中心的启动会数据"""
|
||||
stmt = select(
|
||||
KickoffMeeting.site_id,
|
||||
KickoffMeeting.kickoff_date
|
||||
).where(
|
||||
and_(
|
||||
KickoffMeeting.study_id == study_id,
|
||||
KickoffMeeting.site_id.in_(site_ids)
|
||||
)
|
||||
).order_by(KickoffMeeting.created_at.desc())
|
||||
|
||||
result = await db.execute(stmt)
|
||||
rows = result.all()
|
||||
|
||||
data = {}
|
||||
for row in rows:
|
||||
site_id_str = str(row.site_id)
|
||||
if site_id_str not in data:
|
||||
data[site_id_str] = {
|
||||
"kickoff_date": row.kickoff_date.isoformat() if row.kickoff_date else None,
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
|
||||
async def _get_enrollment_data(db: AsyncSession, study_id: uuid.UUID, site_ids: List[uuid.UUID]) -> Dict:
|
||||
"""获取各中心的入组数据"""
|
||||
stmt = select(
|
||||
Subject.site_id,
|
||||
func.count(Subject.id).label("count")
|
||||
).where(
|
||||
and_(
|
||||
Subject.study_id == study_id,
|
||||
Subject.site_id.in_(site_ids),
|
||||
Subject.enrollment_date.isnot(None)
|
||||
)
|
||||
).group_by(Subject.site_id)
|
||||
|
||||
result = await db.execute(stmt)
|
||||
rows = result.all()
|
||||
|
||||
data = {}
|
||||
for row in rows:
|
||||
data[str(row.site_id)] = {"actual": row.count}
|
||||
|
||||
return data
|
||||
|
||||
|
||||
async def _get_monthly_enrollment(db: AsyncSession, study_id: uuid.UUID) -> List[Dict]:
|
||||
"""获取月度入组统计"""
|
||||
month_col = func.to_char(Subject.enrollment_date, 'YYYY-MM').label("month")
|
||||
|
||||
stmt = select(
|
||||
month_col,
|
||||
func.count(Subject.id).label("count")
|
||||
).where(
|
||||
and_(
|
||||
Subject.study_id == study_id,
|
||||
Subject.enrollment_date.isnot(None)
|
||||
)
|
||||
).group_by(month_col).order_by(month_col)
|
||||
|
||||
result = await db.execute(stmt)
|
||||
rows = result.all()
|
||||
|
||||
return [{"month": row.month, "count": row.count} for row in rows]
|
||||
Reference in New Issue
Block a user