108 lines
4.2 KiB
Python
108 lines
4.2 KiB
Python
import sqlite3
|
||
from datetime import datetime
|
||
from collections import defaultdict
|
||
|
||
conn = sqlite3.connect('/app/state/progress.db')
|
||
conn.row_factory = sqlite3.Row
|
||
|
||
rows = conn.execute('''
|
||
SELECT h.created_at, h.chapter_number, h.volume, m.title, h.manga_url
|
||
FROM history h
|
||
LEFT JOIN mangas m ON h.manga_url = m.url
|
||
WHERE h.event_type IN ("downloaded","auto_downloaded")
|
||
ORDER BY h.created_at
|
||
''').fetchall()
|
||
|
||
if not rows:
|
||
print("История пуста")
|
||
conn.close()
|
||
exit()
|
||
|
||
times = [datetime.fromisoformat(r["created_at"]) for r in rows]
|
||
total_dur = (times[-1] - times[0]).total_seconds()
|
||
|
||
print("=== ОБЩАЯ СТАТИСТИКА ===")
|
||
print(f"Глав скачано: {len(rows)}")
|
||
print(f"Период: {times[0].strftime('%d.%m %H:%M:%S')} — {times[-1].strftime('%d.%m %H:%M:%S')}")
|
||
print(f"Общее время: {total_dur/3600:.2f} ч ({total_dur/60:.0f} мин)")
|
||
print(f"Средняя скорость: {len(rows)/(total_dur/60):.2f} глав/мин ({total_dur/len(rows):.1f} сек/глава)")
|
||
|
||
# --- По мангам ---
|
||
print("\n=== ПО МАНГАМ ===")
|
||
by_manga = defaultdict(list)
|
||
for i, r in enumerate(rows):
|
||
by_manga[r["manga_url"]].append(times[i])
|
||
|
||
for url, ts in sorted(by_manga.items(), key=lambda x: x[1][0]):
|
||
title = next((r["title"] for r in rows if r["manga_url"] == url and r["title"]), url[-40:])
|
||
dur = (ts[-1] - ts[0]).total_seconds() if len(ts) > 1 else 0
|
||
rate = len(ts) / (dur / 60) if dur > 0 else 0
|
||
print(f" {(title or url)[:38]:38} {len(ts):4d} гл {dur/60:5.0f} мин {rate:.2f} гл/мин")
|
||
|
||
# --- По часам ---
|
||
print("\n=== ГЛАВЫ ПО ЧАСАМ ===")
|
||
by_hour = defaultdict(int)
|
||
for t in times:
|
||
by_hour[t.strftime('%d.%m %H:00')] += 1
|
||
for hour, cnt in sorted(by_hour.items()):
|
||
bar = '█' * min(cnt, 60)
|
||
print(f" {hour} {cnt:4d} глав {bar}")
|
||
|
||
# --- Паузы > 5 мин ---
|
||
print("\n=== ПАУЗЫ > 5 МИН (между главами) ===")
|
||
big_gaps = []
|
||
for i in range(len(times) - 1):
|
||
sec = (times[i+1] - times[i]).total_seconds()
|
||
if sec > 300:
|
||
big_gaps.append((times[i], times[i+1], sec, rows[i]["title"], rows[i+1]["title"]))
|
||
|
||
if big_gaps:
|
||
for t1, t2, sec, m1, m2 in big_gaps:
|
||
same = (rows[big_gaps.index((t1,t2,sec,m1,m2)) if False else 0])
|
||
label = f"{(m1 or '')[:20]} → {(m2 or '')[:20]}" if m1 != m2 else (m1 or "")[:40]
|
||
print(f" {t1.strftime('%H:%M:%S')} → {t2.strftime('%H:%M:%S')} {sec/60:5.1f} мин [{label}]")
|
||
else:
|
||
print(" Пауз > 5 мин не обнаружено")
|
||
|
||
# --- Скорость по 10-мин окнам ---
|
||
print("\n=== СКОРОСТЬ ПО 10-МИНУТНЫМ ОКНАМ ===")
|
||
window = 10 * 60
|
||
bucket_start = times[0]
|
||
bucket_count = 0
|
||
windows = []
|
||
for t in times:
|
||
if (t - bucket_start).total_seconds() < window:
|
||
bucket_count += 1
|
||
else:
|
||
elapsed = (t - bucket_start).total_seconds()
|
||
windows.append((bucket_start, bucket_count, elapsed))
|
||
bucket_start = t
|
||
bucket_count = 1
|
||
if bucket_count:
|
||
windows.append((bucket_start, bucket_count, (times[-1] - bucket_start).total_seconds() or 1))
|
||
|
||
max_cnt = max(w[1] for w in windows) if windows else 1
|
||
for ws, cnt, elapsed in windows:
|
||
rate = cnt / (elapsed / 60) if elapsed > 0 else 0
|
||
bar_len = int(cnt / max_cnt * 40)
|
||
bar = '▓' * bar_len + '░' * (40 - bar_len)
|
||
print(f" {ws.strftime('%H:%M')} {cnt:3d} гл {rate:4.1f}/мин |{bar}|")
|
||
|
||
# --- Перцентили интервалов ---
|
||
gaps_sec = sorted((times[i+1] - times[i]).total_seconds() for i in range(len(times)-1))
|
||
if gaps_sec:
|
||
n = len(gaps_sec)
|
||
print(f"\n=== ИНТЕРВАЛЫ МЕЖДУ ГЛАВАМИ ===")
|
||
print(f" Минимум: {gaps_sec[0]:.1f} сек")
|
||
print(f" Медиана: {gaps_sec[n//2]:.1f} сек")
|
||
print(f" P90: {gaps_sec[int(n*0.9)]:.1f} сек")
|
||
print(f" P99: {gaps_sec[int(n*0.99)]:.1f} сек")
|
||
print(f" Максимум: {gaps_sec[-1]:.1f} сек ({gaps_sec[-1]/60:.1f} мин)")
|
||
over_2min = sum(1 for g in gaps_sec if g > 120)
|
||
over_5min = sum(1 for g in gaps_sec if g > 300)
|
||
print(f" > 2 мин: {over_2min} ({over_2min/n*100:.1f}%)")
|
||
print(f" > 5 мин: {over_5min} ({over_5min/n*100:.1f}%)")
|
||
|
||
conn.close()
|
||
|