{"id":"7fERM3VzUV","url":"https://pastebin.ca/7fERM3VzUV","raw_url":"https://raw.anybin.ca/7fERM3VzUV","visibility":"public","access":"public","created_at":1789101712139,"expires_at":1789188112139,"fetch_limit":null,"fetches_used":0,"reads_remaining":null,"size_bytes":52848,"syntax_hint":"python","title":null,"filename":null,"change_note":null,"cipher":null,"cipher_meta":null,"parent_id":null,"root_id":"7fERM3VzUV","version":1,"owner_id":"06G565D7Z0FC42YKBY0XB93MTQ","recipient_id":null,"body":"import json\nimport os\nimport math\nimport requests\nimport time\nimport shutil\nfrom datetime import datetime\nfrom collections import defaultdict\n\n# ========== 配置 ==========\nRAW_MATCHES_FILE = \"1_data_fetcher/output/raw_matches.json\"\n# AI 降级补全开关（排名缺失时是否启用 AI 补全）\nAI_COMPLETE_MISSING_RANK = True   # True=启用AI补全，False=直接跳过\nPREVIEW_FILE = \"1_data_fetcher/output/preview_data.json\"\nOUTPUT_DIR = \"5_predictor/output\"\nHISTORY_DIR = os.path.join(OUTPUT_DIR, \"history\")\nDAILY_DIR = os.path.join(OUTPUT_DIR, \"daily\")\nSUMMARY_DIR = os.path.join(OUTPUT_DIR, \"summary\")\nCONFIG_DIR = \"5_predictor/config\"\nLEAGUE_PARAMS_FILE = os.path.join(CONFIG_DIR, \"league_params.json\")\n\nfor d in [OUTPUT_DIR, HISTORY_DIR, DAILY_DIR, SUMMARY_DIR, CONFIG_DIR]:\n    os.makedirs(d, exist_ok=True)\n\n# DeepSeek API 配置\nDEEPSEEK_API_KEY = \"sk-&\"\nDEEPSEEK_URL = \"https://api.deepseek.com/v1/chat/completions\"\n\n# ========== 常量 ==========\nSCORE_LABELS = [\n    \"1:0\", \"2:0\", \"2:1\", \"3:0\", \"3:1\", \"3:2\",\n    \"4:0\", \"4:1\", \"4:2\", \"5:0\", \"5:1\", \"5:2\", \"胜其它\",\n    \"0:0\", \"1:1\", \"2:2\", \"3:3\", \"平其它\",\n    \"0:1\", \"0:2\", \"1:2\", \"0:3\", \"1:3\", \"2:3\",\n    \"0:4\", \"1:4\", \"2:4\", \"0:5\", \"1:5\", \"2:5\", \"负其它\"\n]\nTOTAL_GOALS_LABELS = [\"0球\", \"1球\", \"2球\", \"3球\", \"4球\", \"5球\", \"6球\", \"7+球\"]\nHALF_FULL_LABELS = [\"胜胜\", \"胜平\", \"胜负\", \"平胜\", \"平平\", \"平负\", \"负胜\", \"负平\", \"负负\"]\n\n# ========== 默认联赛参数 ==========\nDEFAULT_LEAGUE_PARAMS = {\n    \"avg_goals\": 2.6,\n    \"draw_base\": 0.24,\n    \"draw_range\": 0.10,\n    \"home_advantage\": 1.15,\n    \"handicap_factor\": 7.5,\n    \"half_home\": 0.43,\n    \"half_draw\": 0.33,\n    \"half_away\": 0.24\n}\n\n# ========== 加载联赛参数 ==========\ndef load_league_params():\n    if os.path.exists(LEAGUE_PARAMS_FILE):\n        with open(LEAGUE_PARAMS_FILE, 'r', encoding='utf-8') as f:\n            return json.load(f)\n    return {}\n\ndef save_league_params(params):\n    with open(LEAGUE_PARAMS_FILE, 'w', encoding='utf-8') as f:\n        json.dump(params, f, ensure_ascii=False, indent=2)\n\nLEAGUE_PARAMS = load_league_params()\n\n# ========== AI 生成联赛参数 ==========\ndef ai_generate_league_params(league_name):\n    if not DEEPSEEK_API_KEY or DEEPSEEK_API_KEY == \"sk-你的API密钥\":\n        return DEFAULT_LEAGUE_PARAMS.copy()\n    prompt = f\"\"\"你是一位足球数据分析专家，请为以下联赛生成推演参数。\n联赛名称：{league_name}\n请根据你对这个联赛的了解，提供以下参数（用JSON格式）：\n- avg_goals: 场均进球数（2.0-3.5）\n- draw_base: 平局概率基准值（0.15-0.35）\n- draw_range: 平局概率波动范围（0.05-0.15）\n- home_advantage: 主场优势系数（1.0-1.4）\n- handicap_factor: 让球调整系数（6.0-9.0）\n- half_home: 半场主胜概率（0.35-0.50）\n- half_draw: 半场平局概率（0.25-0.40）\n- half_away: 半场客胜概率（0.20-0.35）\n只输出JSON，不要有其他内容。\"\"\"\n    try:\n        response = requests.post(DEEPSEEK_URL, headers={\"Authorization\": f\"Bearer {DEEPSEEK_API_KEY}\", \"Content-Type\": \"application/json\"}, json={\"model\": \"deepseek-chat\", \"messages\": [{\"role\": \"system\", \"content\": \"你是一个足球数据分析专家，只输出JSON格式的参数。\"}, {\"role\": \"user\", \"content\": prompt}], \"temperature\": 0.3, \"response_format\": {\"type\": \"json_object\"}}, timeout=30)\n        result = response.json()\n        params = json.loads(result['choices'][0]['message']['content'])\n        for key in DEFAULT_LEAGUE_PARAMS:\n            if key not in params:\n                params[key] = DEFAULT_LEAGUE_PARAMS[key]\n        return params\n    except:\n        return DEFAULT_LEAGUE_PARAMS.copy()\n\ndef get_league_params(league):\n    if not league:\n        return DEFAULT_LEAGUE_PARAMS.copy()\n    league_key = league.strip()\n    if league_key in LEAGUE_PARAMS:\n        return LEAGUE_PARAMS[league_key]\n    for key in LEAGUE_PARAMS:\n        if key in league_key or league_key in key:\n            return LEAGUE_PARAMS[key]\n    params = ai_generate_league_params(league_key)\n    LEAGUE_PARAMS[league_key] = params\n    save_league_params(LEAGUE_PARAMS)\n    return params\n\n# ========== 实力分计算 ==========\ndef calculate_strength(team_data, is_home, league_params):\n    rank = team_data.get('rank', 15)\n    wins = team_data.get('season_wins', 0)\n    draws = team_data.get('season_draws', 0)\n    losses = team_data.get('season_losses', 0)\n    total = wins + draws + losses\n    win_rate = wins / total if total > 0 else 0\n    goals_for = team_data.get('goals_for', 0)\n    goals_against = team_data.get('goals_against', 0)\n    rank_score = max(0, 100 - (rank - 1) * 2.5)\n    win_rate_score = win_rate * 30\n    home_adv = league_params.get('home_advantage', 1.10)\n    if is_home:\n        home_wins = team_data.get('home_wins', 0)\n        home_draws = team_data.get('home_draws', 0)\n        home_losses = team_data.get('home_losses', 0)\n        home_total = home_wins + home_draws + home_losses\n        home_rate = home_wins / home_total if home_total > 0 else 0\n        home_bonus = home_rate * 12 * home_adv\n    else:\n        away_wins = team_data.get('away_wins', 0)\n        away_draws = team_data.get('away_draws', 0)\n        away_losses = team_data.get('away_losses', 0)\n        away_total = away_wins + away_draws + away_losses\n        away_rate = away_wins / away_total if away_total > 0 else 0\n        home_bonus = away_rate * 10 / home_adv\n    goal_diff = goals_for - goals_against\n    goal_score = max(0, min(goal_diff / 2, 1)) * 15\n    return round(rank_score + win_rate_score + home_bonus + goal_score, 1)\n\ndef expected_goals(home_strength, away_strength, league_params):\n    avg_goals = league_params.get('avg_goals', 2.6)\n    diff = (home_strength - away_strength) / 100\n    home_xg = avg_goals / 2 * (1 + diff * 0.6)\n    away_xg = avg_goals / 2 * (1 - diff * 0.6)\n    return max(0.4, home_xg), max(0.4, away_xg)\n\ndef poisson_prob(xg, k):\n    if k < 0:\n        return 0\n    return (math.exp(-xg) * (xg ** k)) / math.factorial(k)\n\n# ========== 各玩法分析函数 ==========\ndef analyze_spf(home_strength, away_strength, spf_odds, league_params):\n    \"\"\"分析SPF（胜平负）赔率偏差，增加反向推荐\"\"\"\n    if not spf_odds or len(spf_odds) < 3:\n        return None\n    \n    try:\n        home_odds = float(spf_odds[0])\n        draw_odds = float(spf_odds[1])\n        away_odds = float(spf_odds[2])\n    except:\n        return None\n    \n    # 计算隐含概率\n    total = 1/home_odds + 1/draw_odds + 1/away_odds\n    market_home = (1/home_odds) / total\n    market_draw = (1/draw_odds) / total\n    market_away = (1/away_odds) / total\n    \n    # 实力估算概率（加入联赛平局系数）\n    strength_total = home_strength + away_strength\n    if strength_total == 0:\n        return None\n    \n    raw_home = home_strength / strength_total\n    raw_away = away_strength / strength_total\n    # 平局概率 = 1 - 主客概率之和，但加入联赛基准平局系数\n    draw_base = league_params.get('draw_base', 0.28)\n    raw_draw = max(0, draw_base - abs(raw_home - raw_away) * 0.3)\n    # 重新归一化\n    total_raw = raw_home + raw_draw + raw_away\n    if total_raw == 0:\n        return None\n    model_home = raw_home / total_raw\n    model_draw = raw_draw / total_raw\n    model_away = raw_away / total_raw\n    \n    # 偏差 = 市场 - 模型\n    bias_home = market_home - model_home\n    bias_draw = market_draw - model_draw\n    bias_away = market_away - model_away\n    \n    result = {\n        'bias': {'home': bias_home, 'draw': bias_draw, 'away': bias_away},\n        'market': {'home': market_home, 'draw': market_draw, 'away': market_away},\n        'model': {'home': model_home, 'draw': model_draw, 'away': model_away},\n    }\n    \n    # ---------- 新增反向推荐逻辑 ----------\n    # 检查每个选项是否存在显著负偏差（<-8%），若有则反向推荐对立选项\n    reverse_recommend = None\n    for key, bias in result['bias'].items():\n        if bias < -0.08:  # 负偏差超过阈值\n            # 确定反向选项（取偏差最大的对立选项）\n            if key == 'home':\n                # 比较客胜和平局的偏差，选较大的\n                if result['bias']['away'] > result['bias']['draw']:\n                    opposite = 'away'\n                else:\n                    opposite = 'draw'\n            elif key == 'away':\n                if result['bias']['home'] > result['bias']['draw']:\n                    opposite = 'home'\n                else:\n                    opposite = 'draw'\n            else:  # draw\n                if result['bias']['home'] > result['bias']['away']:\n                    opposite = 'home'\n                else:\n                    opposite = 'away'\n            \n            reverse_recommend = {\n                'original': key,\n                'original_bias': bias,\n                'recommend': opposite,\n                'recommend_bias': -bias  # 反向偏差 = 正值\n            }\n            break  # 只取第一个明显负偏差\n    \n    if reverse_recommend:\n        result['reverse'] = reverse_recommend\n    \n    return result\n\ndef analyze_rqspf(home_strength, away_strength, rqspf_odds, handicap, league_params):\n    \"\"\"分析让球胜平负赔率偏差，增加反向推荐\"\"\"\n    if not rqspf_odds or len(rqspf_odds) < 3:\n        return None\n    \n    try:\n        home_odds = float(rqspf_odds[0])\n        draw_odds = float(rqspf_odds[1])\n        away_odds = float(rqspf_odds[2])\n    except:\n        return None\n    \n    # 计算隐含概率\n    total = 1/home_odds + 1/draw_odds + 1/away_odds\n    market_home = (1/home_odds) / total\n    market_draw = (1/draw_odds) / total\n    market_away = (1/away_odds) / total\n    \n    # ---- 根据让球数调整实力分 ----\n    adjusted_home = home_strength\n    adjusted_away = away_strength\n\n    # handicap 格式如 \"-1\"、\"+1\"、\"-2\" 等\n    if handicap:\n        try:\n            handicap_val = int(str(handicap).replace('+', ''))\n            # 让球数越大，主队实力折算越高（因为要赢更多球）\n            # 每让1球，相当于主队实力增加 15 分\n            adjusted_home = home_strength + (handicap_val * 15)\n            adjusted_away = away_strength - (handicap_val * 15)\n        except:\n            pass  # 如果转换失败，使用原始实力分\n\n    # 确保实力分不为负数\n    adjusted_home = max(adjusted_home, 10)\n    adjusted_away = max(adjusted_away, 10)\n\n    strength_total = adjusted_home + adjusted_away\n    if strength_total == 0:\n        return None\n    raw_home = adjusted_home / strength_total\n    raw_away = adjusted_away / strength_total\n    draw_base = league_params.get('draw_base', 0.28)\n    raw_draw = max(0, draw_base - abs(raw_home - raw_away) * 0.3)\n    total_raw = raw_home + raw_draw + raw_away\n    if total_raw == 0:\n        return None\n    model_home = raw_home / total_raw\n    model_draw = raw_draw / total_raw\n    model_away = raw_away / total_raw\n    \n    bias_home = market_home - model_home\n    bias_draw = market_draw - model_draw\n    bias_away = market_away - model_away\n    \n    result = {\n        'bias': {'home': bias_home, 'draw': bias_draw, 'away': bias_away},\n        'market': {'home': market_home, 'draw': market_draw, 'away': market_away},\n        'model': {'home': model_home, 'draw': model_draw, 'away': model_away},\n    }\n    \n    # ---------- 新增反向推荐逻辑（同SPF） ----------\n    reverse_recommend = None\n    for key, bias in result['bias'].items():\n        if bias < -0.08:\n            if key == 'home':\n                if result['bias']['away'] > result['bias']['draw']:\n                    opposite = 'away'\n                else:\n                    opposite = 'draw'\n            elif key == 'away':\n                if result['bias']['home'] > result['bias']['draw']:\n                    opposite = 'home'\n                else:\n                    opposite = 'draw'\n            else:\n                if result['bias']['home'] > result['bias']['away']:\n                    opposite = 'home'\n                else:\n                    opposite = 'away'\n            reverse_recommend = {\n                'original': key,\n                'original_bias': bias,\n                'recommend': opposite,\n                'recommend_bias': -bias\n            }\n            break\n    if reverse_recommend:\n        result['reverse'] = reverse_recommend\n    \n    return result\n\ndef analyze_total_goals(home_xg, away_xg, total_odds):\n    if len(total_odds) < 8:\n        return None\n    total_xg = home_xg + away_xg\n    model_probs = []\n    for k in range(8):\n        if k < 7:\n            prob = poisson_prob(total_xg, k)\n        else:\n            prob = 1 - sum(poisson_prob(total_xg, i) for i in range(7))\n        model_probs.append(prob)\n    imp_probs = [1/float(odds) for odds in total_odds]\n    total_imp = sum(imp_probs)\n    imp_probs = [p/total_imp for p in imp_probs]\n    biases = [round(model_probs[i] - imp_probs[i], 3) for i in range(8)]\n    max_bias = max(biases)\n    max_idx = biases.index(max_bias)\n    return {'model': model_probs, 'implied': imp_probs, 'bias': biases, 'max_bias': max_bias, 'max_index': max_idx}\n\ndef analyze_score(home_xg, away_xg, score_odds):\n    if len(score_odds) < 31:\n        return None\n    max_goals = 9\n    score_probs = {}\n    for h in range(max_goals + 1):\n        for a in range(max_goals + 1):\n            prob = poisson_prob(home_xg, h) * poisson_prob(away_xg, a)\n            score_probs[f\"{h}:{a}\"] = prob\n    model_probs = []\n    for label in SCORE_LABELS:\n        if ':' in label and '胜' not in label and '平' not in label and '负' not in label:\n            h, a = map(int, label.split(':'))\n            prob = score_probs.get(f\"{h}:{a}\", 0)\n        elif label == '胜其它':\n            prob = sum(score_probs.get(f\"{h}:{a}\", 0) for h in range(max_goals + 1) for a in range(max_goals + 1) if h > a and f\"{h}:{a}\" not in SCORE_LABELS[:13])\n        elif label == '平其它':\n            prob = sum(score_probs.get(f\"{h}:{a}\", 0) for h in range(max_goals + 1) for a in range(max_goals + 1) if h == a and h > 3 and f\"{h}:{a}\" not in SCORE_LABELS[13:18])\n        elif label == '负其它':\n            prob = sum(score_probs.get(f\"{h}:{a}\", 0) for h in range(max_goals + 1) for a in range(max_goals + 1) if h < a and f\"{h}:{a}\" not in SCORE_LABELS[18:])\n        else:\n            prob = 0\n        model_probs.append(prob)\n    total_model = sum(model_probs)\n    if total_model > 0:\n        model_probs = [p/total_model for p in model_probs]\n    imp_probs = []\n    for odds in score_odds[:31]:\n        try:\n            imp_probs.append(1 / float(odds))\n        except:\n            imp_probs.append(0)\n    total_imp = sum(imp_probs)\n    if total_imp > 0:\n        imp_probs = [p/total_imp for p in imp_probs]\n    biases = []\n    for i in range(min(len(model_probs), len(imp_probs))):\n        biases.append(round(model_probs[i] - imp_probs[i], 3))\n    max_bias = max(biases) if biases else 0\n    max_idx = biases.index(max_bias) if biases else -1\n        # 在 return 之前添加\n    print(f\"===== 比分调试 =====\")\n    print(f\"home_xg: {home_xg}, away_xg: {away_xg}\")\n    print(f\"score_odds 长度: {len(score_odds)}\")\n    if len(score_odds) > 0:\n        print(f\"score_odds 前5: {score_odds[:5]}\")\n    print(f\"model_probs 前5: {model_probs[:5] if model_probs else '空'}\")\n    print(f\"imp_probs 前5: {imp_probs[:5] if imp_probs else '空'}\")\n    print(f\"biases 前5: {biases[:5] if biases else '空'}\")\n    print(f\"max_bias: {max_bias}, recommend: {SCORE_LABELS[max_idx] if max_idx >= 0 else 'N/A'}\")\n    print(f\"==================\")\n    return {\n        'max_bias': max_bias,\n        'max_index': max_idx,\n        'recommend': SCORE_LABELS[max_idx] if max_idx >= 0 else None,\n        'bias': biases\n    }\n\ndef analyze_half_full(home_strength, away_strength, half_full_odds, league_params):\n    if len(half_full_odds) < 9:\n        return None\n    diff = home_strength - away_strength\n    half_home = league_params.get('half_home', 0.43) + (diff / 100) * 0.08\n    half_draw = league_params.get('half_draw', 0.33) - abs(diff) / 100 * 0.05\n    half_away = 1 - half_home - half_draw\n    full_home = 1 / (1 + 10 ** (-diff / 100 * 1.2))\n    full_away = 1 - full_home\n    full_draw = 0.15 + 0.1 * (1 - abs(diff) / 100)\n    full_draw = max(0.10, min(0.35, full_draw))\n    full_home *= (1 - full_draw)\n    full_away *= (1 - full_draw)\n    model_probs = [\n        half_home * full_home * 0.8,\n        half_home * full_draw * 0.3,\n        half_home * full_away * 0.2,\n        half_draw * full_home * 0.5,\n        half_draw * full_draw * 0.8,\n        half_draw * full_away * 0.5,\n        half_away * full_home * 0.2,\n        half_away * full_draw * 0.3,\n        half_away * full_away * 0.8\n    ]\n    total_model = sum(model_probs)\n    if total_model > 0:\n        model_probs = [p/total_model for p in model_probs]\n    imp_probs = [1/float(odds) for odds in half_full_odds]\n    total_imp = sum(imp_probs)\n    if total_imp > 0:\n        imp_probs = [p/total_imp for p in imp_probs]\n    biases = [round(imp_probs[i] - model_probs[i], 3) for i in range(9)]\n    max_bias = max(biases)\n    max_idx = biases.index(max_bias)\n    return {\n        'max_bias': max_bias,\n        'max_index': max_idx,\n        'recommend': HALF_FULL_LABELS[max_idx],\n        'bias': biases\n    }\ndef generate_detailed_analysis(match, signals, comparison=None):\n    \"\"\"生成详细推演分析矩阵\n    返回：字符串，包含推演路径、剧本排序、投注建议\n    \"\"\"\n    if not signals:\n        return \"无有效信号，建议观望\"\n    \n    # ---- 分离正负偏差 ----\n    positive = [s for s in signals if s['bias'] > 0.08]\n    negative = [s for s in signals if s['bias'] < -0.03]\n    \n    # ---- 新增：将负偏差转化为反向推荐 ----\n    reverse_signals = []\n    for s in negative:\n        if s['option'] == 'home':\n            reverse_option = 'away'\n        elif s['option'] == 'away':\n            reverse_option = 'home'\n        elif s['option'] == 'draw':\n            reverse_option = 'home'  # 平局负偏差时，默认推荐主胜（可优化）\n        else:\n            continue\n        reverse_signals.append({\n            'play': s['play'],\n            'option': reverse_option,\n            'bias': -s['bias'],\n            'signal_type': '反向挖掘'\n        })\n    \n    # ---- 合并正偏差 + 反向推荐 ----\n    all_signals = positive + reverse_signals\n    \n    if not all_signals:\n        neg_list = \", \".join([f\"{s['play']} {s['option']}（{s['bias']:+.0%}）\" for s in negative])\n        return f\"⚠️ 仅负偏差信号：{neg_list}。建议回避，考虑反向选项或放弃。\"\n    \n    # 按偏差绝对值排序\n    all_signals.sort(key=lambda x: abs(x['bias']), reverse=True)\n    \n    # ---- 构建推演路径 ----\n    lines = []\n    lines.append(\"**推演分析矩阵**\")\n    lines.append(\"\")\n    \n    # 1. 信号汇总\n    lines.append(\"**正偏差（被低估）**：\")\n    for s in all_signals:\n        tag = \" [反向挖掘]\" if s.get('signal_type') == '反向挖掘' else \"\"\n        lines.append(f\" + {s['play']} {s['option']}（偏差 {s['bias']:+.0%}）{tag}\")\n    if negative:\n        lines.append(\"**负偏差（被高估，回避）**：\")\n        for s in negative:\n            lines.append(f\" - {s['play']} {s['option']}（偏差 {s['bias']:+.0%}）\")\n    lines.append(\"\")\n    \n    # 2. 推演剧本生成\n    core = all_signals[0]\n    core_play = core['play']\n    core_option = core['option']\n    core_bias = core['bias']\n    \n    # 确定方向\n    if \"主\" in core_option or \"home\" in core_option:\n        direction = \"主队\"\n    elif \"客\" in core_option or \"away\" in core_option:\n        direction = \"客队\"\n    else:\n        direction = \"平局\"\n    \n    aux_signals = all_signals[1:]\n    scripts = []\n    \n    # 剧本1：核心方向 + 辅助信号组合\n    script1 = f\"**核心剧本：{direction}胜**\"\n    if core_play == \"半全场\":\n        script1 += f\"（{core_option}）\"\n    elif core_play in [\"SPF\", \"RQSPF\"]:\n        script1 += f\"（{core_option}）\"\n    if aux_signals:\n        aux_desc = []\n        for a in aux_signals[:3]:\n            if a['play'] in [\"比分\", \"总进球\", \"半全场\"]:\n                aux_desc.append(f\"{a['play']} {a['option']}（+{abs(a['bias']):.0%}）\")\n        if aux_desc:\n            script1 += \"，配合 \" + \"、\".join(aux_desc)\n    \n    # 赔率变化分析（仅当 comparison 不为 None 且有数据时）\n    if comparison and isinstance(comparison, dict) and comparison.get('changes'):\n        changes = comparison['changes']\n        for key, change in changes.items():\n            if isinstance(change, dict) and 'direction' in change:\n                if (direction == \"主队\" and \"spf_home\" in key and change['direction'] == \"降低\") or \\\n                   (direction == \"客队\" and \"spf_away\" in key and change['direction'] == \"降低\"):\n                    script1 += \"；赔率变化确认该方向\"\n                elif (direction == \"主队\" and \"spf_home\" in key and change['direction'] == \"升高\") or \\\n                     (direction == \"客队\" and \"spf_away\" in key and change['direction'] == \"升高\"):\n                    script1 += \"；但赔率变化与该方向相悖，需谨慎\"\n    script1 += \"。\"\n    scripts.append(script1)\n    \n    # 剧本2：备选方向（平局或主/客不败）\n    draw_signals = [s for s in all_signals if \"draw\" in s['option'] or \"平\" in s['option']]\n    if draw_signals and draw_signals[0]['bias'] > 0.03:\n        script2 = f\"**备选剧本：平局**（{draw_signals[0]['play']} 偏差 {draw_signals[0]['bias']:+.0%}）\"\n        scripts.append(script2)\n    elif negative:\n        neg_option = negative[0]['option']\n        if \"home\" in neg_option or \"主胜\" in neg_option:\n            script2 = \"**备选剧本：客队不败**（主胜被高估，客队或平局有机会）\"\n            scripts.append(script2)\n        elif \"away\" in neg_option or \"客胜\" in neg_option:\n            script2 = \"**备选剧本：主队不败**（客胜被高估，主队或平局有机会）\"\n            scripts.append(script2)\n    \n    # 剧本3：比分关注\n    score_signal = next((s for s in all_signals if s['play'] == \"比分\"), None)\n    if score_signal and score_signal['bias'] > 0.05:\n        script3 = f\"**高赔比分关注**：{score_signal['option']}（偏差 {score_signal['bias']:+.0%}）\"\n        scripts.append(script3)\n    \n    lines.append(\"**推演剧本（按可能性排序）**：\")\n    for i, script in enumerate(scripts, 1):\n        lines.append(f\" {i}. {script}\")\n    lines.append(\"\")\n    \n    # 3. 最终投注建议（差异化信心阈值）\n    play = core['play']\n    if play in ['SPF', 'RQSPF']:\n        threshold_high = 0.12\n    elif play in ['总进球', '半全场']:\n        threshold_high = 0.15\n    else:  # 比分等\n        threshold_high = 0.20\n    \n    if core_bias >= threshold_high:\n        confidence = \"高\"\n        emoji = \"✅\"\n    else:\n        confidence = \"低\"\n        emoji = \"⚠️\"\n    \n    # 胜负平分类\n    if \"主\" in core_option or \"home\" in core_option:\n        bet_type = \"主胜\"\n    elif \"客\" in core_option or \"away\" in core_option:\n        bet_type = \"客胜\"\n    else:\n        bet_type = \"平局\"\n    \n    lines.append(f\"**📊 投注建议**：\")\n    tag = \" [反向挖掘]\" if core.get('signal_type') == '反向挖掘' else \"\"\n    lines.append(f\" 核心推荐：{emoji} {core_play} {bet_type}（偏差 {core_bias:+.0%}，信心 {confidence}）{tag}\")\n    \n    if len(all_signals) > 1:\n        aux_parts = []\n        for s in all_signals[1:3]:\n            aux_tag = \" [反向]\" if s.get('signal_type') == '反向挖掘' else \"\"\n            aux_parts.append(f\"{s['play']} {s['option']}{aux_tag}\")\n        if aux_parts:\n            lines.append(f\" 辅助关注：{'、'.join(aux_parts)}\")\n\n    if negative:\n        neg_plays = [f\"{s['play']} {s['option']}\" for s in negative[:2]]\n        lines.append(f\" ⚠️ 回避：{'、'.join(neg_plays)}（被高估）\")\n    \n    return \"\\n\".join(lines)\n\n# ========== AI 软信息分析 ==========\ndef ai_analyze_match(match, home_strength, away_strength, home_rank, away_rank, bias_signals):\n    if not DEEPSEEK_API_KEY or DEEPSEEK_API_KEY == \"sk-你的API密钥\":\n        return {\"修正建议\": \"坚持信号\", \"信心等级\": 3, \"说明\": \"未配置API Key，使用硬数据\"}\n    bias_summary = \"\\n\".join([f\"  {s['play']}: {s['option']} 偏差{s['bias']:+.0%}\" for s in bias_signals]) if bias_signals else \"无明显偏差\"\n    prompt = f\"\"\"你是一位专业的足球比赛分析师，请分析以下比赛。\n主队：{match['home_team']}（排名第{home_rank}，实力分{home_strength:.1f}）\n客队：{match['away_team']}（排名第{away_rank}，实力分{away_strength:.1f}）\n联赛：{match['league']}\n硬数据信号：\n{bias_summary}\n请分析战意、伤病、人气陷阱、状态异常，输出JSON：\n{{\"战意\": \"...\", \"伤病影响\": \"...\", \"人气陷阱\": \"...\", \"状态异常\": \"...\", \"修正建议\": \"坚持信号/降低信心/放弃\", \"信心等级\": 1-5, \"说明\": \"...\"}}\"\"\"\n    try:\n        response = requests.post(DEEPSEEK_URL, headers={\"Authorization\": f\"Bearer {DEEPSEEK_API_KEY}\", \"Content-Type\": \"application/json\"}, json={\"model\": \"deepseek-chat\", \"messages\": [{\"role\": \"user\", \"content\": prompt}], \"temperature\": 0.3, \"response_format\": {\"type\": \"json_object\"}}, timeout=30)\n        result = response.json()\n        return json.loads(result['choices'][0]['message']['content'])\n    except:\n        return {\"修正建议\": \"坚持信号\", \"信心等级\": 3, \"说明\": \"AI分析失败\"}\n    \ndef ai_complete_missing_data(match, home_team, away_team, league):\n    \"\"\"\n    当排名缺失时，调用AI补全球队实力信息\n    返回：{'home_rank': int, 'away_rank': int, 'home_strength': float, 'away_strength': float, 'confidence': str}\n    \"\"\"\n    if not DEEPSEEK_API_KEY or DEEPSEEK_API_KEY == \"sk-你的API密钥\":\n        return None\n    \n    prompt = f\"\"\"请根据你对足球的了解，补全以下比赛的数据：\n联赛：{league}\n主队：{home_team}\n客队：{away_team}\n\n请估算：\n1. 两队在当前联赛中的大概排名（1-20之间的整数）\n2. 两队的综合实力分（60-130之间的浮点数，主队和客队都要）\n\n请以JSON格式输出，不要有其他内容：\n{{\"home_rank\": 排名, \"away_rank\": 排名, \"home_strength\": 实力分, \"away_strength\": 实力分, \"confidence\": \"高/中/低\", \"reason\": \"简要理由\"}}\n\"\"\"\n    try:\n        response = requests.post(\n            DEEPSEEK_URL,\n            headers={\"Authorization\": f\"Bearer {DEEPSEEK_API_KEY}\", \"Content-Type\": \"application/json\"},\n            json={\n                \"model\": \"deepseek-chat\",\n                \"messages\": [{\"role\": \"user\", \"content\": prompt}],\n                \"temperature\": 0.5,\n                \"response_format\": {\"type\": \"json_object\"}\n            },\n            timeout=30\n        )\n        result = response.json()\n        data = json.loads(result['choices'][0]['message']['content'])\n        return {\n            'home_rank': int(data.get('home_rank', 15)),\n            'away_rank': int(data.get('away_rank', 15)),\n            'home_strength': float(data.get('home_strength', 70)),\n            'away_strength': float(data.get('away_strength', 70)),\n            'confidence': data.get('confidence', '低'),\n            'reason': data.get('reason', 'AI估算')\n        }\n    except Exception as e:\n        print(f\"  ⚠️ AI补全失败: {e}\")\n        return None    \n\n# ========== 🆕 历史记录加载与对比分析 ==========\ndef load_history_for_match(match_id):\n    \"\"\"加载同一场比赛的历史推演记录\"\"\"\n    history_file = os.path.join(HISTORY_DIR, f\"{match_id}.json\")\n    if not os.path.exists(history_file):\n        return None\n    try:\n        with open(history_file, 'r', encoding='utf-8') as f:\n            return json.load(f)\n    except:\n        return None\n    \ndef signals_are_identical(current_signals, prev_signals):\n    \"\"\"\n    比较当前信号和历史信号的偏差是否完全相同\n    返回：True 表示完全相同，False 表示有变化\n    \"\"\"\n    if not prev_signals:\n        return False\n    \n    def key(s):\n        return (s.get('play', ''), s.get('option', ''), round(s.get('bias', 0), 3))\n    \n    current_keys = sorted([key(s) for s in current_signals])\n    prev_keys = sorted([key(s) for s in prev_signals])\n    \n    return current_keys == prev_keys\n\ndef save_history_for_match(match_id, record):\n    \"\"\"保存推演记录到历史文件（追加）\"\"\"\n    history_file = os.path.join(HISTORY_DIR, f\"{match_id}.json\")\n    existing = load_history_for_match(match_id)\n    if existing:\n        # 追加新记录（保留最近50条）\n        if isinstance(existing, list):\n            existing.append(record)\n            if len(existing) > 50:\n                existing = existing[-50:]\n        else:\n            existing = [existing, record]\n    else:\n        existing = [record]\n    with open(history_file, 'w', encoding='utf-8') as f:\n        json.dump(existing, f, ensure_ascii=False, indent=2)\n\ndef compare_odds_changes(current, previous):\n    \"\"\"\n    对比当前与历史赔率的变化\n    返回：变化分析字典\n    \"\"\"\n    if not previous:\n        return None\n    \n    changes = {}\n    timestamp = datetime.now().isoformat()\n    \n    # 对比 SPF\n    prev_spf = previous.get('odds', {}).get('spf', [])\n    curr_spf = current.get('odds', {}).get('spf', [])\n    if prev_spf and curr_spf and len(prev_spf) >= 3 and len(curr_spf) >= 3:\n        for idx, label in enumerate(['home', 'draw', 'away']):\n            try:\n                prev_val = float(prev_spf[idx])\n                curr_val = float(curr_spf[idx])\n                diff = curr_val - prev_val\n                if abs(diff) > 0.01:\n                    direction = \"升高\" if diff > 0 else \"降低\"\n                    changes[f\"spf_{label}\"] = {\n                        \"from\": prev_val,\n                        \"to\": curr_val,\n                        \"diff\": round(diff, 2),\n                        \"direction\": direction,\n                        \"interpretation\": \"市场看衰\" if diff > 0 else \"市场看好\"\n                    }\n            except:\n                pass\n    \n    # 对比 RQSPF\n    prev_rq = previous.get('odds', {}).get('rqspf', [])\n    curr_rq = current.get('odds', {}).get('rqspf', [])\n    if prev_rq and curr_rq and len(prev_rq) >= 3 and len(curr_rq) >= 3:\n        for idx, label in enumerate(['home', 'draw', 'away']):\n            try:\n                prev_val = float(prev_rq[idx])\n                curr_val = float(curr_rq[idx])\n                diff = curr_val - prev_val\n                if abs(diff) > 0.01:\n                    direction = \"升高\" if diff > 0 else \"降低\"\n                    changes[f\"rqspf_{label}\"] = {\n                        \"from\": prev_val,\n                        \"to\": curr_val,\n                        \"diff\": round(diff, 2),\n                        \"direction\": direction,\n                        \"interpretation\": \"市场看衰\" if diff > 0 else \"市场看好\"\n                    }\n            except:\n                pass\n    \n    # 对比让球数\n    prev_handicap = previous.get('handicap', '0')\n    curr_handicap = current.get('handicap', '0')\n    if prev_handicap != curr_handicap:\n        changes['handicap'] = {\n            \"from\": prev_handicap,\n            \"to\": curr_handicap,\n            \"diff\": f\"{prev_handicap} → {curr_handicap}\",\n            \"interpretation\": f\"让球数变化，可能反映实力预期调整\"\n        }\n    \n    return changes if changes else None\n\ndef analyze_market_impact(changes, bias_signals):\n    \"\"\"\n    分析赔率变化对偏差信号的影响\n    返回：综合判断\n    \"\"\"\n    if not changes:\n        return None\n    \n    analysis = {\n        \"summary\": [],\n        \"confidence_adjustment\": 0,  # -1 到 +1\n        \"recommendation\": \"坚持原信号\"\n    }\n    \n    for key, change in changes.items():\n        # 解析变化方向与偏差方向是否一致\n        if key.startswith('spf_home') or key.startswith('rqspf_home'):\n            # 主胜赔率变化\n            if change['direction'] == '降低':\n                # 赔率降低 → 市场更看好主队\n                # 检查偏差信号中是否有主胜\n                for signal in bias_signals:\n                    if signal.get('option') == 'home' or signal.get('option') == '主胜':\n                        if signal.get('bias', 0) > 0:\n                            analysis['summary'].append(f\"主胜赔率{change['direction']}，与偏差信号(+{signal['bias']:.0%})方向一致，信号增强\")\n                            analysis['confidence_adjustment'] += 0.2\n                        else:\n                            analysis['summary'].append(f\"主胜赔率{change['direction']}，但偏差为负，存在矛盾，需谨慎\")\n                            analysis['confidence_adjustment'] -= 0.2\n            elif change['direction'] == '升高':\n                for signal in bias_signals:\n                    if signal.get('option') == 'home' or signal.get('option') == '主胜':\n                        if signal.get('bias', 0) > 0:\n                            analysis['summary'].append(f\"主胜赔率{change['direction']}，与偏差信号(+{signal['bias']:.0%})方向相反，可能信号被市场消化\")\n                            analysis['confidence_adjustment'] -= 0.1\n                        else:\n                            analysis['summary'].append(f\"主胜赔率{change['direction']}，偏差为负，双重看衰\")\n                            analysis['confidence_adjustment'] -= 0.1\n        \n        elif key.startswith('spf_away') or key.startswith('rqspf_away'):\n            if change['direction'] == '降低':\n                for signal in bias_signals:\n                    if signal.get('option') == 'away' or signal.get('option') == '客胜':\n                        if signal.get('bias', 0) > 0:\n                            analysis['summary'].append(f\"客胜赔率{change['direction']}，与偏差信号(+{signal['bias']:.0%})方向一致，信号增强\")\n                            analysis['confidence_adjustment'] += 0.2\n    \n    # 综合判断\n    if analysis['confidence_adjustment'] >= 0.3:\n        analysis['recommendation'] = \"信号增强，可加大关注\"\n    elif analysis['confidence_adjustment'] <= -0.3:\n        analysis['recommendation'] = \"信号减弱，建议降低信心或放弃\"\n    else:\n        analysis['recommendation'] = \"无明显变化，维持原判断\"\n    \n    return analysis\n\n# ========== 主函数 ==========\ndef main():\n    print(\"=\" * 60)\n    print(f\"足球预测推演系统 (动态追踪版) - {datetime.now().strftime('%Y-%m-%d %H:%M')}\")\n    print(\"=\" * 60)\n    print()\n    \n    if not os.path.exists(RAW_MATCHES_FILE):\n        print(\"❌ raw_matches.json 不存在\")\n        return\n    if not os.path.exists(PREVIEW_FILE):\n        print(\"❌ preview_data.json 不存在\")\n        return\n    \n    with open(RAW_MATCHES_FILE, 'r', encoding='utf-8') as f:\n        matches = json.load(f)\n    with open(PREVIEW_FILE, 'r', encoding='utf-8') as f:\n        previews = json.load(f)\n    \n    match_results = []\n    ai_usage_count = 0\n    new_leagues = []\n    comparison_count = 0\n    \n    for match in matches:\n        mid = match.get('match_id')\n        league = match.get('league', '默认')\n        league_params = get_league_params(league)\n        if league not in LEAGUE_PARAMS and league not in new_leagues:\n            new_leagues.append(league)\n        \n        preview = previews.get(mid, {}).get('preview', {})\n        home_preview = preview.get('home', {})\n        away_preview = preview.get('away', {})\n\n        home_rank = home_preview.get('rank')\n        away_rank = away_preview.get('rank')\n        home_strength = None\n        away_strength = None\n        ai_completed = False\n\n        # ---- 数据完整性检查：如果排名缺失，尝试AI补全 ----\n        if home_rank is None or away_rank is None:\n            if AI_COMPLETE_MISSING_RANK:\n                print(f\"  🔄 {match.get('match_num', '')} {match.get('home_team', '')} vs {match.get('away_team', '')} 排名缺失，尝试AI补全...\")\n                \n                ai_data = ai_complete_missing_data(\n                    match,\n                    match.get('home_team', ''),\n                    match.get('away_team', ''),\n                    league\n                )\n                \n                if ai_data:\n                    home_rank = ai_data['home_rank']\n                    away_rank = ai_data['away_rank']\n                    home_strength = ai_data['home_strength']\n                    away_strength = ai_data['away_strength']\n                    ai_completed = True\n                    print(f\"  ✅ AI补全完成: 主{home_rank}位({home_strength:.1f})，客{away_rank}位({away_strength:.1f})，置信度:{ai_data.get('confidence', '未知')}\")\n                else:\n                    print(f\"  ⏭️ AI补全失败，跳过该场\")\n                    continue\n            else:\n                print(f\"  ⏭️ {match.get('match_num', '')} {match.get('home_team', '')} vs {match.get('away_team', '')} 排名缺失（AI补全已关闭），跳过\")\n                continue\n        else:\n            # 排名存在，正常计算\n            home_rank = int(home_rank)\n            away_rank = int(away_rank)\n            # 正常计算实力分\n            home_strength = calculate_strength(home_preview, True, league_params)\n            away_strength = calculate_strength(away_preview, False, league_params)\n            ai_completed = False\n\n        # 确保实力分有值\n        if home_strength is None or away_strength is None:\n            print(f\"  ⚠️ 实力分缺失，跳过\")\n            continue\n        # ---- 计算预期进球（新增这一行） ----\n        home_xg, away_xg = expected_goals(home_strength, away_strength, league_params)\n\n        result = {\n            'match_num': match['match_num'],\n            'ai_completed': ai_completed,\n            'home_team': match['home_team'],\n            'away_team': match['away_team'],\n            'league': league,\n            'strength': {'home': home_strength, 'away': away_strength},\n            'rank': {'home': home_rank, 'away': away_rank},\n            'expected_goals': {'home': round(home_xg, 2), 'away': round(away_xg, 2)},\n            'odds': {\n                'spf': match.get('spf', []),\n                'rqspf': match.get('rqspf', []),\n                'total_goals': match.get('total_goals', []),\n                'score': match.get('score', []),\n                'half_full': match.get('half_full', [])\n            },\n            'handicap': match.get('handicap', '0'),\n            'recommendations': [],\n            'trial_recommendations': [],\n            'ai_analysis': None,\n            'comparison': None\n        }\n        \n        # ---- 各玩法分析 ----\n        bias_signals = []\n        trial_signals = []\n        skip_ai = False\n        spf_result = analyze_spf(home_strength, away_strength, match.get('spf', []), league_params)\n        if spf_result:\n            if 'reverse' in spf_result:\n                rev = spf_result['reverse']\n                bias_signals.append({\n                    'play': 'SPF',\n                    'option': rev['recommend'],\n                    'bias': rev['recommend_bias'],\n                    'signal_type': '反向挖掘',\n                    'match_id': mid\n                })\n            else:\n                max_key = max(spf_result['bias'], key=lambda k: abs(spf_result['bias'][k]))\n                if abs(spf_result['bias'][max_key]) > 0.08 and spf_result['bias'][max_key] > 0:\n                    bias_signals.append({\n                        'play': 'SPF',\n                        'option': max_key,\n                        'bias': spf_result['bias'][max_key],\n                        'signal_type': '正向偏差',\n                        'match_id': mid\n                    })\n        \n        rqspf_result = analyze_rqspf(home_strength, away_strength, match.get('rqspf', []), match.get('handicap', '0'), league_params)\n        if rqspf_result:\n            if 'reverse' in rqspf_result:\n                rev = rqspf_result['reverse']\n                bias_signals.append({\n                    'play': 'RQSPF',\n                    'option': rev['recommend'],\n                    'bias': rev['recommend_bias'],\n                    'signal_type': '反向挖掘',\n                    'match_id': mid\n                })\n            else:\n                max_key = max(rqspf_result['bias'], key=lambda k: abs(rqspf_result['bias'][k]))\n                if abs(rqspf_result['bias'][max_key]) > 0.08 and rqspf_result['bias'][max_key] > 0:\n                    bias_signals.append({\n                        'play': 'RQSPF',\n                        'option': max_key,\n                        'bias': rqspf_result['bias'][max_key],\n                        'signal_type': '正向偏差',\n                        'match_id': mid\n                    })\n        \n        tg_result = analyze_total_goals(home_xg, away_xg, match.get('total_goals', []))\n        if tg_result and tg_result['max_bias'] > 0.15:\n            bias_signals.append({\n                'play': '总进球',\n                'option': TOTAL_GOALS_LABELS[tg_result['max_index']],\n                'bias': tg_result['max_bias'],\n                'signal_type': '正向偏差',\n                'match_id': mid\n            })\n\n                # ---- 平局推荐约束：只在实力接近时推荐 ----\n        strength_diff = abs(home_strength - away_strength)\n        before_count = len(bias_signals)\n        bias_signals = [\n            s for s in bias_signals \n            if not (s.get('option') in ['draw', '平局'] and strength_diff >= 20)\n        ]\n        if before_count > len(bias_signals):\n            print(f\"  ⚠️ 平局信号被过滤（实力分差{strength_diff:.1f} ≥ 20）\")\n        # ---- 试推演：半全场和比分（仅观察，不进入正式推荐） ----\n        trial_signals = []\n        \n        # 半全场试推演\n        try:\n            hf_result = analyze_half_full(home_strength, away_strength, match.get('half_full', []), league_params)\n            if hf_result and hf_result.get('max_bias') is not None:\n                if abs(hf_result['max_bias']) > 0.05:  # 试推演阈值放宽到5%\n                    trial_signals.append({\n                        'play': '半全场',\n                        'option': hf_result['recommend'],\n                        'bias': hf_result['max_bias'],\n                        'signal_type': '试推演',\n                        'match_id': mid\n                    })\n        except Exception as e:\n            print(f\"  ⚠️ 半全场试推演失败: {e}\")\n        \n        # 比分试推演\n        try:\n            score_result = analyze_score(home_xg, away_xg, match.get('score', []))\n            if score_result and score_result.get('max_bias') is not None:\n                if abs(score_result['max_bias']) > 0.03:  # 试推演阈值放宽到3%\n                    trial_signals.append({\n                        'play': '比分',\n                        'option': score_result['recommend'],\n                        'bias': score_result['max_bias'],\n                        'signal_type': '试推演',\n                        'match_id': mid\n                    })\n        except Exception as e:\n            print(f\"  ⚠️ 比分试推演失败: {e}\")\n        \n        # ---- 信心赋值（先赋值，再过滤） ----\n        for s in bias_signals:\n            s['confidence'] = '高' if abs(s['bias']) > 0.12 else '低'\n        \n        # ---- 只保留高信心信号 ----\n        bias_signals = [s for s in bias_signals if s['confidence'] == '高']\n\n        # ---- 🆕 加载历史记录并对比 ----\n        if mid:\n            history = load_history_for_match(mid)\n            if history and len(history) > 0:\n                prev_record = history[-1]\n                prev_signals = prev_record.get('recommendations', [])\n                \n                # ---- 检查当前信号与历史是否完全相同 ----\n                if bias_signals and prev_signals and signals_are_identical(bias_signals, prev_signals):\n                    # 完全一样：直接沿用历史，跳过AI分析\n                    bias_signals = prev_signals.copy()\n                    for s in bias_signals:\n                        s['match_id'] = mid\n                        if 'signal_type' not in s:\n                            s['signal_type'] = '历史沿用（无变化）'\n                    print(f\"  ⏭️ 偏差与历史完全相同，直接沿用 ({len(bias_signals)} 条)，跳过AI\")\n                    skip_ai = True\n                    result['comparison'] = {\n                        'previous_time': prev_record.get('generated_at', '未知'),\n                        'changes': None,\n                        'market_analysis': {'recommendation': '无变化，沿用历史'}\n                    }\n                else:\n                    # 有变化或新比赛：正常走对比\n                    changes = compare_odds_changes(result, prev_record)\n                    if changes:\n                        comparison_count += 1\n                        market_analysis = analyze_market_impact(changes, bias_signals)\n                        result['comparison'] = {\n                            'previous_time': prev_record.get('generated_at', '未知'),\n                            'changes': changes,\n                            'market_analysis': market_analysis\n                        }\n                    else:\n                        result['comparison'] = {\n                            'previous_time': prev_record.get('generated_at', '未知'),\n                            'changes': None,\n                            'market_analysis': {'recommendation': '无赔率变化'}\n                        }\n                \n                # ---- 当前无信号但有历史推荐，则沿用 ----\n                if not bias_signals and prev_signals:\n                    bias_signals = prev_signals.copy()\n                    for s in bias_signals:\n                        s['match_id'] = mid\n                        if 'signal_type' not in s:\n                            s['signal_type'] = '历史沿用'\n                    print(f\"  📋 沿用历史推荐 ({len(bias_signals)} 条)\")\n                    skip_ai = True\n            else:\n                # 没有历史记录\n                skip_ai = False\n        else:\n            skip_ai = False\n        \n        # ---- AI 分析（如果 skip_ai 为 True，则跳过） ----\n        if not skip_ai:\n            need_ai = any(abs(s['bias']) > 0.08 for s in bias_signals)\n            if need_ai:\n                ai_output = ai_analyze_match(match, home_strength, away_strength, home_rank, away_rank, bias_signals)\n                result['ai_analysis'] = ai_output\n                ai_usage_count += 1\n                if ai_output.get('修正建议') == '放弃':\n                    bias_signals = []\n                elif ai_output.get('修正建议') == '降低信心':\n                    for s in bias_signals:\n                        s['confidence'] = '低'\n        else:\n            # 直接沿用历史，无需AI分析\n            pass\n\n        result['recommendations'] = bias_signals\n        result['trial_recommendations'] = trial_signals\n        \n        # ---- 保存历史记录 ----\n        if mid:\n            history_record = {\n                'generated_at': datetime.now().isoformat(),\n                'match_num': match['match_num'],\n                'odds': result['odds'],\n                'handicap': result['handicap'],\n                'strength': result['strength'],\n                'recommendations': result['recommendations']\n            }\n            save_history_for_match(mid, history_record)\n        # ---- 生成详细推演分析 ----\n        detailed = generate_detailed_analysis(match, bias_signals, result.get('comparison'))\n        result['detailed_analysis'] = detailed\n        \n        match_results.append(result)\n        \n        # ---- 终端输出 ----\n        print(f\"【{result['match_num']}】 {result['home_team']} vs {result['away_team']} ({result['league']})\")\n        print(f\"  实力: {home_strength} - {away_strength} (排名:{home_rank} vs {away_rank})\")\n        print(f\"  预期进球: {home_xg:.2f} - {away_xg:.2f}\")\n        \n        if result.get('comparison'):\n            changes = result['comparison'].get('changes')\n            if changes and isinstance(changes, dict) and len(changes) > 0:\n                print(f\"  📊 赔率变化: 与 {result['comparison']['previous_time'][:16]} 对比\")\n                for key, change in changes.items():\n                    if isinstance(change, dict) and 'direction' in change:\n                        print(f\"    {key}: {change['from']} → {change['to']} ({change['direction']})\")\n            else:\n                print(f\"  📊 赔率变化: 无变化（与 {result['comparison']['previous_time'][:16]} 对比）\")\n            \n            if result['comparison'].get('market_analysis'):\n                ma = result['comparison']['market_analysis']\n                print(f\"  📈 市场分析: {ma.get('recommendation', '无变化')}\")\n                for s in ma.get('summary', [])[:3]:\n                    print(f\"    {s}\")\n\n        if bias_signals:\n            for s in bias_signals:\n                print(f\"  → {s['play']}: {s['option']} (偏差{s['bias']:+.0%}, 信心:{s.get('confidence', '中')})\")\n            print(\"  \" + \"-\"*40)\n            for line in detailed.split('\\n'):\n                if line.strip():\n                    print(f\"  {line}\")\n            print(\"  \" + \"-\"*40)\n\n        # ---- 试推演输出（仅观察） ----\n        if trial_signals:\n            print(f\"  📝 试推演（仅供观察，不投注）：\")\n            for t in trial_signals:\n                print(f\"    {t['play']}: {t['option']} (偏差{t['bias']:+.0%})\")    \n\n        if result.get('ai_analysis'):\n            print(f\"  💬 {result['ai_analysis'].get('说明', '')[:60]}\")\n        print()\n        time.sleep(0.1)\n    \n    if new_leagues:\n        print(f\"📌 发现 {len(new_leagues)} 个新联赛，已自动生成参数\")\n    \n    # ---- 保存报告（带时间戳，不覆盖） ----\n    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')\n    \n    report = {\n        'generated_at': datetime.now().isoformat(),\n        'total_matches': len(matches),\n        'ai_analyzed': ai_usage_count,\n        'compared_matches': comparison_count,\n        'new_leagues': new_leagues,\n        'matches': match_results\n    }\n    \n    json_file = os.path.join(DAILY_DIR, f\"report_{timestamp}.json\")\n    with open(json_file, 'w', encoding='utf-8') as f:\n        json.dump(report, f, ensure_ascii=False, indent=2)\n    print(f\"✅ 完整报告已保存: {json_file}\")\n    \n    # ---- 文本摘要 ----\n    txt_file = os.path.join(SUMMARY_DIR, f\"summary_{timestamp}.txt\")\n    with open(txt_file, 'w', encoding='utf-8') as f:\n        f.write(f\"足球预测推演报告 (动态追踪版) - {datetime.now().strftime('%Y-%m-%d %H:%M')}\\n\")\n        f.write(\"=\" * 60 + \"\\n\\n\")\n        f.write(f\"总场次: {len(matches)}, AI分析: {ai_usage_count}, 赔率对比: {comparison_count}\\n\\n\")\n        for r in match_results:\n            f.write(f\"【{r['match_num']}】 {r['home_team']} vs {r['away_team']} ({r['league']})\\n\")\n            if r.get('comparison'):\n                ma = r['comparison'].get('market_analysis', {})\n                f.write(f\"  市场分析: {ma.get('recommendation', '无变化')}\\n\")\n            if r['recommendations']:\n                for s in r['recommendations']:\n                    f.write(f\"  → {s['play']}: {s['option']} (偏差{s['bias']:+.0%})\\n\")\n            if r.get('trial_recommendations'):\n                f.write(\"  📝 试推演（仅观察）：\\n\")\n                for t in r['trial_recommendations']:\n                    f.write(f\"    {t['play']}: {t['option']} (偏差{t['bias']:+.0%})\\n\")\n            else:\n                f.write(f\"  → 无推荐\\n\")\n            if r.get('ai_analysis'):\n                f.write(f\"  💬 {r['ai_analysis'].get('说明', '')}\\n\")\n            # ✅ 新增：写入详细推演分析\n            if r.get('detailed_analysis'):\n                f.write(r['detailed_analysis'] + \"\\n\")\n            f.write(\"\\n\")\n    print(f\"✅ 文本摘要已保存: {txt_file}\")\n    print(f\"📊 共 {len(matches)} 场比赛，AI分析 {ai_usage_count} 场，赔率对比 {comparison_count} 场\")\n\nif __name__ == '__main__':\n    main()"}