feat(regulatory): 增加适用条件确认暂停恢复

This commit is contained in:
2026-06-07 09:19:31 +08:00
parent bd805203f1
commit bbd2d3532a
12 changed files with 535 additions and 1 deletions

View File

@@ -0,0 +1,50 @@
# Generated by Django 5.2.14 on 2026-06-07 01:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("review_agent", "0003_regulatoryartifact_regulatoryissue_and_more"),
]
operations = [
migrations.AddField(
model_name="regulatoryreviewbatch",
name="condition_json",
field=models.JSONField(blank=True, default=dict),
),
migrations.AlterField(
model_name="regulatoryreviewbatch",
name="status",
field=models.CharField(
choices=[
("pending", "待执行"),
("running", "执行中"),
("waiting_user", "等待用户确认"),
("success", "成功"),
("failed", "失败"),
],
default="pending",
max_length=20,
),
),
migrations.AlterField(
model_name="workflownoderun",
name="status",
field=models.CharField(
choices=[
("pending", "等待中"),
("running", "执行中"),
("waiting_user", "等待用户确认"),
("retrying", "重试中"),
("success", "成功"),
("failed", "失败"),
("skipped", "跳过"),
],
default="pending",
max_length=20,
),
),
]

View File

@@ -253,6 +253,7 @@ class WorkflowNodeRun(models.Model):
class Status(models.TextChoices): class Status(models.TextChoices):
PENDING = "pending", "等待中" PENDING = "pending", "等待中"
RUNNING = "running", "执行中" RUNNING = "running", "执行中"
WAITING_USER = "waiting_user", "等待用户确认"
RETRYING = "retrying", "重试中" RETRYING = "retrying", "重试中"
SUCCESS = "success", "成功" SUCCESS = "success", "成功"
FAILED = "failed", "失败" FAILED = "failed", "失败"
@@ -402,6 +403,7 @@ class RegulatoryReviewBatch(models.Model):
class Status(models.TextChoices): class Status(models.TextChoices):
PENDING = "pending", "待执行" PENDING = "pending", "待执行"
RUNNING = "running", "执行中" RUNNING = "running", "执行中"
WAITING_USER = "waiting_user", "等待用户确认"
SUCCESS = "success", "成功" SUCCESS = "success", "成功"
FAILED = "failed", "失败" FAILED = "failed", "失败"
@@ -436,6 +438,7 @@ class RegulatoryReviewBatch(models.Model):
) )
batch_no = models.CharField(max_length=64, unique=True) batch_no = models.CharField(max_length=64, unique=True)
status = models.CharField(max_length=20, choices=Status.choices, default=Status.PENDING) status = models.CharField(max_length=20, choices=Status.choices, default=Status.PENDING)
condition_json = models.JSONField(default=dict, blank=True)
risk_summary = models.JSONField(default=dict, blank=True) risk_summary = models.JSONField(default=dict, blank=True)
work_dir = models.CharField(max_length=500, blank=True, default="") work_dir = models.CharField(max_length=500, blank=True, default="")
error_message = models.TextField(blank=True, default="") error_message = models.TextField(blank=True, default="")

View File

