from __future__ import annotations import json from django.conf import settings from django.http import Http404, JsonResponse from django.views.decorators.http import require_http_methods from django.contrib.auth.decorators import login_required from review_agent.models import FileSummaryBatch, RegulatoryReviewBatch, WorkflowNodeRun from review_agent.regulatory_review.events import record_event from review_agent.regulatory_review.services.rectification_review import review_missing_issues from review_agent.regulatory_review.workflow import create_regulatory_review_batch, start_regulatory_review_workflow @require_http_methods(["GET"]) @login_required def batch_status(request, batch_id: int): batch = RegulatoryReviewBatch.objects.filter(pk=batch_id, user=request.user).first() if not batch: raise Http404("批次不存在。") nodes = WorkflowNodeRun.objects.filter( workflow_type="regulatory_review", workflow_batch_id=batch.pk, ).order_by("id") payload = { "batch": { "id": batch.pk, "workflow_type": "regulatory_review", "batch_no": batch.batch_no, "status": batch.status, "source_summary_batch_id": batch.source_summary_batch_id, "risk_summary": batch.risk_summary, "risk_summary_text": _format_risk_summary(batch.risk_summary or {}), "error_message": batch.error_message, }, "nodes": [ { "node_code": node.node_code, "node_name": node.node_name, "status": node.status, "progress": node.progress, "message": node.message, } for node in nodes ], } if batch.status == RegulatoryReviewBatch.Status.WAITING_USER and (batch.condition_json or {}).get("candidates"): payload["condition_confirmation"] = { "batch_id": batch.pk, "batch_no": batch.batch_no, "confirm_url": f"/api/review-agent/regulatory-review/{batch.pk}/conditions/", "candidates": batch.condition_json["candidates"], } return JsonResponse(payload) @require_http_methods(["POST"]) @login_required def confirm_conditions(request, batch_id: int): batch = RegulatoryReviewBatch.objects.filter(pk=batch_id, user=request.user).first() if not batch: raise Http404("批次不存在。") try: payload = json.loads(request.body.decode("utf-8") or "{}") except json.JSONDecodeError: return JsonResponse({"error": "请求体不是有效 JSON。"}, status=400) conditions = payload.get("conditions") if not isinstance(conditions, dict): return JsonResponse({"error": "conditions 必须是对象。"}, status=400) batch.condition_json = { **(batch.condition_json or {}), "confirmed": True, "confirmed_conditions": _normalize_conditions(conditions), } batch.status = RegulatoryReviewBatch.Status.RUNNING batch.save(update_fields=["condition_json", "status"]) WorkflowNodeRun.objects.filter( workflow_type="regulatory_review", workflow_batch_id=batch.pk, node_code="condition_confirm", ).update( status=WorkflowNodeRun.Status.SUCCESS, progress=100, message="适用条件已确认", ) record_event( batch, "condition_confirmed", {"conditions": batch.condition_json["confirmed_conditions"], "resume_from": "rule_scope"}, ) start_regulatory_review_workflow( batch, async_run=getattr(settings, "REGULATORY_REVIEW_ASYNC", True), ) batch.refresh_from_db() return JsonResponse( { "batch": { "id": batch.pk, "workflow_type": "regulatory_review", "batch_no": batch.batch_no, "status": batch.status, "condition_json": batch.condition_json, } } ) @require_http_methods(["POST"]) @login_required def start_full_review(request, batch_id: int): source_batch = RegulatoryReviewBatch.objects.filter(pk=batch_id, user=request.user).first() if not source_batch: raise Http404("批次不存在。") payload, error_response = _json_payload(request) if error_response: return error_response summary_batch = FileSummaryBatch.objects.filter( pk=payload.get("file_summary_batch_id"), conversation=source_batch.conversation, user=request.user, status=FileSummaryBatch.Status.SUCCESS, ).first() if not summary_batch: return JsonResponse({"error": "file_summary_batch_id 不存在或未成功。"}, status=400) new_batch = create_regulatory_review_batch( conversation=source_batch.conversation, user=request.user, source_summary_batch=summary_batch, ) new_batch.condition_json = { "source_review_batch_id": source_batch.pk, "regenerated_from": { "batch_id": source_batch.pk, "batch_no": source_batch.batch_no, "file_summary_batch_id": source_batch.source_summary_batch_id, "file_summary_batch_no": source_batch.source_summary_batch.batch_no, }, "confirmed": True, "confirmed_conditions": source_batch.condition_json.get("confirmed_conditions", {}), } new_batch.save(update_fields=["condition_json"]) record_event( new_batch, "full_package_review_started", {"source_review_batch_id": source_batch.pk, "source_review_batch_no": source_batch.batch_no}, ) start_regulatory_review_workflow( new_batch, async_run=getattr(settings, "REGULATORY_REVIEW_ASYNC", True), ) new_batch.refresh_from_db() return JsonResponse( { "batch": { "id": new_batch.pk, "workflow_type": "regulatory_review", "batch_no": new_batch.batch_no, "status": new_batch.status, "source_review_batch_id": source_batch.pk, } } ) @require_http_methods(["POST"]) @login_required def review_issues(request, batch_id: int): batch = RegulatoryReviewBatch.objects.filter(pk=batch_id, user=request.user).first() if not batch: raise Http404("批次不存在。") payload, error_response = _json_payload(request) if error_response: return error_response issue_ids = payload.get("issue_ids") if not isinstance(issue_ids, list): return JsonResponse({"error": "issue_ids 必须是列表。"}, status=400) summary_batch = FileSummaryBatch.objects.filter( pk=payload.get("file_summary_batch_id"), conversation=batch.conversation, user=request.user, status=FileSummaryBatch.Status.SUCCESS, ).first() if not summary_batch: return JsonResponse({"error": "file_summary_batch_id 不存在或未成功。"}, status=400) record = review_missing_issues(batch=batch, issue_ids=[int(item) for item in issue_ids], file_summary_batch=summary_batch) return JsonResponse({"review_record": record}) def _format_risk_summary(risk_summary: dict) -> str: labels = [ ("blocking", "阻断项"), ("high", "高风险"), ("medium", "中风险"), ("low", "低风险"), ("info", "提示"), ] return " · ".join( f"{label} {int(risk_summary.get(key) or 0)}" for key, label in labels if int(risk_summary.get(key) or 0) ) def _normalize_conditions(conditions: dict) -> dict[str, str]: allowed = [ "product_category", "registration_type", "clinical_evaluation_path", "product_name", "model_spec", "intended_use", ] return {key: str(conditions.get(key) or "").strip() for key in allowed} def _json_payload(request): try: return json.loads(request.body.decode("utf-8") or "{}"), None except json.JSONDecodeError: return {}, JsonResponse({"error": "请求体不是有效 JSON。"}, status=400)