feat(Метрики МП): top KPI cards, grid/list toggle, clickable cards with chart modal

- 4 top KPI cards (registrations, downloads by OS+total, MAU, DAU) from
  drb_iliyas_telecomkz_daily_info + telecomkz_mau_stats; JSON gets top{} block
- grid/list view toggle (list = full-width cards), persisted in localStorage
- click any card -> modal with Chart.js line chart + per-month table; tooltip
  shows monthly delta for both years; ESC/overlay/✕ to close
This commit is contained in:
Iliyas 2026-06-17 13:09:00 +05:00
parent 9d331db8a3
commit b3ec72ca34
2 changed files with 554 additions and 129 deletions

File diff suppressed because it is too large Load Diff

View File

@ -145,6 +145,121 @@ order by t.report_period_id
""".strip()
# ─── Верхние KPI: регистрации / скачивания / MAU / DAU ───
TOP_SQL = """
with t as (
select report_period_id, entry_date, sum(registered) over (order by entry_date) as registered_total, sum(ios_installs) over (order by entry_date) as ios_installs, sum(android_installs) over (order by entry_date) as android_installs,
sum(ios_installs+android_installs) over (order by entry_date) as sum_installs
from drb.drb_iliyas_telecomkz_daily_info
)
, t1 as (
select period as report_period_id,
cast(date_add(DATE '1970-01-01', entry_date) as date) AS entry_date,
dau_wo_none_plat as dau, mau_wo_none_plat as mau
from drb.drb_iliyas_telecomkz_mau_stats
where cast(date_add(DATE '1970-01-01', entry_date) as date) < cast(now() as date)
)
select t.report_period_id, t.entry_date, t.registered_total, t.ios_installs, t.android_installs, t.sum_installs, t1.mau, t1.dau
from t
left join t1 on t.entry_date = t1.entry_date
""".strip()
TOP_TREND_MONTHS = 13 # сколько последних месяцев показывать в тренде верхних карточек
def fetch_top(conn):
cur = conn.cursor()
log.info("Выполнение запроса верхних KPI...")
cur.execute(TOP_SQL)
rows = cur.fetchall()
names = [d[0].lower() for d in cur.description]
cur.close()
idx = {n: i for i, n in enumerate(names)}
def g(r, name):
v = r[idx[name]] if name in idx else None
return v
recs = []
for r in rows:
recs.append({
"rpid": int(g(r, "report_period_id")) if g(r, "report_period_id") is not None else None,
"date": str(g(r, "entry_date")),
"registered_total": g(r, "registered_total"),
"ios_installs": g(r, "ios_installs"),
"android_installs": g(r, "android_installs"),
"sum_installs": g(r, "sum_installs"),
"mau": g(r, "mau"),
"dau": g(r, "dau"),
})
recs.sort(key=lambda x: x["date"])
log.info("Верхние KPI: дней %d, по %s", len(recs), recs[-1]["date"] if recs else "")
return recs
def build_top(recs):
if not recs:
return None
last = recs[-1]
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"]
mau_v, _ = last_nonnull("mau")
dau_v, _ = last_nonnull("dau")
# помесячная агрегация: month-end для кумулятивных, среднее для активности
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,
})
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"])
if r["mau"] is not None:
m["mau_sum"] += int(r["mau"]); m["mau_cnt"] += 1
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 = [], [], [], [], []
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)
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
return {
"as_of": last["date"],
"registered_total": _i(last["registered_total"]),
"installs_total": _i(last["sum_installs"]),
"installs_ios": _i(last["ios_installs"]),
"installs_android": _i(last["android_installs"]),
"mau": mau_v,
"dau": dau_v,
"trend_labels": labels,
"registered_series": reg_s,
"installs_series": inst_s,
"mau_series": mau_s,
"dau_series": dau_s,
}
def fetch_monthly(conn, sql):
cur = conn.cursor()
log.info("Выполнение запроса метрик МП (помесячно)...")
@ -256,6 +371,7 @@ def main() -> int:
conn = base.connect_impala(cfg)
try:
rows = fetch_monthly(conn, sql)
top = build_top(fetch_top(conn))
finally:
try:
conn.close()
@ -268,6 +384,7 @@ def main() -> int:
return 1
payload = build_payload(rows, b)
payload["top"] = top
if write_if_changed(payload):
base.git_commit_push(cfg, [OUT_REL],
f"data: update app metrics {dt.date.today():%Y-%m-%d}")