diff --git a/src/server.py b/src/server.py index 74ab2f8..8b04e1e 100644 --- a/src/server.py +++ b/src/server.py @@ -89,6 +89,11 @@ async def company_dinner() -> str: """Attend company dinner with random events! Could be amazing or terrible.""" return await tools.company_dinner(state_manager) + @mcp.tool() + async def generate_report() -> str: + """Generate a report of your break-taking habits.""" + return await tools.generate_report(state_manager) + @mcp.tool() async def snack_time() -> str: """Take a snack break at the convenience store! Get some treats to boost your mood.""" diff --git a/src/state_manager.py b/src/state_manager.py index 23b9823..bcc7e1b 100644 --- a/src/state_manager.py +++ b/src/state_manager.py @@ -28,6 +28,7 @@ def __init__(self, config: Config): # Use double underscore for true private variables self.__stress_level: int = 0 # 0-100 self.__boss_alert_level: int = 0 # 0-5 + self.history: list = [] self._last_stress_update: float = time.time() self._last_boss_cooldown: float = time.time() self._lock = asyncio.Lock() @@ -231,7 +232,8 @@ def _save_state(self) -> None: try: state_data = { "stress_level": self._stress_level, - "boss_alert_level": self._boss_alert_level + "boss_alert_level": self._boss_alert_level, + "history": self.history, } with open(self.STATE_FILE, 'w') as f: json.dump(state_data, f, indent=2) @@ -253,6 +255,7 @@ def _load_state(self) -> None: # Setter is called but won't save because _loading is True self._stress_level = state_data.get("stress_level", 0) self._boss_alert_level = state_data.get("boss_alert_level", 0) + self.history = state_data.get("history", []) # Reset timestamps to current time (don't accumulate time while server was off) self._last_stress_update = time.time() @@ -264,3 +267,13 @@ def _load_state(self) -> None: # Fail silently - if file doesn't exist or is corrupted, start fresh self._loading = False pass + + def add_history_event(self, tool_name: str, stress_change: int, boss_alert_change: int) -> None: + """Add a break event to the history and save the state.""" + self.history.append({ + "tool_name": tool_name, + "timestamp": time.time(), + "stress_change": stress_change, + "boss_alert_change": boss_alert_change, + }) + self._save_state() diff --git a/src/statistics.py b/src/statistics.py new file mode 100644 index 0000000..1d75507 --- /dev/null +++ b/src/statistics.py @@ -0,0 +1,41 @@ +"""Statistics and reporting module for ChillMCP server.""" + +import json +from pathlib import Path +from collections import Counter +from datetime import datetime + +# State file path (in project root) +STATE_FILE = Path(__file__).parent.parent / ".chillmcp_state.json" + +def get_break_statistics() -> dict: + """ + Analyze break history and generate statistics. + + Returns: + dict: A dictionary containing break statistics. + """ + if not STATE_FILE.exists(): + return {"error": "No break history found."} + + with open(STATE_FILE, 'r') as f: + data = json.load(f) + history = data.get("history", []) + + if not history: + return {"error": "Break history is empty."} + + total_breaks = len(history) + tool_counter = Counter(item['tool_name'] for item in history) + most_common_tool = tool_counter.most_common(1)[0][0] if tool_counter else "N/A" + + # Analyze break times by hour + hour_counter = Counter(datetime.fromtimestamp(item['timestamp']).hour for item in history) + most_common_hour = hour_counter.most_common(1)[0][0] if hour_counter else "N/A" + + return { + "total_breaks": total_breaks, + "most_common_tool": most_common_tool, + "most_common_hour": f"{most_common_hour}:00 - {most_common_hour+1}:00", + "breaks_by_tool": dict(tool_counter), + } diff --git a/src/tools.py b/src/tools.py index 19d6f0b..1dcb211 100644 --- a/src/tools.py +++ b/src/tools.py @@ -4,7 +4,7 @@ import random from typing import List -from . import ascii_art +from . import ascii_art, statistics from .response_formatter import format_response from .state_manager import StateManager @@ -96,6 +96,10 @@ async def execute_break_tool( # Potentially increase boss alert boss_increased, old_boss_level = await state_manager.increase_boss_alert() + boss_alert_change = 1 if boss_increased else 0 + + # Save history + state_manager.add_history_event(tool_name, -stress_decrease, boss_alert_change) # Get current state state = await state_manager.get_state() @@ -284,6 +288,9 @@ async def chimaek(state_manager: StateManager) -> str: boss_increase = random.randint(2, 3) await state_manager.change_boss_alert(boss_increase) + # Save history + state_manager.add_history_event("chimaek", -stress_relief, boss_increase) + # Get updated state state = await state_manager.get_state() @@ -309,9 +316,17 @@ async def leave_work(state_manager: StateManager) -> str: Returns: str: Formatted response. """ + # Save current levels before reset for history + current_state = await state_manager.get_state() + stress_before = current_state["stress_level"] + boss_before = current_state["boss_alert_level"] + # 퇴근하면 모든 스트레스와 Boss Alert 리셋! await state_manager.reset() + # Save history (negative values mean decrease) + state_manager.add_history_event("leave_work", -stress_before, -boss_before) + # Get state state = await state_manager.get_state() @@ -359,6 +374,7 @@ async def company_dinner(state_manager: StateManager) -> str: await state_manager.increase_stress(amount=stress_change) # Boss alert changes slightly + boss_alert_change = -1 if is_positive else 1 if is_positive: # Positive event: boss alert decreases a bit await state_manager.change_boss_alert(-1) @@ -366,6 +382,9 @@ async def company_dinner(state_manager: StateManager) -> str: # Negative event: boss alert increases await state_manager.change_boss_alert(1) + # Save history (use negative stress_change for decrease) + state_manager.add_history_event("company_dinner", -stress_change if stress_change < 0 else stress_change, boss_alert_change) + # Get state state = await state_manager.get_state() @@ -381,6 +400,45 @@ async def company_dinner(state_manager: StateManager) -> str: ) +async def generate_report(state_manager: StateManager) -> str: + """ + Generate a report of break statistics. + + Args: + state_manager: The state manager instance. + + Returns: + str: Formatted response with statistics. + """ + stats = statistics.get_break_statistics() + + if "error" in stats: + return format_response( + break_summary=stats["error"], + stress_level=(await state_manager.get_state())["stress_level"], + boss_alert_level=(await state_manager.get_state())["boss_alert_level"], + tool_name="generate_report" + ) + + report = f"""**Breakdown of Your Break Habits** + + - **Total Breaks Taken:** {stats["total_breaks"]} + - **Favorite Break Tool:** {stats["most_common_tool"]} + - **Busiest Break Time:** {stats["most_common_hour"]} + + **Breaks by Tool:** + """ + for tool, count in stats["breaks_by_tool"].items(): + report += f"- {tool}: {count}\n" + + return format_response( + break_summary=report, + stress_level=(await state_manager.get_state())["stress_level"], + boss_alert_level=(await state_manager.get_state())["boss_alert_level"], + tool_name="generate_report" + ) + + async def snack_time(state_manager: StateManager) -> str: """ Take a snack break at the convenience store!