Compare commits

..

5 Commits

Author SHA1 Message Date
unknown e2d6e72d23 合并:兼容比价设备信息与提现汇总更新 2026-07-27 16:43:36 +08:00
linkeyu 36ce18a250 修复比价记录机型与 ROM 版本展示 (#183)
## 改动说明
- 比价记录列表和详情接口补充可读机型名
- 列表接口返回 ROM 大版本
- 保留原始设备编码,方便排查
- 增加列表与详情接口回归测试

## 验证
- 后端测试:20 项通过

---------

Co-authored-by: guke <guke@wonderable.ai>
Co-authored-by: unknown <798648091@qq.com>
Reviewed-on: #183
Co-authored-by: linkeyu <linkeyu@wonderable.ai>
Co-committed-by: linkeyu <linkeyu@wonderable.ai>
2026-07-27 16:38:56 +08:00
linkeyu 58d609e5d2 修复提现审核汇总按来源和状态统计 (#185)
## 问题

邀请提现页的汇总接口未按提现来源过滤,导致页签数字、顶部“待审核/待审核金额”与邀请提现细则不一致;同时接口缺少已到账、已拒绝和全部的历史总数。

## 改动

- 汇总接口支持并校验 `source=coin_cash|invite_cash`
- 页签数量及顶部待审核数量、金额统一按提现来源过滤
- 增加 `success_count`、`rejected_count` 和 `total_count`
- 保留今日到账/拒绝指标,并同步按来源过滤
- 增加逐来源、逐状态校验“汇总数 = 列表 total”的回归测试
- 增加“顶部待审核金额 = 当前来源待审核细则金额之和”的回归测试

## 验证

- 提现审核专项测试通过
- 后台读取接口测试:19 项通过
- Ruff(排除主干既有规则告警)通过
- 线上数据库只读复核:其他提现与邀请提现的待审核数量、金额已按来源分别核算

---------

Co-authored-by: unknown <798648091@qq.com>
Reviewed-on: #185
Co-authored-by: linkeyu <linkeyu@wonderable.ai>
Co-committed-by: linkeyu <linkeyu@wonderable.ai>
2026-07-27 16:26:02 +08:00
unknown 959d2d53e7 合并:解决比价下单状态与最新主干冲突 2026-07-27 16:16:51 +08:00
unknown 51ce90afef 功能:为比价记录补充是否下单
后台比价记录列表和详情按真实下单口径批量标记下单状态,并增加接口回归测试。
2026-07-27 14:56:55 +08:00
3 changed files with 153 additions and 1 deletions
+38
View File
@@ -63,6 +63,40 @@ def _device_marketing_name(model: str | None) -> str | None:
return _DEVICE_MARKETING_NAMES.get(model.strip().upper())
def _attach_comparison_order_status(db: Session, items: list[ComparisonRecord]) -> None:
"""按 C 端既有口径给比价记录批量补充是否真实下单。"""
user_ids = {item.user_id for item in items if item.user_id is not None}
shop_names = {item.store_name for item in items if item.store_name}
ordered_pairs: set[tuple[int, str]] = set()
if user_ids and shop_names:
rows = db.execute(
select(SavingsRecord.user_id, SavingsRecord.shop_name)
.where(
SavingsRecord.user_id.in_(user_ids),
SavingsRecord.source == "compare",
SavingsRecord.shop_name.in_(shop_names),
)
.distinct()
).all()
ordered_pairs = {
(row.user_id, row.shop_name)
for row in rows
if row.shop_name is not None
}
for item in items:
item.ordered = bool(
item.user_id is not None
and item.store_name
and (item.user_id, item.store_name) in ordered_pairs
)
def _attach_comparison_device_details(items: list[ComparisonRecord]) -> None:
"""给比价记录补充可读机型名,同时保留原始设备编码。"""
for item in items:
item.device_model_name = _device_marketing_name(item.device_model)
def _attach_feedback_device_details(db: Session, feedbacks: list[Feedback]) -> None:
"""按同一用户、同一设备编码及提交时间补齐厂商和 ROM 大版本。"""
candidates = [
@@ -344,6 +378,8 @@ def list_comparison_records(
limit=limit, cursor=cursor,
)
_attach_user_info(db, items)
_attach_comparison_order_status(db, items)
_attach_comparison_device_details(items)
# 「本次比价看广告的预估收益」:按本页 trace_id 一次性聚合(同 _attach_user_info 逐页范式)。
# ad_revenue_yuan 非 ORM 列,仅瞬态挂实例上供 AdminComparisonListItem(from_attributes)读出。
rev = ad_ecpm.revenue_yuan_by_trace(db, [it.trace_id for it in items])
@@ -490,6 +526,8 @@ def get_comparison_record(db: Session, record_id: int) -> ComparisonRecord | Non
rec = db.get(ComparisonRecord, record_id)
if rec is not None:
_attach_user_info(db, [rec])
_attach_comparison_order_status(db, [rec])
_attach_comparison_device_details([rec])
return rec
+3 -1
View File
@@ -29,6 +29,7 @@ class AdminComparisonListItem(BaseModel):
source_price_cents: int | None = None
best_price_cents: int | None = None
saved_amount_cents: int | None = None
ordered: bool = False
# debug 概览
total_ms: int | None = None
step_count: int | None = None
@@ -39,8 +40,10 @@ class AdminComparisonListItem(BaseModel):
# 本次比价 LLM 总成本(元,按当时价冻结);旧记录/未回填为 None → 前端「成本」列回退估算。见 services/llm_cost.py。
llm_cost_yuan: float | None = None
device_model: str | None = None
device_model_name: str | None = None
rom_vendor: str | None = None
rom_name: str | None = None
rom_version: int | None = None
android_version: str | None = None
app_version: str | None = None
ad_revenue_yuan: float = 0.0 # 本次比价看的信息流广告预估收益(元),queries 瞬态挂 ORM 实例上
@@ -84,7 +87,6 @@ class AdminComparisonDetail(AdminComparisonListItem):
skipped_dish_names: list = []
# 全量环境
device_manufacturer: str | None = None
rom_version: int | None = None
android_sdk: int | None = None
app_version_code: int | None = None
source_app_version: str | None = None
+112
View File
@@ -593,6 +593,118 @@ def test_withdraw_summary_counts_match_status_lists_by_source(
).status_code == 422
def test_comparison_records_show_readable_device_and_rom_version(
admin_client: TestClient, admin_token: str
) -> None:
db = SessionLocal()
try:
user = user_repo.upsert_user_for_login(
db, phone="13800009041", register_channel="sms"
)
record = ComparisonRecord(
user_id=user.id,
trace_id="comparison-readable-device",
status="success",
device_model="V2166BA",
device_manufacturer="vivo",
rom_vendor="vivo",
rom_name="OriginOS",
rom_version=4,
android_version="14",
)
db.add(record)
db.commit()
record_id = record.id
user_id = user.id
finally:
db.close()
response = admin_client.get(
"/admin/api/comparison-records",
params={"user_id": user_id},
headers=_auth(admin_token),
)
assert response.status_code == 200, response.text
item = response.json()["items"][0]
assert item["device_model"] == "V2166BA"
assert item["device_model_name"] == "vivo Y77e"
assert item["rom_name"] == "OriginOS"
assert item["rom_version"] == 4
detail = admin_client.get(
f"/admin/api/comparison-records/{record_id}",
headers=_auth(admin_token),
)
assert detail.status_code == 200, detail.text
assert detail.json()["device_model_name"] == "vivo Y77e"
assert detail.json()["rom_version"] == 4
def test_comparison_records_show_real_order_status(
admin_client: TestClient, admin_token: str
) -> None:
db = SessionLocal()
try:
user = user_repo.upsert_user_for_login(
db, phone="13800009042", register_channel="sms"
)
ordered_record = ComparisonRecord(
user_id=user.id,
trace_id="comparison-ordered-shop",
status="success",
store_name="真实下单店",
)
unordered_record = ComparisonRecord(
user_id=user.id,
trace_id="comparison-demo-order-shop",
status="success",
store_name="演示下单店",
)
db.add_all([ordered_record, unordered_record])
db.flush()
db.add_all(
[
SavingsRecord(
user_id=user.id,
order_amount_cents=1800,
saved_amount_cents=300,
shop_name="真实下单店",
source="compare",
client_event_id="admin-comparison-real-order",
),
SavingsRecord(
user_id=user.id,
order_amount_cents=1500,
saved_amount_cents=200,
shop_name="演示下单店",
source="demo",
),
]
)
db.commit()
ordered_record_id = ordered_record.id
user_id = user.id
finally:
db.close()
response = admin_client.get(
"/admin/api/comparison-records",
params={"user_id": user_id},
headers=_auth(admin_token),
)
assert response.status_code == 200, response.text
by_trace = {item["trace_id"]: item for item in response.json()["items"]}
assert by_trace["comparison-ordered-shop"]["ordered"] is True
assert by_trace["comparison-demo-order-shop"]["ordered"] is False
detail = admin_client.get(
f"/admin/api/comparison-records/{ordered_record_id}",
headers=_auth(admin_token),
)
assert detail.status_code == 200, detail.text
assert detail.json()["ordered"] is True
def test_ad_coin_audit_full_count_truncate_and_only_mismatch(
admin_client: TestClient, admin_token: str
) -> None: