Files
MBTI_wang/scripts/seed_ent5_orders_and_distribution_mysql.py

402 lines
14 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
为指定企业补「订单运营 + 分销推广」种子数据,量级与 wechat_users 池大致匹配(取总用户约 1/12 订单条数作中间值)。
- ordersenterpriseId 对齐orderNo 前缀 SEED5_ 可幂等跳过
- 为已支付/已完成订单写入一条 test_results带 orderId供分销「产品佣金分布」按测评类型归类
- distribution_agents + distribution_bindingsenterprise 维度)+ commission_records
读 api/.env 的 DATABASE_*。
用法:
python3 scripts/seed_ent5_orders_and_distribution_mysql.py --enterprise-id 5
python3 scripts/seed_ent5_orders_and_distribution_mysql.py --enterprise-id 5 --dry-run
"""
from __future__ import annotations
import argparse
import hashlib
import random
import re
import sys
import time
from pathlib import Path
try:
import pymysql
except ImportError:
sys.exit("需要: pip install pymysql")
ROOT = Path(__file__).resolve().parents[1]
ENV_PATH = ROOT / "api" / ".env"
ORDER_PREFIX = "SEED5_"
def load_env(path: Path) -> dict[str, str]:
cfg: dict[str, str] = {}
if not path.is_file():
return cfg
for line in path.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
m = re.match(r"^([A-Z0-9_]+)\s*=\s*(.*)$", line)
if m:
cfg[m.group(1)] = m.group(2).strip()
return cfg
def pick_type(i: int) -> str:
r = (i * 7919) % 100
if r < 55:
return "face"
if r < 72:
return "mbti"
if r < 86:
return "disc"
return "pdp"
def product_title(pt: str) -> str:
return {
"face": "AI人脸性格分析完整报告",
"mbti": "MBTI 职业性格测评",
"disc": "DISC 行为风格测评",
"pdp": "PDP 天赋特质测评",
}.get(pt, "测评服务")
def mini_result_json(pt: str) -> str:
import json
if pt == "mbti":
return json.dumps({"mbtiType": "INTJ", "type": "INTJ"}, ensure_ascii=False)
if pt == "disc":
return json.dumps({"description": {"type": "D型"}}, ensure_ascii=False)
if pt == "pdp":
return json.dumps({"description": {"type": "老虎型"}}, ensure_ascii=False)
return json.dumps(
{"overview": "订单关联占位", "mbti": {"type": "INTJ"}},
ensure_ascii=False,
)
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--enterprise-id", type=int, default=5)
ap.add_argument("--dry-run", action="store_true")
ap.add_argument("--inviters", type=int, default=80)
ap.add_argument("--bindings", type=int, default=520)
args = ap.parse_args()
eid = args.enterprise_id
env = load_env(ENV_PATH)
host = env.get("DATABASE_HOSTNAME", "127.0.0.1")
port = int(env.get("DATABASE_HOSTPORT", "3306"))
user = env.get("DATABASE_USERNAME", "root")
password = env.get("DATABASE_PASSWORD", "")
database = env.get("DATABASE_DATABASE", "mbti")
prefix = env.get("DATABASE_PREFIX", "mbti_")
orders_t = f"{prefix}orders"
tr_t = f"{prefix}test_results"
wu_t = f"{prefix}wechat_users"
ag_t = f"{prefix}distribution_agents"
bd_t = f"{prefix}distribution_bindings"
cr_t = f"{prefix}commission_records"
conn = pymysql.connect(
host=host,
port=port,
user=user,
password=password,
database=database,
charset="utf8mb4",
autocommit=False,
)
rnd = random.Random(20260330 + eid)
now = int(time.time())
try:
with conn.cursor() as cur:
cur.execute(
f"SELECT COUNT(*) FROM `{wu_t}` WHERE `enterpriseId`=%s",
(eid,),
)
n_users = int(cur.fetchone()[0])
cur.execute(
f"SELECT `id`,`nickname` FROM `{wu_t}` WHERE `enterpriseId`=%s ORDER BY `id` ASC",
(eid,),
)
rows = cur.fetchall()
user_ids = [int(r[0]) for r in rows]
nick_map = {int(r[0]): (r[1] or "") for r in rows}
if not user_ids:
print({"ok": False, "error": "no users"})
return
target_orders = max(400, min(1400, n_users // 12))
existing_seed = 0
with conn.cursor() as cur:
cur.execute(
f"SELECT COUNT(*) FROM `{orders_t}` WHERE `orderNo` LIKE %s",
(ORDER_PREFIX + "%",),
)
existing_seed = int(cur.fetchone()[0])
if existing_seed >= target_orders * 0.9 and not args.dry_run:
print(
{
"ok": True,
"skipped": True,
"reason": "SEED5 orders already near target",
"existing_seed_orders": existing_seed,
"target_orders": target_orders,
}
)
return
need_orders = max(0, target_orders - existing_seed)
if args.dry_run:
print(
{
"ok": True,
"dry_run": True,
"n_users": n_users,
"target_orders": target_orders,
"existing_seed": existing_seed,
"would_insert_orders": need_orders,
"inviters": min(args.inviters, len(user_ids) // 4),
"bindings_cap": min(args.bindings, len(user_ids) // 2),
}
)
return
inviter_n = min(args.inviters, max(20, len(user_ids) // 6))
inviters = user_ids[:inviter_n]
pool_bind = user_ids[inviter_n + 50 : inviter_n + 50 + args.bindings]
if len(pool_bind) < 100:
pool_bind = user_ids[inviter_n + 10 :]
# 1) distribution_agents
inviter_to_agent: dict[int, int] = {}
with conn.cursor() as cur:
for uid in inviters:
cur.execute(f"SELECT id FROM `{ag_t}` WHERE userId=%s LIMIT 1", (uid,))
ex = cur.fetchone()
if ex:
inviter_to_agent[uid] = int(ex[0])
continue
name = (nick_map.get(uid) or f"用户{uid}")[:100]
cur.execute(
f"""
INSERT INTO `{ag_t}`
(`userId`,`agentName`,`contactPhone`,`contactEmail`,`totalOrders`,`totalCommission`,
`availableCommission`,`status`,`createdAt`,`updatedAt`)
VALUES (%s,%s,NULL,NULL,0,0,0,1,%s,%s)
""",
(uid, name, now, now),
)
inviter_to_agent[uid] = int(cur.lastrowid)
conn.commit()
# 2) orders + linked test_results (paid/completed only)
order_rows: list[tuple] = []
expire_at = now + 86400 * 400
ins_order = f"""
INSERT INTO `{orders_t}`
(`orderNo`,`userId`,`enterpriseId`,`productType`,`productTitle`,`amount`,`status`,
`payMethod`,`payTime`,`createdAt`,`updatedAt`)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
"""
ins_tr = f"""
INSERT INTO `{tr_t}`
(`userId`,`testType`,`resultData`,`requiresPayment`,`isPaid`,`paidAmount`,`paidAt`,
`createdAt`,`updatedAt`,`enterpriseId`,`testScope`,`orderId`)
VALUES (%s,%s,%s,0,1,%s,%s,%s,%s,%s,'enterprise',%s)
"""
for k in range(need_orders):
uid = user_ids[(k * 9973) % len(user_ids)]
pt = pick_type(k)
amt = rnd.choice([100, 150, 200, 299, 399, 101, 201])
st = rnd.choices(
["paid", "completed", "pending"],
weights=[42, 28, 30],
k=1,
)[0]
ts = now - rnd.randint(0, 86400 * 20)
pay_t = ts if st in ("paid", "completed") else None
h = hashlib.md5(f"{uid}{k}{ts}".encode()).hexdigest()[:10]
ono = f"{ORDER_PREFIX}{ts}{uid}{k}{h}"[:48]
order_rows.append(
(ono, uid, eid, pt, product_title(pt), amt, st, "wechat", pay_t, ts, ts)
)
oid_map: list[int] = []
with conn.cursor() as cur:
for row in order_rows:
cur.execute(ins_order, row)
oid_map.append(int(cur.lastrowid))
oid = oid_map[-1]
st = row[6]
if st not in ("paid", "completed"):
continue
uid = row[1]
pt = row[3]
amt = row[5]
ts = row[9]
pay_t = row[8] or ts
cur.execute(
ins_tr,
(
uid,
pt,
mini_result_json(pt),
amt,
pay_t,
ts,
ts,
eid,
oid,
),
)
conn.commit()
# 3) bindings
bindings_done = 0
binding_ids: list[tuple[int, int, int]] = [] # id, inviter, invitee
with conn.cursor() as cur:
for i, invitee in enumerate(pool_bind[: args.bindings]):
inv = inviters[i % len(inviters)]
if invitee == inv:
continue
cur.execute(
f"""
SELECT id FROM `{bd_t}`
WHERE inviteeId=%s AND scope='enterprise' AND enterpriseId=%s
""",
(invitee, eid),
)
if cur.fetchone():
continue
tsb = now - rnd.randint(86400, 86400 * 60)
cur.execute(
f"""
INSERT INTO `{bd_t}`
(`inviterId`,`inviteeId`,`scope`,`enterpriseId`,`expireAt`,`status`,
`prevInviterId`,`overriddenAt`,`createdAt`,`updatedAt`)
VALUES (%s,%s,'enterprise',%s,%s,'active',NULL,NULL,%s,%s)
""",
(inv, invitee, eid, expire_at, tsb, tsb),
)
bid = int(cur.lastrowid)
binding_ids.append((bid, inv, invitee))
bindings_done += 1
conn.commit()
# 4) commission_records与订单/绑定挂钩)
ins_cr = f"""
INSERT INTO `{cr_t}`
(`agentId`,`orderId`,`commissionRate`,`commissionAmount`,`status`,`paidAt`,
`createdAt`,`updatedAt`,`scope`,`enterpriseId`,`inviterId`,`inviteeId`,
`bindingId`,`orderAmount`,`commissionFen`,`frozenAt`,`unfrozenAt`,
`testResultId`,`commissionSource`)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,'enterprise',%s,%s,%s,%s,%s,%s,NULL,NULL,NULL,%s)
"""
cr_n = 0
with conn.cursor() as cur:
for j, oid in enumerate(oid_map[: min(len(oid_map), 450)]):
row = order_rows[j]
if row[6] not in ("paid", "completed"):
continue
uid_buyer = row[1]
inv = inviters[j % len(inviters)]
if uid_buyer == inv:
inv = inviters[(j + 1) % len(inviters)]
agent_id = inviter_to_agent.get(inv)
if not agent_id:
continue
bid_row = next((b for b in binding_ids if b[2] == uid_buyer), None)
bid = bid_row[0] if bid_row else None
oamt = row[5]
cfen = max(10, min(500, int(oamt * rnd.uniform(0.08, 0.22))))
st_f = rnd.choices(["paid", "frozen"], weights=[82, 18], k=1)[0]
paid_at = now - rnd.randint(0, 86400 * 14) if st_f == "paid" else None
tsr = paid_at or (now - rnd.randint(0, 86400 * 10))
camt = round(cfen / 100, 2)
cur.execute(
ins_cr,
(
agent_id,
oid,
10.0,
camt,
st_f,
paid_at,
tsr,
tsr,
eid,
inv,
uid_buyer,
bid,
oamt,
cfen,
"payment",
),
)
cr_n += 1
extra = min(120, len(binding_ids))
for j in range(extra):
bid, inv, invitee = binding_ids[j]
agent_id = inviter_to_agent.get(inv)
if not agent_id:
continue
cfen = rnd.randint(20, 180)
st_f = "paid"
tsr = now - rnd.randint(0, 86400 * 7)
cur.execute(
ins_cr,
(
agent_id,
None,
0.0,
round(cfen / 100, 2),
st_f,
tsr,
tsr,
tsr,
eid,
inv,
invitee,
bid,
0,
cfen,
"seed_invite",
),
)
cr_n += 1
conn.commit()
print(
{
"ok": True,
"enterpriseId": eid,
"n_users": n_users,
"inserted_orders": len(oid_map),
"bindings_inserted": bindings_done,
"commission_rows": cr_n,
"note": "已支付订单各带 1 条 test_results(orderId),概览「已完成测试」会增加;分销饼图按订单关联测评类型归类。",
}
)
finally:
conn.close()
if __name__ == "__main__":
main()