CI / lint / typecheck / import-linter / tests / secrets (push) Canceled after 0s
- POST /uploads/direct:原始字节 body + Content-Type 申报 + scene query(默认 report_photo),场景/类型/大小三重校验复用 storage 既有规则,落盘 var/uploads/<scene>/<user_id>/<uuid>.<ext>(config 新增 upload_dir),key 布局与 COS 同构;不加 python-multipart 依赖 - storage/local.py:save_direct_upload + require_local_upload(resolve 归并 + is_relative_to 归属收敛 + 存在性校验,越界/模拟/不存在一律 404) - report/service.create_report:资格校验后、配额预扣前插入照片存在性校验(模拟 key 被拦且不消耗额度;regenerate 复用已验 photo_meta 不复核) - 测试:test_upload_direct 14 单测 + test_upload_direct_flow 4 集成(真 key 全链路/假 key 404 零流水/401/40010) 偏差说明:test_report_flow._create_report 改走 /uploads/direct 拿真 key(存在性校验必然封死假 key),40920 与幂等重放两条断言语义未动。
112 lines
3.5 KiB
Python
112 lines
3.5 KiB
Python
"""报告域的请求/响应模型。
|
||
|
||
Wire 响应统一走 ok() 信封(架构文档 §9.2),这里的模型只描述 ``data`` 部分;
|
||
雪花 ID 一律以字符串返回,避免 JS 精度丢失(NFR-MAINT-01 契约先行)。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from datetime import datetime
|
||
from typing import Literal
|
||
|
||
from pydantic import BaseModel, Field
|
||
|
||
|
||
class ReportCreateRequest(BaseModel):
|
||
questionnaire: dict[str, object] = Field(
|
||
default_factory=dict, description="问卷答案;字段结构由产品定稿后在路由层收紧"
|
||
)
|
||
photo_object_key: str = Field(
|
||
min_length=1,
|
||
max_length=255,
|
||
description=(
|
||
"已上传照片的对象键(report_photo 场景);本地降级路线下为 "
|
||
"POST /uploads/direct 返回的相对路径,建报告时做存在性校验"
|
||
),
|
||
)
|
||
request_key: str | None = Field(
|
||
default=None, max_length=128, description="幂等键;相同键返回同一任务且不重复扣配额"
|
||
)
|
||
|
||
|
||
class ReportRegenerateRequest(BaseModel):
|
||
request_key: str | None = Field(
|
||
default=None, max_length=128, description="幂等键;相同键返回同一任务且不重复扣配额"
|
||
)
|
||
|
||
|
||
class ReportExportRequest(BaseModel):
|
||
request_key: str | None = Field(
|
||
default=None, max_length=128, description="幂等键;相同键返回同一导出任务且不重复扣配额"
|
||
)
|
||
|
||
|
||
class ReportExportResponse(BaseModel):
|
||
task_id: str
|
||
status: str = Field(description="tk_task 状态;导出完成后任务中心可取到 asset_key")
|
||
|
||
|
||
class ReportSubmitResponse(BaseModel):
|
||
task_id: str
|
||
report_id: str
|
||
status: str = Field(description="tk_task 状态;报告本体状态与其同步推进")
|
||
version: int
|
||
|
||
|
||
class ReportItem(BaseModel):
|
||
report_id: str
|
||
tier: str
|
||
status: str
|
||
version: int
|
||
result: dict[str, object] = Field(
|
||
description="按阶段键组织的产物;failed 报告只透错误摘要(中间态留给流水线复用)"
|
||
)
|
||
created_at: datetime
|
||
completed_at: datetime | None
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# T-P3-21:维度级结论反馈
|
||
# ---------------------------------------------------------------------------
|
||
FeedbackVerdict = Literal["accurate", "inaccurate"]
|
||
|
||
|
||
class FeedbackCreateRequest(BaseModel):
|
||
dimension_key: str = Field(
|
||
min_length=1, max_length=64, description="报告 result.diagnosis.dimensions 的 key"
|
||
)
|
||
verdict: FeedbackVerdict = Field(description="准(accurate)/ 不准(inaccurate)")
|
||
correction: str | None = Field(
|
||
default=None, max_length=200, description="verdict=inaccurate 时的可选修正,≤200 字"
|
||
)
|
||
|
||
|
||
class FeedbackUpsertResponse(BaseModel):
|
||
dimension_key: str
|
||
verdict: str
|
||
updated: bool = Field(description="true=同维度已有反馈被覆盖更新;false=新建")
|
||
|
||
|
||
class FeedbackItem(BaseModel):
|
||
dimension_key: str
|
||
verdict: str
|
||
correction: str | None
|
||
created_at: datetime
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# T-P3-22:预约留资(topic 预留通用性,首期 full_report)
|
||
# ---------------------------------------------------------------------------
|
||
class ReservationCreateRequest(BaseModel):
|
||
topic: str = Field(min_length=1, max_length=32)
|
||
|
||
|
||
class ReservationResponse(BaseModel):
|
||
topic: str
|
||
created: bool = Field(description="false=已存在(幂等命中,HTTP 仍 200)")
|
||
created_at: datetime
|
||
|
||
|
||
class ReservationListResponse(BaseModel):
|
||
topics: list[str]
|