Files
group_xinghuo_jinrong/scripts/seed/import_script_templates.py
T
zhanghongyu_0626 70aa861983 feat(advisor-agent): Introduce advisor agent functionalities with compliance, KYC, and script templates
- Added new modules for advisor compliance, KYC sessions, and script templates, enhancing the advisor agent's capabilities.
- Implemented a comprehensive API structure under the `/api/advisor-agent` prefix, ensuring clear organization and access to new features.
- Established database models and repositories for compliance rules and KYC sessions, facilitating robust data management.
- Integrated exception handling and response models to improve error management and user feedback.
- Updated settings to include new configurations for compliance and KYC features, ensuring flexibility and adaptability.

This update significantly expands the advisor agent's functionality, providing essential tools for compliance and customer interaction while maintaining a structured API design.
2026-09-12 16:33:07 +08:00

223 lines
7.0 KiB
Python

"""Import script templates from the Sprint 2 Markdown dataset."""
from __future__ import annotations
import argparse
import sys
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from app.advisor_db import AgentSessionLocal
from app.model.entities_advisor import ScriptTemplate
from app.model.advisor_schemas import ComplianceCheckRequest
from app.service.compliance_check_service import ComplianceCheckService
DEFAULT_DATASET = ROOT / "docs" / "开发文档" / "29-Sprint2首批话术模板数据集.md"
ACTOR_ID = "seed:template_test_data"
REQUIRED_HEADERS = [
"序号",
"scene",
"customer_type",
"title",
"content",
"tags",
"is_active",
"审核状态",
"审核人",
"备注",
]
@dataclass(frozen=True)
class SeedScriptTemplate:
sequence: int
scene: str
customer_type: str | None
title: str
content: str
tags: list[str]
is_active: bool
review_status: str
reviewer: str | None
note: str
@property
def is_approved(self) -> bool:
return self.review_status == "approved"
@dataclass(frozen=True)
class ImportResult:
total: int
created: int
updated: int
def load_templates_from_markdown(path: Path = DEFAULT_DATASET) -> list[SeedScriptTemplate]:
rows = _read_markdown_table(path)
templates: list[SeedScriptTemplate] = []
for row in rows:
sequence = int(row["序号"])
scene = row["scene"].strip()
title = row["title"].strip()
content = row["content"].strip()
if not scene:
raise ValueError(f"Missing scene at row {sequence}")
if not title:
raise ValueError(f"Missing title at row {sequence}")
if not content:
raise ValueError(f"Missing content at row {sequence}")
templates.append(
SeedScriptTemplate(
sequence=sequence,
scene=scene,
customer_type=row["customer_type"].strip() or None,
title=title,
content=content,
tags=_parse_tags(row["tags"]),
is_active=_parse_bool(row["is_active"]),
review_status=_normalize_review_status(row["审核状态"], row["备注"]),
reviewer=row["审核人"].strip() or None,
note=row["备注"].strip(),
)
)
return templates
def import_templates_from_markdown(
path: Path = DEFAULT_DATASET,
*,
actor_id: str = ACTOR_ID,
) -> ImportResult:
templates = load_templates_from_markdown(path)
compliance_service = ComplianceCheckService()
created = 0
updated = 0
with AgentSessionLocal() as session:
for seed_template in templates:
check_result = compliance_service.check_text(
ComplianceCheckRequest(text=seed_template.content, scene=seed_template.scene)
)
if check_result.risk_level == "BLOCK":
raise ValueError(f"Template row {seed_template.sequence} failed BLOCK compliance check")
existing = (
session.query(ScriptTemplate)
.filter(
ScriptTemplate.created_by == actor_id,
ScriptTemplate.scene == seed_template.scene,
ScriptTemplate.title == seed_template.title,
)
.one_or_none()
)
values = {
"customer_type": seed_template.customer_type,
"content": seed_template.content,
"tags": seed_template.tags,
"is_active": seed_template.is_active,
"is_approved": seed_template.is_approved,
"approved_by": seed_template.reviewer if seed_template.is_approved else None,
"approved_at": datetime.now(timezone.utc).replace(tzinfo=None) if seed_template.is_approved else None,
"updated_by": actor_id,
}
if existing is None:
session.add(
ScriptTemplate(
scene=seed_template.scene,
title=seed_template.title,
created_by=actor_id,
version=1,
usage_count=0,
**values,
)
)
created += 1
else:
if _content_changed(existing, values):
existing.version += 1
for key, value in values.items():
setattr(existing, key, value)
updated += 1
session.commit()
return ImportResult(total=len(templates), created=created, updated=updated)
def _content_changed(existing: ScriptTemplate, values: dict) -> bool:
return any(
[
existing.customer_type != values["customer_type"],
existing.content != values["content"],
existing.tags != values["tags"],
existing.is_active != values["is_active"],
existing.is_approved != values["is_approved"],
]
)
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_tags(value: str) -> list[str]:
return [tag.strip() for tag in value.split(";") if tag.strip()]
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"
def main() -> None:
parser = argparse.ArgumentParser(description="Import Sprint 2 script templates.")
parser.add_argument("--dataset", type=Path, default=DEFAULT_DATASET)
args = parser.parse_args()
result = import_templates_from_markdown(args.dataset)
print(f"Imported script templates: total={result.total}, created={result.created}, updated={result.updated}")
if __name__ == "__main__":
main()