@@ -0,0 +1,81 @@
from __future__ import annotations
from review_agent.models import FileSummaryBatch
OPTION_FIELDS = {
"product_category": ["体外诊断试剂", "医疗器械", "其他"],
"registration_type": ["首次注册", "变更注册", "延续注册"],
"clinical_evaluation_path": ["临床试验", "免临床", "同品种比对", "待确认"],
}
def detect_regulatory_condition_candidates(summary_batch: FileSummaryBatch) -> dict[str, dict[str, object]]:
"""Infers review-scope conditions from the summary batch and file names."""
corpus_parts = [summary_batch.product_name or ""]
for item in summary_batch.items.order_by("file_index"):
corpus_parts.extend([item.directory_level, item.file_name, item.relative_path])
corpus = "\n".join(part for part in corpus_parts if part)
return {
"product_category": {
"label": "产品类别",
"input_type": "select",
"options": OPTION_FIELDS["product_category"],
"suggested": _detect_product_category(corpus),
},
"registration_type": {
"label": "注册类型",
"input_type": "select",
"options": OPTION_FIELDS["registration_type"],
"suggested": _detect_registration_type(corpus),
},
"clinical_evaluation_path": {
"label": "临床评价路径",
"input_type": "select",
"options": OPTION_FIELDS["clinical_evaluation_path"],
"suggested": _detect_clinical_path(corpus),
},
"product_name": {
"label": "产品名称",
"input_type": "text",
"suggested": summary_batch.product_name or "",
},
"model_spec": {
"label": "型号规格",
"input_type": "text",
"suggested": "",
},
"intended_use": {
"label": "预期用途",
"input_type": "text",
"suggested": "",
},
}
def _detect_product_category(corpus: str) -> str:
if any(keyword in corpus for keyword in ["体外诊断", "检测试剂", "试剂盒", "IVD"]):
return "体外诊断试剂"
if "医疗器械" in corpus:
return "医疗器械"
return "其他"
def _detect_registration_type(corpus: str) -> str:
if "延续" in corpus:
return "延续注册"
if "变更" in corpus:
return "变更注册"
return "首次注册"
def _detect_clinical_path(corpus: str) -> str:
if "免临床" in corpus or "免于临床" in corpus:
return "免临床"
if "同品种" in corpus or "同类" in corpus:
return "同品种比对"
if "临床试验" in corpus:
return "临床试验"
return "待确认"

View File

