feat(regulatory): 增加法规规则版本检查

This commit is contained in:
2026-06-07 00:26:19 +08:00
parent f52dcc197d
commit 2a4dd6cfab
9 changed files with 264 additions and 0 deletions

View File

@@ -0,0 +1,106 @@
from __future__ import annotations
import hashlib
from dataclasses import dataclass
from pathlib import Path
import yaml
from django.conf import settings
from review_agent.models import RegulatoryRuleVersion
DEFAULT_RULE_CODE = "nmpa_ivd_registration_v1"
DEFAULT_RULE_PATH = (
Path(settings.BASE_DIR)
/ "review_agent"
/ "regulatory_review"
/ "rules"
/ "nmpa_ivd_registration_v1.yaml"
)
@dataclass(frozen=True)
class RuleVersionCheck:
status: str
code: str
path: Path
current_hash: str
database_hash: str = ""
record: RegulatoryRuleVersion | None = None
def compute_file_sha256(path: str | Path) -> str:
file_path = Path(path)
digest = hashlib.sha256()
with file_path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def load_rule_file(path: str | Path | None = None) -> dict:
rule_path = Path(path) if path else DEFAULT_RULE_PATH
with rule_path.open("r", encoding="utf-8") as handle:
payload = yaml.safe_load(handle) or {}
if payload.get("code") != DEFAULT_RULE_CODE:
raise ValueError(f"规则 code 必须为 {DEFAULT_RULE_CODE}")
if not isinstance(payload.get("requirements"), list) or not payload["requirements"]:
raise ValueError("规则文件必须包含 requirements 列表。")
return payload
def check_rule_version(
*,
path: str | Path | None = None,
update_missing: bool = True,
) -> RuleVersionCheck:
rule_path = Path(path) if path else DEFAULT_RULE_PATH
rule_set = load_rule_file(rule_path)
current_hash = compute_file_sha256(rule_path)
record = RegulatoryRuleVersion.objects.filter(code=rule_set["code"]).first()
yaml_path = str(rule_path.relative_to(settings.BASE_DIR))
if record is None:
if not update_missing:
return RuleVersionCheck(
status="missing",
code=rule_set["code"],
path=rule_path,
current_hash=current_hash,
)
record = RegulatoryRuleVersion.objects.create(
code=rule_set["code"],
name=rule_set.get("name") or rule_set["code"],
yaml_path=yaml_path,
yaml_hash=current_hash,
rag_collection=rule_set.get("rag_collection", ""),
status=RegulatoryRuleVersion.Status.ACTIVE,
)
return RuleVersionCheck(
status="created",
code=record.code,
path=rule_path,
current_hash=current_hash,
database_hash=record.yaml_hash,
record=record,
)
if record.yaml_hash != current_hash:
return RuleVersionCheck(
status="mismatch",
code=record.code,
path=rule_path,
current_hash=current_hash,
database_hash=record.yaml_hash,
record=record,
)
return RuleVersionCheck(
status="ok",
code=record.code,
path=rule_path,
current_hash=current_hash,
database_hash=record.yaml_hash,
record=record,
)