seed.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. """Seed realistic rows into the group-buy funnel tables for local dev.
  2. Inserts:
  3. * ~10 historical daily rows (distinct ``dt``) into ``ads_trd_group_funnel_daily``,
  4. ending yesterday, so the single-day date picker has history;
  5. * one rolling row as-of yesterday into ``ads_trd_group_funnel_rolling`` for the
  6. 7d/30d windows.
  7. Each row carries a clean descending group-buy funnel
  8. (start > show > detail > order > paid).
  9. Run from apps/api (needs a live database; set USE_FAKE_DATA=false to read it):
  10. python -m scripts.seed
  11. # or
  12. python scripts/seed.py
  13. """
  14. from __future__ import annotations
  15. import asyncio
  16. import sys
  17. from datetime import date, datetime, timedelta
  18. from pathlib import Path
  19. # Allow running as a bare script.
  20. sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
  21. from sqlalchemy import delete # noqa: E402
  22. from app.db.models import ( # noqa: E402
  23. AdsTrdGroupFunnelDaily,
  24. AdsTrdGroupFunnelRolling,
  25. )
  26. from app.db.session import get_engine, get_sessionmaker # noqa: E402
  27. # Number of historical daily rows to insert (ending yesterday).
  28. HISTORY_DAYS = 10
  29. # Top-step (start) UV for the most recent daily row and the rolling windows.
  30. DAILY_TOP = 12000
  31. ROLLING_TOP_7D = 74000
  32. ROLLING_TOP_30D = 295000
  33. # Cumulative keep-rate down the fixed funnel: start, show, detail, order, paid.
  34. FUNNEL_KEEP = [1.0, 0.82, 0.51, 0.22, 0.18]
  35. STEP_KEYS = ["start", "show", "detail", "order", "paid"]
  36. def _funnel_for(top: int) -> list[int]:
  37. """Return descending UVs for the five steps given the top-step UV."""
  38. return [max(0, int(top * keep)) for keep in FUNNEL_KEEP]
  39. async def seed() -> None:
  40. today = date.today()
  41. yesterday = today - timedelta(days=1)
  42. now = datetime.utcnow()
  43. sessionmaker = get_sessionmaker()
  44. async with sessionmaker() as session:
  45. # Idempotent: clear previous seed data in both tables.
  46. await session.execute(delete(AdsTrdGroupFunnelDaily))
  47. await session.execute(delete(AdsTrdGroupFunnelRolling))
  48. # Daily history rows, ending yesterday; older days a touch smaller.
  49. for day_offset in range(HISTORY_DAYS):
  50. snapshot_day = yesterday - timedelta(days=day_offset)
  51. dt = snapshot_day.strftime("%Y%m%d")
  52. scale = 1.0 - 0.04 * day_offset
  53. uvs = _funnel_for(int(DAILY_TOP * scale))
  54. session.add(
  55. AdsTrdGroupFunnelDaily(
  56. dt=dt,
  57. etl_time=now,
  58. **{f"uv_{key}": uv for key, uv in zip(STEP_KEYS, uvs)},
  59. )
  60. )
  61. # Single rolling row, as-of yesterday.
  62. uvs_7d = _funnel_for(ROLLING_TOP_7D)
  63. uvs_30d = _funnel_for(ROLLING_TOP_30D)
  64. rolling_values: dict[str, int] = {}
  65. for key, uv in zip(STEP_KEYS, uvs_7d):
  66. rolling_values[f"uv_{key}_7d"] = uv
  67. for key, uv in zip(STEP_KEYS, uvs_30d):
  68. rolling_values[f"uv_{key}_30d"] = uv
  69. session.add(
  70. AdsTrdGroupFunnelRolling(
  71. dt=yesterday.strftime("%Y%m%d"),
  72. etl_time=now,
  73. **rolling_values,
  74. )
  75. )
  76. await session.commit()
  77. await get_engine().dispose()
  78. print(
  79. f"seed complete: inserted {HISTORY_DAYS} daily rows + 1 rolling row "
  80. f"(as-of {yesterday.strftime('%Y%m%d')})"
  81. )
  82. def main() -> None:
  83. asyncio.run(seed())
  84. if __name__ == "__main__":
  85. main()