@@ -1,10 +1,15 @@
from __future__ import annotations from __future__ import annotations
import json
from django.conf import settings
from django.http import Http404, JsonResponse from django.http import Http404, JsonResponse
from django.views.decorators.http import require_http_methods from django.views.decorators.http import require_http_methods
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from review_agent.models import RegulatoryReviewBatch, WorkflowNodeRun from review_agent.models import RegulatoryReviewBatch, WorkflowNodeRun
from review_agent.regulatory_review.events import record_event
from review_agent.regulatory_review.workflow import start_regulatory_review_workflow
@require_http_methods(["GET"]) @require_http_methods(["GET"])
@@ -43,6 +48,59 @@ def batch_status(request, batch_id: int):
) )
@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,
}
}
)
def _format_risk_summary(risk_summary: dict) -> str: def _format_risk_summary(risk_summary: dict) -> str:
labels = [ labels = [
("blocking", "阻断项"), ("blocking", "阻断项"),
@@ -56,3 +114,15 @@ def _format_risk_summary(risk_summary: dict) -> str:
for key, label in labels for key, label in labels
if int(risk_summary.get(key) or 0) 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}

View File

@@ -19,6 +19,7 @@ from review_agent.models import (
from review_agent.regulatory_review.services.completeness_check import run_completeness_check from review_agent.regulatory_review.services.completeness_check import run_completeness_check
from review_agent.regulatory_review.services.consistency_check import run_consistency_check from review_agent.regulatory_review.services.consistency_check import run_consistency_check
from review_agent.regulatory_review.services.export import build_assistant_summary, export_review_results from review_agent.regulatory_review.services.export import build_assistant_summary, export_review_results
from review_agent.regulatory_review.services.info_extract import detect_regulatory_condition_candidates
from review_agent.regulatory_review.services.risk_assess import persist_findings from review_agent.regulatory_review.services.risk_assess import persist_findings
from review_agent.regulatory_review.services.rule_loader import load_rule_file from review_agent.regulatory_review.services.rule_loader import load_rule_file
from review_agent.regulatory_review.services.structure_check import run_structure_check from review_agent.regulatory_review.services.structure_check import run_structure_check
@@ -29,6 +30,7 @@ from .events import record_event
NODE_DEFINITIONS = [ NODE_DEFINITIONS = [
("prepare", "准备", "prepare"), ("prepare", "准备", "prepare"),
("condition_confirm", "适用条件确认", "condition_confirm"),
("rule_scope", "规则范围", "rule_scope"), ("rule_scope", "规则范围", "rule_scope"),
("completeness_check", "完整性核查", "completeness_check"), ("completeness_check", "完整性核查", "completeness_check"),
("text_extract", "文本抽取", "text_extract"), ("text_extract", "文本抽取", "text_extract"),
@@ -43,6 +45,10 @@ NODE_DEFINITIONS = [
logger = logging.getLogger("review_agent.regulatory_review.workflow") logger = logging.getLogger("review_agent.regulatory_review.workflow")
class WorkflowPausedForUser(Exception):
pass
def build_batch_no() -> str: def build_batch_no() -> str:
return f"RR-{timezone.localtime().strftime('%Y%m%d%H%M%S')}-{uuid4().hex[:6]}" return f"RR-{timezone.localtime().strftime('%Y%m%d%H%M%S')}-{uuid4().hex[:6]}"
@@ -108,7 +114,11 @@ class RegulatoryWorkflowExecutor:
try: try:
for node in self._nodes(): for node in self._nodes():
if node.status == WorkflowNodeRun.Status.SUCCESS:
continue
self._run_node(node) self._run_node(node)
except WorkflowPausedForUser:
return
except Exception as exc: except Exception as exc:
logger.exception("Regulatory workflow failed", extra={"batch_id": self.batch.pk}) logger.exception("Regulatory workflow failed", extra={"batch_id": self.batch.pk})
self.batch.status = RegulatoryReviewBatch.Status.FAILED self.batch.status = RegulatoryReviewBatch.Status.FAILED
@@ -155,6 +165,9 @@ class RegulatoryWorkflowExecutor:
) )
def _execute_node(self, node_code: str) -> None: def _execute_node(self, node_code: str) -> None:
if node_code == "condition_confirm":
self._pause_for_condition_confirmation()
return
if node_code == "rule_scope": if node_code == "rule_scope":
self.rule_set = load_rule_file() self.rule_set = load_rule_file()
return return
@@ -181,6 +194,34 @@ class RegulatoryWorkflowExecutor:
content=build_assistant_summary(self.batch, exports), content=build_assistant_summary(self.batch, exports),
) )
def _pause_for_condition_confirmation(self) -> None:
if self.batch.condition_json.get("confirmed"):
return
candidates = detect_regulatory_condition_candidates(self.batch.source_summary_batch)
self.batch.condition_json = {
**(self.batch.condition_json or {}),
"confirmed": False,
"resume_from": "rule_scope",
"candidates": candidates,
}
self.batch.status = RegulatoryReviewBatch.Status.WAITING_USER
self.batch.save(update_fields=["status", "condition_json"])
node = WorkflowNodeRun.objects.get(
workflow_type="regulatory_review",
workflow_batch_id=self.batch.pk,
node_code="condition_confirm",
)
node.status = WorkflowNodeRun.Status.WAITING_USER
node.progress = 50
node.message = "请确认产品类别、注册类型、临床评价路径等适用条件"
node.save(update_fields=["status", "progress", "message"])
record_event(
self.batch,
"waiting_user",
{"node_code": "condition_confirm", "candidates": candidates, "resume_from": "rule_scope"},
)
raise WorkflowPausedForUser()
def _rules(self) -> dict: def _rules(self) -> dict:
if self.rule_set is None: if self.rule_set is None:
self.rule_set = load_rule_file() self.rule_set = load_rule_file()

View File

@@ -10,7 +10,10 @@ from .file_summary.views import (
conversation_messages, conversation_messages,
export_download, export_download,
) )
from .regulatory_review.views import batch_status as regulatory_review_batch_status from .regulatory_review.views import (
batch_status as regulatory_review_batch_status,
confirm_conditions as regulatory_review_confirm_conditions,
)
urlpatterns = [ urlpatterns = [
@@ -64,4 +67,9 @@ urlpatterns = [
regulatory_review_batch_status, regulatory_review_batch_status,
name="regulatory_review_batch_status", name="regulatory_review_batch_status",
), ),
path(
"api/review-agent/regulatory-review/<int:batch_id>/conditions/",
regulatory_review_confirm_conditions,
name="regulatory_review_confirm_conditions",
),
] ]

View File

