agus1111 commited on
Commit
b1ae7ed
Β·
verified Β·
1 Parent(s): 5790013

Update server.py

Browse files
Files changed (1) hide show
  1. server.py +197 -183
server.py CHANGED
@@ -1,184 +1,198 @@
1
- # summary_daily.py
2
  import os
3
- import sqlite3
4
- from datetime import datetime, timezone, timedelta, date
5
- from collections import defaultdict
6
-
7
- DB_PATH = os.environ.get("BOTSIGNAL_DB", "/tmp/botsignal.db")
8
- OUT_DIR = os.environ.get("SUMMARY_OUT_DIR", "summaries")
9
-
10
- def _db():
11
- conn = sqlite3.connect(DB_PATH)
12
- conn.execute("PRAGMA journal_mode=WAL;")
13
- return conn
14
-
15
- def _ensure_tables():
16
- conn = _db()
17
- conn.executescript("""
18
- CREATE TABLE IF NOT EXISTS summary_calls (
19
- keyword TEXT PRIMARY KEY,
20
- posted_at INTEGER NOT NULL,
21
- tier TEXT NOT NULL,
22
- msg_id INTEGER NOT NULL
23
- );
24
- CREATE TABLE IF NOT EXISTS summary_milestones (
25
- keyword TEXT NOT NULL,
26
- reply_msg_id INTEGER,
27
- multiple REAL,
28
- replied_at INTEGER NOT NULL
29
- );
30
- CREATE TABLE IF NOT EXISTS summary_outcomes (
31
- keyword TEXT PRIMARY KEY,
32
- is_deleted INTEGER DEFAULT 0
33
- );
34
- """)
35
- conn.commit()
36
- conn.close()
37
-
38
- def _day_bounds_utc(d: date):
39
- # ambil batas harian [00:00, 23:59:59] UTC untuk tanggal d (UTC)
40
- sod = datetime(d.year, d.month, d.day, tzinfo=timezone.utc)
41
- eod = datetime(d.year, d.month, d.day, 23, 59, 59, tzinfo=timezone.utc)
42
- return int(sod.timestamp()), int(eod.timestamp())
43
-
44
- def _fmt(num):
45
- return f"{num:,}".replace(",", ".")
46
-
47
- def compute_summary_for_date(d: date):
48
- _ensure_tables()
49
- sod, eod = _day_bounds_utc(d)
50
- conn = _db(); cur = conn.cursor()
51
-
52
- cur.execute("""
53
- SELECT keyword, tier, posted_at, msg_id
54
- FROM summary_calls
55
- WHERE posted_at BETWEEN ? AND ?
56
- """, (sod, eod))
57
- rows = cur.fetchall()
58
- total_calls = len(rows)
59
- calls = {r[0]: {"tier": r[1], "posted_at": r[2], "msg_id": r[3]} for r in rows}
60
-
61
- max_mult_per_kw = {}
62
- have_reply = set()
63
- if calls:
64
- q = ",".join("?" for _ in calls)
65
- cur.execute(f"""
66
- SELECT keyword, MAX(multiple)
67
- FROM summary_milestones
68
- WHERE keyword IN ({q})
69
- AND replied_at BETWEEN ? AND ?
70
- GROUP BY keyword
71
- """, (*calls.keys(), sod, eod))
72
- for kw, mx in cur.fetchall():
73
- if mx is not None:
74
- have_reply.add(kw)
75
- max_mult_per_kw[kw] = mx
76
-
77
- deleted = set()
78
- if calls:
79
- q = ",".join("?" for _ in calls)
80
- cur.execute(f"""
81
- SELECT keyword
82
- FROM summary_outcomes
83
- WHERE keyword IN ({q})
84
- AND is_deleted = 1
85
- """, (*calls.keys(),))
86
- for (kw,) in cur.fetchall():
87
- deleted.add(kw)
88
-
89
- conn.close()
90
-
91
- wins = have_reply
92
- loses = (deleted | (set(calls.keys()) - have_reply))
93
-
94
- calls_per_tier = defaultdict(int)
95
- for _, meta in calls.items():
96
- calls_per_tier[meta["tier"]] += 1
97
-
98
- win_per_tier = defaultdict(int)
99
- lose_per_tier = defaultdict(int)
100
- for kw, meta in calls.items():
101
- if kw in wins:
102
- win_per_tier[meta["tier"]] += 1
103
- if kw in loses:
104
- lose_per_tier[meta["tier"]] += 1
105
-
106
- top_kw, top_mult = None, None
107
- for kw, mult in max_mult_per_kw.items():
108
- if top_mult is None or mult > top_mult:
109
- top_kw, top_mult = kw, mult
110
-
111
- return {
112
- "total_calls": total_calls,
113
- "win_rate": (len(wins) / total_calls) * 100 if total_calls else 0.0,
114
- "calls_per_tier": dict(sorted(calls_per_tier.items())),
115
- "total_win": len(wins),
116
- "total_lose": len(loses),
117
- "win_per_tier": dict(sorted(win_per_tier.items())),
118
- "lose_per_tier": dict(sorted(lose_per_tier.items())),
119
- "top_call": {"keyword": top_kw, "max_multiple": top_mult} if top_kw else None,
120
- }
121
-
122
- def render_telegram_html(summary: dict, label_date: str) -> str:
123
- # Pakai HTML biar rapi (parse_mode='html' di send_message)
124
- lines = []
125
- lines.append(f"<b>Daily Summary β€” {label_date}</b>")
126
- lines.append("")
127
- lines.append(f"<b>Total Call:</b> {_fmt(summary['total_calls'])}")
128
- lines.append(f"<b>Win Rate:</b> {summary['win_rate']:.2f}%")
129
- lines.append("")
130
- lines.append("<b>Total Call per Tier</b>")
131
- if summary["calls_per_tier"]:
132
- for tier, cnt in summary["calls_per_tier"].items():
133
- lines.append(f"β€’ {tier}: {_fmt(cnt)}")
134
- else:
135
- lines.append("β€’ (kosong)")
136
- lines.append("")
137
- lines.append("<b>Total Win & Lose</b>")
138
- lines.append(f"β€’ Win: {_fmt(summary['total_win'])}")
139
- lines.append(f"β€’ Lose: {_fmt(summary['total_lose'])}")
140
- lines.append("")
141
- lines.append("<b>Detail per Tier</b>")
142
- lines.append("<u>Win</u>")
143
- if summary["win_per_tier"]:
144
- for tier, cnt in summary["win_per_tier"].items():
145
- lines.append(f"β€’ {tier}: {_fmt(cnt)}")
146
- else:
147
- lines.append("β€’ (kosong)")
148
- lines.append("<u>Lose</u>")
149
- if summary["lose_per_tier"]:
150
- for tier, cnt in summary["lose_per_tier"].items():
151
- lines.append(f"β€’ {tier}: {_fmt(cnt)}")
152
- else:
153
- lines.append("β€’ (kosong)")
154
- lines.append("")
155
- lines.append("<b>Top Call (Max Reply Multiple)</b>")
156
- if summary["top_call"]:
157
- lines.append(f"β€’ CA: <code>{summary['top_call']['keyword']}</code>")
158
- lines.append(f"β€’ Max: {summary['top_call']['max_multiple']}Γ—")
159
- else:
160
- lines.append("β€’ (belum ada reply)")
161
- return "\n".join(lines)
162
-
163
- if __name__ == "__main__":
164
- from datetime import date
165
- from telethon import TelegramClient
166
- import asyncio
167
- import os
168
-
169
- # ======== CONFIG TELEGRAM =========
170
- API_ID = int(os.environ.get("API_ID", "0"))
171
- API_HASH = os.environ.get("API_HASH", "")
172
- STRING_SESSION = os.environ.get("STRING_SESSION", "")
173
- TARGET_CHANNEL = os.environ.get("TARGET_CHANNEL", "@MidasTouchsignalll") # contoh
174
-
175
- # ======== GENERATE SUMMARY HARINI =========
176
- summary = compute_summary_for_date(date.today())
177
- html_text = render_telegram_html(summary, date.today().strftime("%Y-%m-%d"))
178
-
179
- async def main():
180
- async with TelegramClient( STRING_SESSION, API_ID, API_HASH) as client:
181
- await client.send_message(TARGET_CHANNEL, html_text, parse_mode="html")
182
- print("[SUMMARY] posted to Telegram")
183
-
184
- asyncio.run(main())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # server.py β€” tambah scheduler rekap harian 06:00 WIB (H-1) + endpoint force summary
2
  import os
