Files
shaguabijia-app-server/tests/test_coupon_slots.py
T
guke f984b07a0c feat(coupon-data): 领券成功率看板(整单/点位/分平台 + 按券)
- coupon_session.platform_success:整单成功率②/点位成功率③ + 分平台点位成功率
- coupon_claim_record.app_env:按 coupon_id「按券成功率」聚合 + 子端点 /admin/api/coupon-data/coupons
- /step 复用 record_claims 同一 SessionLocal 写入(fire-and-forget),pricebot 转发/负载不变
- 迁移 coupon_session_platform_success + coupon_claim_app_env;新增 14 测试
- 设计见 docs/guides/领券成功率指标-设计与埋点.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 15:26:52 +08:00

138 lines
5.8 KiB
Python

"""每券成功率(§13):app_env 落库 + coupon_slot_report 聚合。"""
from __future__ import annotations
from datetime import UTC, date, datetime
from sqlalchemy import delete, select
from app.admin.repositories.coupon_data import coupon_slot_report
from app.db.session import SessionLocal
from app.models.coupon_state import CouponClaimRecord, CouponSession
from app.repositories.coupon_state import record_claims, session_app_env
def test_session_app_env_lookup() -> None:
db = SessionLocal()
trace = "slot-env-1"
try:
db.add(CouponSession(
trace_id=trace, device_id="d-slot", status="started", app_env="dev",
started_at=datetime(2020, 2, 1, tzinfo=UTC), started_date=date(2020, 2, 1),
))
db.commit()
assert session_app_env(db, trace) == "dev"
assert session_app_env(db, "no-such-trace") is None
assert session_app_env(db, None) is None
finally:
db.execute(delete(CouponSession).where(CouponSession.trace_id == trace))
db.commit()
db.close()
def test_record_claims_stamps_app_env() -> None:
db = SessionLocal()
dev = "d-slot-stamp"
try:
record_claims(db, dev, None, "t-stamp",
[{"coupon_id": "mt_x", "status": "success", "name": "美团券"}],
app_env="prod")
row = db.execute(select(CouponClaimRecord).where(
CouponClaimRecord.device_id == dev)).scalar_one()
assert row.app_env == "prod"
# UPDATE 路:app_env=None 不覆盖已有值
record_claims(db, dev, None, "t-stamp",
[{"coupon_id": "mt_x", "status": "already_claimed", "name": "美团券"}],
app_env=None)
db.expire_all()
row = db.execute(select(CouponClaimRecord).where(
CouponClaimRecord.device_id == dev)).scalar_one()
assert row.app_env == "prod"
assert row.status == "already_claimed"
finally:
db.execute(delete(CouponClaimRecord).where(CouponClaimRecord.device_id == dev))
db.commit()
db.close()
def _claim(db, device, coupon_id, status, app_env, name=None, d=date(2020, 2, 2)):
db.add(CouponClaimRecord(
device_id=device, coupon_id=coupon_id, claim_date=d,
status=status, coupon_name=name, app_env=app_env,
))
def test_coupon_slot_report_aggregates() -> None:
"""按 coupon_id 聚合:tried=成功+失败(skipped 排除)、rate、env 过滤、tried 倒序。"""
db = SessionLocal()
try:
# mt_a(prod):dev1 success + dev2 already_claimed + dev3 failed → 2/3;dev4 skipped 不计
_claim(db, "sdev1", "mt_a", "success", "prod", "美团外卖红包天天领")
_claim(db, "sdev2", "mt_a", "already_claimed", "prod", "美团外卖红包天天领")
_claim(db, "sdev3", "mt_a", "failed", "prod", "美团外卖红包天天领")
_claim(db, "sdev4", "mt_a", "skipped", "prod", "美团外卖红包天天领")
_claim(db, "sdev1", "tb_b", "success", "prod", "淘宝券") # 1/1
_claim(db, "sdev9", "mt_a", "failed", "dev", "美团外卖红包天天领") # 仅 dev/全部
db.commit()
prod = coupon_slot_report(
db, date_from="2020-02-02", date_to="2020-02-02", app_env="prod"
)["items"]
by_id = {r["coupon_id"]: r for r in prod}
assert by_id["mt_a"]["tried"] == 3 # skipped 排除
assert by_id["mt_a"]["succeeded"] == 2
assert by_id["mt_a"]["success_rate"] == round(2 / 3, 4)
assert by_id["mt_a"]["platform"] == "meituan-waimai"
assert by_id["mt_a"]["coupon_name"] == "美团外卖红包天天领"
assert by_id["tb_b"]["success_rate"] == 1.0
assert by_id["tb_b"]["platform"] == "taobao-shanguang"
assert [r["coupon_id"] for r in prod] == ["mt_a", "tb_b"] # tried 倒序
dev = coupon_slot_report(
db, date_from="2020-02-02", date_to="2020-02-02", app_env="dev"
)["items"]
assert {r["coupon_id"]: r["tried"] for r in dev} == {"mt_a": 1}
allenv = coupon_slot_report(
db, date_from="2020-02-02", date_to="2020-02-02", app_env=None
)["items"]
assert {r["coupon_id"]: r["tried"] for r in allenv}["mt_a"] == 4
finally:
db.execute(delete(CouponClaimRecord).where(CouponClaimRecord.claim_date == date(2020, 2, 2)))
db.commit()
db.close()
def test_coupon_slots_endpoint() -> None:
from fastapi.testclient import TestClient
from app.admin.main import admin_app
from app.admin.repositories import admin_user as admin_repo
from app.admin.security import create_admin_token
db = SessionLocal()
try:
_claim(db, "epdev1", "mt_ep", "success", "prod", "端点券", d=date(2020, 2, 3))
_claim(db, "epdev2", "mt_ep", "failed", "prod", "端点券", d=date(2020, 2, 3))
admin = admin_repo.create_admin(
db, username="slot_admin", password="pass1234", role="super_admin"
)
token, _exp = create_admin_token(admin_id=admin.id, role=admin.role)
db.commit()
finally:
db.close()
try:
c = TestClient(admin_app)
r = c.get(
"/admin/api/coupon-data/coupons",
params={"date_from": "2020-02-03", "date_to": "2020-02-03", "app_env": "prod"},
headers={"Authorization": f"Bearer {token}"},
)
assert r.status_code == 200, r.text
row = next(x for x in r.json()["items"] if x["coupon_id"] == "mt_ep")
assert row["tried"] == 2 and row["succeeded"] == 1 and row["success_rate"] == 0.5
assert row["coupon_name"] == "端点券" and row["platform"] == "meituan-waimai"
finally:
db = SessionLocal()
db.execute(delete(CouponClaimRecord).where(CouponClaimRecord.claim_date == date(2020, 2, 3)))
db.commit()
db.close()