yeref 0.24.59__tar.gz → 0.24.61__tar.gz
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-0.24.59 → yeref-0.24.61}/PKG-INFO +1 -1
- {yeref-0.24.59 → yeref-0.24.61}/setup.py +1 -1
- {yeref-0.24.59 → yeref-0.24.61}/yeref/yeref.py +113 -35
- {yeref-0.24.59 → yeref-0.24.61}/yeref.egg-info/PKG-INFO +1 -1
- {yeref-0.24.59 → yeref-0.24.61}/pyproject.toml +0 -0
- {yeref-0.24.59 → yeref-0.24.61}/setup.cfg +0 -0
- {yeref-0.24.59 → yeref-0.24.61}/yeref/__init__.py +0 -0
- {yeref-0.24.59 → yeref-0.24.61}/yeref/l_.py +0 -0
- {yeref-0.24.59 → yeref-0.24.61}/yeref/tonweb.js +0 -0
- {yeref-0.24.59 → yeref-0.24.61}/yeref.egg-info/SOURCES.txt +0 -0
- {yeref-0.24.59 → yeref-0.24.61}/yeref.egg-info/dependency_links.txt +0 -0
- {yeref-0.24.59 → yeref-0.24.61}/yeref.egg-info/top_level.txt +0 -0
@@ -16124,18 +16124,15 @@ async def return_activity_metrics(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
|
|
16124
16124
|
|
16125
16125
|
async def return_unit_metrics(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
|
16126
16126
|
try:
|
16127
|
-
# собрать всех пользователей
|
16128
16127
|
sql = 'SELECT USER_TID, USER_VARS, USER_LSTS FROM "USER"'
|
16129
16128
|
data_users = await db_select_pg(sql, (), BASE_P)
|
16130
16129
|
|
16131
|
-
# структура: метрики по месяцу
|
16132
16130
|
metrics = defaultdict(lambda: {
|
16133
|
-
"
|
16131
|
+
"new_users": 0,
|
16134
16132
|
"sum_amount": 0.0,
|
16135
16133
|
"payments_count": 0,
|
16136
16134
|
"churn_count": 0
|
16137
16135
|
})
|
16138
|
-
# набор пользователей, чтобы не дважды считать N в одном месяце
|
16139
16136
|
seen_new = set()
|
16140
16137
|
|
16141
16138
|
months = ["2025-06", "2025-07", "2025-08", "2025-09"]
|
@@ -16203,72 +16200,65 @@ async def return_unit_metrics(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
|
|
16203
16200
|
print(f"gen {data_users=}")
|
16204
16201
|
|
16205
16202
|
for USER_TID, USER_VARS, USER_LSTS in data_users:
|
16206
|
-
USER_VARS = json.loads(USER_VARS or
|
16207
|
-
USER_LSTS = json.loads(USER_LSTS or
|
16203
|
+
USER_VARS = json.loads(USER_VARS or "{}")
|
16204
|
+
USER_LSTS = json.loads(USER_LSTS or "{}")
|
16208
16205
|
USER_DT = USER_VARS.get("USER_DT", "")
|
16209
16206
|
USER_PAYMENTS = USER_LSTS.get("USER_PAYMENTS", [])
|
16210
16207
|
USER_STATUSES = USER_LSTS.get("USER_STATUSES", [])
|
16211
16208
|
|
16212
16209
|
if USER_DT:
|
16213
|
-
|
16214
|
-
mo = dt_obj.strftime("%Y-%m")
|
16210
|
+
mo = datetime.strptime(USER_DT, "%d-%m-%Y_%H-%M-%S").strftime("%Y-%m")
|
16215
16211
|
if (USER_TID, mo) not in seen_new:
|
16216
16212
|
seen_new.add((USER_TID, mo))
|
16217
|
-
metrics[mo]["
|
16213
|
+
metrics[mo]["new_users"] += 1
|
16218
16214
|
|
16219
|
-
# платежи этого пользователя
|
16220
16215
|
for pay in USER_PAYMENTS:
|
16221
|
-
|
16222
|
-
amt = pay.get("AMOUNT", 0)
|
16223
|
-
|
16224
|
-
dt_p = datetime.strptime(start, "%d-%m-%Y_%H-%M-%S")
|
16216
|
+
dt_p = datetime.strptime(pay.get("DT_START", ""), "%d-%m-%Y_%H-%M-%S")
|
16225
16217
|
mo_p = dt_p.strftime("%Y-%m")
|
16226
|
-
|
16227
|
-
metrics[mo_p]["sum_amount"] +=
|
16218
|
+
amt = float(pay.get("AMOUNT", 0)) * 0.013
|
16219
|
+
metrics[mo_p]["sum_amount"] += amt
|
16228
16220
|
metrics[mo_p]["payments_count"] += 1
|
16229
16221
|
|
16230
|
-
# статусы (отток)
|
16231
16222
|
for status in USER_STATUSES:
|
16232
16223
|
key, ts = next(iter(status.items()))
|
16233
16224
|
if key in ("left", "kicked"):
|
16234
|
-
|
16235
|
-
mo_s = dt_s.strftime("%Y-%m")
|
16225
|
+
mo_s = datetime.strptime(ts, "%d-%m-%Y_%H-%M-%S").strftime("%Y-%m")
|
16236
16226
|
metrics[mo_s]["churn_count"] += 1
|
16237
16227
|
break
|
16238
16228
|
|
16239
16229
|
all_months = sorted(metrics.keys())
|
16230
|
+
cumulative_users = 0
|
16240
16231
|
results = []
|
16241
|
-
|
16232
|
+
|
16242
16233
|
for idx, mo in enumerate(all_months):
|
16243
16234
|
data = metrics[mo]
|
16244
|
-
|
16245
|
-
|
16235
|
+
new_u = data["new_users"]
|
16236
|
+
cumulative_users += new_u
|
16246
16237
|
|
16238
|
+
MRR = data["sum_amount"]
|
16247
16239
|
def fmt(x):
|
16248
|
-
return
|
16249
|
-
mrr_fmt = fmt(MRR)
|
16240
|
+
return f"{x:.2f}".rstrip("0").rstrip(".") if x is not None else ""
|
16250
16241
|
|
16242
|
+
mrr_fmt = fmt(MRR)
|
16243
|
+
N = cumulative_users
|
16251
16244
|
ARPU = MRR / N if N else None
|
16252
16245
|
ARR = MRR * 12 if N else None
|
16246
|
+
|
16253
16247
|
churn = data["churn_count"]
|
16254
16248
|
ChurnR = churn / N if N else None
|
16255
16249
|
LTV1 = (ARPU / ChurnR) if (ARPU is not None and ChurnR and ChurnR > 0) else None
|
16256
16250
|
|
16257
16251
|
pay_cnt = data["payments_count"]
|
16258
|
-
# LTV2 = (payments_count/N) * (sum_amount/payments_count)
|
16259
16252
|
if N and pay_cnt:
|
16260
16253
|
LTV2 = (pay_cnt / N) * (MRR / pay_cnt)
|
16261
16254
|
else:
|
16262
16255
|
LTV2 = None
|
16263
16256
|
|
16264
|
-
|
16265
|
-
# CMGR: от первого месяца до текущего
|
16266
|
-
if idx == 0 or first_mrr is None or first_mrr == 0:
|
16257
|
+
if idx == 0:
|
16267
16258
|
CMGR = None
|
16268
|
-
|
16269
|
-
first_mrr = MRR
|
16259
|
+
first_mrr = MRR
|
16270
16260
|
else:
|
16271
|
-
CMGR = ((MRR / first_mrr) ** (1 / idx)) - 1
|
16261
|
+
CMGR = ((MRR / first_mrr) ** (1 / idx)) - 1 if first_mrr and MRR is not None else None
|
16272
16262
|
|
16273
16263
|
results.append({
|
16274
16264
|
"MO": mo,
|
@@ -16293,14 +16283,102 @@ async def return_unit_metrics(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
|
|
16293
16283
|
row["ChurnR"], row["LTV1"], row["LTV2"], row["CMGR"], row["CAC"]
|
16294
16284
|
])
|
16295
16285
|
|
16296
|
-
|
16297
|
-
if
|
16298
|
-
factors = [1 + v for v in
|
16286
|
+
cmgr_vals = [float(r["CMGR"]) for r in results if r["CMGR"] != ""]
|
16287
|
+
if cmgr_vals:
|
16288
|
+
factors = [1 + v for v in cmgr_vals]
|
16299
16289
|
avg = math.prod(factors) ** (1 / len(factors))
|
16300
16290
|
writer.writerow([])
|
16301
16291
|
writer.writerow([f"Rev ~ ×{round(avg, 2)} monthly"])
|
16302
16292
|
|
16303
|
-
|
16293
|
+
thumb = types.FSInputFile(os.path.join(EXTRA_D, "parse.jpg"))
|
16294
|
+
await bot.send_document(chat_id=my_tid, document=types.FSInputFile(path), thumbnail=thumb)
|
16295
|
+
except Exception as e:
|
16296
|
+
logger.info(log_ % str(e))
|
16297
|
+
await asyncio.sleep(round(random.uniform(0, 1), 2))
|
16298
|
+
|
16299
|
+
|
16300
|
+
async def return_cohort_metrics(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
|
16301
|
+
try:
|
16302
|
+
# Получаем всех пользователей
|
16303
|
+
sql = 'SELECT USER_TID, USER_VARS, USER_LSTS FROM "USER"'
|
16304
|
+
data_users = await db_select_pg(sql, (), BASE_P)
|
16305
|
+
|
16306
|
+
# Собираем для каждого пользователя: месяц входа и набор месяцев активности
|
16307
|
+
cohorts = defaultdict(set) # cohort_month -> set(USER_TID)
|
16308
|
+
activity_months = defaultdict(set) # USER_TID -> set месяцов, в которые был DAU
|
16309
|
+
|
16310
|
+
for USER_TID, USER_VARS, USER_LSTS in data_users:
|
16311
|
+
USER_VARS = json.loads(USER_VARS or "{}")
|
16312
|
+
USER_LSTS = json.loads(USER_LSTS or "{}")
|
16313
|
+
USER_DT = USER_VARS.get("USER_DT", "")
|
16314
|
+
USER_DAU = USER_LSTS.get("USER_DAU", [])
|
16315
|
+
|
16316
|
+
# вычисляем месяц входа
|
16317
|
+
entry_mo = datetime.strptime(USER_DT, "%d-%m-%Y_%H-%M-%S").strftime("%Y-%m")
|
16318
|
+
cohorts[entry_mo].add(USER_TID)
|
16319
|
+
|
16320
|
+
# сохраняем месяцы активности
|
16321
|
+
for day_str in USER_DAU:
|
16322
|
+
try:
|
16323
|
+
mo = datetime.strptime(day_str, "%Y-%m-%d").strftime("%Y-%m")
|
16324
|
+
activity_months[USER_TID].add(mo)
|
16325
|
+
except:
|
16326
|
+
pass
|
16327
|
+
|
16328
|
+
# Список уникальных месяцев cohort, отсортированный
|
16329
|
+
cohort_months = sorted(cohorts.keys())
|
16330
|
+
|
16331
|
+
# Функция для прибавления месяцев
|
16332
|
+
def add_months(mo_str, n):
|
16333
|
+
y, m = map(int, mo_str.split("-"))
|
16334
|
+
total = m + n
|
16335
|
+
new_y = y + (total - 1) // 12
|
16336
|
+
new_m = (total - 1) % 12 + 1
|
16337
|
+
return f"{new_y:04d}-{new_m:02d}"
|
16338
|
+
|
16339
|
+
num_cohorts = len(cohort_months)
|
16340
|
+
|
16341
|
+
# Формируем таблицу: строки M1..M{num_cohorts}
|
16342
|
+
table = []
|
16343
|
+
header = ["Месяц/Когорта"]
|
16344
|
+
# Добавляем в заголовок каждый cohort + его размер
|
16345
|
+
for mo in cohort_months:
|
16346
|
+
header.append(f"{mo} ({len(cohorts[mo])})")
|
16347
|
+
header.append("∑")
|
16348
|
+
table.append(header)
|
16349
|
+
|
16350
|
+
# Для каждой строки-месяца кортаций
|
16351
|
+
for i in range(num_cohorts):
|
16352
|
+
row = [f"M{i+1}"]
|
16353
|
+
row_sum = 0
|
16354
|
+
for j, cohort_mo in enumerate(cohort_months):
|
16355
|
+
# вычисляем целевой месяц = cohort_mo + i месяцев
|
16356
|
+
target_mo = add_months(cohort_mo, i)
|
16357
|
+
if i == 0:
|
16358
|
+
# M1 = размер когорты
|
16359
|
+
val = len(cohorts[cohort_mo])
|
16360
|
+
else:
|
16361
|
+
# считаем, сколько пользователей из когорты были активны в target_mo
|
16362
|
+
cnt = 0
|
16363
|
+
for uid in cohorts[cohort_mo]:
|
16364
|
+
if target_mo in activity_months.get(uid, set()):
|
16365
|
+
cnt += 1
|
16366
|
+
val = cnt
|
16367
|
+
if val:
|
16368
|
+
row.append(str(val))
|
16369
|
+
row_sum += val
|
16370
|
+
else:
|
16371
|
+
row.append("")
|
16372
|
+
row.append(str(row_sum))
|
16373
|
+
table.append(row)
|
16374
|
+
|
16375
|
+
# Записываем CSV
|
16376
|
+
path = os.path.join(EXTRA_D, "3_cohort_metrics.csv")
|
16377
|
+
with open(path, "w", newline="", encoding="utf-8") as f:
|
16378
|
+
writer = csv.writer(f)
|
16379
|
+
for r in table:
|
16380
|
+
writer.writerow(r)
|
16381
|
+
|
16304
16382
|
thumb = types.FSInputFile(os.path.join(EXTRA_D, "parse.jpg"))
|
16305
16383
|
await bot.send_document(chat_id=my_tid, document=types.FSInputFile(path), thumbnail=thumb)
|
16306
16384
|
except Exception as e:
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|