yeref 0.24.59__py3-none-any.whl → 0.24.60__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
yeref/yeref.py
CHANGED
@@ -16122,7 +16122,7 @@ async def return_activity_metrics(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
|
|
16122
16122
|
await asyncio.sleep(round(random.uniform(0, 1), 2))
|
16123
16123
|
|
16124
16124
|
|
16125
|
-
async def
|
16125
|
+
async def return_unit_metrics2(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
|
16126
16126
|
try:
|
16127
16127
|
# собрать всех пользователей
|
16128
16128
|
sql = 'SELECT USER_TID, USER_VARS, USER_LSTS FROM "USER"'
|
@@ -16306,6 +16306,182 @@ async def return_unit_metrics(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
|
|
16306
16306
|
except Exception as e:
|
16307
16307
|
logger.info(log_ % str(e))
|
16308
16308
|
await asyncio.sleep(round(random.uniform(0, 1), 2))
|
16309
|
+
|
16310
|
+
|
16311
|
+
async def return_unit_metrics(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
|
16312
|
+
try:
|
16313
|
+
sql = 'SELECT USER_TID, USER_VARS, USER_LSTS FROM "USER"'
|
16314
|
+
data_users = await db_select_pg(sql, (), BASE_P)
|
16315
|
+
|
16316
|
+
metrics = defaultdict(lambda: {
|
16317
|
+
"new_users": 0,
|
16318
|
+
"sum_amount": 0.0,
|
16319
|
+
"payments_count": 0,
|
16320
|
+
"churn_count": 0
|
16321
|
+
})
|
16322
|
+
seen_new = set()
|
16323
|
+
|
16324
|
+
months = ["2025-06", "2025-07", "2025-08", "2025-09"]
|
16325
|
+
data_users = []
|
16326
|
+
for _ in range(20):
|
16327
|
+
# дата входа
|
16328
|
+
entry_month = random.choice(months)
|
16329
|
+
entry_day = random.randint(1, 28)
|
16330
|
+
entry_date = f"{entry_month}-{entry_day:02}"
|
16331
|
+
entry_dt_obj = datetime.strptime(entry_date, '%Y-%m-%d')
|
16332
|
+
entry_dt = f"{entry_dt_obj.strftime('%d-%m-%Y')}_{datetime.now().strftime('%H-%M-%S')}"
|
16333
|
+
utm = random.choice(["/start", "/startapp"])
|
16334
|
+
|
16335
|
+
# месяцы от входа и дальше
|
16336
|
+
valid_months = [m for m in months if datetime.strptime(m + "-01", "%Y-%m-%d") >= entry_dt_obj.replace(day=1)]
|
16337
|
+
if not valid_months:
|
16338
|
+
valid_months = [entry_month]
|
16339
|
+
|
16340
|
+
user_mau = sorted(random.sample(valid_months, k=random.randint(1, len(valid_months))))
|
16341
|
+
user_dau_dates = set()
|
16342
|
+
txs, payments = [], []
|
16343
|
+
|
16344
|
+
# платеж
|
16345
|
+
if user_mau:
|
16346
|
+
pay_month = random.choice(user_mau)
|
16347
|
+
pay_day = random.randint(1, 28)
|
16348
|
+
pay_date = f"{pay_month}-{pay_day:02}"
|
16349
|
+
dt_pay = datetime.strptime(pay_date, "%Y-%m-%d")
|
16350
|
+
# if dt_pay >= entry_dt_obj:
|
16351
|
+
payments = [{
|
16352
|
+
"TYPE": random.choice(["don", "sub", "pst"]),
|
16353
|
+
"DT_START": f"{dt_pay.strftime('%d-%m-%Y')}_14-00-00",
|
16354
|
+
"DT_END": "0",
|
16355
|
+
"AMOUNT": str(random.randint(1, 10))
|
16356
|
+
}]
|
16357
|
+
user_dau_dates.add(pay_date)
|
16358
|
+
|
16359
|
+
# вход в приложение
|
16360
|
+
user_dau_dates.add(entry_date)
|
16361
|
+
for m in user_mau:
|
16362
|
+
day = random.randint(1, 28)
|
16363
|
+
visit = f"{m}-{day:02}"
|
16364
|
+
dt_visit = datetime.strptime(visit, "%Y-%m-%d")
|
16365
|
+
if dt_visit >= entry_dt_obj and random.random() < 0.7:
|
16366
|
+
user_dau_dates.add(visit)
|
16367
|
+
|
16368
|
+
# статусы (отток) с низкой вероятностью
|
16369
|
+
USER_STATUSES = []
|
16370
|
+
if random.random() < 0.2: # 10% шанс оттока
|
16371
|
+
churn_month = random.choice(valid_months)
|
16372
|
+
churn_day = random.randint(1, 28)
|
16373
|
+
churn_date = f"{churn_month}-{churn_day:02}"
|
16374
|
+
churn_ts = datetime.strptime(churn_date, "%Y-%m-%d").strftime("%d-%m-%Y") + "_23-59-59"
|
16375
|
+
USER_STATUSES = [{random.choice(["left", "kicked"]): churn_ts}]
|
16376
|
+
|
16377
|
+
user_dau = sorted(user_dau_dates)
|
16378
|
+
wallet = f"wallet{random.randint(1, 100)}" if txs else random.choice([f"wallet{random.randint(1, 100)}", ""])
|
16379
|
+
|
16380
|
+
data_users.append((
|
16381
|
+
random.randint(100000, 999999),
|
16382
|
+
json.dumps({"USER_WALLET": wallet, "USER_UTM": utm, "USER_DT": entry_dt}),
|
16383
|
+
json.dumps({"USER_DAU": user_dau, "USER_MAU": user_mau, "USER_TXS": txs,
|
16384
|
+
"USER_PAYMENTS": payments, "USER_STATUSES": USER_STATUSES})
|
16385
|
+
))
|
16386
|
+
print(f"gen {data_users=}")
|
16387
|
+
|
16388
|
+
for USER_TID, USER_VARS, USER_LSTS in data_users:
|
16389
|
+
USER_VARS = json.loads(USER_VARS or "{}")
|
16390
|
+
USER_LSTS = json.loads(USER_LSTS or "{}")
|
16391
|
+
USER_DT = USER_VARS.get("USER_DT", "")
|
16392
|
+
USER_PAYMENTS = USER_LSTS.get("USER_PAYMENTS", [])
|
16393
|
+
USER_STATUSES = USER_LSTS.get("USER_STATUSES", [])
|
16394
|
+
|
16395
|
+
if USER_DT:
|
16396
|
+
mo = datetime.strptime(USER_DT, "%d-%m-%Y_%H-%M-%S").strftime("%Y-%m")
|
16397
|
+
if (USER_TID, mo) not in seen_new:
|
16398
|
+
seen_new.add((USER_TID, mo))
|
16399
|
+
metrics[mo]["new_users"] += 1
|
16400
|
+
|
16401
|
+
for pay in USER_PAYMENTS:
|
16402
|
+
dt_p = datetime.strptime(pay.get("DT_START", ""), "%d-%m-%Y_%H-%M-%S")
|
16403
|
+
mo_p = dt_p.strftime("%Y-%m")
|
16404
|
+
amt = float(pay.get("AMOUNT", 0)) * 0.013
|
16405
|
+
metrics[mo_p]["sum_amount"] += amt
|
16406
|
+
metrics[mo_p]["payments_count"] += 1
|
16407
|
+
|
16408
|
+
for status in USER_STATUSES:
|
16409
|
+
key, ts = next(iter(status.items()))
|
16410
|
+
if key in ("left", "kicked"):
|
16411
|
+
mo_s = datetime.strptime(ts, "%d-%m-%Y_%H-%M-%S").strftime("%Y-%m")
|
16412
|
+
metrics[mo_s]["churn_count"] += 1
|
16413
|
+
break
|
16414
|
+
|
16415
|
+
all_months = sorted(metrics.keys())
|
16416
|
+
cumulative_users = 0
|
16417
|
+
results = []
|
16418
|
+
|
16419
|
+
for idx, mo in enumerate(all_months):
|
16420
|
+
data = metrics[mo]
|
16421
|
+
new_u = data["new_users"]
|
16422
|
+
cumulative_users += new_u
|
16423
|
+
|
16424
|
+
MRR = data["sum_amount"]
|
16425
|
+
def fmt(x):
|
16426
|
+
return f"{x:.2f}".rstrip("0").rstrip(".") if x is not None else ""
|
16427
|
+
|
16428
|
+
mrr_fmt = fmt(MRR)
|
16429
|
+
N = cumulative_users
|
16430
|
+
ARPU = MRR / N if N else None
|
16431
|
+
ARR = MRR * 12 if N else None
|
16432
|
+
|
16433
|
+
churn = data["churn_count"]
|
16434
|
+
ChurnR = churn / N if N else None
|
16435
|
+
LTV1 = (ARPU / ChurnR) if (ARPU is not None and ChurnR and ChurnR > 0) else None
|
16436
|
+
|
16437
|
+
pay_cnt = data["payments_count"]
|
16438
|
+
if N and pay_cnt:
|
16439
|
+
LTV2 = (pay_cnt / N) * (MRR / pay_cnt)
|
16440
|
+
else:
|
16441
|
+
LTV2 = None
|
16442
|
+
|
16443
|
+
if idx == 0:
|
16444
|
+
CMGR = None
|
16445
|
+
first_mrr = MRR
|
16446
|
+
else:
|
16447
|
+
CMGR = ((MRR / first_mrr) ** (1 / idx)) - 1 if first_mrr and MRR is not None else None
|
16448
|
+
|
16449
|
+
results.append({
|
16450
|
+
"MO": mo,
|
16451
|
+
"N": str(N),
|
16452
|
+
"MRR": mrr_fmt,
|
16453
|
+
"ARPU": fmt(ARPU),
|
16454
|
+
"ARR": fmt(ARR),
|
16455
|
+
"ChurnR": fmt(ChurnR),
|
16456
|
+
"LTV1": fmt(LTV1),
|
16457
|
+
"LTV2": fmt(LTV2),
|
16458
|
+
"CMGR": fmt(CMGR),
|
16459
|
+
"CAC": ""
|
16460
|
+
})
|
16461
|
+
|
16462
|
+
path = os.path.join(EXTRA_D, "2_unit_metrics.csv")
|
16463
|
+
with open(path, "w", newline="", encoding="utf-8") as f:
|
16464
|
+
writer = csv.writer(f)
|
16465
|
+
writer.writerow(["MO", "N", "MRR", "ARPU", "ARR", "ChurnR", "LTV1", "LTV2", "CMGR", "CAC"])
|
16466
|
+
for row in results:
|
16467
|
+
writer.writerow([
|
16468
|
+
row["MO"], row["N"], row["MRR"], row["ARPU"], row["ARR"],
|
16469
|
+
row["ChurnR"], row["LTV1"], row["LTV2"], row["CMGR"], row["CAC"]
|
16470
|
+
])
|
16471
|
+
|
16472
|
+
cmgr_vals = [float(r["CMGR"]) for r in results if r["CMGR"] != ""]
|
16473
|
+
if cmgr_vals:
|
16474
|
+
factors = [1 + v for v in cmgr_vals]
|
16475
|
+
avg = math.prod(factors) ** (1 / len(factors))
|
16476
|
+
writer.writerow([])
|
16477
|
+
writer.writerow([f"Rev ~ ×{round(avg, 2)} monthly"])
|
16478
|
+
|
16479
|
+
thumb = types.FSInputFile(os.path.join(EXTRA_D, "parse.jpg"))
|
16480
|
+
await bot.send_document(chat_id=my_tid, document=types.FSInputFile(path), thumbnail=thumb)
|
16481
|
+
except Exception as e:
|
16482
|
+
logger.info(log_ % str(e))
|
16483
|
+
await asyncio.sleep(round(random.uniform(0, 1), 2))
|
16484
|
+
|
16309
16485
|
# endregion
|
16310
16486
|
|
16311
16487
|
|
@@ -0,0 +1,8 @@
|
|
1
|
+
yeref/__init__.py,sha256=Qpv3o6Xa78VdLcsSRmctGtpnYE9btpAkCekgGhgJyXM,49
|
2
|
+
yeref/l_.py,sha256=LMX_olmJwq-tgoALJCnhV_fGrL_i_43yBLkLIcEVqGo,1176743
|
3
|
+
yeref/tonweb.js,sha256=Jf6aFOQ1OIY4q7fINYz-m5LsI3seMus124M5SYYZmtE,443659
|
4
|
+
yeref/yeref.py,sha256=xp4p1Kz_G-4O-ExGlelMry9JppEm7N2Ky6CvwPGII_E,1051287
|
5
|
+
yeref-0.24.60.dist-info/METADATA,sha256=fPl-asX2-FJ_aqVXMb81Gck0z1XdclNMbKMq-ObmRO0,119
|
6
|
+
yeref-0.24.60.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
7
|
+
yeref-0.24.60.dist-info/top_level.txt,sha256=yCQKchWHbfV-3OuQPYRdi2loypD-nmbDJbtt3OuKKkY,6
|
8
|
+
yeref-0.24.60.dist-info/RECORD,,
|
yeref-0.24.59.dist-info/RECORD
DELETED
@@ -1,8 +0,0 @@
|
|
1
|
-
yeref/__init__.py,sha256=Qpv3o6Xa78VdLcsSRmctGtpnYE9btpAkCekgGhgJyXM,49
|
2
|
-
yeref/l_.py,sha256=LMX_olmJwq-tgoALJCnhV_fGrL_i_43yBLkLIcEVqGo,1176743
|
3
|
-
yeref/tonweb.js,sha256=Jf6aFOQ1OIY4q7fINYz-m5LsI3seMus124M5SYYZmtE,443659
|
4
|
-
yeref/yeref.py,sha256=agNJ36MiGH-INGAhoaeSPgym6QO9nlJmrKf5ZObMZr0,1043932
|
5
|
-
yeref-0.24.59.dist-info/METADATA,sha256=klrdD5dBXpfBzMifbDoE3Vm79Z6FRhgZPOJyTSFHNQY,119
|
6
|
-
yeref-0.24.59.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
7
|
-
yeref-0.24.59.dist-info/top_level.txt,sha256=yCQKchWHbfV-3OuQPYRdi2loypD-nmbDJbtt3OuKKkY,6
|
8
|
-
yeref-0.24.59.dist-info/RECORD,,
|
File without changes
|
File without changes
|