Sprint 6: report review workflow, CI/K8s, and client tabs.
CI / api-test (push) Has been cancelled
CI / web-build (push) Has been cancelled

Add coach review APIs, pose calibration thresholds, Gitea CI, Kubernetes skeleton, H5 practice page, and mini program tab bar.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-07-23 11:44:11 +08:00
parent 1aaef71f52
commit a400130c67
27 changed files with 636 additions and 27 deletions
+4 -2
View File
@@ -56,16 +56,18 @@ def create_report_for_task(db: Session, task: AnalysisTask, analysis: dict) -> R
)
else:
payload = {**MOCK_REPORT, **report_data, "childId": task.child_id, "taskId": task.id}
risk_level = payload.get("riskLevel", "medium")
report_status = "pending_review" if risk_level in ("high", "review_required") else "published"
report = Report(
child_id=task.child_id,
task_id=task.id,
report_type="posture_screening",
risk_level=payload.get("riskLevel", "medium"),
risk_level=risk_level,
summary=payload.get("summary", MOCK_REPORT["summary"]),
metrics=payload.get("metrics", MOCK_REPORT["metrics"]),
recommendations=payload.get("recommendations", MOCK_REPORT["recommendations"]),
disclaimer=payload.get("disclaimer", DISCLAIMER_SCREENING),
status="published",
status=report_status,
)
db.add(report)
db.flush()
+82
View File
@@ -0,0 +1,82 @@
"""Report review workflow for staff."""
from datetime import datetime, timezone
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.db.models import Child, Report
def report_admin_dict(report: Report, child: Child | None = None) -> dict:
return {
"id": report.id,
"childId": report.child_id,
"childName": child.name if child else None,
"taskId": report.task_id,
"reportType": report.report_type,
"riskLevel": report.risk_level,
"summary": report.summary,
"status": report.status,
"reviewedBy": report.reviewed_by,
"createdAt": report.created_at.isoformat() if report.created_at else None,
}
def list_pending_reports(db: Session, tenant_id: int | None = None) -> list[dict]:
query = (
select(Report, Child)
.join(Child, Child.id == Report.child_id)
.where(Report.status == "pending_review")
.order_by(Report.id.desc())
)
if tenant_id:
query = query.where(Child.tenant_id == tenant_id)
rows = db.execute(query).all()
return [report_admin_dict(report, child) for report, child in rows]
def get_report_for_review(db: Session, report_id: int, tenant_id: int | None = None) -> Report | None:
report = db.get(Report, report_id)
if not report:
return None
child = db.get(Child, report.child_id)
if not child:
return None
if tenant_id and child.tenant_id != tenant_id:
return None
return report
def review_report(
db: Session,
reviewer_id: int,
report_id: int,
action: str,
tenant_id: int | None = None,
note: str | None = None,
) -> tuple[Report | None, str | None]:
report = get_report_for_review(db, report_id, tenant_id)
if not report:
return None, "report_not_found"
if report.status != "pending_review":
return None, "invalid_status"
if action == "approve":
report.status = "published"
elif action == "reject":
report.status = "rejected"
else:
return None, "invalid_action"
report.reviewed_by = reviewer_id
report.updated_at = datetime.now(timezone.utc)
if note:
recommendations = list(report.recommendations or [])
recommendations.append(f"复核备注:{note}")
report.recommendations = recommendations
db.commit()
db.refresh(report)
return report, None
+1
View File
@@ -37,6 +37,7 @@ def report_to_dict(report: Report) -> dict:
"metrics": report.metrics,
"recommendations": report.recommendations,
"disclaimer": report.disclaimer,
"status": report.status,
"reviewedBy": report.reviewed_by,
}