@@ -138,6 +138,8 @@ def build_workflow_cards(conversation: Conversation) -> list[dict[str, object]]:
"status": batch.status, "status": batch.status,
"error_message": batch.error_message, "error_message": batch.error_message,
"risk_label": _format_risk_label(batch.risk_summary or {}), "risk_label": _format_risk_label(batch.risk_summary or {}),
"condition_json": batch.condition_json or {},
"condition_candidates": (batch.condition_json or {}).get("candidates") or {},
"created_at": batch.created_at, "created_at": batch.created_at,
"nodes": list( "nodes": list(
WorkflowNodeRun.objects.filter( WorkflowNodeRun.objects.filter(

View File

@@ -796,6 +796,60 @@
}); });
} }
function bindConditionConfirmForms() {
document.querySelectorAll("[data-condition-confirm-form]").forEach(function (form) {
if (form.dataset.bound === "true") {
return;
}
form.dataset.bound = "true";
form.addEventListener("submit", async function (event) {
event.preventDefault();
var batchId = form.getAttribute("data-batch-id");
var status = form.querySelector("[data-condition-confirm-status]");
var submitButton = form.querySelector('button[type="submit"]');
var formData = new FormData(form);
var conditions = {};
formData.forEach(function (value, key) {
if (key !== "csrfmiddlewaretoken") {
conditions[key] = value;
}
});
if (submitButton) {
submitButton.disabled = true;
}
if (status) {
status.textContent = "正在恢复法规核查...";
}
try {
var response = await fetch(form.getAttribute("data-confirm-url"), {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": formData.get("csrfmiddlewaretoken"),
},
body: JSON.stringify({ conditions: conditions }),
});
if (!response.ok) {
throw new Error("确认失败。");
}
if (status) {
status.textContent = "已确认,工作流继续执行。";
}
form.classList.add("confirmed");
startWorkflowPolling(batchId);
await refreshWorkflowCard(batchId, "regulatory_review");
} catch (error) {
if (status) {
status.textContent = "确认失败,请稍后重试。";
}
if (submitButton) {
submitButton.disabled = false;
}
}
});
});
}
async function streamChat(event) { async function streamChat(event) {
event.preventDefault(); event.preventDefault();
if (!composer || !promptInput || !sendButton || !chatStage) { if (!composer || !promptInput || !sendButton || !chatStage) {
@@ -955,6 +1009,7 @@
renderExistingAssistantMessages(); renderExistingAssistantMessages();
refreshWorkflowBatchCarousel(0); refreshWorkflowBatchCarousel(0);
bindWorkflowBatchCarouselControls(); bindWorkflowBatchCarouselControls();
bindConditionConfirmForms();
refreshRunningWorkflowCards(); refreshRunningWorkflowCards();
if (chatScroll) { if (chatScroll) {

View File

@@ -240,6 +240,33 @@
{% if batch.error_message %} {% if batch.error_message %}
<p class="workflow-error">{{ batch.error_message }}</p> <p class="workflow-error">{{ batch.error_message }}</p>
{% endif %} {% endif %}
{% if batch.workflow_type == "regulatory_review" and batch.status == "waiting_user" and batch.condition_candidates %}
<form
class="condition-confirm-form"
data-condition-confirm-form
data-batch-id="{{ batch.id }}"
data-confirm-url="/api/review-agent/regulatory-review/{{ batch.id }}/conditions/"
>
{% csrf_token %}
<strong>适用条件确认</strong>
{% for field, config in batch.condition_candidates.items %}
<label>
<span>{{ config.label }}</span>
{% if config.input_type == "select" %}
<select name="{{ field }}">
{% for option in config.options %}
<option value="{{ option }}"{% if option == config.suggested %} selected{% endif %}>{{ option }}</option>
{% endfor %}
</select>
{% else %}
<input type="text" name="{{ field }}" value="{{ config.suggested|default:'' }}">
{% endif %}
</label>
{% endfor %}
<button type="submit">确认并继续</button>
<p class="condition-confirm-status" data-condition-confirm-status></p>
</form>
{% endif %}
<ol> <ol>
{% for node in batch.nodes %} {% for node in batch.nodes %}
<li class="node-status status-{{ node.status }}" data-node-code="{{ node.node_code }}"> <li class="node-status status-{{ node.status }}" data-node-code="{{ node.node_code }}">

View File

@@ -0,0 +1,139 @@
import json
import pytest
from django.urls import reverse
from review_agent.models import (
Conversation,
FileSummaryBatch,
FileSummaryItem,
RegulatoryReviewBatch,
WorkflowEvent,
WorkflowNodeRun,
)
from review_agent.regulatory_review.services.info_extract import detect_regulatory_condition_candidates
from review_agent.regulatory_review.workflow import (
create_regulatory_review_batch,
start_regulatory_review_workflow,
)
pytestmark = pytest.mark.django_db
def test_detect_regulatory_condition_candidates_from_summary_items(django_user_model):
user = django_user_model.objects.create_user(username="owner", password="pass")
conversation = Conversation.objects.create(user=user, title="会话")
summary = FileSummaryBatch.objects.create(
conversation=conversation,
user=user,
batch_no="FS-COND",
status=FileSummaryBatch.Status.SUCCESS,
product_name="甲胎蛋白检测试剂盒",
)
FileSummaryItem.objects.create(
batch=summary,
file_index=1,
directory_level="临床评价资料",
file_name="免临床评价资料.docx",
file_type="docx",
relative_path="4.临床评价资料/免临床评价资料.docx",
storage_path="missing.docx",
)
candidates = detect_regulatory_condition_candidates(summary)
assert candidates["product_category"]["suggested"] == "体外诊断试剂"
assert candidates["registration_type"]["suggested"] == "首次注册"
assert candidates["clinical_evaluation_path"]["suggested"] == "免临床"
assert candidates["product_name"]["suggested"] == "甲胎蛋白检测试剂盒"
def test_workflow_pauses_before_rule_scope_until_conditions_confirmed(settings, tmp_path, django_user_model):
settings.MEDIA_ROOT = tmp_path
user = django_user_model.objects.create_user(username="owner", password="pass")
conversation = Conversation.objects.create(user=user, title="会话")
summary = FileSummaryBatch.objects.create(
conversation=conversation,
user=user,
batch_no="FS-COND",
status=FileSummaryBatch.Status.SUCCESS,
product_name="甲胎蛋白检测试剂盒",
)
batch = create_regulatory_review_batch(
conversation=conversation,
user=user,
source_summary_batch=summary,
)
start_regulatory_review_workflow(batch, async_run=False)
batch.refresh_from_db()
condition_node = WorkflowNodeRun.objects.get(
workflow_type="regulatory_review",
workflow_batch_id=batch.pk,
node_code="condition_confirm",
)
rule_scope_node = WorkflowNodeRun.objects.get(
workflow_type="regulatory_review",
workflow_batch_id=batch.pk,
node_code="rule_scope",
)
assert batch.status == RegulatoryReviewBatch.Status.WAITING_USER
assert condition_node.status == WorkflowNodeRun.Status.WAITING_USER
assert rule_scope_node.status == WorkflowNodeRun.Status.PENDING
assert batch.condition_json["candidates"]["product_category"]["suggested"] == "体外诊断试剂"
assert WorkflowEvent.objects.filter(
workflow_type="regulatory_review",
workflow_batch_id=batch.pk,
event_type="waiting_user",
).exists()
def test_confirm_conditions_endpoint_resumes_workflow(client, settings, tmp_path, django_user_model):
settings.MEDIA_ROOT = tmp_path
settings.REGULATORY_REVIEW_ASYNC = False
user = django_user_model.objects.create_user(username="owner", password="pass")
conversation = Conversation.objects.create(user=user, title="会话")
summary = FileSummaryBatch.objects.create(
conversation=conversation,
user=user,
batch_no="FS-COND",
status=FileSummaryBatch.Status.SUCCESS,
product_name="甲胎蛋白检测试剂盒",
)
batch = create_regulatory_review_batch(
conversation=conversation,
user=user,
source_summary_batch=summary,
)
start_regulatory_review_workflow(batch, async_run=False)
client.force_login(user)
response = client.post(
reverse("regulatory_review_confirm_conditions", args=[batch.pk]),
data=json.dumps(
{
"conditions": {
"product_category": "体外诊断试剂",
"registration_type": "首次注册",
"clinical_evaluation_path": "免临床",
"product_name": "甲胎蛋白检测试剂盒",
"model_spec": "卡型",
"intended_use": "用于甲胎蛋白检测",
}
}
),
content_type="application/json",
)
batch.refresh_from_db()
assert response.status_code == 200
assert response.json()["batch"]["status"] == RegulatoryReviewBatch.Status.SUCCESS
assert batch.condition_json["confirmed"] is True
assert batch.condition_json["confirmed_conditions"]["model_spec"] == "卡型"
assert WorkflowNodeRun.objects.get(
workflow_type="regulatory_review",
workflow_batch_id=batch.pk,
node_code="condition_confirm",
).status == WorkflowNodeRun.Status.SUCCESS

View File

@@ -50,9 +50,63 @@ def test_workspace_renders_regulatory_workflow_card(client, django_user_model):
assert "data-regulatory-status-url-template" in content assert "data-regulatory-status-url-template" in content
def test_workspace_renders_condition_confirmation_form(client, django_user_model):
user = django_user_model.objects.create_user(username="owner", password="pass")
conversation = Conversation.objects.create(user=user, title="会话")
summary = FileSummaryBatch.objects.create(
conversation=conversation,
user=user,
batch_no="FS-OK",
status=FileSummaryBatch.Status.SUCCESS,
)
regulatory = RegulatoryReviewBatch.objects.create(
conversation=conversation,
user=user,
source_summary_batch=summary,
batch_no="RR-WAIT",
status=RegulatoryReviewBatch.Status.WAITING_USER,
condition_json={
"confirmed": False,
"candidates": {
"product_category": {
"label": "产品类别",
"input_type": "select",
"options": ["体外诊断试剂", "医疗器械", "其他"],
"suggested": "体外诊断试剂",
},
"product_name": {
"label": "产品名称",
"input_type": "text",
"suggested": "甲胎蛋白检测试剂盒",
},
},
},
)
WorkflowNodeRun.objects.create(
workflow_type="regulatory_review",
workflow_batch_id=regulatory.pk,
node_group="condition_confirm",
node_code="condition_confirm",
node_name="适用条件确认",
status=WorkflowNodeRun.Status.WAITING_USER,
progress=50,
)
client.force_login(user)
response = client.get(f"{reverse('home')}?conversation={conversation.pk}")
content = response.content.decode("utf-8")
assert "适用条件确认" in content
assert "data-condition-confirm-form" in content
assert "体外诊断试剂" in content
assert "甲胎蛋白检测试剂盒" in content
def test_frontend_selects_status_url_by_workflow_type(): def test_frontend_selects_status_url_by_workflow_type():
script = open("static/js/app.js", encoding="utf-8").read() script = open("static/js/app.js", encoding="utf-8").read()
assert "workflow_type" in script assert "workflow_type" in script
assert "data-regulatory-status-url-template" in script assert "data-regulatory-status-url-template" in script
assert "statusUrlForWorkflow" in script assert "statusUrlForWorkflow" in script
assert "bindConditionConfirmForms" in script
assert "data-condition-confirm-form" in script

View File

@@ -102,6 +102,8 @@ def test_start_regulatory_review_workflow_runs_synchronously(django_user_model):
user=user, user=user,
source_summary_batch=summary, source_summary_batch=summary,
) )
batch.condition_json = {"confirmed": True, "confirmed_conditions": {"product_category": "体外诊断试剂"}}
batch.save(update_fields=["condition_json"])
start_regulatory_review_workflow(batch, async_run=False) start_regulatory_review_workflow(batch, async_run=False)
@@ -187,6 +189,8 @@ def test_workflow_generates_issues_exports_and_assistant_summary(settings, tmp_p
user=user, user=user,
source_summary_batch=summary, source_summary_batch=summary,
) )
batch.condition_json = {"confirmed": True, "confirmed_conditions": {"product_category": "体外诊断试剂"}}
batch.save(update_fields=["condition_json"])
start_regulatory_review_workflow(batch, async_run=False) start_regulatory_review_workflow(batch, async_run=False)