feat(ai): v2 新增 GenerateReport RPC + ReportService

第 9 个 RPC GenerateReport(学情报告生成):data-ana 学情数据 → LLM 生成 → 结构化提取

新增 ReportService 业务编排层 + GenerateReportRequest/GeneratedReport 模型

gRPC servicer + HTTP POST /v1/ai/generate/report(权限 ai:report:generate)

proto_gen 重新生成 + 测试覆盖(servicer/service/HTTP/模型/权限 共 26 用例)

402 测试通过,覆盖率 88.5%
This commit is contained in:
SpecialX
2026-07-14 22:57:57 +08:00
parent 843b370b3d
commit aac26c7c6f
21 changed files with 1954 additions and 278 deletions

View File

@@ -6,6 +6,10 @@ from pydantic import ValidationError
from src.ai.models.chat import ChatData, ChatMessage, ChatRequest, Usage
from src.ai.models.expression import OptimizeExpressionRequest
from src.ai.models.question import GeneratedQuestionData, GenerateQuestionRequest
from src.ai.models.report import (
GeneratedReportData,
GenerateReportRequest,
)
from src.ai.models.workflow import (
ConfirmRequest,
LessonPreparationRequest,
@@ -52,8 +56,11 @@ class TestChatModels:
def test_chat_data_degraded_fields(self) -> None:
data = ChatData(
content="x", model="m", usage=Usage(),
degraded=True, degraded_reason="test",
content="x",
model="m",
usage=Usage(),
degraded=True,
degraded_reason="test",
)
assert data.degraded is True
@@ -131,3 +138,37 @@ class TestWorkflowModels:
assert data.questions == []
assert data.error is None
assert data.degraded is False
class TestReportModels:
"""学情报告模型测试."""
def test_generate_report_request_defaults(self) -> None:
req = GenerateReportRequest(class_id="c-1")
assert req.report_type == "class_summary"
assert req.student_id is None
assert req.user_id is None
assert req.data_scope is None
def test_generate_report_request_invalid_type(self) -> None:
with pytest.raises(ValidationError):
GenerateReportRequest(class_id="c-1", report_type="invalid")
def test_generate_report_request_missing_class_id(self) -> None:
with pytest.raises(ValidationError):
GenerateReportRequest()
def test_generate_report_request_student_detail(self) -> None:
req = GenerateReportRequest(
class_id="c-1",
report_type="student_detail",
student_id="s-001",
)
assert req.report_type == "student_detail"
assert req.student_id == "s-001"
def test_generated_report_data_defaults(self) -> None:
data = GeneratedReportData(id="r-1", content="内容", summary="摘要")
assert data.recommendations == []
assert data.degraded is False
assert data.degraded_reason == ""