feat(Метрики МП): daily scrollable charts, MAU max/month, DAU avg, iOS+Android lines
- updater: monthly MAU = MAX over month, monthly DAU = AVG; add monthly
iOS/Android series + daily series (last 540 days) to top{} JSON
- modal: monthly chart + scrollable daily chart (scrolls to latest); downloads
show Всего + semi-transparent iOS/Android lines (both monthly & daily)
This commit is contained in:
parent
60abc04ea5
commit
5b78a24439
3965
app_stats/index.html
3965
app_stats/index.html
File diff suppressed because it is too large
Load Diff
@ -164,7 +164,8 @@ from t
|
||||
left join t1 on t.entry_date = t1.entry_date
|
||||
""".strip()
|
||||
|
||||
TOP_TREND_MONTHS = 13 # сколько последних месяцев показывать в тренде верхних карточек
|
||||
TOP_TREND_MONTHS = 13 # сколько последних месяцев показывать в месячном тренде
|
||||
TOP_DAILY_DAYS = 540 # сколько последних дней отдавать в дневной график (со скроллом)
|
||||
|
||||
|
||||
def fetch_top(conn):
|
||||
@ -202,47 +203,64 @@ def build_top(recs):
|
||||
return None
|
||||
last = recs[-1]
|
||||
|
||||
def _i(v):
|
||||
return int(v) if v is not None else 0
|
||||
|
||||
def last_nonnull(field):
|
||||
for r in reversed(recs):
|
||||
if r[field] is not None:
|
||||
return int(r[field]), r["date"]
|
||||
return 0, last["date"]
|
||||
return int(r[field])
|
||||
return 0
|
||||
|
||||
mau_v, _ = last_nonnull("mau")
|
||||
dau_v, _ = last_nonnull("dau")
|
||||
|
||||
# помесячная агрегация: month-end для кумулятивных, среднее для активности
|
||||
# помесячная агрегация:
|
||||
# registered/installs — month-end (последнее значение нарастающего итога в месяце)
|
||||
# MAU — МАКСИМУМ за месяц; DAU — СРЕДНЕЕ за месяц
|
||||
months = {} # rpid -> aggregate
|
||||
for r in recs:
|
||||
m = months.setdefault(r["rpid"], {
|
||||
"last_date": "", "registered_total": 0, "sum_installs": 0,
|
||||
"mau_sum": 0, "mau_cnt": 0, "dau_sum": 0, "dau_cnt": 0,
|
||||
"last_date": "", "registered_total": 0, "sum_installs": 0, "ios": 0, "android": 0,
|
||||
"mau_max": 0, "dau_sum": 0, "dau_cnt": 0,
|
||||
})
|
||||
if r["date"] >= m["last_date"]:
|
||||
m["last_date"] = r["date"]
|
||||
if r["registered_total"] is not None:
|
||||
m["registered_total"] = int(r["registered_total"])
|
||||
if r["sum_installs"] is not None:
|
||||
m["sum_installs"] = int(r["sum_installs"])
|
||||
m["registered_total"] = _i(r["registered_total"])
|
||||
m["sum_installs"] = _i(r["sum_installs"])
|
||||
m["ios"] = _i(r["ios_installs"])
|
||||
m["android"] = _i(r["android_installs"])
|
||||
if r["mau"] is not None:
|
||||
m["mau_sum"] += int(r["mau"]); m["mau_cnt"] += 1
|
||||
m["mau_max"] = max(m["mau_max"], int(r["mau"]))
|
||||
if r["dau"] is not None:
|
||||
m["dau_sum"] += int(r["dau"]); m["dau_cnt"] += 1
|
||||
|
||||
ordered = sorted(m for m in months if m is not None)
|
||||
ordered = ordered[-TOP_TREND_MONTHS:]
|
||||
labels, reg_s, inst_s, mau_s, dau_s = [], [], [], [], []
|
||||
ordered = sorted(months)[-TOP_TREND_MONTHS:]
|
||||
labels, reg_s, inst_s, ios_s, andr_s, mau_s, dau_s = [], [], [], [], [], [], []
|
||||
for rpid in ordered:
|
||||
a = months[rpid]
|
||||
y, mo = rpid // 100, rpid % 100
|
||||
labels.append(f"{_MONTHS_RU_SHORT[mo]} {y % 100:02d}")
|
||||
reg_s.append(a["registered_total"])
|
||||
inst_s.append(a["sum_installs"])
|
||||
mau_s.append(round(a["mau_sum"] / a["mau_cnt"]) if a["mau_cnt"] else 0)
|
||||
ios_s.append(a["ios"])
|
||||
andr_s.append(a["android"])
|
||||
mau_s.append(a["mau_max"])
|
||||
dau_s.append(round(a["dau_sum"] / a["dau_cnt"]) if a["dau_cnt"] else 0)
|
||||
|
||||
def _i(v):
|
||||
return int(v) if v is not None else 0
|
||||
# MAU в карточке — максимум за текущий (последний) месяц; DAU — последнее дневное значение
|
||||
cur_month_max_mau = months[last["rpid"]]["mau_max"]
|
||||
mau_headline = cur_month_max_mau if cur_month_max_mau else last_nonnull("mau")
|
||||
dau_headline = last_nonnull("dau")
|
||||
|
||||
# дневные ряды (последние N дней) для графика со скроллом
|
||||
daily_recs = recs[-TOP_DAILY_DAYS:]
|
||||
daily = {
|
||||
"labels": [r["date"] for r in daily_recs],
|
||||
"registered": [_i(r["registered_total"]) for r in daily_recs],
|
||||
"installs": [_i(r["sum_installs"]) for r in daily_recs],
|
||||
"installs_ios": [_i(r["ios_installs"]) for r in daily_recs],
|
||||
"installs_android": [_i(r["android_installs"]) for r in daily_recs],
|
||||
"mau": [int(r["mau"]) if r["mau"] is not None else None for r in daily_recs],
|
||||
"dau": [int(r["dau"]) if r["dau"] is not None else None for r in daily_recs],
|
||||
}
|
||||
|
||||
return {
|
||||
"as_of": last["date"],
|
||||
@ -250,13 +268,16 @@ def build_top(recs):
|
||||
"installs_total": _i(last["sum_installs"]),
|
||||
"installs_ios": _i(last["ios_installs"]),
|
||||
"installs_android": _i(last["android_installs"]),
|
||||
"mau": mau_v,
|
||||
"dau": dau_v,
|
||||
"mau": mau_headline,
|
||||
"dau": dau_headline,
|
||||
"trend_labels": labels,
|
||||
"registered_series": reg_s,
|
||||
"installs_series": inst_s,
|
||||
"installs_ios_series": ios_s,
|
||||
"installs_android_series": andr_s,
|
||||
"mau_series": mau_s,
|
||||
"dau_series": dau_s,
|
||||
"daily": daily,
|
||||
}
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user