mirror of
https://github.com/Nezumi-2711/astrbot_plugin_qq_group_daily_analysis.git
synced 2026-09-22 13:38:43 +00:00
fix: prevent Chinese output in group analysis command
This commit is contained in:
@@ -16,3 +16,4 @@ tests/ export-ignore
|
|||||||
|
|
||||||
### Các template ATRI hiện dùng CRLF; không coi CR cuối dòng là khoảng trắng thừa
|
### Các template ATRI hiện dùng CRLF; không coi CR cuối dòng là khoảng trắng thừa
|
||||||
src/infrastructure/reporting/templates/ATRI/*.html whitespace=cr-at-eol
|
src/infrastructure/reporting/templates/ATRI/*.html whitespace=cr-at-eol
|
||||||
|
*.py whitespace=cr-at-eol
|
||||||
|
|||||||
@@ -506,7 +506,9 @@ class GroupDailyAnalysis(Star):
|
|||||||
self._background_tasks.add(current_task)
|
self._background_tasks.add(current_task)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
event.should_call_llm(True) # Ngăn LLM mặc định phân tích.
|
# Chặn cả LLM mặc định lẫn các handler tiếp theo xử lý lại slash command.
|
||||||
|
event.should_call_llm(True)
|
||||||
|
event.stop_event()
|
||||||
group_id = self._get_group_id_from_event(event)
|
group_id = self._get_group_id_from_event(event)
|
||||||
platform_id = self._get_platform_id_from_event(event)
|
platform_id = self._get_platform_id_from_event(event)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
name: astrbot_plugin_qq_group_daily_analysis # Mã định danh duy nhất của plugin.
|
name: astrbot_plugin_qq_group_daily_analysis # Mã định danh duy nhất của plugin.
|
||||||
display_name: Plugin tổng hợp và phân tích nhóm # Tên hiển thị của plugin.
|
display_name: Plugin tổng hợp và phân tích nhóm # Tên hiển thị của plugin.
|
||||||
desc: "Plugin tổng hợp và phân tích hoạt động nhóm hằng ngày - hỗ trợ OneBot (NapCat, LLOneBot, Snowluma), bot QQ chính thức, Telegram và Discord; tạo báo cáo phân tích trò chuyện nhóm trực quan, hỗ trợ phân tích chủ đề, chân dung thành viên và trích dẫn nổi bật." # Mô tả ngắn của plugin.
|
desc: "Plugin tổng hợp và phân tích hoạt động nhóm hằng ngày - hỗ trợ OneBot (NapCat, LLOneBot, Snowluma), bot QQ chính thức, Telegram và Discord; tạo báo cáo phân tích trò chuyện nhóm trực quan, hỗ trợ phân tích chủ đề, chân dung thành viên và trích dẫn nổi bật." # Mô tả ngắn của plugin.
|
||||||
version: v4.11.2 # Phiên bản plugin, theo định dạng v1.1.1 hoặc v1.1.
|
version: v4.11.3 # Phiên bản plugin, theo định dạng v1.1.1 hoặc v1.1.
|
||||||
author: SXP-Simon # Tác giả.
|
author: SXP-Simon # Tác giả.
|
||||||
astrbot_version: ">=4.16.0"
|
astrbot_version: ">=4.16.0"
|
||||||
support_platforms:
|
support_platforms:
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from markupsafe import Markup
|
|||||||
|
|
||||||
from ...domain.repositories.report_repository import IReportGenerator
|
from ...domain.repositories.report_repository import IReportGenerator
|
||||||
from ...utils.logger import logger
|
from ...utils.logger import logger
|
||||||
|
from ...shared.vietnamese_language import sanitize_analysis_result_language
|
||||||
from ..utils.template_utils import render_template
|
from ..utils.template_utils import render_template
|
||||||
from ..visualization.activity_charts import ActivityVisualizer
|
from ..visualization.activity_charts import ActivityVisualizer
|
||||||
from .qq_official_markdown import QQOfficialMarkdownReportGenerator
|
from .qq_official_markdown import QQOfficialMarkdownReportGenerator
|
||||||
@@ -171,6 +172,17 @@ class ReportGenerator(IReportGenerator):
|
|||||||
self._avatar_session = None
|
self._avatar_session = None
|
||||||
self._profile_asset_manifest = self._load_profile_asset_manifest()
|
self._profile_asset_manifest = self._load_profile_asset_manifest()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _enforce_vietnamese_report_content(analysis_result: dict) -> None:
|
||||||
|
"""Loại nội dung sinh còn chữ Hán trước mọi đường kết xuất báo cáo."""
|
||||||
|
removed_fields = sanitize_analysis_result_language(analysis_result)
|
||||||
|
if removed_fields:
|
||||||
|
logger.warning(
|
||||||
|
"Đã loại %s trường không phải tiếng Việt trước khi tạo báo cáo: %s",
|
||||||
|
len(removed_fields),
|
||||||
|
", ".join(removed_fields),
|
||||||
|
)
|
||||||
|
|
||||||
def _load_profile_asset_manifest(self) -> dict[str, dict]:
|
def _load_profile_asset_manifest(self) -> dict[str, dict]:
|
||||||
"""Tải manifest tài nguyên hồ sơ."""
|
"""Tải manifest tài nguyên hồ sơ."""
|
||||||
manifest_path = (
|
manifest_path = (
|
||||||
@@ -426,6 +438,7 @@ class ReportGenerator(IReportGenerator):
|
|||||||
"""
|
"""
|
||||||
html_content = None
|
html_content = None
|
||||||
try:
|
try:
|
||||||
|
self._enforce_vietnamese_report_content(analysis_result)
|
||||||
# Chuẩn bị dữ liệu render.
|
# Chuẩn bị dữ liệu render.
|
||||||
render_payload = await self._prepare_render_data(
|
render_payload = await self._prepare_render_data(
|
||||||
analysis_result,
|
analysis_result,
|
||||||
@@ -596,6 +609,7 @@ class ReportGenerator(IReportGenerator):
|
|||||||
Tuple path tệp HTML và JSON.
|
Tuple path tệp HTML và JSON.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
self._enforce_vietnamese_report_content(analysis_result)
|
||||||
import json
|
import json
|
||||||
|
|
||||||
# Đảm bảo thư mục output tồn tại mà không block event loop.
|
# Đảm bảo thư mục output tồn tại mà không block event loop.
|
||||||
@@ -742,6 +756,7 @@ class ReportGenerator(IReportGenerator):
|
|||||||
|
|
||||||
def generate_text_report(self, analysis_result: dict) -> str:
|
def generate_text_report(self, analysis_result: dict) -> str:
|
||||||
"""Tạo báo cáo phân tích dạng văn bản."""
|
"""Tạo báo cáo phân tích dạng văn bản."""
|
||||||
|
self._enforce_vietnamese_report_content(analysis_result)
|
||||||
stats = analysis_result["statistics"]
|
stats = analysis_result["statistics"]
|
||||||
topics = analysis_result["topics"]
|
topics = analysis_result["topics"]
|
||||||
user_titles = analysis_result["user_titles"]
|
user_titles = analysis_result["user_titles"]
|
||||||
@@ -785,6 +800,7 @@ class ReportGenerator(IReportGenerator):
|
|||||||
self, analysis_result: dict, html_render_func=None
|
self, analysis_result: dict, html_render_func=None
|
||||||
) -> tuple[str, str]:
|
) -> tuple[str, str]:
|
||||||
"""Delegate QQ-only text generation to the platform-specific module."""
|
"""Delegate QQ-only text generation to the platform-specific module."""
|
||||||
|
self._enforce_vietnamese_report_content(analysis_result)
|
||||||
generator = getattr(self, "_qq_official_markdown_generator", None)
|
generator = getattr(self, "_qq_official_markdown_generator", None)
|
||||||
if generator is None:
|
if generator is None:
|
||||||
generator = QQOfficialMarkdownReportGenerator(
|
generator = QQOfficialMarkdownReportGenerator(
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
"""Tiện ích thuần để kiểm tra ngôn ngữ đầu ra báo cáo."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
HAN_CHARACTER_PATTERN = re.compile(r"[\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]")
|
||||||
|
|
||||||
|
|
||||||
|
def contains_han_characters(value: object) -> bool:
|
||||||
|
"""Kiểm tra chuỗi có chứa chữ Hán hay không."""
|
||||||
|
return bool(HAN_CHARACTER_PATTERN.search(str(value or "")))
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_analysis_result_language(analysis_result: dict) -> list[str]:
|
||||||
|
"""Loại nội dung sinh còn chữ Hán trước khi tạo báo cáo.
|
||||||
|
|
||||||
|
Đây là cổng phòng vệ cuối cho dữ liệu cũ hoặc đường gọi không đi qua
|
||||||
|
analyzer. Các trường danh tính như tên người dùng, người gửi, ID và người
|
||||||
|
tham gia không bị kiểm tra.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_value(container: object, field: str, default=None):
|
||||||
|
if isinstance(container, dict):
|
||||||
|
return container.get(field, default)
|
||||||
|
return getattr(container, field, default)
|
||||||
|
|
||||||
|
def set_value(container: object, field: str, value: object) -> None:
|
||||||
|
if isinstance(container, dict):
|
||||||
|
container[field] = value
|
||||||
|
else:
|
||||||
|
setattr(container, field, value)
|
||||||
|
|
||||||
|
known_identities: set[str] = set()
|
||||||
|
user_analysis = analysis_result.get("user_analysis", {})
|
||||||
|
if isinstance(user_analysis, dict):
|
||||||
|
for user_id, user_data in user_analysis.items():
|
||||||
|
known_identities.add(str(user_id).strip())
|
||||||
|
if isinstance(user_data, dict):
|
||||||
|
for field in ("nickname", "name", "card"):
|
||||||
|
known_identities.add(str(user_data.get(field, "")).strip())
|
||||||
|
|
||||||
|
for topic in analysis_result.get("topics", []):
|
||||||
|
contributors = get_value(topic, "contributors", [])
|
||||||
|
if isinstance(contributors, list):
|
||||||
|
known_identities.update(str(item).strip() for item in contributors)
|
||||||
|
for title in analysis_result.get("user_titles", []):
|
||||||
|
known_identities.add(str(get_value(title, "name", "")).strip())
|
||||||
|
|
||||||
|
statistics = analysis_result.get("statistics")
|
||||||
|
if statistics is not None:
|
||||||
|
quotes = get_value(statistics, "golden_quotes", [])
|
||||||
|
if isinstance(quotes, list):
|
||||||
|
for quote in quotes:
|
||||||
|
known_identities.add(str(get_value(quote, "sender", "")).strip())
|
||||||
|
|
||||||
|
known_identities.discard("")
|
||||||
|
|
||||||
|
def contains_untranslated_han(value: object) -> bool:
|
||||||
|
text = str(value or "")
|
||||||
|
for identity in sorted(known_identities, key=len, reverse=True):
|
||||||
|
text = text.replace(identity, "")
|
||||||
|
return contains_han_characters(text)
|
||||||
|
|
||||||
|
removed: list[str] = []
|
||||||
|
for collection_name, fields in (
|
||||||
|
("topics", ("topic", "detail")),
|
||||||
|
("user_titles", ("title", "reason")),
|
||||||
|
):
|
||||||
|
collection = analysis_result.get(collection_name, [])
|
||||||
|
if not isinstance(collection, list):
|
||||||
|
continue
|
||||||
|
kept = []
|
||||||
|
for index, item in enumerate(collection):
|
||||||
|
invalid_fields = [
|
||||||
|
field
|
||||||
|
for field in fields
|
||||||
|
if contains_untranslated_han(get_value(item, field, ""))
|
||||||
|
]
|
||||||
|
if invalid_fields:
|
||||||
|
removed.extend(
|
||||||
|
f"{collection_name}[{index}].{field}" for field in invalid_fields
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
kept.append(item)
|
||||||
|
analysis_result[collection_name] = kept
|
||||||
|
|
||||||
|
if statistics is not None:
|
||||||
|
quotes = get_value(statistics, "golden_quotes", [])
|
||||||
|
if isinstance(quotes, list):
|
||||||
|
kept_quotes = []
|
||||||
|
for index, quote in enumerate(quotes):
|
||||||
|
invalid_fields = [
|
||||||
|
field
|
||||||
|
for field in ("content", "reason")
|
||||||
|
if contains_untranslated_han(get_value(quote, field, ""))
|
||||||
|
]
|
||||||
|
if invalid_fields:
|
||||||
|
removed.extend(
|
||||||
|
f"statistics.golden_quotes[{index}].{field}"
|
||||||
|
for field in invalid_fields
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
kept_quotes.append(quote)
|
||||||
|
set_value(statistics, "golden_quotes", kept_quotes)
|
||||||
|
|
||||||
|
quality_review = analysis_result.get("chat_quality_review")
|
||||||
|
if quality_review is None and statistics is not None:
|
||||||
|
quality_review = get_value(statistics, "chat_quality_review")
|
||||||
|
if quality_review is not None:
|
||||||
|
invalid_fields = [
|
||||||
|
field
|
||||||
|
for field in ("title", "subtitle", "summary")
|
||||||
|
if contains_untranslated_han(get_value(quality_review, field, ""))
|
||||||
|
]
|
||||||
|
dimensions = get_value(quality_review, "dimensions", [])
|
||||||
|
if isinstance(dimensions, list):
|
||||||
|
for index, dimension in enumerate(dimensions):
|
||||||
|
invalid_fields.extend(
|
||||||
|
f"dimensions[{index}].{field}"
|
||||||
|
for field in ("name", "comment")
|
||||||
|
if contains_untranslated_han(get_value(dimension, field, ""))
|
||||||
|
)
|
||||||
|
if invalid_fields:
|
||||||
|
removed.extend(f"chat_quality_review.{field}" for field in invalid_fields)
|
||||||
|
analysis_result["chat_quality_review"] = None
|
||||||
|
if statistics is not None:
|
||||||
|
set_value(statistics, "chat_quality_review", None)
|
||||||
|
|
||||||
|
return removed
|
||||||
@@ -56,9 +56,9 @@ def test_standard_text_report_keeps_existing_identity_format():
|
|||||||
most_active_period="12:00-13:00",
|
most_active_period="12:00-13:00",
|
||||||
golden_quotes=[
|
golden_quotes=[
|
||||||
SimpleNamespace(
|
SimpleNamespace(
|
||||||
content="测试内容",
|
content="Nội dung kiểm thử",
|
||||||
sender=openid,
|
sender=openid,
|
||||||
reason=f"由 {openid} 发出",
|
reason=f"Được phát biểu bởi {openid}",
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@@ -66,17 +66,17 @@ def test_standard_text_report_keeps_existing_identity_format():
|
|||||||
"statistics": statistics,
|
"statistics": statistics,
|
||||||
"topics": [
|
"topics": [
|
||||||
SimpleNamespace(
|
SimpleNamespace(
|
||||||
topic="测试话题",
|
topic="Chủ đề kiểm thử",
|
||||||
contributors=[openid],
|
contributors=[openid],
|
||||||
detail=f"{openid} 参与讨论",
|
detail=f"{openid} tham gia thảo luận",
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
"user_titles": [
|
"user_titles": [
|
||||||
SimpleNamespace(
|
SimpleNamespace(
|
||||||
name=openid,
|
name=openid,
|
||||||
title="龙王",
|
title="Vua trò chuyện",
|
||||||
mbti="ENTP",
|
mbti="ENTP",
|
||||||
reason=f"{openid} 发言最多",
|
reason=f"{openid} phát biểu nhiều nhất",
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
"user_analysis": {openid: {"nickname": openid}},
|
"user_analysis": {openid: {"nickname": openid}},
|
||||||
@@ -85,11 +85,11 @@ def test_standard_text_report_keeps_existing_identity_format():
|
|||||||
report = generator.generate_text_report(analysis_result)
|
report = generator.generate_text_report(analysis_result)
|
||||||
|
|
||||||
assert openid in report
|
assert openid in report
|
||||||
assert "测试内容" in report
|
assert "Nội dung kiểm thử" in report
|
||||||
assert "龙王" in report
|
assert "Vua trò chuyện" in report
|
||||||
assert f"Người tham gia: {openid}" in report
|
assert f"Người tham gia: {openid}" in report
|
||||||
assert f"• {openid} - 龙王 (ENTP)" in report
|
assert f"• {openid} - Vua trò chuyện (ENTP)" in report
|
||||||
assert f'1. "测试内容" —— {openid}' in report
|
assert f'1. "Nội dung kiểm thử" —— {openid}' in report
|
||||||
|
|
||||||
|
|
||||||
def test_standard_text_report_api_has_no_qq_platform_switches():
|
def test_standard_text_report_api_has_no_qq_platform_switches():
|
||||||
@@ -136,9 +136,9 @@ def test_qq_official_markdown_uses_mentions_for_all_identity_sections():
|
|||||||
most_active_period="12:00-13:00",
|
most_active_period="12:00-13:00",
|
||||||
golden_quotes=[
|
golden_quotes=[
|
||||||
SimpleNamespace(
|
SimpleNamespace(
|
||||||
content=f"[{openid}] 说了一句话",
|
content=f"[{openid}] đã nói một câu",
|
||||||
sender=nickname,
|
sender=nickname,
|
||||||
reason=f"{nickname} 的发言很精彩",
|
reason=f"Phát biểu của {nickname} rất ấn tượng",
|
||||||
user_id=openid,
|
user_id=openid,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@@ -147,19 +147,19 @@ def test_qq_official_markdown_uses_mentions_for_all_identity_sections():
|
|||||||
"statistics": statistics,
|
"statistics": statistics,
|
||||||
"topics": [
|
"topics": [
|
||||||
SimpleNamespace(
|
SimpleNamespace(
|
||||||
topic="测试话题",
|
topic="Chủ đề kiểm thử",
|
||||||
contributors=[nickname],
|
contributors=[nickname],
|
||||||
contributor_ids=[openid],
|
contributor_ids=[openid],
|
||||||
detail=f"{nickname} 和 {openid} 参与讨论",
|
detail=f"{nickname} và {openid} tham gia thảo luận",
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
"user_titles": [
|
"user_titles": [
|
||||||
SimpleNamespace(
|
SimpleNamespace(
|
||||||
name=nickname,
|
name=nickname,
|
||||||
user_id=openid,
|
user_id=openid,
|
||||||
title="龙王",
|
title="Vua trò chuyện",
|
||||||
mbti="ENTP",
|
mbti="ENTP",
|
||||||
reason=f"[{openid}] 发言最多",
|
reason=f"[{openid}] phát biểu nhiều nhất",
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
"user_analysis": {openid: {"nickname": nickname}},
|
"user_analysis": {openid: {"nickname": nickname}},
|
||||||
@@ -173,10 +173,10 @@ def test_qq_official_markdown_uses_mentions_for_all_identity_sections():
|
|||||||
assert nickname not in without_mentions
|
assert nickname not in without_mentions
|
||||||
assert "## 💬 Chủ đề nổi bật" in report
|
assert "## 💬 Chủ đề nổi bật" in report
|
||||||
assert "**Người tham gia**" in report
|
assert "**Người tham gia**" in report
|
||||||
assert "**龙王**" in report
|
assert "**Vua trò chuyện**" in report
|
||||||
assert f"- **1. <@{openid}> 说了一句话** — <@{openid}>" in report
|
assert f"- **1. <@{openid}> đã nói một câu** — <@{openid}>" in report
|
||||||
assert f" > <@{openid}> 的发言很精彩" in report
|
assert f" > Phát biểu của <@{openid}> rất ấn tượng" in report
|
||||||
assert f"> 1. <@{openid}> 说了一句话" not in report
|
assert f"> 1. <@{openid}> đã nói một câu" not in report
|
||||||
|
|
||||||
|
|
||||||
def test_qq_official_markdown_keeps_content_when_identity_id_is_missing():
|
def test_qq_official_markdown_keeps_content_when_identity_id_is_missing():
|
||||||
@@ -189,9 +189,9 @@ def test_qq_official_markdown_keeps_content_when_identity_id_is_missing():
|
|||||||
most_active_period="12:00-13:00",
|
most_active_period="12:00-13:00",
|
||||||
golden_quotes=[
|
golden_quotes=[
|
||||||
SimpleNamespace(
|
SimpleNamespace(
|
||||||
content="测试内容",
|
content="Nội dung kiểm thử",
|
||||||
sender="无法映射的用户",
|
sender="无法映射的用户",
|
||||||
reason="理由保留",
|
reason="Lý do được giữ lại",
|
||||||
user_id="",
|
user_id="",
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@@ -203,9 +203,9 @@ def test_qq_official_markdown_keeps_content_when_identity_id_is_missing():
|
|||||||
SimpleNamespace(
|
SimpleNamespace(
|
||||||
name="无法映射的用户",
|
name="无法映射的用户",
|
||||||
user_id="",
|
user_id="",
|
||||||
title="龙王",
|
title="Vua trò chuyện",
|
||||||
mbti="",
|
mbti="",
|
||||||
reason="称号理由",
|
reason="Lý do trao danh hiệu",
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
"user_analysis": {},
|
"user_analysis": {},
|
||||||
@@ -214,14 +214,14 @@ def test_qq_official_markdown_keeps_content_when_identity_id_is_missing():
|
|||||||
report = generate_qq_markdown(generator, analysis_result)
|
report = generate_qq_markdown(generator, analysis_result)
|
||||||
|
|
||||||
assert "<@" not in report
|
assert "<@" not in report
|
||||||
assert "龙王" in report
|
assert "Vua trò chuyện" in report
|
||||||
assert "称号理由" in report
|
assert "Lý do trao danh hiệu" in report
|
||||||
assert "测试内容" in report
|
assert "Nội dung kiểm thử" in report
|
||||||
assert "理由保留" in report
|
assert "Lý do được giữ lại" in report
|
||||||
assert "无法映射的用户" not in report
|
assert "无法映射的用户" not in report
|
||||||
assert "- **1. 测试内容**" in report
|
assert "- **1. Nội dung kiểm thử**" in report
|
||||||
assert " > 理由保留" in report
|
assert " > Lý do được giữ lại" in report
|
||||||
assert "> 1. 测试内容" not in report
|
assert "> 1. Nội dung kiểm thử" not in report
|
||||||
|
|
||||||
|
|
||||||
def test_qq_official_scripture_spacing_and_optional_reason():
|
def test_qq_official_scripture_spacing_and_optional_reason():
|
||||||
@@ -234,13 +234,13 @@ def test_qq_official_scripture_spacing_and_optional_reason():
|
|||||||
most_active_period="12:00-13:00",
|
most_active_period="12:00-13:00",
|
||||||
golden_quotes=[
|
golden_quotes=[
|
||||||
SimpleNamespace(
|
SimpleNamespace(
|
||||||
content="第一条",
|
content="Câu nói thứ nhất",
|
||||||
sender="甲",
|
sender="甲",
|
||||||
reason="第一条理由",
|
reason="Lý do thứ nhất",
|
||||||
user_id="A_OPENID",
|
user_id="A_OPENID",
|
||||||
),
|
),
|
||||||
SimpleNamespace(
|
SimpleNamespace(
|
||||||
content="第二条",
|
content="Câu nói thứ hai",
|
||||||
sender="乙",
|
sender="乙",
|
||||||
reason="",
|
reason="",
|
||||||
user_id="",
|
user_id="",
|
||||||
@@ -258,8 +258,11 @@ def test_qq_official_scripture_spacing_and_optional_reason():
|
|||||||
|
|
||||||
report = generate_qq_markdown(generator, analysis_result)
|
report = generate_qq_markdown(generator, analysis_result)
|
||||||
|
|
||||||
assert "- **1. 第一条** — <@A_OPENID>\n > 第一条理由\n\n- **2. 第二条**" in report
|
assert (
|
||||||
assert "- **2. 第二条** —" not in report
|
"- **1. Câu nói thứ nhất** — <@A_OPENID>\n > Lý do thứ nhất\n\n- **2. Câu nói thứ hai**"
|
||||||
|
in report
|
||||||
|
)
|
||||||
|
assert "- **2. Câu nói thứ hai** —" not in report
|
||||||
|
|
||||||
|
|
||||||
def test_qq_official_markdown_renders_simple_hourly_bar_chart():
|
def test_qq_official_markdown_renders_simple_hourly_bar_chart():
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import json
|
import json
|
||||||
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from src.infrastructure.analysis.analyzers.base_analyzer import (
|
from src.infrastructure.analysis.analyzers.base_analyzer import (
|
||||||
@@ -11,6 +12,7 @@ from src.infrastructure.analysis.utils.response_validation import (
|
|||||||
validate_topic_items,
|
validate_topic_items,
|
||||||
validate_user_title_items,
|
validate_user_title_items,
|
||||||
)
|
)
|
||||||
|
from src.shared.vietnamese_language import sanitize_analysis_result_language
|
||||||
|
|
||||||
|
|
||||||
class DummyAnalyzer(BaseAnalyzer[dict, list[dict]]):
|
class DummyAnalyzer(BaseAnalyzer[dict, list[dict]]):
|
||||||
@@ -152,3 +154,82 @@ def test_default_golden_quote_prompt_requires_translation():
|
|||||||
|
|
||||||
assert "dịch phát biểu sang tiếng Việt" in prompt
|
assert "dịch phát biểu sang tiếng Việt" in prompt
|
||||||
assert "content` phải giữ nguyên lời nói gốc" not in prompt
|
assert "content` phải giữ nguyên lời nói gốc" not in prompt
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ReportItem:
|
||||||
|
topic: str = ""
|
||||||
|
detail: str = ""
|
||||||
|
name: str = ""
|
||||||
|
title: str = ""
|
||||||
|
reason: str = ""
|
||||||
|
content: str = ""
|
||||||
|
sender: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ReportStatistics:
|
||||||
|
golden_quotes: list[ReportItem]
|
||||||
|
chat_quality_review: dict | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def test_report_boundary_removes_chinese_semantics_but_keeps_identity():
|
||||||
|
chinese_identity = "测试用户"
|
||||||
|
vietnamese_identity = "Nguyễn Văn A"
|
||||||
|
analysis_result = {
|
||||||
|
"topics": [
|
||||||
|
ReportItem(topic="技术讨论", detail="讨论 rất sâu"),
|
||||||
|
ReportItem(topic="Kế hoạch cuối tuần", detail="Cả nhóm sẽ đi chơi."),
|
||||||
|
],
|
||||||
|
"user_titles": [
|
||||||
|
ReportItem(name=chinese_identity, title="技术专家", reason="Rất giỏi"),
|
||||||
|
ReportItem(
|
||||||
|
name=chinese_identity,
|
||||||
|
title="Chuyên gia kỹ thuật",
|
||||||
|
reason="Thường xuyên hỗ trợ mọi người.",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
"statistics": ReportStatistics(
|
||||||
|
golden_quotes=[
|
||||||
|
ReportItem(content="代码能跑就不要动", sender=chinese_identity),
|
||||||
|
ReportItem(
|
||||||
|
content="Chạy được thì đừng sửa.", sender=vietnamese_identity
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
"chat_quality_review": {
|
||||||
|
"title": "今日群聊",
|
||||||
|
"subtitle": "Một ngày vui vẻ",
|
||||||
|
"dimensions": [],
|
||||||
|
"summary": "Mọi người trò chuyện tích cực.",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
removed = sanitize_analysis_result_language(analysis_result)
|
||||||
|
|
||||||
|
assert len(analysis_result["topics"]) == 1
|
||||||
|
assert analysis_result["topics"][0].topic == "Kế hoạch cuối tuần"
|
||||||
|
assert len(analysis_result["user_titles"]) == 1
|
||||||
|
assert analysis_result["user_titles"][0].name == chinese_identity
|
||||||
|
assert len(analysis_result["statistics"].golden_quotes) == 1
|
||||||
|
assert analysis_result["statistics"].golden_quotes[0].sender == vietnamese_identity
|
||||||
|
assert analysis_result["chat_quality_review"] is None
|
||||||
|
assert analysis_result["statistics"].chat_quality_review is None
|
||||||
|
assert "topics[0].topic" in removed
|
||||||
|
assert "user_titles[0].title" in removed
|
||||||
|
assert "statistics.golden_quotes[0].content" in removed
|
||||||
|
assert "chat_quality_review.title" in removed
|
||||||
|
|
||||||
|
|
||||||
|
def test_group_analysis_command_stops_event_propagation():
|
||||||
|
main_path = Path(__file__).resolve().parents[1] / "main.py"
|
||||||
|
source = main_path.read_text(encoding="utf-8")
|
||||||
|
command_start = source.index("async def analyze_group_daily(")
|
||||||
|
command_end = source.index("async def _send_analysis_report(", command_start)
|
||||||
|
command_source = source[command_start:command_end]
|
||||||
|
|
||||||
|
assert "event.should_call_llm(True)" in command_source
|
||||||
|
assert "event.stop_event()" in command_source
|
||||||
|
assert command_source.index("event.stop_event()") < command_source.index(
|
||||||
|
"execute_daily_analysis"
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user