CI / lint / typecheck / import-linter / tests / secrets (push) Canceled after 0s
- agent: ag_conversation/ag_message 表(迁移0007),上下文组装注入最近报告画像摘要 (report.profile.latest_profile_summary 经 apps 层闭包注入,同层不互调),历史带最近 AGENT_HISTORY_TURNS=10 轮;POST/GET /agent/conversations 与消息历史接口 - agent 修复:ChatRequest.conversation_id 改 str(雪花ID超2^53,JS Number丢精度); _build_context 挪进 reserve commit 后同一 try,异常走 refund 兜底 - report: rp_feedback(维度反馈 upsert,认同率/修正率 SQL 可算,T-P3-21 最小版—— L1 记忆未建,不回写结论)+ rp_reservation(预约留资幂等,T-P3-22,预约率口径 行数/full_report.card_view 去重曝光,门槛≥8%),迁移0008 - 测试:全量 606 passed;ruff/mypy/lint-imports 全绿;E2E 真实 deepseek-v4-flash 验证 画像注入与历史携带
84 lines
3.4 KiB
Python
84 lines
3.4 KiB
Python
"""report.profile.latest_profile_summary:repo 打桩,验证摘要选取纪律。"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
from types import SimpleNamespace
|
||
from typing import Any
|
||
|
||
import pytest
|
||
|
||
from packages.report import repo
|
||
from packages.report.profile import latest_profile_summary
|
||
|
||
_DIAGNOSIS = {
|
||
"dimensions": [
|
||
{
|
||
"key": "body_shape",
|
||
"label": "体型",
|
||
"status": "ok",
|
||
"value": "梨形",
|
||
"confidence": 0.9,
|
||
"evidence": ["问卷自报肩窄胯宽"],
|
||
},
|
||
{"key": "style_impression", "status": "skipped", "value": None},
|
||
],
|
||
"rule_hits": [
|
||
{"rule_key": "pear_lower_dark", "dimension": "body_shape", "suggestion": "下装选深色"}
|
||
],
|
||
}
|
||
|
||
|
||
def _report(report_id: int, *, status: str, result: dict[str, Any]) -> Any:
|
||
return SimpleNamespace(report_id=report_id, status=status, result=result)
|
||
|
||
|
||
@pytest.fixture
|
||
def stub_list(monkeypatch: pytest.MonkeyPatch) -> dict[str, Any]:
|
||
state: dict[str, Any] = {"reports": []}
|
||
|
||
async def fake_list(session: Any, *, user_id: int, limit: int = 20) -> list[Any]:
|
||
state["limit"] = limit
|
||
return state["reports"]
|
||
|
||
monkeypatch.setattr(repo, "list_reports", fake_list)
|
||
return state
|
||
|
||
|
||
class TestLatestProfileSummary:
|
||
async def test_no_reports_returns_none(self, stub_list: dict[str, Any]) -> None:
|
||
assert await latest_profile_summary(None, user_id=1, max_chars=800) is None # type: ignore[arg-type]
|
||
|
||
async def test_succeeded_report_with_diagnosis(self, stub_list: dict[str, Any]) -> None:
|
||
stub_list["reports"] = [_report(1, status="succeeded", result={"diagnosis": _DIAGNOSIS})]
|
||
summary = await latest_profile_summary(None, user_id=1, max_chars=800) # type: ignore[arg-type]
|
||
assert summary is not None
|
||
payload = json.loads(summary)
|
||
values = {d["key"]: d["value"] for d in payload["dimensions"]}
|
||
assert values["body_shape"] == "梨形"
|
||
# skipped 维度不进素材(防幻觉第一道闸)
|
||
assert "style_impression" not in values
|
||
assert payload["rule_hits"][0]["rule_key"] == "pear_lower_dark"
|
||
|
||
async def test_latest_failed_falls_back_to_earlier_succeeded(
|
||
self, stub_list: dict[str, Any]
|
||
) -> None:
|
||
stub_list["reports"] = [
|
||
_report(2, status="failed", result={}),
|
||
_report(1, status="succeeded", result={"diagnosis": _DIAGNOSIS}),
|
||
]
|
||
summary = await latest_profile_summary(None, user_id=1, max_chars=800) # type: ignore[arg-type]
|
||
assert summary is not None and "梨形" in summary
|
||
|
||
async def test_succeeded_without_diagnosis_is_skipped(self, stub_list: dict[str, Any]) -> None:
|
||
stub_list["reports"] = [_report(1, status="succeeded", result={"vision": {}})]
|
||
assert await latest_profile_summary(None, user_id=1, max_chars=800) is None # type: ignore[arg-type]
|
||
|
||
async def test_budget_is_forwarded(self, stub_list: dict[str, Any]) -> None:
|
||
"""max_chars 收紧时低优先级维度被裁、硬结论保留(复用 copy 的裁剪纪律)。"""
|
||
stub_list["reports"] = [_report(1, status="succeeded", result={"diagnosis": _DIAGNOSIS})]
|
||
summary = await latest_profile_summary(None, user_id=1, max_chars=60) # type: ignore[arg-type]
|
||
assert summary is not None
|
||
assert len(summary) > 60 # 硬结论永不裁,载荷可超预算(copy.py 既定语义)
|
||
assert "梨形" in summary
|