create-arete-workspace 0.2.0
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.
- package/README.md +77 -0
- package/bin/arete.js +156 -0
- package/bin/create.js +111 -0
- package/lib/install-openclaw.js +50 -0
- package/lib/scaffold.js +213 -0
- package/lib/setup-wizard.js +88 -0
- package/lib/updater.js +130 -0
- package/package.json +34 -0
- package/packages/gatsaeng-os/README.md +36 -0
- package/packages/gatsaeng-os/components.json +23 -0
- package/packages/gatsaeng-os/eslint.config.mjs +18 -0
- package/packages/gatsaeng-os/next.config.ts +7 -0
- package/packages/gatsaeng-os/package.json +59 -0
- package/packages/gatsaeng-os/postcss.config.mjs +7 -0
- package/packages/gatsaeng-os/public/file.svg +1 -0
- package/packages/gatsaeng-os/public/globe.svg +1 -0
- package/packages/gatsaeng-os/public/next.svg +1 -0
- package/packages/gatsaeng-os/public/vercel.svg +1 -0
- package/packages/gatsaeng-os/public/window.svg +1 -0
- package/packages/gatsaeng-os/python/api_server.py +248 -0
- package/packages/gatsaeng-os/python/briefing.py +145 -0
- package/packages/gatsaeng-os/python/config.py +55 -0
- package/packages/gatsaeng-os/python/goal_context_agent.py +193 -0
- package/packages/gatsaeng-os/python/gyeokguk.py +171 -0
- package/packages/gatsaeng-os/python/proactive.py +158 -0
- package/packages/gatsaeng-os/python/requirements.txt +11 -0
- package/packages/gatsaeng-os/python/run.py +28 -0
- package/packages/gatsaeng-os/python/scoring.py +44 -0
- package/packages/gatsaeng-os/python/streak.py +70 -0
- package/packages/gatsaeng-os/python/telegram_bot.py +331 -0
- package/packages/gatsaeng-os/python/timing_engine.py +117 -0
- package/packages/gatsaeng-os/python/vault_io.py +423 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/areas/[id]/page.tsx +215 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/areas/page.tsx +161 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/books/[id]/page.tsx +215 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/books/page.tsx +268 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/calendar/page.tsx +379 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/error.tsx +30 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/focus/page.tsx +293 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/goals/[id]/page.tsx +426 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/goals/page.tsx +178 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/layout.tsx +29 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/notes/[id]/page.tsx +147 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/notes/page.tsx +254 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/page.tsx +26 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/projects/[id]/page.tsx +86 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/projects/page.tsx +215 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/review/page.tsx +475 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/routines/page.tsx +436 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/tasks/[id]/page.tsx +210 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/tasks/page.tsx +307 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/voice/page.tsx +212 -0
- package/packages/gatsaeng-os/src/app/api/areas/[id]/route.ts +26 -0
- package/packages/gatsaeng-os/src/app/api/areas/route.ts +22 -0
- package/packages/gatsaeng-os/src/app/api/auth/login/route.ts +52 -0
- package/packages/gatsaeng-os/src/app/api/auth/logout/route.ts +8 -0
- package/packages/gatsaeng-os/src/app/api/books/[id]/route.ts +27 -0
- package/packages/gatsaeng-os/src/app/api/books/route.ts +20 -0
- package/packages/gatsaeng-os/src/app/api/calendar/[id]/route.ts +24 -0
- package/packages/gatsaeng-os/src/app/api/calendar/import/route.ts +52 -0
- package/packages/gatsaeng-os/src/app/api/calendar/route.ts +37 -0
- package/packages/gatsaeng-os/src/app/api/daily/route.ts +51 -0
- package/packages/gatsaeng-os/src/app/api/goals/[id]/route.ts +34 -0
- package/packages/gatsaeng-os/src/app/api/goals/route.ts +30 -0
- package/packages/gatsaeng-os/src/app/api/logs/energy/route.ts +40 -0
- package/packages/gatsaeng-os/src/app/api/logs/focus/route.ts +22 -0
- package/packages/gatsaeng-os/src/app/api/logs/routine/route.ts +54 -0
- package/packages/gatsaeng-os/src/app/api/milestones/[id]/route.ts +26 -0
- package/packages/gatsaeng-os/src/app/api/milestones/route.ts +47 -0
- package/packages/gatsaeng-os/src/app/api/notes/[id]/route.ts +29 -0
- package/packages/gatsaeng-os/src/app/api/notes/route.ts +37 -0
- package/packages/gatsaeng-os/src/app/api/profile/route.ts +17 -0
- package/packages/gatsaeng-os/src/app/api/projects/[id]/route.ts +27 -0
- package/packages/gatsaeng-os/src/app/api/projects/route.ts +25 -0
- package/packages/gatsaeng-os/src/app/api/reviews/[id]/route.ts +26 -0
- package/packages/gatsaeng-os/src/app/api/reviews/route.ts +29 -0
- package/packages/gatsaeng-os/src/app/api/routines/[id]/route.ts +26 -0
- package/packages/gatsaeng-os/src/app/api/routines/route.ts +28 -0
- package/packages/gatsaeng-os/src/app/api/tasks/[id]/route.ts +28 -0
- package/packages/gatsaeng-os/src/app/api/tasks/route.ts +66 -0
- package/packages/gatsaeng-os/src/app/api/timing/current/route.ts +63 -0
- package/packages/gatsaeng-os/src/app/api/voice/chat/route.ts +50 -0
- package/packages/gatsaeng-os/src/app/api/voice/transcribe/route.ts +25 -0
- package/packages/gatsaeng-os/src/app/api/voice/tts/route.ts +36 -0
- package/packages/gatsaeng-os/src/app/error.tsx +30 -0
- package/packages/gatsaeng-os/src/app/favicon.ico +0 -0
- package/packages/gatsaeng-os/src/app/globals.css +208 -0
- package/packages/gatsaeng-os/src/app/layout.tsx +33 -0
- package/packages/gatsaeng-os/src/app/login/page.tsx +87 -0
- package/packages/gatsaeng-os/src/app/providers.tsx +27 -0
- package/packages/gatsaeng-os/src/components/ErrorBoundary.tsx +46 -0
- package/packages/gatsaeng-os/src/components/dashboard/DashboardGrid.tsx +86 -0
- package/packages/gatsaeng-os/src/components/dashboard/DdayWidget.tsx +88 -0
- package/packages/gatsaeng-os/src/components/dashboard/EnergyTracker.tsx +87 -0
- package/packages/gatsaeng-os/src/components/dashboard/FocusTimer.tsx +139 -0
- package/packages/gatsaeng-os/src/components/dashboard/GatsaengScore.tsx +30 -0
- package/packages/gatsaeng-os/src/components/dashboard/GoalRings.tsx +107 -0
- package/packages/gatsaeng-os/src/components/dashboard/ProactiveBar.tsx +98 -0
- package/packages/gatsaeng-os/src/components/dashboard/RoutineChecklist.tsx +81 -0
- package/packages/gatsaeng-os/src/components/dashboard/TimingWidget.tsx +86 -0
- package/packages/gatsaeng-os/src/components/dashboard/WidgetCustomizer.tsx +95 -0
- package/packages/gatsaeng-os/src/components/dashboard/WidgetWrapper.tsx +33 -0
- package/packages/gatsaeng-os/src/components/dashboard/ZeigarnikPanel.tsx +43 -0
- package/packages/gatsaeng-os/src/components/editor/EditorToolbar.tsx +186 -0
- package/packages/gatsaeng-os/src/components/editor/TiptapEditor.tsx +114 -0
- package/packages/gatsaeng-os/src/components/layout/Header.tsx +47 -0
- package/packages/gatsaeng-os/src/components/layout/MobileBottomNav.tsx +122 -0
- package/packages/gatsaeng-os/src/components/layout/MobileSidebar.tsx +29 -0
- package/packages/gatsaeng-os/src/components/layout/Sidebar.tsx +142 -0
- package/packages/gatsaeng-os/src/components/onboarding/OnboardingFlow.tsx +229 -0
- package/packages/gatsaeng-os/src/components/onboarding/OnboardingGate.tsx +78 -0
- package/packages/gatsaeng-os/src/components/projects/CalendarView.tsx +152 -0
- package/packages/gatsaeng-os/src/components/projects/KanbanView.tsx +180 -0
- package/packages/gatsaeng-os/src/components/projects/ListView.tsx +82 -0
- package/packages/gatsaeng-os/src/components/projects/TableView.tsx +206 -0
- package/packages/gatsaeng-os/src/components/projects/TaskCard.tsx +154 -0
- package/packages/gatsaeng-os/src/components/projects/TaskForm.tsx +128 -0
- package/packages/gatsaeng-os/src/components/projects/ViewSwitcher.tsx +40 -0
- package/packages/gatsaeng-os/src/components/search/GlobalSearch.tsx +179 -0
- package/packages/gatsaeng-os/src/components/shared/InlineEdit.tsx +77 -0
- package/packages/gatsaeng-os/src/components/shared/PinButton.tsx +42 -0
- package/packages/gatsaeng-os/src/components/tasks/DDayBadge.tsx +34 -0
- package/packages/gatsaeng-os/src/components/ui/badge.tsx +48 -0
- package/packages/gatsaeng-os/src/components/ui/button.tsx +64 -0
- package/packages/gatsaeng-os/src/components/ui/card.tsx +92 -0
- package/packages/gatsaeng-os/src/components/ui/checkbox.tsx +32 -0
- package/packages/gatsaeng-os/src/components/ui/command.tsx +184 -0
- package/packages/gatsaeng-os/src/components/ui/dialog.tsx +158 -0
- package/packages/gatsaeng-os/src/components/ui/input.tsx +21 -0
- package/packages/gatsaeng-os/src/components/ui/label.tsx +24 -0
- package/packages/gatsaeng-os/src/components/ui/popover.tsx +89 -0
- package/packages/gatsaeng-os/src/components/ui/progress.tsx +31 -0
- package/packages/gatsaeng-os/src/components/ui/select.tsx +190 -0
- package/packages/gatsaeng-os/src/components/ui/sheet.tsx +143 -0
- package/packages/gatsaeng-os/src/components/ui/tabs.tsx +91 -0
- package/packages/gatsaeng-os/src/components/ui/toggle-group.tsx +83 -0
- package/packages/gatsaeng-os/src/components/ui/toggle.tsx +47 -0
- package/packages/gatsaeng-os/src/components/ui/tooltip.tsx +57 -0
- package/packages/gatsaeng-os/src/hooks/useAreas.ts +53 -0
- package/packages/gatsaeng-os/src/hooks/useBooks.ts +62 -0
- package/packages/gatsaeng-os/src/hooks/useCalendar.ts +59 -0
- package/packages/gatsaeng-os/src/hooks/useDaily.ts +15 -0
- package/packages/gatsaeng-os/src/hooks/useGlobalTasks.ts +45 -0
- package/packages/gatsaeng-os/src/hooks/useGoals.ts +53 -0
- package/packages/gatsaeng-os/src/hooks/useMilestones.ts +75 -0
- package/packages/gatsaeng-os/src/hooks/useNotes.ts +65 -0
- package/packages/gatsaeng-os/src/hooks/useProjects.ts +102 -0
- package/packages/gatsaeng-os/src/hooks/useRoutines.ts +76 -0
- package/packages/gatsaeng-os/src/hooks/useTiming.ts +27 -0
- package/packages/gatsaeng-os/src/lib/apiFetch.ts +14 -0
- package/packages/gatsaeng-os/src/lib/auth.ts +32 -0
- package/packages/gatsaeng-os/src/lib/date.ts +7 -0
- package/packages/gatsaeng-os/src/lib/editor/markdown.ts +35 -0
- package/packages/gatsaeng-os/src/lib/llm-governor.ts +167 -0
- package/packages/gatsaeng-os/src/lib/neuroscience/energyCycle.ts +35 -0
- package/packages/gatsaeng-os/src/lib/neuroscience/habitStack.ts +22 -0
- package/packages/gatsaeng-os/src/lib/neuroscience/scoring.ts +32 -0
- package/packages/gatsaeng-os/src/lib/routes.ts +15 -0
- package/packages/gatsaeng-os/src/lib/utils.ts +6 -0
- package/packages/gatsaeng-os/src/lib/vault/config.ts +29 -0
- package/packages/gatsaeng-os/src/lib/vault/frontmatter.ts +84 -0
- package/packages/gatsaeng-os/src/lib/vault/index.ts +180 -0
- package/packages/gatsaeng-os/src/lib/vault/schemas.ts +274 -0
- package/packages/gatsaeng-os/src/middleware.ts +34 -0
- package/packages/gatsaeng-os/src/stores/dashboardStore.ts +26 -0
- package/packages/gatsaeng-os/src/stores/favoritesStore.ts +47 -0
- package/packages/gatsaeng-os/src/stores/timerStore.ts +65 -0
- package/packages/gatsaeng-os/src/types/index.ts +320 -0
- package/packages/gatsaeng-os/tsconfig.json +34 -0
- package/templates/scripts/forge_qa.sh.tmpl +237 -0
- package/templates/scripts/forge_ship.sh.tmpl +183 -0
- package/templates/scripts/session_indexer.py.tmpl +420 -0
- package/templates/scripts/tracer.py.tmpl +266 -0
- package/templates/workspace/AGENTS.md.tmpl +190 -0
- package/templates/workspace/BOOTSTRAP.md.tmpl +27 -0
- package/templates/workspace/HEARTBEAT.md.tmpl +23 -0
- package/templates/workspace/MEMORY.md.tmpl +35 -0
- package/templates/workspace/SOUL.md.tmpl +258 -0
- package/templates/workspace/TOOLS.md.tmpl +28 -0
- package/templates/workspace/USER.md.tmpl +43 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Proactive Engine β ν¨ν΄ κ°μ§ + μ¬μ μλ¦Ό μμ€ν
."""
|
|
2
|
+
|
|
3
|
+
from datetime import date, timedelta, datetime
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import vault_io
|
|
7
|
+
import timing_engine
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def check_all() -> list[dict]:
|
|
11
|
+
"""λͺ¨λ proactive 체ν¬λ₯Ό μ€ννκ³ μλ¦Ό λͺ©λ‘ λ°ν."""
|
|
12
|
+
alerts = []
|
|
13
|
+
alerts.extend(check_skipped_routines())
|
|
14
|
+
alerts.extend(check_dday_warnings())
|
|
15
|
+
alerts.extend(check_streak_danger())
|
|
16
|
+
alerts.extend(check_reanalysis_due())
|
|
17
|
+
alerts.extend(check_energy_pattern())
|
|
18
|
+
return alerts
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def check_skipped_routines() -> list[dict]:
|
|
22
|
+
"""2μΌ μ΄μ μ°μ μ€ν΅λ λ£¨ν΄ κ°μ§."""
|
|
23
|
+
alerts = []
|
|
24
|
+
skips = vault_io.get_consecutive_skips()
|
|
25
|
+
|
|
26
|
+
for rid, count in skips.items():
|
|
27
|
+
if count >= 2:
|
|
28
|
+
routine = vault_io.get_entity('routines', rid)
|
|
29
|
+
if not routine:
|
|
30
|
+
continue
|
|
31
|
+
title = routine['data'].get('title', rid)
|
|
32
|
+
|
|
33
|
+
if count >= 4:
|
|
34
|
+
severity = 'critical'
|
|
35
|
+
msg = f'"{title}" {count}μΌ μ°μ μ€ν΅. μ΅κ΄ μ²΄μΈ λκΈΈ μν. λ£¨ν΄ μ¬μ€κ³ κ²ν νμ.'
|
|
36
|
+
elif count >= 2:
|
|
37
|
+
severity = 'warning'
|
|
38
|
+
msg = f'"{title}" {count}μΌ μ°μ μ€ν΅. μ€λ λ°λμ μ€ννλΌ.'
|
|
39
|
+
|
|
40
|
+
alerts.append({
|
|
41
|
+
'type': 'skipped_routine',
|
|
42
|
+
'severity': severity,
|
|
43
|
+
'title': f'{title} β {count}μΌ μ°μ λ―Έμ€ν',
|
|
44
|
+
'message': msg,
|
|
45
|
+
'routine_id': rid,
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
return alerts
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def check_dday_warnings() -> list[dict]:
|
|
52
|
+
"""λ§μΌμ€ν€ D-day κ²½κ³ ."""
|
|
53
|
+
alerts = []
|
|
54
|
+
milestones = vault_io.get_active_milestones()
|
|
55
|
+
|
|
56
|
+
for m in milestones:
|
|
57
|
+
d = m.get('d_day', 999)
|
|
58
|
+
title = m.get('title', '?')
|
|
59
|
+
progress = 0
|
|
60
|
+
target = m.get('target_value', 0)
|
|
61
|
+
if target > 0:
|
|
62
|
+
progress = round(m.get('current_value', 0) / target * 100)
|
|
63
|
+
|
|
64
|
+
if d <= 0:
|
|
65
|
+
alerts.append({
|
|
66
|
+
'type': 'deadline',
|
|
67
|
+
'severity': 'critical',
|
|
68
|
+
'title': f'{title} β D-Day μ΄κ³Ό!',
|
|
69
|
+
'message': f'λ§κ° {abs(d)}μΌ μ΄κ³Ό. μ§νλ₯ {progress}%. μ¦μ μλ£νκ±°λ λ§κ° μ¬μ‘°μ .',
|
|
70
|
+
'milestone_id': m.get('id'),
|
|
71
|
+
})
|
|
72
|
+
elif d <= 3:
|
|
73
|
+
alerts.append({
|
|
74
|
+
'type': 'milestone_dday',
|
|
75
|
+
'severity': 'critical',
|
|
76
|
+
'title': f'{title} β D-{d}',
|
|
77
|
+
'message': f'μ§νλ₯ {progress}%. λ¨μ {m.get("target_value", 0) - m.get("current_value", 0)} {m.get("unit", "")} μ§μ€.',
|
|
78
|
+
'milestone_id': m.get('id'),
|
|
79
|
+
})
|
|
80
|
+
elif d <= 7:
|
|
81
|
+
alerts.append({
|
|
82
|
+
'type': 'milestone_dday',
|
|
83
|
+
'severity': 'warning',
|
|
84
|
+
'title': f'{title} β D-{d}',
|
|
85
|
+
'message': f'μ§νλ₯ {progress}%. λ§κ° {d}μΌ μ .',
|
|
86
|
+
'milestone_id': m.get('id'),
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
return alerts
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def check_streak_danger() -> list[dict]:
|
|
93
|
+
"""μ€νΈλ¦ μν κ°μ§ β μ€ν 9μ μ΄ν λ―Έμλ£ λ£¨ν΄ μ‘΄μ¬ μ."""
|
|
94
|
+
now = datetime.now()
|
|
95
|
+
if now.hour < 21:
|
|
96
|
+
return []
|
|
97
|
+
|
|
98
|
+
unchecked = vault_io.get_unchecked_today()
|
|
99
|
+
if not unchecked:
|
|
100
|
+
return []
|
|
101
|
+
|
|
102
|
+
streaks = vault_io.get_active_streaks()
|
|
103
|
+
current = streaks.get('current', 0)
|
|
104
|
+
|
|
105
|
+
if current >= 3:
|
|
106
|
+
return [{
|
|
107
|
+
'type': 'streak_danger',
|
|
108
|
+
'severity': 'warning',
|
|
109
|
+
'title': f'{current}μΌ μ€νΈλ¦ λκΈΈ μν!',
|
|
110
|
+
'message': f'{len(unchecked)}κ° λ£¨ν΄ λ―Έμλ£. μκΈ° μ μ μ²λ¦¬νλΌ.',
|
|
111
|
+
}]
|
|
112
|
+
|
|
113
|
+
return []
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def check_reanalysis_due() -> list[dict]:
|
|
117
|
+
"""μ¬λΆμ νμν λͺ©ν κ°μ§."""
|
|
118
|
+
alerts = []
|
|
119
|
+
due = timing_engine.get_all_due_reanalyses()
|
|
120
|
+
|
|
121
|
+
for gid in due:
|
|
122
|
+
goal = vault_io.read_goal(gid)
|
|
123
|
+
if not goal:
|
|
124
|
+
continue
|
|
125
|
+
alerts.append({
|
|
126
|
+
'type': 'reanalysis_due',
|
|
127
|
+
'severity': 'info',
|
|
128
|
+
'title': f'"{goal["data"].get("title", "?")}" μ¬λΆμ νμ',
|
|
129
|
+
'message': '2μ£Ό κ²½κ³Ό. Goal Context Agent μ¬μ€ν μΆμ².',
|
|
130
|
+
'goal_id': gid,
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
return alerts
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def check_energy_pattern() -> list[dict]:
|
|
137
|
+
"""μλμ§ ν¨ν΄ κ°μ§ β μ΅κ·Ό 3μΌ μ°μ low energy."""
|
|
138
|
+
alerts = []
|
|
139
|
+
today = date.today()
|
|
140
|
+
|
|
141
|
+
low_days = 0
|
|
142
|
+
for i in range(3):
|
|
143
|
+
d = (today - timedelta(days=i)).isoformat()
|
|
144
|
+
log = vault_io.get_entity_by_date('energy_logs', d)
|
|
145
|
+
if log and 'entries' in log['data']:
|
|
146
|
+
avg = sum(e.get('level', 3) for e in log['data']['entries']) / max(len(log['data']['entries']), 1)
|
|
147
|
+
if avg <= 2:
|
|
148
|
+
low_days += 1
|
|
149
|
+
|
|
150
|
+
if low_days >= 3:
|
|
151
|
+
alerts.append({
|
|
152
|
+
'type': 'energy_warning',
|
|
153
|
+
'severity': 'warning',
|
|
154
|
+
'title': '3μΌ μ°μ μ μλμ§ κ°μ§',
|
|
155
|
+
'message': 'ν볡 μ°μ . 루ν΄μ μ λ°μΌλ‘ μ€μ΄κ³ μλ©΄/μ΄λλΆν°.',
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
return alerts
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Entry point for the gatsaeng-os Python backend (API server only).
|
|
2
|
+
|
|
3
|
+
Telegramμ OpenClaw gatewayκ° μ²λ¦¬ β μ΄ μλ²λ FastAPIλ§ μ€ν.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
import uvicorn
|
|
10
|
+
from config import DASHBOARD_PORT
|
|
11
|
+
|
|
12
|
+
logging.basicConfig(
|
|
13
|
+
level=logging.INFO,
|
|
14
|
+
format='%(asctime)s [%(name)s] %(levelname)s: %(message)s',
|
|
15
|
+
)
|
|
16
|
+
logger = logging.getLogger('gatsaeng')
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
if __name__ == '__main__':
|
|
20
|
+
host = sys.argv[1] if len(sys.argv) > 1 else '127.0.0.1'
|
|
21
|
+
logger.info(f'Starting Gatsaeng API server on {host}:{DASHBOARD_PORT}')
|
|
22
|
+
uvicorn.run(
|
|
23
|
+
'api_server:app',
|
|
24
|
+
host=host,
|
|
25
|
+
port=DASHBOARD_PORT,
|
|
26
|
+
reload='--reload' in sys.argv,
|
|
27
|
+
log_level='info',
|
|
28
|
+
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Gatsaeng scoring system with variable rewards (Skinner)."""
|
|
2
|
+
|
|
3
|
+
import random
|
|
4
|
+
|
|
5
|
+
BASE_SCORES = {
|
|
6
|
+
'routine_complete': 10,
|
|
7
|
+
'task_done': 5,
|
|
8
|
+
'task_done_urgent': 15,
|
|
9
|
+
'milestone_complete': 100,
|
|
10
|
+
'review_written': 20,
|
|
11
|
+
'data_uploaded': 10,
|
|
12
|
+
'goal_25pct': 30,
|
|
13
|
+
'goal_50pct': 60,
|
|
14
|
+
'goal_100pct': 200,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def calculate(event: str, streak: int = 0) -> dict:
|
|
19
|
+
"""Calculate score with streak multiplier and variable bonus."""
|
|
20
|
+
base = BASE_SCORES.get(event, 0)
|
|
21
|
+
|
|
22
|
+
# streak multiplier β λμΌ λ‘μ§μ streak.get_streak_multiplier()μ 곡μ
|
|
23
|
+
if streak >= 30:
|
|
24
|
+
multiplier = 5.0
|
|
25
|
+
elif streak >= 21:
|
|
26
|
+
multiplier = 4.0
|
|
27
|
+
elif streak >= 14:
|
|
28
|
+
multiplier = 3.0
|
|
29
|
+
elif streak >= 7:
|
|
30
|
+
multiplier = 2.0
|
|
31
|
+
elif streak >= 3:
|
|
32
|
+
multiplier = 1.5
|
|
33
|
+
else:
|
|
34
|
+
multiplier = 1.0
|
|
35
|
+
points = round(base * multiplier)
|
|
36
|
+
|
|
37
|
+
# 15% variable bonus (Skinner)
|
|
38
|
+
bonus_message = None
|
|
39
|
+
if random.random() < 0.15:
|
|
40
|
+
bonus = random.choice([5, 10, 15, 25])
|
|
41
|
+
points += bonus
|
|
42
|
+
bonus_message = f'π 보λμ€ +{bonus}μ !'
|
|
43
|
+
|
|
44
|
+
return {'points': points, 'bonus_message': bonus_message}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Streak Engine β μ°μ μ€νμΌ κ³μ° + 보μ ."""
|
|
2
|
+
|
|
3
|
+
from datetime import date, timedelta
|
|
4
|
+
|
|
5
|
+
import vault_io
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def calculate_streak() -> dict:
|
|
9
|
+
"""νμ¬ μ€νΈλ¦μ μ¬κ³μ°νκ³ νλ‘νμ λ°μ."""
|
|
10
|
+
today = date.today()
|
|
11
|
+
streak = 0
|
|
12
|
+
|
|
13
|
+
all_routines = vault_io.list_entities('routines')
|
|
14
|
+
active = [r for r in all_routines if r['data'].get('is_active')]
|
|
15
|
+
|
|
16
|
+
for days_ago in range(1, 365):
|
|
17
|
+
check_date = today - timedelta(days=days_ago)
|
|
18
|
+
d_str = check_date.isoformat()
|
|
19
|
+
|
|
20
|
+
log = vault_io.get_entity_by_date('routine_logs', d_str)
|
|
21
|
+
if not log or not log['data'].get('completions'):
|
|
22
|
+
break
|
|
23
|
+
day_of_week = check_date.isoweekday()
|
|
24
|
+
scheduled = [
|
|
25
|
+
r for r in active
|
|
26
|
+
if day_of_week in r['data'].get('scheduled_days', [1,2,3,4,5,6,7])
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
if not scheduled:
|
|
30
|
+
continue # μ€μΌμ€ μλ λ μ 건λλ
|
|
31
|
+
|
|
32
|
+
completed_ids = {c.get('routine_id', '') for c in log['data'].get('completions', [])}
|
|
33
|
+
scheduled_ids = {r['data'].get('id', '') for r in scheduled}
|
|
34
|
+
|
|
35
|
+
# 50% μ΄μ μλ£ μ μ€νΈλ¦ μ μ§
|
|
36
|
+
if len(completed_ids & scheduled_ids) >= len(scheduled_ids) * 0.5:
|
|
37
|
+
streak += 1
|
|
38
|
+
else:
|
|
39
|
+
break
|
|
40
|
+
|
|
41
|
+
# Update profile
|
|
42
|
+
profile = vault_io.read_profile()
|
|
43
|
+
old_longest = profile.get('longest_streak', 0)
|
|
44
|
+
new_longest = max(old_longest, streak)
|
|
45
|
+
|
|
46
|
+
vault_io.update_profile({
|
|
47
|
+
'current_streak': streak,
|
|
48
|
+
'longest_streak': new_longest,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
'current': streak,
|
|
53
|
+
'longest': new_longest,
|
|
54
|
+
'changed': streak != profile.get('current_streak', 0),
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_streak_multiplier(streak: int) -> float:
|
|
59
|
+
"""μ€νΈλ¦μ λ°λ₯Έ μ μ λ°°μ¨ (μ΅λ 5x)."""
|
|
60
|
+
if streak >= 30:
|
|
61
|
+
return 5.0
|
|
62
|
+
elif streak >= 21:
|
|
63
|
+
return 4.0
|
|
64
|
+
elif streak >= 14:
|
|
65
|
+
return 3.0
|
|
66
|
+
elif streak >= 7:
|
|
67
|
+
return 2.0
|
|
68
|
+
elif streak >= 3:
|
|
69
|
+
return 1.5
|
|
70
|
+
return 1.0
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"""Telegram Bot β Coach_Eve_bot ν΅ν© κ°μ μ½μΉ."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from telegram import Update
|
|
6
|
+
from telegram.ext import (
|
|
7
|
+
Application,
|
|
8
|
+
CommandHandler,
|
|
9
|
+
MessageHandler,
|
|
10
|
+
filters,
|
|
11
|
+
ContextTypes,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
import vault_io
|
|
15
|
+
import briefing
|
|
16
|
+
import proactive
|
|
17
|
+
import gyeokguk
|
|
18
|
+
import timing_engine
|
|
19
|
+
import goal_context_agent
|
|
20
|
+
import streak
|
|
21
|
+
from config import TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def is_authorized(update: Update) -> bool:
|
|
27
|
+
"""Only allow Drake (chat_id from config)."""
|
|
28
|
+
if not TELEGRAM_CHAT_ID:
|
|
29
|
+
return True
|
|
30
|
+
return str(update.effective_chat.id) == TELEGRAM_CHAT_ID.replace('tg:', '')
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# βββ Command Handlers βββ
|
|
34
|
+
|
|
35
|
+
async def cmd_start(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|
36
|
+
if not is_authorized(update):
|
|
37
|
+
return
|
|
38
|
+
await update.message.reply_text(
|
|
39
|
+
"π§ κ°μ μ½μΉ νμ±ν.\n"
|
|
40
|
+
"/morning β λͺ¨λ λΈλ¦¬ν\n"
|
|
41
|
+
"/check β λ£¨ν΄ μ²΄ν¬μΈ\n"
|
|
42
|
+
"/score β μ€λ μ€μ½μ΄\n"
|
|
43
|
+
"/dday β D-day νν©\n"
|
|
44
|
+
"/timing β μ΄λ¬ μ΄κΈ°\n"
|
|
45
|
+
"/gyeokguk β κ²©κ΅ νμ \n"
|
|
46
|
+
"/scorecard β μ±μ₯ μ€μ½μ΄μΉ΄λ\n"
|
|
47
|
+
"/analyze β λͺ©ν AI λΆμ"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
async def cmd_morning(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|
52
|
+
if not is_authorized(update):
|
|
53
|
+
return
|
|
54
|
+
text = briefing.generate_morning_briefing()
|
|
55
|
+
await update.message.reply_text(text, parse_mode=None)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
async def cmd_evening(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|
59
|
+
if not is_authorized(update):
|
|
60
|
+
return
|
|
61
|
+
text = briefing.generate_evening_checkin()
|
|
62
|
+
await update.message.reply_text(text, parse_mode=None)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def cmd_check(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|
66
|
+
"""νμ¬ λ£¨ν΄ μν νμ."""
|
|
67
|
+
if not is_authorized(update):
|
|
68
|
+
return
|
|
69
|
+
routines = vault_io.get_routines_with_status()
|
|
70
|
+
if not routines:
|
|
71
|
+
await update.message.reply_text("νμ± λ£¨ν΄ μμ.")
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
lines = ["μ€λ λ£¨ν΄ νν©:"]
|
|
75
|
+
done = 0
|
|
76
|
+
for r in routines:
|
|
77
|
+
status = 'β
' if r.get('completed_today') else 'β¬'
|
|
78
|
+
lines.append(f"{status} {r.get('title', '?')} (π₯{r.get('streak', 0)}μΌ)")
|
|
79
|
+
if r.get('completed_today'):
|
|
80
|
+
done += 1
|
|
81
|
+
|
|
82
|
+
lines.append(f"\n{done}/{len(routines)} μλ£")
|
|
83
|
+
await update.message.reply_text('\n'.join(lines))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
async def cmd_score(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|
87
|
+
if not is_authorized(update):
|
|
88
|
+
return
|
|
89
|
+
today_score = vault_io.get_today_score()
|
|
90
|
+
streaks = vault_io.get_active_streaks()
|
|
91
|
+
profile = vault_io.read_profile()
|
|
92
|
+
|
|
93
|
+
text = (
|
|
94
|
+
f"μ€λ μ€μ½μ΄: {today_score}μ \n"
|
|
95
|
+
f"λμ : {profile.get('total_score', 0)}μ (Lv.{profile.get('level', 1)})\n"
|
|
96
|
+
f"μ°μ: {streaks['current']}μΌ (μ΅μ₯: {streaks['longest']}μΌ)"
|
|
97
|
+
)
|
|
98
|
+
await update.message.reply_text(text)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
async def cmd_dday(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|
102
|
+
if not is_authorized(update):
|
|
103
|
+
return
|
|
104
|
+
milestones = vault_io.get_active_milestones()
|
|
105
|
+
if not milestones:
|
|
106
|
+
await update.message.reply_text("νμ± λ§μΌμ€ν€ μμ.")
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
lines = ["D-day νν©:"]
|
|
110
|
+
for m in milestones[:8]:
|
|
111
|
+
d = m.get('d_day', 0)
|
|
112
|
+
label = f"D-{d}" if d > 0 else "D-Day" if d == 0 else f"D+{abs(d)}"
|
|
113
|
+
progress = 0
|
|
114
|
+
target = m.get('target_value', 0)
|
|
115
|
+
if target > 0:
|
|
116
|
+
progress = round(m.get('current_value', 0) / target * 100)
|
|
117
|
+
lines.append(f" {label}: {m.get('title', '?')} ({progress}%)")
|
|
118
|
+
|
|
119
|
+
await update.message.reply_text('\n'.join(lines))
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
async def cmd_timing(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|
123
|
+
if not is_authorized(update):
|
|
124
|
+
return
|
|
125
|
+
text = timing_engine.get_monthly_briefing()
|
|
126
|
+
if not text:
|
|
127
|
+
await update.message.reply_text("νμ΄λ° λ°μ΄ν° μμ.")
|
|
128
|
+
return
|
|
129
|
+
await update.message.reply_text(f"μ΄λ¬ μ΄κΈ°:\n{text}")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
async def cmd_gyeokguk(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|
133
|
+
if not is_authorized(update):
|
|
134
|
+
return
|
|
135
|
+
result = gyeokguk.judge_daily_actions()
|
|
136
|
+
|
|
137
|
+
lines = [f"κ²©κ΅ νμ ({result.get('date', '?')}):", ""]
|
|
138
|
+
for d in result.get('details', []):
|
|
139
|
+
arrow = 'β' if d['score'] > 0 else 'β' if d['score'] < 0 else 'β'
|
|
140
|
+
lines.append(f" {arrow} {d.get('action', '?')}: {d.get('reason', '')}")
|
|
141
|
+
|
|
142
|
+
lines.append("")
|
|
143
|
+
lines.append(f"μ μ§ {result.get('forward', 0)} / νν΄ {result.get('backward', 0)} / μ€λ¦½ {result.get('neutral', 0)}")
|
|
144
|
+
lines.append(f"νμ : {result.get('verdict', '')}")
|
|
145
|
+
|
|
146
|
+
await update.message.reply_text('\n'.join(lines))
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
async def cmd_scorecard(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|
150
|
+
if not is_authorized(update):
|
|
151
|
+
return
|
|
152
|
+
sc = gyeokguk.get_growth_scorecard()
|
|
153
|
+
|
|
154
|
+
lines = [f"μ±μ₯ μ€μ½μ΄μΉ΄λ ({sc.get('period', '')}):", ""]
|
|
155
|
+
for d in sc.get('daily', []):
|
|
156
|
+
bar = 'β' * max(d.get('net_score', 0), 0) + 'β' * max(-d.get('net_score', 0), 0)
|
|
157
|
+
lines.append(f" {d['date'][-5:]}: {bar or 'Β·'} ({d.get('net_score', 0):+d})")
|
|
158
|
+
|
|
159
|
+
lines.append("")
|
|
160
|
+
lines.append(f"μ£Όκ° μμ μ§: {sc.get('weekly_net', 0):+d}")
|
|
161
|
+
lines.append(f"νμ : {sc.get('verdict', '')}")
|
|
162
|
+
|
|
163
|
+
await update.message.reply_text('\n'.join(lines))
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
async def cmd_analyze(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|
167
|
+
if not is_authorized(update):
|
|
168
|
+
return
|
|
169
|
+
goals = vault_io.get_active_goals()
|
|
170
|
+
if not goals:
|
|
171
|
+
await update.message.reply_text("νμ± λͺ©ν μμ.")
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
# If goal_id specified
|
|
175
|
+
args = ctx.args
|
|
176
|
+
if args:
|
|
177
|
+
goal_id = args[0]
|
|
178
|
+
else:
|
|
179
|
+
# Show list and analyze first one
|
|
180
|
+
lines = ["λΆμν λͺ©ν:"]
|
|
181
|
+
for g in goals:
|
|
182
|
+
lines.append(f" {g.get('id', '?')}: {g.get('title', '?')}")
|
|
183
|
+
lines.append(f"\n첫 λ²μ§Έ λͺ©ν λΆμ μ€...")
|
|
184
|
+
await update.message.reply_text('\n'.join(lines))
|
|
185
|
+
goal_id = goals[0].get('id', '')
|
|
186
|
+
|
|
187
|
+
await update.message.reply_text(f"π '{goal_id}' λΆμ μ€...")
|
|
188
|
+
result = goal_context_agent.run(goal_id)
|
|
189
|
+
|
|
190
|
+
if result.get('error'):
|
|
191
|
+
await update.message.reply_text(f"λΆμ μ€ν¨: {result['error']}")
|
|
192
|
+
return
|
|
193
|
+
|
|
194
|
+
lines = [
|
|
195
|
+
f"AI μ§λ¨: {result.get('diagnosis', '?')}",
|
|
196
|
+
f"λ°©ν₯: {result.get('direction', '?')}",
|
|
197
|
+
f"λ€μ 리뷰: {result.get('next_review', '?')}",
|
|
198
|
+
]
|
|
199
|
+
if result.get('milestones_created', 0) > 0:
|
|
200
|
+
lines.append(f"μ λ§μΌμ€ν€ {result['milestones_created']}κ° μμ±λ¨.")
|
|
201
|
+
|
|
202
|
+
await update.message.reply_text('\n'.join(lines))
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
async def cmd_alerts(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|
206
|
+
if not is_authorized(update):
|
|
207
|
+
return
|
|
208
|
+
alerts = proactive.check_all()
|
|
209
|
+
if not alerts:
|
|
210
|
+
await update.message.reply_text("νμ¬ μλ¦Ό μμ. μ νκ³ μλ€.")
|
|
211
|
+
return
|
|
212
|
+
|
|
213
|
+
lines = ["π¨ μλ¦Ό:"]
|
|
214
|
+
for a in alerts:
|
|
215
|
+
severity = {'critical': 'π΄', 'warning': 'π‘', 'info': 'π΅'}.get(a.get('severity', 'info'), 'βͺ')
|
|
216
|
+
lines.append(f" {severity} [{a.get('type', '?')}] {a.get('title', '')}: {a.get('message', '')}")
|
|
217
|
+
|
|
218
|
+
await update.message.reply_text('\n'.join(lines))
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# βββ Message Handler (Intent Classification) βββ
|
|
222
|
+
|
|
223
|
+
INTENT_KEYWORDS = {
|
|
224
|
+
'check': ['체ν¬', '루ν΄', 'μλ£', 'νμ΄', 'λ'],
|
|
225
|
+
'score': ['μ μ', 'μ€μ½μ΄', 'λͺμ '],
|
|
226
|
+
'timing': ['μ΄κΈ°', 'μ΄μΈ', 'νμ΄λ°', 'μ΄λ¬'],
|
|
227
|
+
'dday': ['λλ°μ΄', 'd-day', 'λ§κ°', 'λ§μΌμ€ν€'],
|
|
228
|
+
'gyeokguk': ['격κ΅', 'νμ ', 'μ μ§', 'νν΄'],
|
|
229
|
+
'morning': ['μμΉ¨', 'λͺ¨λ', 'λΈλ¦¬ν', 'μ€λ'],
|
|
230
|
+
'evening': ['μ λ
', '체ν¬μΈ', 'ν루', 'λ§λ¬΄λ¦¬'],
|
|
231
|
+
'analyze': ['λΆμ', 'μ§λ¨', 'ai'],
|
|
232
|
+
'alerts': ['μλ¦Ό', 'κ²½κ³ ', 'μ£Όμ'],
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async def handle_message(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|
236
|
+
"""Free-text message intent classification."""
|
|
237
|
+
if not is_authorized(update):
|
|
238
|
+
return
|
|
239
|
+
|
|
240
|
+
text = update.message.text.lower()
|
|
241
|
+
|
|
242
|
+
# Match intent by keywords
|
|
243
|
+
for intent, keywords in INTENT_KEYWORDS.items():
|
|
244
|
+
if any(kw in text for kw in keywords):
|
|
245
|
+
handler_map = {
|
|
246
|
+
'check': cmd_check,
|
|
247
|
+
'score': cmd_score,
|
|
248
|
+
'timing': cmd_timing,
|
|
249
|
+
'dday': cmd_dday,
|
|
250
|
+
'gyeokguk': cmd_gyeokguk,
|
|
251
|
+
'morning': cmd_morning,
|
|
252
|
+
'evening': cmd_evening,
|
|
253
|
+
'analyze': cmd_analyze,
|
|
254
|
+
'alerts': cmd_alerts,
|
|
255
|
+
}
|
|
256
|
+
handler = handler_map.get(intent)
|
|
257
|
+
if handler:
|
|
258
|
+
await handler(update, ctx)
|
|
259
|
+
return
|
|
260
|
+
|
|
261
|
+
# Default: show status summary
|
|
262
|
+
today_score = vault_io.get_today_score()
|
|
263
|
+
routines = vault_io.get_routines_with_status()
|
|
264
|
+
done = sum(1 for r in routines if r.get('completed_today'))
|
|
265
|
+
streaks = vault_io.get_active_streaks()
|
|
266
|
+
|
|
267
|
+
await update.message.reply_text(
|
|
268
|
+
f"μ€μ½μ΄: {today_score}μ | 루ν΄: {done}/{len(routines)} | μ°μ: {streaks['current']}μΌ\n\n"
|
|
269
|
+
"λͺ
λ Ήμ΄: /morning /check /score /dday /timing /gyeokguk /scorecard /analyze /alerts"
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
# βββ Scheduled Jobs βββ
|
|
274
|
+
|
|
275
|
+
async def scheduled_morning_briefing(ctx: ContextTypes.DEFAULT_TYPE):
|
|
276
|
+
"""λ§€μΌ μμΉ¨ μλ λΈλ¦¬ν."""
|
|
277
|
+
chat_id = TELEGRAM_CHAT_ID.replace('tg:', '')
|
|
278
|
+
text = briefing.generate_morning_briefing()
|
|
279
|
+
await ctx.bot.send_message(chat_id=chat_id, text=text)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
async def scheduled_evening_checkin(ctx: ContextTypes.DEFAULT_TYPE):
|
|
283
|
+
"""λ§€μΌ μ λ
μλ 체ν¬μΈ."""
|
|
284
|
+
chat_id = TELEGRAM_CHAT_ID.replace('tg:', '')
|
|
285
|
+
|
|
286
|
+
# Recalculate streak before evening check-in
|
|
287
|
+
streak.calculate_streak()
|
|
288
|
+
|
|
289
|
+
text = briefing.generate_evening_checkin()
|
|
290
|
+
await ctx.bot.send_message(chat_id=chat_id, text=text)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
async def scheduled_proactive_check(ctx: ContextTypes.DEFAULT_TYPE):
|
|
294
|
+
"""μ£ΌκΈ°μ proactive μλ¦Ό."""
|
|
295
|
+
chat_id = TELEGRAM_CHAT_ID.replace('tg:', '')
|
|
296
|
+
alerts = proactive.check_all()
|
|
297
|
+
critical = [a for a in alerts if a.get('severity') == 'critical']
|
|
298
|
+
|
|
299
|
+
if critical:
|
|
300
|
+
lines = ["π¨ κΈ΄κΈ μλ¦Ό:"]
|
|
301
|
+
for a in critical:
|
|
302
|
+
lines.append(f" {a.get('title', '')}: {a.get('message', '')}")
|
|
303
|
+
await ctx.bot.send_message(chat_id=chat_id, text='\n'.join(lines))
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
# βββ Bot Setup βββ
|
|
307
|
+
|
|
308
|
+
def create_bot() -> Application:
|
|
309
|
+
"""Create and configure the Telegram bot."""
|
|
310
|
+
if not TELEGRAM_BOT_TOKEN:
|
|
311
|
+
raise ValueError("TELEGRAM_BOT_TOKEN not set")
|
|
312
|
+
|
|
313
|
+
app = Application.builder().token(TELEGRAM_BOT_TOKEN).build()
|
|
314
|
+
|
|
315
|
+
# Commands
|
|
316
|
+
app.add_handler(CommandHandler('start', cmd_start))
|
|
317
|
+
app.add_handler(CommandHandler('morning', cmd_morning))
|
|
318
|
+
app.add_handler(CommandHandler('evening', cmd_evening))
|
|
319
|
+
app.add_handler(CommandHandler('check', cmd_check))
|
|
320
|
+
app.add_handler(CommandHandler('score', cmd_score))
|
|
321
|
+
app.add_handler(CommandHandler('dday', cmd_dday))
|
|
322
|
+
app.add_handler(CommandHandler('timing', cmd_timing))
|
|
323
|
+
app.add_handler(CommandHandler('gyeokguk', cmd_gyeokguk))
|
|
324
|
+
app.add_handler(CommandHandler('scorecard', cmd_scorecard))
|
|
325
|
+
app.add_handler(CommandHandler('analyze', cmd_analyze))
|
|
326
|
+
app.add_handler(CommandHandler('alerts', cmd_alerts))
|
|
327
|
+
|
|
328
|
+
# Free text
|
|
329
|
+
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
|
|
330
|
+
|
|
331
|
+
return app
|