yeref 0.24.90__tar.gz → 0.24.92__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.90 → yeref-0.24.92}/PKG-INFO +1 -1
- {yeref-0.24.90 → yeref-0.24.92}/setup.py +1 -1
- {yeref-0.24.90 → yeref-0.24.92}/yeref/yeref.py +120 -306
- {yeref-0.24.90 → yeref-0.24.92}/yeref.egg-info/PKG-INFO +1 -1
- {yeref-0.24.90 → yeref-0.24.92}/pyproject.toml +0 -0
- {yeref-0.24.90 → yeref-0.24.92}/setup.cfg +0 -0
- {yeref-0.24.90 → yeref-0.24.92}/yeref/__init__.py +0 -0
- {yeref-0.24.90 → yeref-0.24.92}/yeref/l_.py +0 -0
- {yeref-0.24.90 → yeref-0.24.92}/yeref/tonweb.js +0 -0
- {yeref-0.24.90 → yeref-0.24.92}/yeref.egg-info/SOURCES.txt +0 -0
- {yeref-0.24.90 → yeref-0.24.92}/yeref.egg-info/dependency_links.txt +0 -0
- {yeref-0.24.90 → yeref-0.24.92}/yeref.egg-info/top_level.txt +0 -0
@@ -15926,7 +15926,7 @@ async def upd_user_data(ENT_TID, data, web_app_init_data, PROJECT_USERNAME, BASE
|
|
15926
15926
|
|
15927
15927
|
|
15928
15928
|
# region unit ecomonics
|
15929
|
-
async def
|
15929
|
+
async def calc_metrics(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
|
15930
15930
|
try:
|
15931
15931
|
schema_name = "USER"
|
15932
15932
|
if PROJECT_USERNAME == 'FereyBotBot':
|
@@ -15939,6 +15939,85 @@ async def return_activity_metrics(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
|
|
15939
15939
|
sql = f"SELECT {schema_name}_TID FROM \"{schema_name}\""
|
15940
15940
|
data_ents = await db_select_pg(sql, (), BASE_P)
|
15941
15941
|
|
15942
|
+
sql = 'SELECT USER_TID, USER_VARS, USER_LSTS FROM "USER"'
|
15943
|
+
data_users = await db_select_pg(sql, (), BASE_P)
|
15944
|
+
|
15945
|
+
months = ["2025-06", "2025-07", "2025-08", "2025-09"]
|
15946
|
+
data_users = []
|
15947
|
+
for _ in range(30):
|
15948
|
+
# дата входа
|
15949
|
+
entry_month = random.choice(months)
|
15950
|
+
entry_day = random.randint(1, 28)
|
15951
|
+
entry_date = f"{entry_month}-{entry_day:02}"
|
15952
|
+
entry_dt_obj = datetime.strptime(entry_date, '%Y-%m-%d')
|
15953
|
+
entry_dt = f"{entry_dt_obj.strftime('%d-%m-%Y')}_{datetime.now().strftime('%H-%M-%S')}"
|
15954
|
+
utm = random.choice(["/start", "/startapp"])
|
15955
|
+
|
15956
|
+
# месяцы от входа и дальше
|
15957
|
+
valid_months = [m for m in months if datetime.strptime(m + "-01", "%Y-%m-%d") >= entry_dt_obj.replace(day=1)]
|
15958
|
+
if not valid_months:
|
15959
|
+
valid_months = [entry_month]
|
15960
|
+
|
15961
|
+
user_mau = sorted(random.sample(valid_months, k=random.randint(1, len(valid_months))))
|
15962
|
+
user_dau_dates = set()
|
15963
|
+
txs, payments = [], []
|
15964
|
+
|
15965
|
+
# платеж
|
15966
|
+
if user_mau:
|
15967
|
+
pay_month = random.choice(user_mau)
|
15968
|
+
pay_day = random.randint(1, 28)
|
15969
|
+
pay_date = f"{pay_month}-{pay_day:02}"
|
15970
|
+
dt_pay = datetime.strptime(pay_date, "%Y-%m-%d")
|
15971
|
+
payments = [{
|
15972
|
+
"TYPE": random.choice(["don", "sub", "pst"]),
|
15973
|
+
"DT_START": f"{dt_pay.strftime('%d-%m-%Y')}_14-00-00",
|
15974
|
+
"DT_END": "0",
|
15975
|
+
"AMOUNT": str(random.randint(10, 1000))
|
15976
|
+
}]
|
15977
|
+
user_dau_dates.add(pay_date)
|
15978
|
+
|
15979
|
+
# вход в приложение
|
15980
|
+
user_dau_dates.add(entry_date)
|
15981
|
+
for m in user_mau:
|
15982
|
+
day = random.randint(1, 28)
|
15983
|
+
visit = f"{m}-{day:02}"
|
15984
|
+
dt_visit = datetime.strptime(visit, "%Y-%m-%d")
|
15985
|
+
if dt_visit >= entry_dt_obj and random.random() < 0.7:
|
15986
|
+
user_dau_dates.add(visit)
|
15987
|
+
|
15988
|
+
# статусы (отток) с низкой вероятностью
|
15989
|
+
USER_STATUSES = []
|
15990
|
+
if random.random() < 0.2: # 10% шанс оттока
|
15991
|
+
churn_month = random.choice(valid_months)
|
15992
|
+
churn_day = random.randint(1, 28)
|
15993
|
+
churn_date = f"{churn_month}-{churn_day:02}"
|
15994
|
+
churn_ts = datetime.strptime(churn_date, "%Y-%m-%d").strftime("%d-%m-%Y") + "_23-59-59"
|
15995
|
+
USER_STATUSES = [{random.choice(["left", "kicked"]): churn_ts}]
|
15996
|
+
|
15997
|
+
user_dau = sorted(user_dau_dates)
|
15998
|
+
wallet = f"wallet{random.randint(1, 100)}" if txs else random.choice([f"wallet{random.randint(1, 100)}", ""])
|
15999
|
+
|
16000
|
+
data_users.append((
|
16001
|
+
random.randint(100000, 999999),
|
16002
|
+
json.dumps({"USER_WALLET": wallet, "USER_UTM": utm, "USER_DT": entry_dt}),
|
16003
|
+
json.dumps({"USER_DAU": user_dau, "USER_MAU": user_mau, "USER_TXS": txs,
|
16004
|
+
"USER_PAYMENTS": payments, "USER_STATUSES": USER_STATUSES})
|
16005
|
+
))
|
16006
|
+
print(f"gen {data_users=}")
|
16007
|
+
|
16008
|
+
r1 = await return_activity_metrics(bot, data_users, EXTRA_D, BASE_P, data_ents, schema_name)
|
16009
|
+
r2 = await return_unit_metrics(bot, data_users, EXTRA_D, BASE_P)
|
16010
|
+
r3 = await return_cohort_metrics(bot, data_users, EXTRA_D, BASE_P)
|
16011
|
+
r4 = await return_retention_metrics(bot, data_users, EXTRA_D, BASE_P)
|
16012
|
+
r5 = await return_profit_and_loss_metrics(bot, data_users, EXTRA_D, BASE_P)
|
16013
|
+
except Exception as e:
|
16014
|
+
logger.info(log_ % str(e))
|
16015
|
+
await asyncio.sleep(round(random.uniform(0, 1), 2))
|
16016
|
+
|
16017
|
+
|
16018
|
+
async def return_activity_metrics(bot, data_users, EXTRA_D, BASE_P, data_ents, schema_name):
|
16019
|
+
result = None
|
16020
|
+
try:
|
15942
16021
|
metrics_by_month = defaultdict(lambda: {
|
15943
16022
|
"dau": 0,
|
15944
16023
|
"mau": 0,
|
@@ -15953,79 +16032,8 @@ async def return_activity_metrics(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
|
|
15953
16032
|
seen_dau = set()
|
15954
16033
|
wallets_set = set()
|
15955
16034
|
users_set = set()
|
15956
|
-
pay_set = set()
|
15957
16035
|
|
15958
16036
|
def process_user_rows(rows):
|
15959
|
-
# months = ["2025-06", "2025-07", "2025-08", "2025-09"]
|
15960
|
-
# data_users = []
|
15961
|
-
# for _ in range(3):
|
15962
|
-
# # дата входа
|
15963
|
-
# entry_month = random.choice(months)
|
15964
|
-
# entry_day = random.randint(1, 28)
|
15965
|
-
# entry_date = f"{entry_month}-{entry_day:02}"
|
15966
|
-
# entry_dt_obj = datetime.strptime(entry_date, '%Y-%m-%d')
|
15967
|
-
# entry_dt = f"{entry_dt_obj.strftime('%d-%m-%Y')}_{datetime.now().strftime('%H-%M-%S')}"
|
15968
|
-
# utm = random.choice(["/start", "/startapp"])
|
15969
|
-
#
|
15970
|
-
# # месяцы от входа и дальше
|
15971
|
-
# valid_months = [m for m in months if datetime.strptime(m + "-01", "%Y-%m-%d") >= entry_dt_obj.replace(day=1)]
|
15972
|
-
# if not valid_months:
|
15973
|
-
# valid_months = [entry_month]
|
15974
|
-
#
|
15975
|
-
# user_mau = sorted(random.sample(valid_months, k=random.randint(1, len(valid_months))))
|
15976
|
-
# user_dau_dates = set()
|
15977
|
-
# txs, payments = [], []
|
15978
|
-
#
|
15979
|
-
# # транзакция
|
15980
|
-
# if user_mau and random.random() < 0.5:
|
15981
|
-
# tx_month = random.choice(user_mau)
|
15982
|
-
# tx_day = random.randint(1, 28)
|
15983
|
-
# tx_date = f"{tx_month}-{tx_day:02}"
|
15984
|
-
# dt_tx = datetime.strptime(tx_date, "%Y-%m-%d")
|
15985
|
-
# if dt_tx >= entry_dt_obj:
|
15986
|
-
# txs = [{
|
15987
|
-
# "TYPE": random.choice(["don", "sub", "pst"]),
|
15988
|
-
# "AMOUNT": str(random.randint(1, 10)),
|
15989
|
-
# "ADDRESS": f"address{random.randint(1, 999)}",
|
15990
|
-
# "DT_START": f"{dt_tx.strftime('%d-%m-%Y')}_12-00-00",
|
15991
|
-
# }]
|
15992
|
-
# user_dau_dates.add(tx_date)
|
15993
|
-
#
|
15994
|
-
# # платеж
|
15995
|
-
# if user_mau and random.random() < 0.5:
|
15996
|
-
# pay_month = random.choice(user_mau)
|
15997
|
-
# pay_day = random.randint(1, 28)
|
15998
|
-
# pay_date = f"{pay_month}-{pay_day:02}"
|
15999
|
-
# dt_pay = datetime.strptime(pay_date, "%Y-%m-%d")
|
16000
|
-
# if dt_pay >= entry_dt_obj:
|
16001
|
-
# payments = [{
|
16002
|
-
# "TYPE": random.choice(["don", "sub", "pst"]),
|
16003
|
-
# "DT_START": f"{dt_pay.strftime('%d-%m-%Y')}_14-00-00",
|
16004
|
-
# "DT_END": "0",
|
16005
|
-
# "AMOUNT": str(random.randint(1, 10))
|
16006
|
-
# }]
|
16007
|
-
# user_dau_dates.add(pay_date)
|
16008
|
-
#
|
16009
|
-
# # вход в приложение
|
16010
|
-
# user_dau_dates.add(entry_date)
|
16011
|
-
#
|
16012
|
-
# # остальные посещения
|
16013
|
-
# for m in user_mau:
|
16014
|
-
# day = random.randint(1, 28)
|
16015
|
-
# visit = f"{m}-{day:02}"
|
16016
|
-
# dt_visit = datetime.strptime(visit, "%Y-%m-%d")
|
16017
|
-
# if dt_visit >= entry_dt_obj and random.random() < 0.7:
|
16018
|
-
# user_dau_dates.add(visit)
|
16019
|
-
#
|
16020
|
-
# user_dau = sorted(user_dau_dates)
|
16021
|
-
# wallet = f"wallet{random.randint(1, 100)}" if txs else random.choice([f"wallet{random.randint(1, 100)}", ""])
|
16022
|
-
#
|
16023
|
-
# data_users.append((
|
16024
|
-
# random.randint(100000, 999999),
|
16025
|
-
# json.dumps({"USER_WALLET": wallet, "USER_UTM": utm, "USER_DT": entry_dt}),
|
16026
|
-
# json.dumps({"USER_DAU": user_dau, "USER_MAU": user_mau, "USER_TXS": txs, "USER_PAYMENTS": payments})
|
16027
|
-
# ))
|
16028
|
-
|
16029
16037
|
for USER_TID, USER_VARS, USER_LSTS in rows:
|
16030
16038
|
USER_VARS = json.loads(USER_VARS or "{}")
|
16031
16039
|
USER_LSTS = json.loads(USER_LSTS or "{}")
|
@@ -16081,6 +16089,8 @@ async def return_activity_metrics(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
|
|
16081
16089
|
key = "/startapp" if USER_UTM == "/startapp" else "/start"
|
16082
16090
|
metrics_by_month[month_key][key] += 1
|
16083
16091
|
|
16092
|
+
process_user_rows(data_users)
|
16093
|
+
|
16084
16094
|
for item in data_ents:
|
16085
16095
|
ENT_TID = item[0]
|
16086
16096
|
sql = f'SELECT USER_TID, USER_VARS, USER_LSTS FROM {schema_name}_{ENT_TID}.USER'
|
@@ -16088,11 +16098,6 @@ async def return_activity_metrics(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
|
|
16088
16098
|
print(f"schema_name {data_users=}")
|
16089
16099
|
process_user_rows(data_users)
|
16090
16100
|
|
16091
|
-
sql = f"SELECT USER_TID, USER_VARS, USER_LSTS FROM \"USER\""
|
16092
|
-
data_users = await db_select_pg(sql, (), BASE_P)
|
16093
|
-
print(f"{data_users=}")
|
16094
|
-
process_user_rows(data_users)
|
16095
|
-
|
16096
16101
|
all_months = sorted(metrics_by_month.keys())
|
16097
16102
|
f_name = os.path.join(EXTRA_D, "1_activity_metrics.csv")
|
16098
16103
|
with open(f_name, mode="w", encoding="utf-8", newline="") as csvfile:
|
@@ -16115,18 +16120,19 @@ async def return_activity_metrics(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
|
|
16115
16120
|
csvfile.write(f"Unique wallet count: {len(wallets_set)}\n")
|
16116
16121
|
csvfile.write(f"Unique users count: {len(users_set)}\n")
|
16117
16122
|
|
16123
|
+
result = f_name
|
16118
16124
|
thumb = types.FSInputFile(os.path.join(EXTRA_D, 'parse.jpg'))
|
16119
16125
|
await bot.send_document(chat_id=my_tid, document=types.FSInputFile(f_name), thumbnail=thumb)
|
16120
16126
|
except Exception as e:
|
16121
16127
|
logger.info(log_ % str(e))
|
16122
16128
|
await asyncio.sleep(round(random.uniform(0, 1), 2))
|
16129
|
+
finally:
|
16130
|
+
return result
|
16123
16131
|
|
16124
16132
|
|
16125
|
-
async def return_unit_metrics(bot,
|
16133
|
+
async def return_unit_metrics(bot, data_users, EXTRA_D):
|
16134
|
+
result = None
|
16126
16135
|
try:
|
16127
|
-
sql = 'SELECT USER_TID, USER_VARS, USER_LSTS FROM "USER"'
|
16128
|
-
data_users = await db_select_pg(sql, (), BASE_P)
|
16129
|
-
|
16130
16136
|
metrics = defaultdict(lambda: {
|
16131
16137
|
"new_users": 0,
|
16132
16138
|
"sum_amount": 0.0,
|
@@ -16165,13 +16171,14 @@ async def return_unit_metrics(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
|
|
16165
16171
|
all_months = sorted(metrics.keys())
|
16166
16172
|
cumulative_users = 0
|
16167
16173
|
results = []
|
16168
|
-
|
16174
|
+
first_mrr = None
|
16175
|
+
|
16169
16176
|
for idx, mo in enumerate(all_months):
|
16170
16177
|
data = metrics[mo]
|
16171
16178
|
new_u = data["new_users"]
|
16172
16179
|
cumulative_users += new_u
|
16173
|
-
|
16174
16180
|
MRR = data["sum_amount"]
|
16181
|
+
|
16175
16182
|
def fmt(x):
|
16176
16183
|
return f"{x:.2f}".rstrip("0").rstrip(".") if x is not None else ""
|
16177
16184
|
|
@@ -16209,8 +16216,8 @@ async def return_unit_metrics(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
|
|
16209
16216
|
"CAC": ""
|
16210
16217
|
})
|
16211
16218
|
|
16212
|
-
|
16213
|
-
with open(
|
16219
|
+
f_name = os.path.join(EXTRA_D, "2_unit_metrics.csv")
|
16220
|
+
with open(f_name, "w", newline="", encoding="utf-8") as f:
|
16214
16221
|
writer = csv.writer(f)
|
16215
16222
|
writer.writerow(["MO", "N", "MRR", "ARPU", "ARR", "ChurnR", "LTV1", "LTV2", "CMGR", "CAC"])
|
16216
16223
|
for row in results:
|
@@ -16226,82 +16233,19 @@ async def return_unit_metrics(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
|
|
16226
16233
|
writer.writerow([])
|
16227
16234
|
writer.writerow([f"Rev ~ ×{round(avg, 2)} monthly"])
|
16228
16235
|
|
16236
|
+
result = f_name
|
16229
16237
|
thumb = types.FSInputFile(os.path.join(EXTRA_D, "parse.jpg"))
|
16230
|
-
await bot.send_document(chat_id=my_tid, document=types.FSInputFile(
|
16238
|
+
await bot.send_document(chat_id=my_tid, document=types.FSInputFile(f_name), thumbnail=thumb)
|
16231
16239
|
except Exception as e:
|
16232
16240
|
logger.info(log_ % str(e))
|
16233
16241
|
await asyncio.sleep(round(random.uniform(0, 1), 2))
|
16242
|
+
finally:
|
16243
|
+
return result
|
16234
16244
|
|
16235
16245
|
|
16236
|
-
async def return_cohort_metrics(bot,
|
16246
|
+
async def return_cohort_metrics(bot, data_users, EXTRA_D):
|
16247
|
+
result = None
|
16237
16248
|
try:
|
16238
|
-
sql = 'SELECT USER_TID, USER_VARS, USER_LSTS FROM "USER"'
|
16239
|
-
data_users = await db_select_pg(sql, (), BASE_P)
|
16240
|
-
|
16241
|
-
months = ["2025-06", "2025-07", "2025-08", "2025-09"]
|
16242
|
-
data_users = []
|
16243
|
-
for _ in range(30):
|
16244
|
-
# дата входа
|
16245
|
-
entry_month = random.choice(months)
|
16246
|
-
entry_day = random.randint(1, 28)
|
16247
|
-
entry_date = f"{entry_month}-{entry_day:02}"
|
16248
|
-
entry_dt_obj = datetime.strptime(entry_date, '%Y-%m-%d')
|
16249
|
-
entry_dt = f"{entry_dt_obj.strftime('%d-%m-%Y')}_{datetime.now().strftime('%H-%M-%S')}"
|
16250
|
-
utm = random.choice(["/start", "/startapp"])
|
16251
|
-
|
16252
|
-
# месяцы от входа и дальше
|
16253
|
-
valid_months = [m for m in months if datetime.strptime(m + "-01", "%Y-%m-%d") >= entry_dt_obj.replace(day=1)]
|
16254
|
-
if not valid_months:
|
16255
|
-
valid_months = [entry_month]
|
16256
|
-
|
16257
|
-
user_mau = sorted(random.sample(valid_months, k=random.randint(1, len(valid_months))))
|
16258
|
-
user_dau_dates = set()
|
16259
|
-
txs, payments = [], []
|
16260
|
-
|
16261
|
-
# платеж
|
16262
|
-
if user_mau:
|
16263
|
-
pay_month = random.choice(user_mau)
|
16264
|
-
pay_day = random.randint(1, 28)
|
16265
|
-
pay_date = f"{pay_month}-{pay_day:02}"
|
16266
|
-
dt_pay = datetime.strptime(pay_date, "%Y-%m-%d")
|
16267
|
-
# if dt_pay >= entry_dt_obj:
|
16268
|
-
payments = [{
|
16269
|
-
"TYPE": random.choice(["don", "sub", "pst"]),
|
16270
|
-
"DT_START": f"{dt_pay.strftime('%d-%m-%Y')}_14-00-00",
|
16271
|
-
"DT_END": "0",
|
16272
|
-
"AMOUNT": str(random.randint(1, 10))
|
16273
|
-
}]
|
16274
|
-
user_dau_dates.add(pay_date)
|
16275
|
-
|
16276
|
-
# вход в приложение
|
16277
|
-
user_dau_dates.add(entry_date)
|
16278
|
-
for m in user_mau:
|
16279
|
-
day = random.randint(1, 28)
|
16280
|
-
visit = f"{m}-{day:02}"
|
16281
|
-
dt_visit = datetime.strptime(visit, "%Y-%m-%d")
|
16282
|
-
if dt_visit >= entry_dt_obj and random.random() < 0.7:
|
16283
|
-
user_dau_dates.add(visit)
|
16284
|
-
|
16285
|
-
# статусы (отток) с низкой вероятностью
|
16286
|
-
USER_STATUSES = []
|
16287
|
-
if random.random() < 0.2: # 10% шанс оттока
|
16288
|
-
churn_month = random.choice(valid_months)
|
16289
|
-
churn_day = random.randint(1, 28)
|
16290
|
-
churn_date = f"{churn_month}-{churn_day:02}"
|
16291
|
-
churn_ts = datetime.strptime(churn_date, "%Y-%m-%d").strftime("%d-%m-%Y") + "_23-59-59"
|
16292
|
-
USER_STATUSES = [{random.choice(["left", "kicked"]): churn_ts}]
|
16293
|
-
|
16294
|
-
user_dau = sorted(user_dau_dates)
|
16295
|
-
wallet = f"wallet{random.randint(1, 100)}" if txs else random.choice([f"wallet{random.randint(1, 100)}", ""])
|
16296
|
-
|
16297
|
-
data_users.append((
|
16298
|
-
random.randint(100000, 999999),
|
16299
|
-
json.dumps({"USER_WALLET": wallet, "USER_UTM": utm, "USER_DT": entry_dt}),
|
16300
|
-
json.dumps({"USER_DAU": user_dau, "USER_MAU": user_mau, "USER_TXS": txs,
|
16301
|
-
"USER_PAYMENTS": payments, "USER_STATUSES": USER_STATUSES})
|
16302
|
-
))
|
16303
|
-
print(f"gen {data_users=}")
|
16304
|
-
|
16305
16249
|
cohorts = defaultdict(set)
|
16306
16250
|
activity_months = defaultdict(set)
|
16307
16251
|
|
@@ -16324,13 +16268,6 @@ async def return_cohort_metrics(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
|
|
16324
16268
|
cohort_months = sorted(cohorts.keys())
|
16325
16269
|
num_months = len(cohort_months)
|
16326
16270
|
|
16327
|
-
def add_months(mo_str, n):
|
16328
|
-
y, m = map(int, mo_str.split("-"))
|
16329
|
-
total = m + n
|
16330
|
-
new_y = y + (total - 1) // 12
|
16331
|
-
new_m = (total - 1) % 12 + 1
|
16332
|
-
return f"{new_y:04d}-{new_m:02d}"
|
16333
|
-
|
16334
16271
|
# Собираем таблицу посменно по календарным месяцам
|
16335
16272
|
table = []
|
16336
16273
|
header = ["Месяц/Когорта"]
|
@@ -16383,90 +16320,27 @@ async def return_cohort_metrics(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
|
|
16383
16320
|
|
16384
16321
|
avg_churn = (total_lost / total_start) if total_start else 0
|
16385
16322
|
|
16386
|
-
|
16387
|
-
with open(
|
16323
|
+
f_name = os.path.join(EXTRA_D, "3_cohort_metrics.csv")
|
16324
|
+
with open(f_name, "w", newline="", encoding="utf-8") as f:
|
16388
16325
|
writer = csv.writer(f)
|
16389
16326
|
for r in table:
|
16390
16327
|
writer.writerow(r)
|
16391
16328
|
writer.writerow([])
|
16392
16329
|
writer.writerow([f"Churn ~ ×{avg_churn:.2f} monthly"])
|
16393
16330
|
|
16331
|
+
result = f_name
|
16394
16332
|
thumb = types.FSInputFile(os.path.join(EXTRA_D, "parse.jpg"))
|
16395
|
-
await bot.send_document(chat_id=my_tid, document=types.FSInputFile(
|
16333
|
+
await bot.send_document(chat_id=my_tid, document=types.FSInputFile(f_name), thumbnail=thumb)
|
16396
16334
|
except Exception as e:
|
16397
16335
|
logger.info(log_ % str(e))
|
16398
16336
|
await asyncio.sleep(round(random.uniform(0, 1), 2))
|
16337
|
+
finally:
|
16338
|
+
return result
|
16399
16339
|
|
16400
16340
|
|
16401
|
-
async def return_retention_metrics(bot,
|
16341
|
+
async def return_retention_metrics(bot, data_users, EXTRA_D):
|
16342
|
+
result = None
|
16402
16343
|
try:
|
16403
|
-
sql = 'SELECT USER_TID, USER_VARS, USER_LSTS FROM "USER"'
|
16404
|
-
data_users = await db_select_pg(sql, (), BASE_P)
|
16405
|
-
|
16406
|
-
months = ["2025-06", "2025-07", "2025-08", "2025-09"]
|
16407
|
-
data_users = []
|
16408
|
-
for _ in range(30):
|
16409
|
-
# дата входа
|
16410
|
-
entry_month = random.choice(months)
|
16411
|
-
entry_day = random.randint(1, 28)
|
16412
|
-
entry_date = f"{entry_month}-{entry_day:02}"
|
16413
|
-
entry_dt_obj = datetime.strptime(entry_date, '%Y-%m-%d')
|
16414
|
-
entry_dt = f"{entry_dt_obj.strftime('%d-%m-%Y')}_{datetime.now().strftime('%H-%M-%S')}"
|
16415
|
-
utm = random.choice(["/start", "/startapp"])
|
16416
|
-
|
16417
|
-
# месяцы от входа и дальше
|
16418
|
-
valid_months = [m for m in months if datetime.strptime(m + "-01", "%Y-%m-%d") >= entry_dt_obj.replace(day=1)]
|
16419
|
-
if not valid_months:
|
16420
|
-
valid_months = [entry_month]
|
16421
|
-
|
16422
|
-
user_mau = sorted(random.sample(valid_months, k=random.randint(1, len(valid_months))))
|
16423
|
-
user_dau_dates = set()
|
16424
|
-
txs, payments = [], []
|
16425
|
-
|
16426
|
-
# платеж
|
16427
|
-
if user_mau:
|
16428
|
-
pay_month = random.choice(user_mau)
|
16429
|
-
pay_day = random.randint(1, 28)
|
16430
|
-
pay_date = f"{pay_month}-{pay_day:02}"
|
16431
|
-
dt_pay = datetime.strptime(pay_date, "%Y-%m-%d")
|
16432
|
-
# if dt_pay >= entry_dt_obj:
|
16433
|
-
payments = [{
|
16434
|
-
"TYPE": random.choice(["don", "sub", "pst"]),
|
16435
|
-
"DT_START": f"{dt_pay.strftime('%d-%m-%Y')}_14-00-00",
|
16436
|
-
"DT_END": "0",
|
16437
|
-
"AMOUNT": str(random.randint(1, 10))
|
16438
|
-
}]
|
16439
|
-
user_dau_dates.add(pay_date)
|
16440
|
-
|
16441
|
-
# вход в приложение
|
16442
|
-
user_dau_dates.add(entry_date)
|
16443
|
-
for m in user_mau:
|
16444
|
-
day = random.randint(1, 28)
|
16445
|
-
visit = f"{m}-{day:02}"
|
16446
|
-
dt_visit = datetime.strptime(visit, "%Y-%m-%d")
|
16447
|
-
if dt_visit >= entry_dt_obj and random.random() < 0.7:
|
16448
|
-
user_dau_dates.add(visit)
|
16449
|
-
|
16450
|
-
# статусы (отток) с низкой вероятностью
|
16451
|
-
USER_STATUSES = []
|
16452
|
-
if random.random() < 0.2: # 10% шанс оттока
|
16453
|
-
churn_month = random.choice(valid_months)
|
16454
|
-
churn_day = random.randint(1, 28)
|
16455
|
-
churn_date = f"{churn_month}-{churn_day:02}"
|
16456
|
-
churn_ts = datetime.strptime(churn_date, "%Y-%m-%d").strftime("%d-%m-%Y") + "_23-59-59"
|
16457
|
-
USER_STATUSES = [{random.choice(["left", "kicked"]): churn_ts}]
|
16458
|
-
|
16459
|
-
user_dau = sorted(user_dau_dates)
|
16460
|
-
wallet = f"wallet{random.randint(1, 100)}" if txs else random.choice([f"wallet{random.randint(1, 100)}", ""])
|
16461
|
-
|
16462
|
-
data_users.append((
|
16463
|
-
random.randint(100000, 999999),
|
16464
|
-
json.dumps({"USER_WALLET": wallet, "USER_UTM": utm, "USER_DT": entry_dt}),
|
16465
|
-
json.dumps({"USER_DAU": user_dau, "USER_MAU": user_mau, "USER_TXS": txs,
|
16466
|
-
"USER_PAYMENTS": payments, "USER_STATUSES": USER_STATUSES})
|
16467
|
-
))
|
16468
|
-
print(f"gen {data_users=}")
|
16469
|
-
|
16470
16344
|
rev_by_cohort = defaultdict(lambda: defaultdict(float))
|
16471
16345
|
cohort_users = defaultdict(set)
|
16472
16346
|
|
@@ -16505,8 +16379,8 @@ async def return_retention_metrics(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
|
|
16505
16379
|
num_cohorts = len(cohort_months)
|
16506
16380
|
first_cohort = cohort_months[0]
|
16507
16381
|
|
16508
|
-
|
16509
|
-
with open(
|
16382
|
+
f_name = os.path.join(EXTRA_D, "4_retention_metrics.csv")
|
16383
|
+
with open(f_name, "w", newline="", encoding="utf-8") as f:
|
16510
16384
|
writer = csv.writer(f)
|
16511
16385
|
header = ["Месяц/Когорта"] + [
|
16512
16386
|
f"{c} ({len(cohort_users[c])})" for c in cohort_months
|
@@ -16546,81 +16420,19 @@ async def return_retention_metrics(bot, PROJECT_USERNAME, EXTRA_D, BASE_P):
|
|
16546
16420
|
writer.writerow([])
|
16547
16421
|
writer.writerow([f"NRR ~ ×{avg_multiplier:.2f} monthly"])
|
16548
16422
|
|
16423
|
+
result = f_name
|
16549
16424
|
thumb = types.FSInputFile(os.path.join(EXTRA_D, "parse.jpg"))
|
16550
|
-
await bot.send_document(chat_id=my_tid, document=types.FSInputFile(
|
16425
|
+
await bot.send_document(chat_id=my_tid, document=types.FSInputFile(f_name), thumbnail=thumb)
|
16551
16426
|
except Exception as e:
|
16552
16427
|
logger.info(log_ % str(e))
|
16553
16428
|
await asyncio.sleep(round(random.uniform(0, 1), 2))
|
16429
|
+
finally:
|
16430
|
+
return result
|
16554
16431
|
|
16555
16432
|
|
16556
|
-
async def return_profit_and_loss_metrics(bot,
|
16433
|
+
async def return_profit_and_loss_metrics(bot, data_users, EXTRA_D):
|
16434
|
+
result = None
|
16557
16435
|
try:
|
16558
|
-
sql = 'SELECT USER_TID, USER_VARS, USER_LSTS FROM "USER"'
|
16559
|
-
data_users = await db_select_pg(sql, (), BASE_P)
|
16560
|
-
|
16561
|
-
months = ["2025-06", "2025-07", "2025-08", "2025-09"]
|
16562
|
-
data_users = []
|
16563
|
-
for _ in range(30):
|
16564
|
-
# дата входа
|
16565
|
-
entry_month = random.choice(months)
|
16566
|
-
entry_day = random.randint(1, 28)
|
16567
|
-
entry_date = f"{entry_month}-{entry_day:02}"
|
16568
|
-
entry_dt_obj = datetime.strptime(entry_date, '%Y-%m-%d')
|
16569
|
-
entry_dt = f"{entry_dt_obj.strftime('%d-%m-%Y')}_{datetime.now().strftime('%H-%M-%S')}"
|
16570
|
-
utm = random.choice(["/start", "/startapp"])
|
16571
|
-
|
16572
|
-
# месяцы от входа и дальше
|
16573
|
-
valid_months = [m for m in months if datetime.strptime(m + "-01", "%Y-%m-%d") >= entry_dt_obj.replace(day=1)]
|
16574
|
-
if not valid_months:
|
16575
|
-
valid_months = [entry_month]
|
16576
|
-
|
16577
|
-
user_mau = sorted(random.sample(valid_months, k=random.randint(1, len(valid_months))))
|
16578
|
-
user_dau_dates = set()
|
16579
|
-
txs, payments = [], []
|
16580
|
-
|
16581
|
-
# платеж
|
16582
|
-
if user_mau:
|
16583
|
-
pay_month = random.choice(user_mau)
|
16584
|
-
pay_day = random.randint(1, 28)
|
16585
|
-
pay_date = f"{pay_month}-{pay_day:02}"
|
16586
|
-
dt_pay = datetime.strptime(pay_date, "%Y-%m-%d")
|
16587
|
-
payments = [{
|
16588
|
-
"TYPE": random.choice(["don", "sub", "pst"]),
|
16589
|
-
"DT_START": f"{dt_pay.strftime('%d-%m-%Y')}_14-00-00",
|
16590
|
-
"DT_END": "0",
|
16591
|
-
"AMOUNT": str(random.randint(10, 1000))
|
16592
|
-
}]
|
16593
|
-
user_dau_dates.add(pay_date)
|
16594
|
-
|
16595
|
-
# вход в приложение
|
16596
|
-
user_dau_dates.add(entry_date)
|
16597
|
-
for m in user_mau:
|
16598
|
-
day = random.randint(1, 28)
|
16599
|
-
visit = f"{m}-{day:02}"
|
16600
|
-
dt_visit = datetime.strptime(visit, "%Y-%m-%d")
|
16601
|
-
if dt_visit >= entry_dt_obj and random.random() < 0.7:
|
16602
|
-
user_dau_dates.add(visit)
|
16603
|
-
|
16604
|
-
# статусы (отток) с низкой вероятностью
|
16605
|
-
USER_STATUSES = []
|
16606
|
-
if random.random() < 0.2: # 10% шанс оттока
|
16607
|
-
churn_month = random.choice(valid_months)
|
16608
|
-
churn_day = random.randint(1, 28)
|
16609
|
-
churn_date = f"{churn_month}-{churn_day:02}"
|
16610
|
-
churn_ts = datetime.strptime(churn_date, "%Y-%m-%d").strftime("%d-%m-%Y") + "_23-59-59"
|
16611
|
-
USER_STATUSES = [{random.choice(["left", "kicked"]): churn_ts}]
|
16612
|
-
|
16613
|
-
user_dau = sorted(user_dau_dates)
|
16614
|
-
wallet = f"wallet{random.randint(1, 100)}" if txs else random.choice([f"wallet{random.randint(1, 100)}", ""])
|
16615
|
-
|
16616
|
-
data_users.append((
|
16617
|
-
random.randint(100000, 999999),
|
16618
|
-
json.dumps({"USER_WALLET": wallet, "USER_UTM": utm, "USER_DT": entry_dt}),
|
16619
|
-
json.dumps({"USER_DAU": user_dau, "USER_MAU": user_mau, "USER_TXS": txs,
|
16620
|
-
"USER_PAYMENTS": payments, "USER_STATUSES": USER_STATUSES})
|
16621
|
-
))
|
16622
|
-
print(f"gen {data_users=}")
|
16623
|
-
|
16624
16436
|
metrics = defaultdict(lambda: {"sum_amount": 0.0})
|
16625
16437
|
|
16626
16438
|
for USER_TID, USER_VARS, USER_LSTS in data_users:
|
@@ -16666,8 +16478,8 @@ async def return_profit_and_loss_metrics(bot, PROJECT_USERNAME, EXTRA_D, BASE_P)
|
|
16666
16478
|
fmt(NP)
|
16667
16479
|
])
|
16668
16480
|
|
16669
|
-
|
16670
|
-
with open(
|
16481
|
+
f_name = os.path.join(EXTRA_D, "5_profit_and_loss_metrics.csv")
|
16482
|
+
with open(f_name, "w", newline="", encoding="utf-8") as f:
|
16671
16483
|
writer = csv.writer(f)
|
16672
16484
|
writer.writerow([
|
16673
16485
|
"Mo", "MRR", "COGS", "Gross Profit",
|
@@ -16676,12 +16488,14 @@ async def return_profit_and_loss_metrics(bot, PROJECT_USERNAME, EXTRA_D, BASE_P)
|
|
16676
16488
|
for row in results:
|
16677
16489
|
writer.writerow(row)
|
16678
16490
|
|
16491
|
+
result = f_name
|
16679
16492
|
thumb = types.FSInputFile(os.path.join(EXTRA_D, "parse.jpg"))
|
16680
|
-
await bot.send_document(chat_id=my_tid, document=types.FSInputFile(
|
16493
|
+
await bot.send_document(chat_id=my_tid, document=types.FSInputFile(f_name), thumbnail=thumb)
|
16681
16494
|
except Exception as e:
|
16682
16495
|
logger.info(log_ % str(e))
|
16683
16496
|
await asyncio.sleep(round(random.uniform(0, 1), 2))
|
16684
|
-
|
16497
|
+
finally:
|
16498
|
+
return result
|
16685
16499
|
# endregion
|
16686
16500
|
|
16687
16501
|
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|