Files
group_xinghuo_jinrong/scripts/seed/import_compliance_rules.py
T

178 lines
5.4 KiB
Python

"""Import compliance rules from the Sprint 1 Markdown dataset."""
from __future__ import annotations
import sys
from dataclasses import dataclass
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from app.config.database import AgentSessionLocal
from app.model.entities import ComplianceRule
DEFAULT_DATASET = ROOT / "docs" / "开发文档" / "20-Sprint1首批合规规则数据集.md"
ALLOWED_RULE_TYPES = {"keyword", "regex", "semantic"}
ALLOWED_SEVERITIES = {"block", "warn", "info"}
REQUIRED_HEADERS = [
"序号",
"rule_type",
"pattern",
"severity",
"category",
"suggestion",
"is_active",
"priority",
"审核状态",
"备注",
]
@dataclass(frozen=True)
class SeedComplianceRule:
rule_code: str
rule_type: str
pattern: str
severity: str
category: str
suggestion: str | None
is_active: bool
priority: int
review_status: str
note: str
@dataclass(frozen=True)
class ImportResult:
total: int
created: int
updated: int
def load_rules_from_markdown(path: Path = DEFAULT_DATASET) -> list[SeedComplianceRule]:
rows = _read_markdown_table(path)
rules: list[SeedComplianceRule] = []
for row in rows:
sequence = int(row["序号"])
rule_type = row["rule_type"].lower()
severity = row["severity"].lower()
priority = int(row["priority"])
pattern = row["pattern"].strip()
category = row["category"].strip()
suggestion = row["suggestion"].strip() or None
note = row["备注"].strip()
if rule_type not in ALLOWED_RULE_TYPES:
raise ValueError(f"Unsupported rule_type at row {sequence}: {rule_type}")
if severity not in ALLOWED_SEVERITIES:
raise ValueError(f"Unsupported severity at row {sequence}: {severity}")
if not pattern:
raise ValueError(f"Missing pattern at row {sequence}")
if not category:
raise ValueError(f"Missing category at row {sequence}")
if priority < 1:
raise ValueError(f"Priority must be positive at row {sequence}")
rules.append(
SeedComplianceRule(
rule_code=f"CR-TEST-{sequence:03d}",
rule_type=rule_type,
pattern=pattern,
severity=severity,
category=category,
suggestion=suggestion,
is_active=_parse_bool(row["is_active"]),
priority=priority,
review_status=_normalize_review_status(row["审核状态"], note),
note=note,
)
)
return rules
def import_rules_from_markdown(
path: Path = DEFAULT_DATASET,
*,
actor_id: str = "seed:test_data",
) -> ImportResult:
rules = load_rules_from_markdown(path)
created = 0
updated = 0
with AgentSessionLocal() as session:
for seed_rule in rules:
existing = session.query(ComplianceRule).filter(ComplianceRule.rule_code == seed_rule.rule_code).one_or_none()
values = {
"rule_type": seed_rule.rule_type,
"pattern": seed_rule.pattern,
"severity": seed_rule.severity,
"category": seed_rule.category,
"suggestion": seed_rule.suggestion,
"is_active": seed_rule.is_active,
"priority": seed_rule.priority,
"updated_by": actor_id,
}
if existing is None:
session.add(ComplianceRule(rule_code=seed_rule.rule_code, created_by=actor_id, **values))
created += 1
else:
for key, value in values.items():
setattr(existing, key, value)
updated += 1
session.commit()
return ImportResult(total=len(rules), created=created, updated=updated)
def _read_markdown_table(path: Path) -> list[dict[str, str]]:
if not path.exists():
raise FileNotFoundError(path)
table_lines = [
line.strip()
for line in path.read_text(encoding="utf-8").splitlines()
if line.strip().startswith("|") and line.strip().endswith("|")
]
if len(table_lines) < 3:
raise ValueError("Markdown table is missing or empty")
headers = _split_markdown_row(table_lines[0])
if headers != REQUIRED_HEADERS:
raise ValueError(f"Unexpected table headers: {headers}")
rows: list[dict[str, str]] = []
for line in table_lines[2:]:
cells = _split_markdown_row(line)
if len(cells) != len(headers):
raise ValueError(f"Column count mismatch: {line}")
rows.append(dict(zip(headers, cells, strict=True)))
return rows
def _split_markdown_row(line: str) -> list[str]:
return [cell.strip() for cell in line.strip().strip("|").split("|")]
def _parse_bool(value: str) -> bool:
normalized = value.strip().lower()
if normalized == "true":
return True
if normalized == "false":
return False
raise ValueError(f"Expected true/false, got: {value}")
def _normalize_review_status(value: str, note: str) -> str:
if "已审核" in value:
return "approved"
if "测试数据" in value or "测试数据" in note:
return "test_data"
return "pending"
if __name__ == "__main__":
result = import_rules_from_markdown()
print(f"Imported compliance rules: total={result.total}, created={result.created}, updated={result.updated}")