chore(prep): shared groundwork for batch-4 (report pipeline)

第四批(T15 编排/T16 视觉诊断/T17 文案校验)协调提交:
- 迁移 0006:rp_report(result JSONB 按阶段键承载中间态)/rp_input
 (derived_features 视觉中间态)/rp_share(V1 只建结构)
- ORM 三模型;错误码 40420/40920(文案无价格字样)
- 配置:report_pipeline_stages(dotted path 动态装配)+ vision/tier
- packages/report/pipeline.py:Stage Protocol + ReportContext(三任务共同契约,
  动态 import 的动机与代价写在 docstring)
- 路由桩 reports.py 已注册
This commit is contained in:
xsl
2026-08-14 21:51:05 +08:00
parent 7fb1e4514f
commit d974526904
8 changed files with 263 additions and 1 deletions
+19
View File
@@ -213,6 +213,24 @@ class Settings(BaseSettings):
retrieval_cache_ttl_seconds: int = 60
retrieval_cache_max_entries: int = 512
# --- 美学报告流水线(T-P3,第四批)---------------------------------------
# 阶段按 dotted path 动态装配(importlib):T15 编排、T16 供 vision/diagnosis、
# T17 供 copy/validation——三个任务互不改对方文件,靠这份清单衔接。
# 缺失模块/类在装配时报 CONFIG_ERROR 并列出缺失项,绝不静默跳过。
report_pipeline_stages: list[str] = Field(
default_factory=lambda: [
"packages.report.vision_stage:VisionStage",
"packages.report.diagnosis_stage:DiagnosisStage",
"packages.report.copy_stage:CopyStage",
"packages.report.validation_stage:ValidationStage",
],
alias="REPORT_PIPELINE_STAGES_JSON",
)
# 视觉提供者:deterministic = V1 骨架的确定性假实现(真实 RTMPose/BiRefNet
# 是 Sprint 1 结论落地后的替换项,接口位见 packages/report/vision.py)。
report_vision_provider: str = "deterministic"
report_tier: str = "basic"
# --- Observability -----------------------------------------------------
otel_enabled: bool = False
otel_service_name: str = "meida-api"
@@ -228,6 +246,7 @@ class Settings(BaseSettings):
"task_bonus_rules",
"upload_scenes",
"retrieval_rerank_weights",
"report_pipeline_stages",
mode="before",
)
@classmethod
+4
View File
@@ -41,8 +41,10 @@ class ErrorCode(IntEnum):
TASK_BONUS_CAP_REACHED = 40310
NOT_FOUND = 40400
TASK_BONUS_UNKNOWN = 40410
REPORT_NOT_FOUND = 40420
STATE_CONFLICT = 40900
TASK_BONUS_ALREADY_CLAIMED = 40910
REPORT_ALREADY_EXISTS = 40920
RATE_LIMITED = 42900
SMS_SEND_TOO_FREQUENT = 42910
SMS_DAILY_LIMIT = 42911
@@ -100,10 +102,12 @@ ERROR_META: dict[ErrorCode, ErrorMeta] = {
),
ErrorCode.NOT_FOUND: ErrorMeta(404, "内容不存在或已删除"),
ErrorCode.TASK_BONUS_UNKNOWN: ErrorMeta(404, "任务不存在或暂未开放"),
ErrorCode.REPORT_NOT_FOUND: ErrorMeta(404, "报告不存在或已删除"),
ErrorCode.STATE_CONFLICT: ErrorMeta(409, "当前状态无法执行该操作"),
ErrorCode.TASK_BONUS_ALREADY_CLAIMED: ErrorMeta(
409, "这个周期的任务奖励已领取", "下个周期再来"
),
ErrorCode.REPORT_ALREADY_EXISTS: ErrorMeta(409, "本期报告已生成过", "每 30 天可重新生成一次"),
ErrorCode.RATE_LIMITED: ErrorMeta(429, "操作太频繁了", "请稍后再试"),
ErrorCode.SMS_SEND_TOO_FREQUENT: ErrorMeta(429, "发送太频繁了", "请稍后再试"),
ErrorCode.SMS_DAILY_LIMIT: ErrorMeta(429, "今日发送次数已达上限", "明天再试"),
+62
View File
@@ -479,3 +479,65 @@ class KbGap(Base):
)
__table_args__ = (Index("ix_kb_gap_query", "query_text"),)
# ---------------------------------------------------------------------------
# report 域(美学报告,架构文档 §6.2 / §7.1)
# ---------------------------------------------------------------------------
class RpReport(Base):
"""报告主表。result JSONB 同时承载流水线中间态与最终产物(按 stage 键)——
阶段逐段落库是 T-P3-09「失败不需重新上传」的结构基础。"""
__tablename__ = "rp_report"
report_id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=False)
user_id: Mapped[int] = mapped_column(BigInteger, nullable=False)
tier: Mapped[str] = mapped_column(String(8), nullable=False, default="basic")
status: Mapped[str] = mapped_column(String(16), nullable=False, default="pending")
version: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
result: Mapped[dict[str, Any]] = mapped_column(JSONB, nullable=False, default=dict)
model_info: Mapped[dict[str, Any]] = mapped_column(JSONB, nullable=False, default=dict)
content_code: Mapped[str | None] = mapped_column(String(32))
rendered_asset_key: Mapped[str | None] = mapped_column(String(255))
cost_cents: Mapped[Decimal] = mapped_column(
Numeric(14, 6), nullable=False, default=Decimal("0")
)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, server_default=func.now()
)
completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
__table_args__ = (Index("ix_rp_report_user_created", "user_id", "created_at"),)
class RpInput(Base):
"""报告输入。derived_features 是视觉分析的中间态:重试/重新生成时
已存在则跳过视觉阶段(T-P3-09)。只存派生特征与元信息,不存原图(CR-02)。"""
__tablename__ = "rp_input"
input_id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=False)
report_id: Mapped[int] = mapped_column(BigInteger, nullable=False)
questionnaire: Mapped[dict[str, Any]] = mapped_column(JSONB, nullable=False, default=dict)
photo_meta: Mapped[dict[str, Any]] = mapped_column(JSONB, nullable=False, default=dict)
derived_features: Mapped[dict[str, Any]] = mapped_column(JSONB, nullable=False, default=dict)
__table_args__ = (UniqueConstraint("report_id", name="uq_rp_input_report"),)
class RpShare(Base):
"""分享链接:可失效、可撤回(V1 只建结构,路由后续任务)。"""
__tablename__ = "rp_share"
share_id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=False)
report_id: Mapped[int] = mapped_column(BigInteger, nullable=False)
share_token: Mapped[str] = mapped_column(String(64), nullable=False)
expire_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
view_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
revoked: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, server_default=func.now()
)
__table_args__ = (UniqueConstraint("share_token", name="uq_rp_share_token"),)