feat(report): 形象深度诊断全链路——POST /reports/{id}/full 派生 tier=full 报告,复用 basic 输入与 32 维结果;full 流水线(无视觉阶段)LLM 扩写四大块(十二季定位/规则色板/4场景穿搭/30天计划),三重校验+语义重试+规则兜底降级;删除终身一次硬校验(40920 下线,重复生成由配额兜底);validation 枚举一致性校验放宽(十二季词表);迁移 0003 SSOT 补 full_report 规则行

This commit is contained in:
xsl
2026-09-15 00:02:44 +08:00
parent a1d0171fc5
commit 5c4f1fc75a
16 changed files with 1491 additions and 69 deletions
+29 -8
View File
@@ -331,29 +331,50 @@ async def _count_user_reports(user_id: int) -> int:
return int(result.scalar_one())
# --- 场景 4:首次资格 40920 ---------------------------------------------------
# --- 场景 4:重复生成不再 40920,由 report_basic 终身配额兜底 ---------------------
async def test_second_first_report_is_rejected_with_40920(fake_stages: None, client: Any) -> None:
async def test_second_report_create_is_blocked_by_lifetime_quota(
fake_stages: None, client: Any
) -> None:
"""产品决策变更:已生成过报告仍可再次提交,不再抛 40920(终身资格硬校验已删)。
重复生成由 report_basic 终身配额兜底(迁移 0003 SSOT:lifetime base_limit=1)。
本地开发库的规则行被人为放宽过(base_limit=10),这里把本用例用户的
bl_quota 快照 limit_value 收敛回 SSOT 值 1,确定性断言额度耗尽:
新 request_key 的第二次创建应返回 403 / QUOTA_EXHAUSTED 40301,
而不是旧的 40920 REPORT_ALREADY_EXISTS。
"""
from sqlalchemy import update
from apps.worker.report_tasks import _generate_report
from packages.shared.db import session_scope
from packages.shared.models import Quota
headers, _ = await _login(client)
headers, user_id = await _login(client)
data = await _create_report(client, headers)
await _generate_report(int(data["task_id"])) # 首单成功,终身资格已用掉
await _generate_report(int(data["task_id"])) # 首单成功,终身额度已用 1 次
# 收敛该用户的终身额度快照到 SSOT base_limit=1(限本用例用户,不动全局规则行)。
async with session_scope() as session:
await session.execute(
update(Quota)
.where(Quota.user_id == user_id, Quota.quota_key == "report_basic")
.values(limit_value=1)
)
resp = await client.post(
"/api/v1/reports",
headers=headers,
json={
"questionnaire": {},
"photo_object_key": "report_photo/u2.jpg",
"photo_object_key": await _upload_photo(client, headers), # 真实 key,先过存在性校验
"request_key": f"rk-{uuid.uuid4().hex}", # 新键:不是幂等重放
},
)
assert resp.status_code == 409
assert resp.status_code == 403
body = resp.json()
assert body["code"] == 40920
assert "30" in (body.get("hint") or "")
assert body["code"] == 40301 # QUOTA_EXHAUSTED,而非 40920
# --- 场景 5:失败退款 + derived_features 保留 ----------------------------------