schemas.py 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  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