yeref 0.24.60__py3-none-any.whl → 0.24.62__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,153 +16122,79 @@ 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 return_unit_metrics2(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
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
- "N": 0,
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
- months = ["2025-06", "2025-07", "2025-08", "2025-09"]
16142
- data_users = []
16143
- for _ in range(20):
16144
- # дата входа
16145
- entry_month = random.choice(months)
16146
- entry_day = random.randint(1, 28)
16147
- entry_date = f"{entry_month}-{entry_day:02}"
16148
- entry_dt_obj = datetime.strptime(entry_date, '%Y-%m-%d')
16149
- entry_dt = f"{entry_dt_obj.strftime('%d-%m-%Y')}_{datetime.now().strftime('%H-%M-%S')}"
16150
- utm = random.choice(["/start", "/startapp"])
16151
-
16152
- # месяцы от входа и дальше
16153
- valid_months = [m for m in months if datetime.strptime(m + "-01", "%Y-%m-%d") >= entry_dt_obj.replace(day=1)]
16154
- if not valid_months:
16155
- valid_months = [entry_month]
16156
-
16157
- user_mau = sorted(random.sample(valid_months, k=random.randint(1, len(valid_months))))
16158
- user_dau_dates = set()
16159
- txs, payments = [], []
16160
-
16161
- # платеж
16162
- if user_mau:
16163
- pay_month = random.choice(user_mau)
16164
- pay_day = random.randint(1, 28)
16165
- pay_date = f"{pay_month}-{pay_day:02}"
16166
- dt_pay = datetime.strptime(pay_date, "%Y-%m-%d")
16167
- # if dt_pay >= entry_dt_obj:
16168
- payments = [{
16169
- "TYPE": random.choice(["don", "sub", "pst"]),
16170
- "DT_START": f"{dt_pay.strftime('%d-%m-%Y')}_14-00-00",
16171
- "DT_END": "0",
16172
- "AMOUNT": str(random.randint(1, 10))
16173
- }]
16174
- user_dau_dates.add(pay_date)
16175
-
16176
- # вход в приложение
16177
- user_dau_dates.add(entry_date)
16178
- for m in user_mau:
16179
- day = random.randint(1, 28)
16180
- visit = f"{m}-{day:02}"
16181
- dt_visit = datetime.strptime(visit, "%Y-%m-%d")
16182
- if dt_visit >= entry_dt_obj and random.random() < 0.7:
16183
- user_dau_dates.add(visit)
16184
-
16185
- # статусы (отток) с низкой вероятностью
16186
- USER_STATUSES = []
16187
- if random.random() < 0.2: # 10% шанс оттока
16188
- churn_month = random.choice(valid_months)
16189
- churn_day = random.randint(1, 28)
16190
- churn_date = f"{churn_month}-{churn_day:02}"
16191
- churn_ts = datetime.strptime(churn_date, "%Y-%m-%d").strftime("%d-%m-%Y") + "_23-59-59"
16192
- USER_STATUSES = [{random.choice(["left", "kicked"]): churn_ts}]
16193
-
16194
- user_dau = sorted(user_dau_dates)
16195
- wallet = f"wallet{random.randint(1, 100)}" if txs else random.choice([f"wallet{random.randint(1, 100)}", ""])
16196
-
16197
- data_users.append((
16198
- random.randint(100000, 999999),
16199
- json.dumps({"USER_WALLET": wallet, "USER_UTM": utm, "USER_DT": entry_dt}),
16200
- json.dumps({"USER_DAU": user_dau, "USER_MAU": user_mau, "USER_TXS": txs,
16201
- "USER_PAYMENTS": payments, "USER_STATUSES": USER_STATUSES})
16202
- ))
16203
- print(f"gen {data_users=}")
16204
-
16205
16138
  for USER_TID, USER_VARS, USER_LSTS in data_users:
16206
- USER_VARS = json.loads(USER_VARS or USER_VARS_)
16207
- USER_LSTS = json.loads(USER_LSTS or USER_LSTS_)
16139
+ USER_VARS = json.loads(USER_VARS or "{}")
16140
+ USER_LSTS = json.loads(USER_LSTS or "{}")
16208
16141
  USER_DT = USER_VARS.get("USER_DT", "")
16209
16142
  USER_PAYMENTS = USER_LSTS.get("USER_PAYMENTS", [])
16210
16143
  USER_STATUSES = USER_LSTS.get("USER_STATUSES", [])
16211
16144
 
16212
16145
  if USER_DT:
16213
- dt_obj = datetime.strptime(USER_DT, "%d-%m-%Y_%H-%M-%S")
16214
- mo = dt_obj.strftime("%Y-%m")
16146
+ mo = datetime.strptime(USER_DT, "%d-%m-%Y_%H-%M-%S").strftime("%Y-%m")
16215
16147
  if (USER_TID, mo) not in seen_new:
16216
16148
  seen_new.add((USER_TID, mo))
16217
- metrics[mo]["N"] += 1
16149
+ metrics[mo]["new_users"] += 1
16218
16150
 
16219
- # платежи этого пользователя
16220
16151
  for pay in USER_PAYMENTS:
16221
- start = pay.get("DT_START", "")
16222
- amt = pay.get("AMOUNT", 0)
16223
-
16224
- dt_p = datetime.strptime(start, "%d-%m-%Y_%H-%M-%S")
16152
+ dt_p = datetime.strptime(pay.get("DT_START", ""), "%d-%m-%Y_%H-%M-%S")
16225
16153
  mo_p = dt_p.strftime("%Y-%m")
16226
- val = float(amt) * 0.013
16227
- metrics[mo_p]["sum_amount"] += val
16154
+ amt = float(pay.get("AMOUNT", 0)) * 0.013
16155
+ metrics[mo_p]["sum_amount"] += amt
16228
16156
  metrics[mo_p]["payments_count"] += 1
16229
16157
 
16230
- # статусы (отток)
16231
16158
  for status in USER_STATUSES:
16232
16159
  key, ts = next(iter(status.items()))
16233
16160
  if key in ("left", "kicked"):
16234
- dt_s = datetime.strptime(ts, "%d-%m-%Y_%H-%M-%S")
16235
- mo_s = dt_s.strftime("%Y-%m")
16161
+ mo_s = datetime.strptime(ts, "%d-%m-%Y_%H-%M-%S").strftime("%Y-%m")
16236
16162
  metrics[mo_s]["churn_count"] += 1
16237
16163
  break
16238
16164
 
16239
16165
  all_months = sorted(metrics.keys())
16166
+ cumulative_users = 0
16240
16167
  results = []
16241
- first_mrr = None
16168
+
16242
16169
  for idx, mo in enumerate(all_months):
16243
16170
  data = metrics[mo]
16244
- N = data["N"]
16245
- MRR = data["sum_amount"]
16171
+ new_u = data["new_users"]
16172
+ cumulative_users += new_u
16246
16173
 
16174
+ MRR = data["sum_amount"]
16247
16175
  def fmt(x):
16248
- return (f"{x:.2f}".rstrip("0").rstrip(".")) if x is not None else ""
16249
- mrr_fmt = fmt(MRR)
16176
+ return f"{x:.2f}".rstrip("0").rstrip(".") if x is not None else ""
16250
16177
 
16178
+ mrr_fmt = fmt(MRR)
16179
+ N = cumulative_users
16251
16180
  ARPU = MRR / N if N else None
16252
16181
  ARR = MRR * 12 if N else None
16182
+
16253
16183
  churn = data["churn_count"]
16254
16184
  ChurnR = churn / N if N else None
16255
16185
  LTV1 = (ARPU / ChurnR) if (ARPU is not None and ChurnR and ChurnR > 0) else None
16256
16186
 
16257
16187
  pay_cnt = data["payments_count"]
16258
- # LTV2 = (payments_count/N) * (sum_amount/payments_count)
16259
16188
  if N and pay_cnt:
16260
16189
  LTV2 = (pay_cnt / N) * (MRR / pay_cnt)
16261
16190
  else:
16262
16191
  LTV2 = None
16263
16192
 
16264
- # LTV1 = LTV2
16265
- # CMGR: от первого месяца до текущего
16266
- if idx == 0 or first_mrr is None or first_mrr == 0:
16193
+ if idx == 0:
16267
16194
  CMGR = None
16268
- if idx == 0:
16269
- first_mrr = MRR
16195
+ first_mrr = MRR
16270
16196
  else:
16271
- CMGR = ((MRR / first_mrr) ** (1 / idx)) - 1
16197
+ CMGR = ((MRR / first_mrr) ** (1 / idx)) - 1 if first_mrr and MRR is not None else None
16272
16198
 
16273
16199
  results.append({
16274
16200
  "MO": mo,
@@ -16293,14 +16219,13 @@ async def return_unit_metrics2(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
16293
16219
  row["ChurnR"], row["LTV1"], row["LTV2"], row["CMGR"], row["CAC"]
16294
16220
  ])
16295
16221
 
16296
- cmgr = [float(r["CMGR"]) for r in results if r["CMGR"] != ""]
16297
- if cmgr:
16298
- factors = [1 + v for v in cmgr]
16222
+ cmgr_vals = [float(r["CMGR"]) for r in results if r["CMGR"] != ""]
16223
+ if cmgr_vals:
16224
+ factors = [1 + v for v in cmgr_vals]
16299
16225
  avg = math.prod(factors) ** (1 / len(factors))
16300
16226
  writer.writerow([])
16301
16227
  writer.writerow([f"Rev ~ ×{round(avg, 2)} monthly"])
16302
16228
 
16303
- # отправить файл
16304
16229
  thumb = types.FSInputFile(os.path.join(EXTRA_D, "parse.jpg"))
16305
16230
  await bot.send_document(chat_id=my_tid, document=types.FSInputFile(path), thumbnail=thumb)
16306
16231
  except Exception as e:
@@ -16308,18 +16233,15 @@ async def return_unit_metrics2(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
16308
16233
  await asyncio.sleep(round(random.uniform(0, 1), 2))
16309
16234
 
16310
16235
 
16311
- async def return_unit_metrics(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
16236
+ async def return_cohort_metrics(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
16312
16237
  try:
16238
+ # Получаем всех пользователей
16313
16239
  sql = 'SELECT USER_TID, USER_VARS, USER_LSTS FROM "USER"'
16314
16240
  data_users = await db_select_pg(sql, (), BASE_P)
16315
16241
 
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()
16242
+ # Собираем для каждого пользователя: месяц входа и набор месяцев активности
16243
+ cohorts = defaultdict(set) # cohort_month -> set(USER_TID)
16244
+ activity_months = defaultdict(set) # USER_TID -> set месяцов, в которые был DAU
16323
16245
 
16324
16246
  months = ["2025-06", "2025-07", "2025-08", "2025-09"]
16325
16247
  data_users = []
@@ -16389,99 +16311,79 @@ async def return_unit_metrics(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
16389
16311
  USER_VARS = json.loads(USER_VARS or "{}")
16390
16312
  USER_LSTS = json.loads(USER_LSTS or "{}")
16391
16313
  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
16314
+ USER_DAU = USER_LSTS.get("USER_DAU", [])
16400
16315
 
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
16316
+ # вычисляем месяц входа
16317
+ entry_mo = datetime.strptime(USER_DT, "%d-%m-%Y_%H-%M-%S").strftime("%Y-%m")
16318
+ cohorts[entry_mo].add(USER_TID)
16407
16319
 
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
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
16448
16327
 
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
- })
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)
16461
16374
 
16462
- path = os.path.join(EXTRA_D, "2_unit_metrics.csv")
16375
+ # Записываем CSV
16376
+ path = os.path.join(EXTRA_D, "3_cohort_metrics.csv")
16463
16377
  with open(path, "w", newline="", encoding="utf-8") as f:
16464
16378
  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"])
16379
+ for r in table:
16380
+ writer.writerow(r)
16478
16381
 
16479
16382
  thumb = types.FSInputFile(os.path.join(EXTRA_D, "parse.jpg"))
16480
16383
  await bot.send_document(chat_id=my_tid, document=types.FSInputFile(path), thumbnail=thumb)
16481
16384
  except Exception as e:
16482
16385
  logger.info(log_ % str(e))
16483
16386
  await asyncio.sleep(round(random.uniform(0, 1), 2))
16484
-
16485
16387
  # endregion
16486
16388
 
16487
16389
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yeref
3
- Version: 0.24.60
3
+ Version: 0.24.62
4
4
  Summary: desc-f
5
5
  Author: john smith
6
6
  Dynamic: author
@@ -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=rAoPHlZIPtypeSgPOOh0OTBFFxTwHk22mfPgHUGAip8,1047232
5
+ yeref-0.24.62.dist-info/METADATA,sha256=e9nb9ZfBv_UznlhqhKZASIsWY6uiaudkFJ6xECWNZp8,119
6
+ yeref-0.24.62.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
+ yeref-0.24.62.dist-info/top_level.txt,sha256=yCQKchWHbfV-3OuQPYRdi2loypD-nmbDJbtt3OuKKkY,6
8
+ yeref-0.24.62.dist-info/RECORD,,
@@ -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=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,,