feat(regulatory): 增加整改复核闭环
This commit is contained in:
@@ -46,12 +46,19 @@ def build_markdown_report(batch: RegulatoryReviewBatch) -> str:
|
||||
"# NMPA 注册资料法规核查报告",
|
||||
"",
|
||||
f"批次号:{batch.batch_no}",
|
||||
"",
|
||||
"## 风险汇总",
|
||||
"",
|
||||
"| 风险等级 | 数量 |",
|
||||
"| --- | --- |",
|
||||
]
|
||||
regenerated_from = (batch.condition_json or {}).get("regenerated_from")
|
||||
if regenerated_from:
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
"## 复核来源",
|
||||
"",
|
||||
f"- 来源法规核查批次:{regenerated_from.get('batch_no')}",
|
||||
f"- 来源文件汇总批次:{regenerated_from.get('file_summary_batch_no')}",
|
||||
]
|
||||
)
|
||||
lines.extend(["", "## 风险汇总", "", "| 风险等级 | 数量 |", "| --- | --- |"])
|
||||
summary = batch.risk_summary or {}
|
||||
for severity, label in SEVERITY_LABELS.items():
|
||||
lines.append(f"| {label} | {summary.get(severity, 0)} |")
|
||||
@@ -60,6 +67,14 @@ def build_markdown_report(batch: RegulatoryReviewBatch) -> str:
|
||||
lines.append(
|
||||
f"| {SEVERITY_LABELS.get(issue.severity, issue.severity)} | {issue.title} | {issue.status} | {issue.suggestion or '-'} |"
|
||||
)
|
||||
review_records = _review_records(batch)
|
||||
if review_records:
|
||||
lines.extend(["", "## 复核记录", "", "| 补充批次 | 问题数 | 通过数 | 未通过数 |", "| --- | --- | --- | --- |"])
|
||||
for record in review_records:
|
||||
items = record.get("items", [])
|
||||
passed = sum(1 for item in items if item.get("status") == RegulatoryIssue.Status.REVIEW_PASSED)
|
||||
failed = sum(1 for item in items if item.get("status") == RegulatoryIssue.Status.REVIEW_FAILED)
|
||||
lines.append(f"| {record.get('file_summary_batch_no')} | {len(items)} | {passed} | {failed} |")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
@@ -67,6 +82,7 @@ def build_result_payload(batch: RegulatoryReviewBatch) -> dict[str, object]:
|
||||
return {
|
||||
"batch_no": batch.batch_no,
|
||||
"source_summary_batch": batch.source_summary_batch.batch_no,
|
||||
"regenerated_from": (batch.condition_json or {}).get("regenerated_from"),
|
||||
"risk_summary": batch.risk_summary,
|
||||
"issues": [
|
||||
{
|
||||
@@ -82,6 +98,7 @@ def build_result_payload(batch: RegulatoryReviewBatch) -> dict[str, object]:
|
||||
}
|
||||
for issue in batch.issues.order_by("id")
|
||||
],
|
||||
"review_records": _review_records(batch),
|
||||
}
|
||||
|
||||
|
||||
@@ -165,3 +182,13 @@ def _create_excel_export(batch: RegulatoryReviewBatch, path: Path) -> ExportedSu
|
||||
file_name=path.name,
|
||||
storage_path=str(path),
|
||||
)
|
||||
|
||||
|
||||
def _review_records(batch: RegulatoryReviewBatch) -> list[dict[str, object]]:
|
||||
records = []
|
||||
for artifact in batch.artifacts.filter(metadata__artifact="review_record").order_by("created_at", "id"):
|
||||
try:
|
||||
records.append(json.loads(Path(artifact.storage_path).read_text(encoding="utf-8")))
|
||||
except (OSError, json.JSONDecodeError):
|
||||
continue
|
||||
return records
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from django.utils import timezone
|
||||
|
||||
from review_agent.models import FileSummaryBatch, RegulatoryIssue, RegulatoryReviewBatch
|
||||
from review_agent.regulatory_review.services.rule_loader import load_rule_file
|
||||
from review_agent.regulatory_review.storage import save_artifact
|
||||
|
||||
|
||||
def review_missing_issues(
|
||||
*,
|
||||
batch: RegulatoryReviewBatch,
|
||||
issue_ids: list[int],
|
||||
file_summary_batch: FileSummaryBatch,
|
||||
) -> dict[str, object]:
|
||||
rule_set = load_rule_file()
|
||||
rules_by_code = {rule["code"]: rule for rule in rule_set.get("requirements", [])}
|
||||
items = list(file_summary_batch.items.order_by("file_index"))
|
||||
record = {
|
||||
"type": "review_record",
|
||||
"reviewed_at": timezone.localtime().isoformat(),
|
||||
"source_review_batch_id": batch.pk,
|
||||
"source_review_batch_no": batch.batch_no,
|
||||
"file_summary_batch_id": file_summary_batch.pk,
|
||||
"file_summary_batch_no": file_summary_batch.batch_no,
|
||||
"items": [],
|
||||
}
|
||||
issues = RegulatoryIssue.objects.filter(batch=batch, pk__in=issue_ids).order_by("id")
|
||||
for issue in issues:
|
||||
rule = rules_by_code.get(issue.rule_code, {})
|
||||
matched_files = _match_items(items, [*rule.get("file_keywords", []), issue.title])
|
||||
passed = bool(matched_files)
|
||||
issue.status = RegulatoryIssue.Status.REVIEW_PASSED if passed else RegulatoryIssue.Status.REVIEW_FAILED
|
||||
issue.evidence = {
|
||||
**(issue.evidence or {}),
|
||||
"latest_review": {
|
||||
"file_summary_batch_id": file_summary_batch.pk,
|
||||
"file_summary_batch_no": file_summary_batch.batch_no,
|
||||
"matched_files": matched_files,
|
||||
},
|
||||
}
|
||||
issue.save(update_fields=["status", "evidence", "updated_at"])
|
||||
record["items"].append(
|
||||
{
|
||||
"issue_id": issue.pk,
|
||||
"rule_code": issue.rule_code,
|
||||
"title": issue.title,
|
||||
"status": issue.status,
|
||||
"matched_files": matched_files,
|
||||
}
|
||||
)
|
||||
artifact = save_artifact(
|
||||
batch,
|
||||
name=f"review_record_{timezone.now().strftime('%Y%m%d%H%M%S')}.json",
|
||||
artifact_type="json",
|
||||
content=json.dumps(record, ensure_ascii=False, indent=2),
|
||||
metadata={"artifact": "review_record", "file_summary_batch_id": file_summary_batch.pk},
|
||||
)
|
||||
record["artifact_id"] = artifact.pk
|
||||
return record
|
||||
|
||||
|
||||
def _match_items(items, keywords: list[str]) -> list[dict[str, str]]:
|
||||
normalized_keywords = [str(keyword).lower() for keyword in keywords if keyword]
|
||||
matched = []
|
||||
for item in items:
|
||||
haystack = f"{item.file_name} {item.relative_path} {item.directory_level}".lower()
|
||||
if any(keyword in haystack for keyword in normalized_keywords):
|
||||
matched.append(
|
||||
{
|
||||
"file_name": item.file_name,
|
||||
"relative_path": item.relative_path,
|
||||
"directory_level": item.directory_level,
|
||||
}
|
||||
)
|
||||
return matched
|
||||
Reference in New Issue
Block a user