3
+ import asyncio
4
+ from datetime import datetime, timedelta, date
5
+ from zoneinfo import ZoneInfo
6
+ from fastapi import FastAPI, HTTPException
7
+
8
+ # Core yang sudah ada
9
+ from botsignal import start_bot_background, client # ← client dipakai untuk kirim summary
10
+
11
+ # Ringkasan harian
12
+ from summary_daily import compute_summary_for_date, render_telegram_html
13
+
14
+ app = FastAPI(title="Telegram Curator Health")
15
+
16
+ # ====== ENV ======
17
+ SUMMARY_CHAT_ID = os.environ.get("SUMMARY_CHAT_ID") # contoh: -1001234567890 atau @nama_channel
18
+ JAKARTA_TZ = ZoneInfo("Asia/Jakarta")
19
+
20
+ # ====== Scheduler util ======
21
+ def _seconds_until_6am_wib() -> float:
22
+ now = datetime.now(JAKARTA_TZ)
23
+ target = now.replace(hour=6, minute=0, second=0, microsecond=0)
24
+ if now >= target:
25
+ target = target + timedelta(days=1)
26
+ return (target - now).total_seconds()
27
+
28
+ async def _send_daily_summary_loop():
29
+ """
30
+ Kirim rekap tiap hari jam 06:00 WIB untuk H-1.
31
+ Jika mau rekap 'hari ini', ubah target_day = today_local.
32
+ """
33
+ while True:
34
+ try:
35
+ # Tunggu sampai 06:00 WIB berikutnya
36
+ sleep_s = _seconds_until_6am_wib()
37
+ await asyncio.sleep(sleep_s)
38
+
39
+ # Rekap H-1 (kemarin)
40
+ today_local = datetime.now(JAKARTA_TZ).date()
41
+ target_day = today_local - timedelta(days=1)
42
+
43
+ # Hitung dan kirim
44
+ data = compute_summary_for_date(target_day)
45
+ text = render_telegram_html(data, label_date=target_day.strftime("%Y-%m-%d"))
46
+ if not SUMMARY_CHAT_ID:
47
+ print("[SUMMARY] SUMMARY_CHAT_ID not set; skip send.")
48
+ else:
49
+ await client.send_message(entity=SUMMARY_CHAT_ID, message=text, parse_mode="html")
50
+ print(f"[SUMMARY] sent for {target_day}")
51
+ except Exception as e:
52
+ print(f"[SUMMARY] loop error: {e}")
53
+ await asyncio.sleep(10) # retry kecil lalu lanjut loop
54
+
55
+ # Simpan task supaya bisa di-cancel saat shutdown (kalau kamu tambahkan event shutdown nanti)
56
+ _summary_task: asyncio.Task | None = None
57
+
58
+ @app.on_event("startup")
59
+ async def startup():
60
+ # (Core) mulai bot di background β€” TETAP seperti semula
61
+ await start_bot_background()
62
+
63
+ # (Baru) mulai scheduler summary harian
64
+ global _summary_task
65
+ if _summary_task is None or _summary_task.done():
66
+ _summary_task = asyncio.create_task(_send_daily_summary_loop())
67
+ print("[SERVER] startup complete (scheduler armed at 06:00 WIB)")
68
+
69
+ @app.get("/")
70
+ async def root():
71
+ # (Core) tetap
72
+ return {"status": "ok"}
73
+
74
+ @app.get("/health")
75
+ async def health():
76
+ # (Core) tetap β€” bisa ditambah info simple kalau mau
77
+ return {"healthy": True}
78
+
79
+ @app.post("/summary/force")
80
+ async def force_summary(day: str | None = None):
81
+ """
82
+ Trigger kirim summary manual.
83
+ - Tanpa 'day' -> rekap H-1 lokal WIB
84
+ - Format 'day' = 'YYYY-MM-DD' (lokal WIB)
85
+ """
86
+ if not SUMMARY_CHAT_ID:
87
+ raise HTTPException(status_code=400, detail="SUMMARY_CHAT_ID not set in environment.")
88
+ try:
89
+ if day:
90
+ target = date.fromisoformat(day)
91
+ else:
92
+ target = datetime.now(JAKARTA_TZ).date() - timedelta(days=1)
93
+
94
+ data = compute_summary_for_date(target)
95
+ text = render_telegram_html(data, label_date=target.strftime("%Y-%m-%d"))
96
+ await client.send_message(entity=SUMMARY_CHAT_ID, message=text, parse_mode="html")
97
+ return {"ok": True, "sent_for": target.isoformat()}
98
+ except Exception as e:
99
+ raise HTTPException(status_code=500, detail=str(e))
100
+ # server.py β€” tambah scheduler rekap harian 06:00 WIB (H-1) + endpoint force summary
101
+ import os
102
+ import asyncio
103
+ from datetime import datetime, timedelta, date
104
+ from zoneinfo import ZoneInfo
105
+ from fastapi import FastAPI, HTTPException
106
+
107
+ # Core yang sudah ada
108
+ from botsignal import start_bot_background, client # ← client dipakai untuk kirim summary
109
+
110
+ # Ringkasan harian
111
+ from summary_daily import compute_summary_for_date, render_telegram_html
112
+
113
+ app = FastAPI(title="Telegram Curator Health")
114
+
115
+ # ====== ENV ======
116
+ SUMMARY_CHAT_ID = os.environ.get("SUMMARY_CHAT_ID") # contoh: -1001234567890 atau @nama_channel
117
+ JAKARTA_TZ = ZoneInfo("Asia/Jakarta")
118
+
119
+ # ====== Scheduler util ======
120
+ def _seconds_until_6am_wib() -> float:
121
+ now = datetime.now(JAKARTA_TZ)
122
+ target = now.replace(hour=6, minute=0, second=0, microsecond=0)
123
+ if now >= target:
124
+ target = target + timedelta(days=1)
125
+ return (target - now).total_seconds()
126
+
127
+ async def _send_daily_summary_loop():
128
+ """
129
+ Kirim rekap tiap hari jam 06:00 WIB untuk H-1.
130
+ Jika mau rekap 'hari ini', ubah target_day = today_local.
131
+ """
132
+ while True:
133
+ try:
134
+ # Tunggu sampai 06:00 WIB berikutnya
135
+ sleep_s = _seconds_until_6am_wib()
136
+ await asyncio.sleep(sleep_s)
137
+
138
+ # Rekap H-1 (kemarin)
139
+ today_local = datetime.now(JAKARTA_TZ).date()
140
+ target_day = today_local - timedelta(days=1)
141
+
142
+ # Hitung dan kirim
143
+ data = compute_summary_for_date(target_day)
144
+ text = render_telegram_html(data, label_date=target_day.strftime("%Y-%m-%d"))
145
+ if not SUMMARY_CHAT_ID:
146
+ print("[SUMMARY] SUMMARY_CHAT_ID not set; skip send.")
147
+ else:
148
+ await client.send_message(entity=SUMMARY_CHAT_ID, message=text, parse_mode="html")
149
+ print(f"[SUMMARY] sent for {target_day}")
150
+ except Exception as e:
151
+ print(f"[SUMMARY] loop error: {e}")
152
+ await asyncio.sleep(10) # retry kecil lalu lanjut loop
153
+
154
+ # Simpan task supaya bisa di-cancel saat shutdown (kalau kamu tambahkan event shutdown nanti)
155
+ _summary_task: asyncio.Task | None = None
156
+
157
+ @app.on_event("startup")
158
+ async def startup():
159
+ # (Core) mulai bot di background β€” TETAP seperti semula
160
+ await start_bot_background()
161
+
162
+ # (Baru) mulai scheduler summary harian
163
+ global _summary_task
164
+ if _summary_task is None or _summary_task.done():
165
+ _summary_task = asyncio.create_task(_send_daily_summary_loop())
166
+ print("[SERVER] startup complete (scheduler armed at 06:00 WIB)")
167
+
168
+ @app.get("/")
169
+ async def root():
170
+ # (Core) tetap
171
+ return {"status": "ok"}
172
+
173
+ @app.get("/health")
174
+ async def health():
175
+ # (Core) tetap β€” bisa ditambah info simple kalau mau
176
+ return {"healthy": True}
177
+
178
+ @app.post("/summary/force")
179
+ async def force_summary(day: str | None = None):
180
+ """
181
+ Trigger kirim summary manual.
182
+ - Tanpa 'day' -> rekap H-1 lokal WIB
183
+ - Format 'day' = 'YYYY-MM-DD' (lokal WIB)
184
+ """
185
+ if not SUMMARY_CHAT_ID:
186
+ raise HTTPException(status_code=400, detail="SUMMARY_CHAT_ID not set in environment.")
187
+ try:
188
+ if day:
189
+ target = date.fromisoformat(day)
190
+ else:
191
+ target = datetime.now(JAKARTA_TZ).date() - timedelta(days=1)
192
+
193
+ data = compute_summary_for_date(target)
194
+ text = render_telegram_html(data, label_date=target.strftime("%Y-%m-%d"))
195
+ await client.send_message(entity=SUMMARY_CHAT_ID, message=text, parse_mode="html")
196
+ return {"ok": True, "sent_for": target.isoformat()}
197
+ except Exception as e:
198
+ raise HTTPException(status_code=500, detail=str(e))