Files
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
2026-08-07 12:53:47 +08:00

1 line
12 KiB
JSON

{"repo": "cgy2056666/github-", "files": {"-111-main/focus_engine/duration_recommender.py": {"acb26d2b6cb6c45d145ab46af47be86ca7700059": {"t": 1786068484.515716, "n": 10695, "h": "050cd801ddda35f0", "c": "from __future__ import annotations\n\nimport os\nfrom typing import Dict, List\n\nimport numpy as np\n\ntry:\n from openai import OpenAI\nexcept ImportError: # pragma: no cover - optional dependency\n OpenAI = None\n\nfrom .goal_profiles import normalize_goal_input\nfrom .history_storage import load_all_history\n\n\nclass FocusDurationRecommender:\n def __init__(self, config):\n self.config = config\n self.history_sessions: List[Dict] = []\n self.model = os.environ.get(\"DEEPSEEK_MODEL\", \"deepseek-chat\")\n self.llm_client = self._build_llm_client()\n self.refresh_history()\n\n def _build_llm_client(self):\n api_key = os.environ.get(\"DEEPSEEK_API_KEY\")\n allow_llm_classifier = os.environ.get(\"FOCUS_ENABLE_LLM_DURATION_CLASSIFIER\", \"\").lower() in {\n \"1\",\n \"true\",\n \"yes\",\n }\n if not api_key or OpenAI is None or not allow_llm_classifier:\n return None\n\n try:\n return OpenAI(\n api_key=api_key,\n base_url=os.environ.get(\"DEEPSEEK_BASE_URL\", \"https://api.deepseek.com\"),\n )\n except Exception as exc: # pragma: no cover - defensive init guard\n print(f\"初始化时长推荐分类器失败,将退回本地规则分类: {exc}\")\n return None\n\n def load_history(self, sessions: List[Dict]):\n self.history_sessions = list(sessions or [])\n\n def refresh_history(self):\n self.load_history(load_all_history())\n\n def _history_by_goal_type(self, goal_type: str | None) -> List[Dict]:\n if not goal_type:\n return []\n return [session for session in self.history_sessions if session.get(\"goal_type\") == goal_type]\n\n def _heuristic_goal_type(self, goal: str) -> str:\n normalized_goal = normalize_goal_input(goal).lower()\n cognitive_keywords = [\n \"学习\",\n \"阅读\",\n \"复习\",\n \"背\",\n \"刷题\",\n \"考试\",\n \"高数\",\n \"python\",\n \"编程\",\n \"代码\",\n \"debug\",\n \"调试\",\n \"英语\",\n \"课程\",\n \"考研\",\n \"考证\",\n \"论文\",\n \"算法\",\n ]\n creative_keywords = [\n \"写作\",\n \"创作\",\n \"设计\",\n \"策划\",\n \"小说\",\n \"文案\",\n \"画图\",\n \"视频\",\n \"剪辑\",\n \"ppt\",\n \"方案\",\n \"海报\",\n \"脚本\",\n \"构思\",\n ]\n mechanical_keywords = [\n \"整理\",\n \"录入\",\n \"核对\",\n \"归档\",\n \"汇总\",\n \"搬运\",\n \"表格\",\n \"统计\",\n \"报销\",\n \"清单\",\n \"流程\",\n \"处理工单\",\n \"上传\",\n ]\n\n if any(keyword in normalized_goal for keyword in cognitive_keywords):\n return \"认知学习类\"\n if any(keyword in normalized_goal for keyword in creative_keywords):\n return \"创意创作类\"\n if any(keyword in normalized_goal for keyword in mechanical_keywords):\n return \"机械执行类\"\n return \"认知学习类\"\n\n def _classify_goal(self, goal: str) -> str:\n prompt = (\n \"将以下专注目标分为三类:认知学习类、创意创作类、机械执行类。\"\n \"只输出分类结果,不要额外内容。目标:\"\n f\"{goal}\"\n )\n\n if self.llm_client is not None:\n try:\n response = self.llm_client.chat.completions.create(\n model=self.model,\n messages=[{\"role\": \"user\", \"content\": prompt}],\n temperature=0.1,\n max_tokens=20,\n )\n result = (response.choices[0].message.content or \"\").strip()\n if result in {\"认知学习类\", \"创意创作类\", \"机械执行类\"}:\n return result\n except Exception as exc:\n print(f\"目标分类失败,默认使用本地规则: {exc}\")\n\n return self._heuristic_goal_type(goal)\n\n def classify_goal(self, goal: str) -> str:\n return self._classify_goal(goal)\n\n def _calculate_baseline(self, goal_type: str | None = None) -> float:\n type_history = self._history_by_goal_type(goal_type)\n if type_history:\n valid_durations = []\n for session in type_history:\n focus_sequence = session.get(\"focus_sequence\", [])\n max_valid_window = 0\n current_window = 0\n for minute_score in focus_sequence:\n if minute_score >= 40:\n current_window += 1\n max_valid_window = max(max_valid_window, current_window)\n else:\n current_window = 0\n if max_valid_window > 0:\n valid_durations.append(max_valid_window)\n if valid_durations:\n return float(np.median(valid_durations))\n return float(self.config.session_default_duration_minutes)\n\n def _calculate_decay_coeff(self, goal_type: str | None = None) -> float:\n type_history = self._history_by_goal_type(goal_type)\n if type_history:\n all_slopes = []\n for session in type_history:\n focus_sequence = session.get(\"focus_sequence\", [])\n if len(focus_sequence) < 5:\n continue\n timeline = np.arange(len(focus_sequence))\n slope, _ = np.polyfit(timeline, focus_sequence, 1)\n all_slopes.append(abs(slope))\n if all_slopes:\n return float(np.median(all_slopes))\n return 0.5\n\n def _calculate_next_recommend(self, current_session: Dict) -> Dict:\n goal_type = current_session.get(\"goal_type\", \"认知学习类\")\n plan_duration = max(1, int(current_session.get(\"plan_duration\", self.config.session_default_duration_minutes) or 0))\n actual_duration = max(0, int(current_session.get(\"actual_duration\", 0) or 0))\n focus_sequence = current_session.get(\"focus_sequence\", [])\n completion_score = float(current_session.get(\"completion_score\", 0) or 0)\n\n completion_rate = actual_duration / plan_duration if plan_duration > 0 else 0\n\n max_valid_window = 0\n current_window = 0\n for minute_score in focus_sequence:\n if minute_score >= 40:\n current_window += 1\n max_valid_window = max(max_valid_window, current_window)\n else:\n current_window = 0\n\n base_baseline = self._calculate_baseline(goal_type)\n if completion_rate >= 0.9 and max_valid_window >= plan_duration * 0.8:\n next_base = base_baseline * 1.1\n feedback = \"本次专注表现优秀,下次可以在保持节奏的前提下适当加一点时长。\"\n elif completion_rate < 0.6 or max_valid_window < plan_duration * 0.5:\n next_base = base_baseline * 0.8\n feedback = \"本次后半程有些吃力,下次建议先缩短一点,优先把稳定感重新建立起来。\"\n else:\n next_base = base_baseline\n feedback = \"本次整体比较平稳,下次可以继续沿用这个基准时长。\"\n\n min_duration = self.config.session_min_duration_minutes\n max_duration = self.config.session_max_duration_minutes\n next_base = max(min_duration, min(next_base, max_duration))\n\n return {\n \"current_summary\": {\n \"plan_duration\": plan_duration,\n \"actual_duration\": actual_duration,\n \"max_valid_focus_minutes\": max_valid_window,\n \"completion_rate\": round(completion_rate * 100, 1),\n \"completion_score\": round(completion_score, 1),\n },\n \"next_recommend\": {\n \"base_recommend\": round(next_base),\n \"challenge_recommend\": round(min(next_base * 1.2, max_duration)),\n \"easy_recommend\": round(max(next_base * 0.7, min_duration)),\n \"rest_recommend\": round(max(next_base / 5, 5)),\n },\n \"goal_type\": goal_type,\n \"feedback_text\": feedback,\n }\n\n def recommend_next(self, current_session: Dict) -> Dict:\n self.refresh_history()\n result = self._calculate_next_recommend(current_session)\n result[\"history_count\"] = len(self._history_by_goal_type(result[\"goal_type\"]))\n return result\n\n def recommend(self, goal: str) -> Dict:\n self.refresh_history()\n normalized_goal = normalize_goal_input(goal)\n goal_type = self._classify_goal(normalized_goal)\n baseline = self._calculate_baseline(goal_type)\n alpha = self._calculate_decay_coeff(goal_type)\n type_history = self._history_by_goal_type(goal_type)\n\n beta_map = {\"认知学习类\": 0.9, \"创意创作类\": 1.2, \"机械执行类\": 1.0}\n beta = beta_map.get(goal_type, 1.0)\n\n gamma = 1.0\n if len(type_history) >= 3:\n completion_rates = [\n max(0, float(session.get(\"actual_duration\", 0) or 0)) / max(float(session.get(\"plan_duration\", 1) or 1), 1.0)\n for session in type_history\n ]\n completion_rate_mean = np.mean(completion_rates)\n if completion_rate_mean >= 0.9:\n gamma = 1.1\n elif completion_rate_mean < 0.6:\n gamma = 0.7\n\n delta = 1.0\n if len(type_history) >= 3:\n high_efficiency_sessions = [session for session in type_history if float(session.get(\"completion_score\", 0) or 0) >= 80]\n if high_efficiency_sessions:\n high_efficiency_mean = np.mean(\n [float(session.get(\"max_valid_focus_minutes\", baseline) or baseline) for session in high_efficiency_sessions]\n )\n if high_efficiency_mean > baseline:\n delta = 1.15\n\n corrected_duration = baseline * beta * gamma * delta\n min_duration = self.config.session_min_duration_minutes\n max_duration = self.config.session_max_duration_minutes\n final_duration = max(min_duration, min(corrected_duration, max_duration))\n\n if type_history:\n explanation = f\"已结合 {len(type_history)} 条同类型历史记录,给你一个更贴近当前节奏的起始时长。\"\n source = \"history\"\n else:\n explanation = \"还没有同类型历史记录,先用默认节奏起步,完成几轮后推荐会更贴身。\"\n source = \"default\"\n\n return {\n \"base_recommend\": round(final_duration),\n \"challenge_recommend\": round(min(final_duration * 1.2, max_duration)),\n \"easy_recommend\": round(max(final_duration * 0.7, min_duration)),\n \"rest_recommend\": round(max(final_duration / 5, 5)),\n \"goal_type\": goal_type,\n \"decay_type\": \"低衰减型\" if alpha <= 0.3 else \"中衰减型\" if alpha <= 0.8 else \"高衰减型\",\n \"history_count\": len(type_history),\n \"source\": source,\n \"explanation\": explanation,\n }\n"}}}}