Files
john 1aaef71f52 Initial commit: Happy Up monorepo through Sprint 5.
Document-driven MVP with FastAPI backend, Vue H5, WeChat mini shell, product demo, and Docker dev stack.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-23 11:42:40 +08:00

67 lines
3.1 KiB
Python

from datetime import datetime, timedelta, timezone
from sqlalchemy import func, select
from sqlalchemy.orm import Session
from app.db.models import AnalysisTask, Child, Report, TrainingPlan, TrainingRecord, Video
def _tenant_child_ids_subquery(tenant_id: int):
return select(Child.id).where(Child.tenant_id == tenant_id).scalar_subquery()
def get_dashboard_metrics(db: Session, tenant_id: int | None = None) -> dict:
since = datetime.now(timezone.utc) - timedelta(days=30)
child_q = select(func.count()).select_from(Child).where(Child.status == "active")
video_q = select(func.count()).select_from(Video)
report_q = select(func.count()).select_from(Report).where(Report.status == "published")
plan_q = select(func.count()).select_from(TrainingPlan).where(TrainingPlan.status == "active")
new_child_q = select(func.count()).select_from(Child).where(Child.created_at >= since)
succeeded_q = select(func.count()).select_from(AnalysisTask).where(
AnalysisTask.status == "SUCCEEDED"
)
reassessment_q = select(func.count()).select_from(AnalysisTask).where(
AnalysisTask.task_type == "reassessment", AnalysisTask.status == "SUCCEEDED"
)
pending_review_q = select(func.count()).select_from(Report).where(Report.reviewed_by.is_(None))
pending_training_q = select(func.count()).select_from(TrainingRecord).where(
TrainingRecord.score.is_not(None)
)
if tenant_id:
tenant_children = _tenant_child_ids_subquery(tenant_id)
child_q = child_q.where(Child.tenant_id == tenant_id)
new_child_q = new_child_q.where(Child.tenant_id == tenant_id)
video_q = video_q.where(Video.child_id.in_(tenant_children))
report_q = report_q.where(Report.child_id.in_(tenant_children))
plan_q = plan_q.where(TrainingPlan.child_id.in_(tenant_children))
succeeded_q = succeeded_q.where(AnalysisTask.child_id.in_(tenant_children))
reassessment_q = reassessment_q.where(AnalysisTask.child_id.in_(tenant_children))
pending_review_q = pending_review_q.where(Report.child_id.in_(tenant_children))
pending_training_q = pending_training_q.where(TrainingRecord.child_id.in_(tenant_children))
new_children = db.scalar(new_child_q) or 0
uploaded_videos = db.scalar(video_q) or 0
completed_reports = db.scalar(report_q) or 0
active_plans = db.scalar(plan_q) or 0
succeeded_tasks = db.scalar(succeeded_q) or 0
reassessment_done = db.scalar(reassessment_q) or 0
conversion_rate = round(completed_reports / max(succeeded_tasks, 1), 2)
reassessment_rate = round(reassessment_done / max(active_plans, 1), 2)
pending_review = db.scalar(pending_review_q) or 0
pending_training = db.scalar(pending_training_q) or 0
return {
"newChildren": new_children,
"uploadedVideos": uploaded_videos,
"completedReports": completed_reports,
"activePlans": active_plans,
"conversionRate": min(conversion_rate, 1.0),
"reassessmentCompletionRate": min(reassessment_rate, 1.0),
"pendingReviewReports": pending_review,
"pendingTrainingReview": pending_training,
}