yeref 0.24.60__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yeref
3
- Version: 0.24.60
3
+ Version: 0.24.61
4
4
  Summary: desc-f
5
5
  Author: john smith
6
6
  Dynamic: author
@@ -2,7 +2,7 @@ from setuptools import setup
2
2
 
3
3
  setup(
4
4
  name='yeref',
5
- version='0.24.60',
5
+ version='0.24.61',
6
6
  description='desc-f',
7
7
  author='john smith',
8
8
  packages=['yeref'],
@@ -16122,192 +16122,6 @@ 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):
16126
- try:
16127
- # собрать всех пользователей
16128
- sql = 'SELECT USER_TID, USER_VARS, USER_LSTS FROM "USER"'
16129
- data_users = await db_select_pg(sql, (), BASE_P)
16130
-
16131
- # структура: метрики по месяцу
16132
- metrics = defaultdict(lambda: {
16133
- "N": 0,
16134
- "sum_amount": 0.0,
16135
- "payments_count": 0,
16136
- "churn_count": 0
16137
- })
16138
- # набор пользователей, чтобы не дважды считать N в одном месяце
16139
- seen_new = set()
16140
-
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
- 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_)
16208
- USER_DT = USER_VARS.get("USER_DT", "")
16209
- USER_PAYMENTS = USER_LSTS.get("USER_PAYMENTS", [])
16210
- USER_STATUSES = USER_LSTS.get("USER_STATUSES", [])
16211
-
16212
- if USER_DT:
16213
- dt_obj = datetime.strptime(USER_DT, "%d-%m-%Y_%H-%M-%S")
16214
- mo = dt_obj.strftime("%Y-%m")
16215
- if (USER_TID, mo) not in seen_new:
16216
- seen_new.add((USER_TID, mo))
16217
- metrics[mo]["N"] += 1
16218
-
16219
- # платежи этого пользователя
16220
- 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")
16225
- mo_p = dt_p.strftime("%Y-%m")
16226
- val = float(amt) * 0.013
16227
- metrics[mo_p]["sum_amount"] += val
16228
- metrics[mo_p]["payments_count"] += 1
16229
-
16230
- # статусы (отток)
16231
- for status in USER_STATUSES:
16232
- key, ts = next(iter(status.items()))
16233
- 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")
16236
- metrics[mo_s]["churn_count"] += 1
16237
- break
16238
-
16239
- all_months = sorted(metrics.keys())
16240
- results = []
16241
- first_mrr = None
16242
- for idx, mo in enumerate(all_months):
16243
- data = metrics[mo]
16244
- N = data["N"]
16245
- MRR = data["sum_amount"]
16246
-
16247
- def fmt(x):
16248
- return (f"{x:.2f}".rstrip("0").rstrip(".")) if x is not None else ""
16249
- mrr_fmt = fmt(MRR)
16250
-
16251
- ARPU = MRR / N if N else None
16252
- ARR = MRR * 12 if N else None
16253
- churn = data["churn_count"]
16254
- ChurnR = churn / N if N else None
16255
- LTV1 = (ARPU / ChurnR) if (ARPU is not None and ChurnR and ChurnR > 0) else None
16256
-
16257
- pay_cnt = data["payments_count"]
16258
- # LTV2 = (payments_count/N) * (sum_amount/payments_count)
16259
- if N and pay_cnt:
16260
- LTV2 = (pay_cnt / N) * (MRR / pay_cnt)
16261
- else:
16262
- LTV2 = None
16263
-
16264
- # LTV1 = LTV2
16265
- # CMGR: от первого месяца до текущего
16266
- if idx == 0 or first_mrr is None or first_mrr == 0:
16267
- CMGR = None
16268
- if idx == 0:
16269
- first_mrr = MRR
16270
- else:
16271
- CMGR = ((MRR / first_mrr) ** (1 / idx)) - 1
16272
-
16273
- results.append({
16274
- "MO": mo,
16275
- "N": str(N),
16276
- "MRR": mrr_fmt,
16277
- "ARPU": fmt(ARPU),
16278
- "ARR": fmt(ARR),
16279
- "ChurnR": fmt(ChurnR),
16280
- "LTV1": fmt(LTV1),
16281
- "LTV2": fmt(LTV2),
16282
- "CMGR": fmt(CMGR),
16283
- "CAC": ""
16284
- })
16285
-
16286
- path = os.path.join(EXTRA_D, "2_unit_metrics.csv")
16287
- with open(path, "w", newline="", encoding="utf-8") as f:
16288
- writer = csv.writer(f)
16289
- writer.writerow(["MO", "N", "MRR", "ARPU", "ARR", "ChurnR", "LTV1", "LTV2", "CMGR", "CAC"])
16290
- for row in results:
16291
- writer.writerow([
16292
- row["MO"], row["N"], row["MRR"], row["ARPU"], row["ARR"],
16293
- row["ChurnR"], row["LTV1"], row["LTV2"], row["CMGR"], row["CAC"]
16294
- ])
16295
-
16296
- cmgr = [float(r["CMGR"]) for r in results if r["CMGR"] != ""]
16297
- if cmgr:
16298
- factors = [1 + v for v in cmgr]
16299
- avg = math.prod(factors) ** (1 / len(factors))
16300
- writer.writerow([])
16301
- writer.writerow([f"Rev ~ ×{round(avg, 2)} monthly"])
16302
-
16303
- # отправить файл
16304
- thumb = types.FSInputFile(os.path.join(EXTRA_D, "parse.jpg"))
16305
- await bot.send_document(chat_id=my_tid, document=types.FSInputFile(path), thumbnail=thumb)
16306
- except Exception as e:
16307
- logger.info(log_ % str(e))
16308
- await asyncio.sleep(round(random.uniform(0, 1), 2))
16309
-
16310
-
16311
16125
  async def return_unit_metrics(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
16312
16126
  try:
16313
16127
  sql = 'SELECT USER_TID, USER_VARS, USER_LSTS FROM "USER"'
@@ -16482,6 +16296,94 @@ async def return_unit_metrics(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
16482
16296
  logger.info(log_ % str(e))
16483
16297
  await asyncio.sleep(round(random.uniform(0, 1), 2))
16484
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
+
16382
+ thumb = types.FSInputFile(os.path.join(EXTRA_D, "parse.jpg"))
16383
+ await bot.send_document(chat_id=my_tid, document=types.FSInputFile(path), thumbnail=thumb)
16384
+ except Exception as e:
16385
+ logger.info(log_ % str(e))
16386
+ await asyncio.sleep(round(random.uniform(0, 1), 2))
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.61
4
4
  Summary: desc-f
5
5
  Author: john smith
6
6
  Dynamic: author
File without changes
File without changes
File without changes
File without changes
File without changes