schemas.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. """Pydantic v2 request/response models for the 拼团 (group-buy) funnel query API.
  2. Field names match the API contract in docs/02 §5 (v3) exactly:
  3. request: period, snapshot_dt (optional)
  4. response: period, snapshot_dt,
  5. results[].step_index, results[].name, results[].event_key,
  6. results[].uv, results[].conversion_rate, results[].dropoff_rate,
  7. data_status
  8. """
  9. from __future__ import annotations
  10. from datetime import date
  11. from enum import Enum
  12. from pydantic import BaseModel, Field
  13. class Period(str, Enum):
  14. """Supported periods (docs/02 §5 v3).
  15. Routing:
  16. day -> table ads_trd_group_funnel_daily (single day, full history).
  17. last_7d -> table ads_trd_group_funnel_rolling, columns uv_*_7d.
  18. last_30d -> table ads_trd_group_funnel_rolling, columns uv_*_30d.
  19. Any other value is rejected with HTTP 422.
  20. """
  21. day = "day"
  22. last_7d = "last_7d"
  23. last_30d = "last_30d"
  24. class FunnelQueryRequest(BaseModel):
  25. """Funnel query request body.
  26. ``snapshot_dt`` (ISO ``YYYY-MM-DD``) is optional and only meaningful for
  27. ``period=day``: omitted -> latest daily row; given -> that historical day
  28. (must be <= yesterday). For ``last_7d`` / ``last_30d`` it is ignored.
  29. """
  30. period: Period
  31. snapshot_dt: date | None = Field(
  32. None,
  33. description=(
  34. "Optional ISO date (YYYY-MM-DD). Only meaningful for period=day; "
  35. "ignored for last_7d/last_30d. Must be <= yesterday."
  36. ),
  37. )
  38. class DataStatus(str, Enum):
  39. """Data completeness status for a query result (docs/02 §5 v3).
  40. ready - the target row exists and the period's columns are non-null.
  41. missing - no target row, or the period columns are NULL. Never zero-filled.
  42. """
  43. ready = "ready"
  44. missing = "missing"
  45. class FunnelStepResult(BaseModel):
  46. """Computed UV and conversion metrics for one fixed step."""
  47. step_index: int = Field(..., ge=1, description="1-based step index")
  48. name: str = Field(..., description="Chinese display name of the step")
  49. event_key: str = Field(..., description="Stable step key (start/show/...)")
  50. uv: int = Field(..., ge=0)
  51. conversion_rate: float | None = Field(
  52. None, description="uv[i] / uv[i-1]; null for step 1 or when uv[i-1] == 0"
  53. )
  54. dropoff_rate: float | None = Field(
  55. None, description="1 - conversion_rate; null when conversion_rate is null"
  56. )
  57. class FunnelQueryResponse(BaseModel):
  58. """Funnel query response body."""
  59. period: Period
  60. snapshot_dt: str | None = Field(
  61. None,
  62. description=(
  63. "dt (yyyyMMdd) of the row actually used; for day = that day, for "
  64. "7d/30d = the rolling row's as-of dt. null when missing."
  65. ),
  66. )
  67. results: list[FunnelStepResult]
  68. data_status: DataStatus
  69. # --------------------------------------------------------------------------- #
  70. # Trend (按日折线) — daily time series over ads_trd_group_funnel_daily #
  71. # --------------------------------------------------------------------------- #
  72. class FunnelTrendRequest(BaseModel):
  73. """Trend query request body.
  74. Both bounds optional (ISO ``YYYY-MM-DD``). Omitted -> the 30 most recent
  75. available days ending at the latest daily ``dt``. ``end_dt`` must be <=
  76. yesterday (T+1); ``start_dt`` must be <= ``end_dt``.
  77. """
  78. start_dt: date | None = Field(
  79. None, description="Inclusive start (YYYY-MM-DD). Default: end - 29 days."
  80. )
  81. end_dt: date | None = Field(
  82. None,
  83. description="Inclusive end (YYYY-MM-DD). Default: latest available day. <= yesterday.",
  84. )
  85. class FunnelTrendPoint(BaseModel):
  86. """One day in the trend series: its ``dt`` + the full per-step funnel.
  87. Reuses :class:`FunnelStepResult` so uv + conversion_rate match the funnel
  88. query口径 exactly (computed by the same server code).
  89. """
  90. dt: str = Field(..., description="Day as yyyyMMdd")
  91. results: list[FunnelStepResult]
  92. class FunnelTrendResponse(BaseModel):
  93. """Trend query response body.
  94. ``points`` is ascending by ``dt``, with leading all-zero (pre-launch) days
  95. trimmed. ``start_dt`` / ``end_dt`` are the bounds of the returned series
  96. (yyyyMMdd) — i.e. the first kept day and the last available day — null when
  97. ``missing``. Calendar gaps inside the range are simply absent points (the
  98. client breaks the line; no zero-fill).
  99. """
  100. start_dt: str | None = Field(None, description="First kept day (yyyyMMdd); null when missing.")
  101. end_dt: str | None = Field(None, description="Last day (yyyyMMdd); null when missing.")
  102. data_status: DataStatus
  103. points: list[FunnelTrendPoint]