{"id":"3KFwz38F8y","url":"https://pastebin.ca/3KFwz38F8y","raw_url":"https://raw.anybin.ca/3KFwz38F8y","visibility":"profile","access":"public","created_at":1788100992656,"expires_at":null,"fetch_limit":null,"fetches_used":0,"reads_remaining":null,"size_bytes":25318,"syntax_hint":"python","title":"root","filename":"core.py","change_note":null,"cipher":null,"cipher_meta":null,"parent_id":null,"root_id":"3KFwz38F8y","version":1,"owner_id":"06G565D7Z0FC42YKBY0XB93MTQ","recipient_id":null,"body":"# core.py\nimport numpy as np\nfrom scipy.stats import poisson\nfrom league_modules import module_registry, MODULE_EXECUTION_ORDER\nfrom rule_engine import RuleEngine, Context\n\nclass FootballPredictor:\n    def __init__(self, **kwargs):\n        self.params = kwargs\n        defaults = {\n            'home_control_rate': 0.50,\n            'away_press_rate': 0.25,\n            'home_defense_loss_rate': 0.15,\n            'away_counter_attacks': 2.0,\n            'home_stadium_condition': 'normal',\n            'is_old_team_return': False,\n            'is_top_club': False,\n            'var_controversy': False,\n            'big_ball_rate_home': 0.50,\n            'big_ball_rate_away': 0.50,\n            'home_team': 'Home',\n            'away_team': 'Away',\n            'home_shots_avg': 0,\n            'away_shots_avg': 0,\n            'home_possession': 0.50,\n            'away_possession': 0.50,\n            'odds_home_handicap': 0,\n            'odds_draw_handicap': 0,\n            'odds_away_handicap': 0,\n        }\n        for key, val in defaults.items():\n            if key not in self.params:\n                self.params[key] = val\n\n        self.league_config = kwargs.get('league_config', {})\n        self.modules_cfg = self.league_config.get('modules', {})\n        self.top_clubs = self.league_config.get('top_clubs', [])\n\n        self.odds_h = self.params['odds_home']\n        self.odds_d = self.params['odds_draw']\n        self.odds_a = self.params['odds_away']\n        self.round = self.params['season_round']\n\n        self.context = Context()\n        self._init_context_from_config()\n        self.result = {}\n\n    def _init_context_from_config(self):\n        cfg = self.league_config.get('modules', {})\n        self.context.set('rdi_threshold', cfg.get('rdi', {}).get('threshold', 0.30))\n        self.context.set('uat_threshold', cfg.get('uat', {}).get('threshold', 0.75))\n        self.context.set('obd_threshold', cfg.get('odds_deviation', {}).get('threshold', 0.15))\n        self.context.set('ds_threshold', cfg.get('dynamic_pressure', {}).get('threshold', 420))\n        self.context.set('acf_threshold', cfg.get('adversity', {}).get('threshold', 1.00))\n        self.context.set('q_attack_boost', 1.0)\n        self.context.set('fpa_boost', 1.0)\n        self.context.set('kpa_boost', 1.0)\n        self.context.set('tcm_boost', 1.0)\n        self.context.set('dta_boost', 1.0)\n        self.context.set('pwl_boost', 1.0)\n        self.context.set('splitter_boost', 1.0)\n        self.context.set('wfl_triggered', False)\n        self.context.set('orc_triggered', False)\n        self.context.set('btp_triggered', False)\n        self.context.set('hfa_triggered', False)\n        self.context.set('obd_triggered', False)\n        self.context.set('drc_triggered', False)\n        self.context.set('rdi_triggered', False)\n        self.context.set('uat_triggered', False)\n        self.context.set('score_corrections', {})\n\n    def _calc_match_probabilities(self, xg_h, xg_a, max_goals=6):\n        ph = [poisson.pmf(k, xg_h) for k in range(max_goals+1)]\n        pa = [poisson.pmf(k, xg_a) for k in range(max_goals+1)]\n        score_probs = {}\n        win_h = draw = win_a = 0.0\n        for i in range(max_goals+1):\n            for j in range(max_goals+1):\n                p = ph[i] * pa[j]\n                score_probs[(i,j)] = p\n                if i > j:\n                    win_h += p\n                elif i == j:\n                    draw += p\n                else:\n                    win_a += p\n        total = win_h + draw + win_a\n        if total > 0:\n            win_h /= total\n            draw /= total\n            win_a /= total\n        best_score = max(score_probs, key=score_probs.get)\n        best_prob = score_probs[best_score]\n        sorted_scores = sorted(score_probs.items(), key=lambda x: -x[1])\n        return {\n            'win_h': win_h, 'draw': draw, 'win_a': win_a,\n            'best_score': best_score,\n            'best_prob': best_prob,\n            'score_probs': score_probs,\n            'sorted_scores': sorted_scores\n        }\n\n    def _generate_score_matrix(self, sorted_scores):\n        core = sorted_scores[0][0] if sorted_scores else (0,0)\n        defend = []\n        for (i,j), p in sorted_scores:\n            if i+j <= 2:\n                defend.append((i,j))\n                if len(defend) >= 3:\n                    break\n        if not defend:\n            defend = [(0,0), (1,0), (1,1)]\n        \n        open_attack_cfg = self.league_config.get('open_attack', {})\n        threshold = open_attack_cfg.get('threshold', 0.50)\n        big_ball_h = self.params.get('big_ball_rate_home', 0.50)\n        big_ball_a = self.params.get('big_ball_rate_away', 0.50)\n        \n        open_attack = (big_ball_h > threshold and big_ball_a > threshold)\n        \n        total_xg = self.result.get('xg_h_after_A', 0) + self.result.get('xg_a_after_A', 0)\n        if not open_attack and total_xg > 2.8:\n            open_attack = True\n            self.result['_debug']['open_attack_forced'] = True\n        \n        attack = []\n        if open_attack:\n            for (i,j), p in sorted_scores:\n                if i+j >= 3:\n                    attack.append((i,j))\n                    if len(attack) >= 3:\n                        break\n        \n        return {'core': core, 'defend': defend, 'attack': attack, 'open_attack': open_attack}\n\n    def _generate_strategy(self):\n        win_h = self.result['prob_win_h']\n        draw = self.result['prob_draw']\n        win_a = self.result['prob_win_a']\n        strategy = {}\n        if win_h > 0.5:\n            strategy['Safe'] = f\"Home Win ({self.odds_h:.2f})\"\n        elif draw > 0.4:\n            strategy['Safe'] = f\"Draw ({self.odds_d:.2f})\"\n        elif win_a > 0.5:\n            strategy['Safe'] = f\"Away Win ({self.odds_a:.2f})\"\n        else:\n            strategy['Safe'] = \"Double chance: Draw/Away\"\n\n        if self.odds_h < 1.30:\n            strategy['Balanced'] = \"Asian Handicap -1 / -1.5\"\n        elif 1.90 <= self.odds_h <= 2.05 and self.result.get('ewc_trigger', False):\n            strategy['Balanced'] = \"Home Win + Under 2.5\"\n        elif 2.00 <= self.odds_h <= 2.30:\n            strategy['Balanced'] = \"Home Win or Draw\"\n        else:\n            strategy['Balanced'] = \"Home Win / Draw\"\n\n        total_xg = self.result['xg_h_after_A'] + self.result['xg_a_after_A']\n        if total_xg < 2.0:\n            strategy['Goals'] = \"1, 2\"\n        elif total_xg < 3.0:\n            strategy['Goals'] = \"2, 3\"\n        else:\n            strategy['Goals'] = \"3, 4\"\n\n        half1 = self._calc_match_probabilities(self.result['xg_h_half1'], self.result['xg_a_half1'])\n        half2 = self._calc_match_probabilities(self.result['xg_h_half2'], self.result['xg_a_half2'])\n        h1_state = 'W' if half1['win_h'] > 0.4 else ('D' if half1['draw'] > 0.4 else 'L')\n        h2_state = 'W' if half2['win_h'] > 0.4 else ('D' if half2['draw'] > 0.4 else 'L')\n        strategy['HT/FT'] = f\"{h1_state}/{h2_state}\"\n        self.result['strategy'] = strategy\n\n    def _generate_tags(self):\n        tags = []\n        if self.result.get('ewc_trigger', False): tags.append('Economic Win')\n        if self.result.get('dsr_iron_draw', False): tags.append('Draw Alert')\n        if self.odds_h < 1.30: tags.append('Deep Odds')\n        if self.params['home_stadium_condition'] != 'normal': tags.append('Home Discount')\n        if self.result.get('vtd_trap', False): tags.append('Trap Warning')\n        if self.result.get('bpr_triggered', False): tags.append('Counter Risk')\n        if self.result.get('open_attack', False): tags.append('Attack Mode')\n        if self.context.get('hfa_triggered'): tags.append('HFA Boost')\n        if self.context.get('btp_triggered'): tags.append('Clutch Alert')\n        if self.context.get('obd_triggered'): tags.append('Odds Deviation')\n        if self.context.get('orc_triggered'): tags.append('ORC Alert')\n        if self.context.get('drc_triggered'): tags.append('DRC Alert')\n        if self.context.get('wfl_triggered'): tags.append('WFL Signal')\n        if not tags: tags.append('Regular')\n        self.result['tags'] = tags\n\n    def _apply_injury_adjustment(self, xg_h, xg_a):\n        home_injuries = self.params.get('home_injuries', 0)\n        away_injuries = self.params.get('away_injuries', 0)\n        home_key_missing = self.params.get('home_key_missing', 0)\n        away_key_missing = self.params.get('away_key_missing', 0)\n\n        injury_cfg = self.league_config.get('injury', {})\n        home_penalty = (home_injuries * injury_cfg.get('home_injury_penalty', 0.04) +\n                        home_key_missing * injury_cfg.get('home_key_penalty', 0.08))\n        away_penalty = (away_injuries * injury_cfg.get('away_injury_penalty', 0.04) +\n                        away_key_missing * injury_cfg.get('away_key_penalty', 0.08))\n\n        min_xg = injury_cfg.get('min_xg_after_injury', 0.25)\n        xg_h = max(min_xg, xg_h - home_penalty)\n        xg_a = max(min_xg, xg_a - away_penalty)\n\n        if home_penalty > 0 or away_penalty > 0:\n            self.result['_debug']['injury_penalty_h'] = round(home_penalty, 3)\n            self.result['_debug']['injury_penalty_a'] = round(away_penalty, 3)\n            self.result['_debug']['xg_h_before_injury'] = round(xg_h + home_penalty, 3)\n            self.result['_debug']['xg_a_before_injury'] = round(xg_a + away_penalty, 3)\n\n        return xg_h, xg_a\n\n    def _apply_handicap_correction(self, score_probs, sorted_scores):\n        handicap_line = self.params.get('handicap_line', 0)\n        odds_home_h = self.params.get('odds_home_handicap', 1.90)\n        odds_away_h = self.params.get('odds_away_handicap', 1.90)\n\n        if handicap_line == 0 or odds_home_h == 0 or odds_away_h == 0:\n            return score_probs, sorted_scores\n\n        prob_home_cover = 1 / odds_home_h if odds_home_h > 0 else 0\n        prob_away_cover = 1 / odds_away_h if odds_away_h > 0 else 0\n        total = prob_home_cover + prob_away_cover\n        if total > 0:\n            prob_home_cover /= total\n            prob_away_cover /= total\n\n        if handicap_line < 0:\n            target_goals = abs(handicap_line)\n            if prob_home_cover > 0.55:\n                for (i, j), p in sorted_scores[:15]:\n                    if i - j >= int(target_goals) + 1:\n                        score_probs[(i, j)] *= 1.15\n                self.result['handicap_signal'] = 'Home Cover (Big Win Expected)'\n            else:\n                for (i, j), p in sorted_scores[:15]:\n                    if 1 <= i - j <= 2:\n                        score_probs[(i, j)] *= 1.10\n                self.result['handicap_signal'] = 'Home Win but Lose Cover (Small Win)'\n        elif handicap_line > 0:\n            if prob_away_cover > 0.55:\n                for (i, j), p in sorted_scores[:15]:\n                    if j - i >= int(handicap_line) + 1:\n                        score_probs[(i, j)] *= 1.15\n                self.result['handicap_signal'] = 'Away Cover (Big Win Expected)'\n            else:\n                for (i, j), p in sorted_scores[:15]:\n                    if 1 <= j - i <= 2:\n                        score_probs[(i, j)] *= 1.10\n                self.result['handicap_signal'] = 'Away Win but Lose Cover (Small Win)'\n\n        new_sorted = sorted(score_probs.items(), key=lambda x: -x[1])\n        return score_probs, new_sorted\n\n    # ==================== AB 线通用评分（修复版） ====================\n    def _apply_ab_scoring(self, xg_h, xg_a):\n        \"\"\"\n        通用 AB 线评分调度器（从配置读取权重，不写死任何联赛逻辑）\n        基于 A 线（基本面）和 B 线（盘口）的交叉验证修正 xG\n        \"\"\"\n        ab_cfg = self.league_config.get('ab_scoring', {})\n        if not ab_cfg.get('active', False):\n            return xg_h, xg_a\n\n        # 1. 计算 A 线（基本面）评分\n        a_cfg = ab_cfg.get('a_line', {})\n        home_power = self.params.get('home_power', 50)\n        away_power = self.params.get('away_power', 50)\n        \n        # 基础战力值归一化 (50->0.5, 100->1.0)\n        a_score_h = 0.5 + (home_power - 50) / 100\n        a_score_a = 0.5 + (away_power - 50) / 100\n        \n        # 伤停修正（从配置读取惩罚系数）\n        injuries_penalty = a_cfg.get('injuries_penalty', 0.05)\n        home_inj = self.params.get('home_injuries', 0) * injuries_penalty\n        away_inj = self.params.get('away_injuries', 0) * injuries_penalty\n        a_score_h = max(0.1, a_score_h - home_inj)\n        a_score_a = max(0.1, a_score_a - away_inj)\n        \n        # 战意修正（从配置读取）\n        mot_boost = a_cfg.get('motivation_boost', 1.1)\n        if self.params.get('motivation_home', 0.5) > 0.6:\n            a_score_h = a_score_h * mot_boost\n        if self.params.get('motivation_away', 0.5) > 0.6:\n            a_score_a = a_score_a * mot_boost\n\n        # 2. 计算 B 线（盘口）评分\n        b_cfg = ab_cfg.get('b_line', {})\n        # 理论胜率（基于 ELO 或 power）\n        elo_h = self.params.get('elo_home', 1500)\n        elo_a = self.params.get('elo_away', 1500)\n        if elo_h == 1500 and elo_a == 1500:\n            # 如果没有 ELO，用 power 映射（50→1300, 100→1700）\n            elo_h = 1300 + (home_power - 50) * 8\n            elo_a = 1300 + (away_power - 50) * 8\n        theo_prob_h = 1 / (1 + 10 ** ((elo_a - elo_h) / 400))\n        theo_prob_a = 1 - theo_prob_h\n        \n        # 市场隐含概率（从赔率提取）\n        implied_h = 1 / self.odds_h if self.odds_h > 0 else 0.5\n        implied_a = 1 / self.odds_a if self.odds_a > 0 else 0.5\n        \n        # B 线评分 = 1 - 偏差（偏差越小，评分越高）\n        b_score_h = 1 - min(1, abs(implied_h - theo_prob_h) * 2)\n        b_score_a = 1 - min(1, abs(implied_a - theo_prob_a) * 2)\n\n        # 3. 交叉验证与置信度计算\n        val_cfg = ab_cfg.get('validation', {})\n        resonance_threshold = val_cfg.get('resonance_threshold', 0.1)\n        divergence_threshold = val_cfg.get('divergence_threshold', 0.3)\n        resonance_boost = val_cfg.get('resonance_boost', 1.2)\n        divergence_penalty = val_cfg.get('divergence_penalty', 0.7)\n\n        # 计算 A/B 线对主胜和客胜评分的平均一致性\n        diff_h = abs(a_score_h - b_score_h)\n        diff_a = abs(a_score_a - b_score_a)\n        avg_diff = (diff_h + diff_a) / 2\n\n        if avg_diff <= resonance_threshold:\n            confidence_boost = resonance_boost\n            self.result['ab_validation'] = 'resonance'\n        elif avg_diff >= divergence_threshold:\n            confidence_boost = divergence_penalty\n            self.result['ab_validation'] = 'divergence'\n        else:\n            confidence_boost = 1.0\n            self.result['ab_validation'] = 'neutral'\n\n        # 4. 将修正系数应用到 xG（修复版）\n        if confidence_boost != 1.0:\n            if confidence_boost > 1.0:\n                # 共振：双方信心都提升\n                if implied_h > theo_prob_h:\n                    # 市场更看好主队 → 主队xG提升，客队适度提升\n                    xg_h = xg_h * confidence_boost\n                    xg_a = xg_a * (1 + (confidence_boost - 1) * 0.5)\n                else:\n                    # 市场更看好客队 → 客队xG提升，主队适度提升\n                    xg_a = xg_a * confidence_boost\n                    xg_h = xg_h * (1 + (confidence_boost - 1) * 0.5)\n            else:\n                # 背离：双方信心都降低（用同一个惩罚系数，而不是一方被放大）\n                # 原来的 xg / confidence_boost（即 xg / 0.7）会放大 xG，这是 bug\n                # 修正为双方都乘以 confidence_boost（即都降低），只是降低幅度不同\n                if implied_h > theo_prob_h:\n                    # 市场更看好主队，但模型与市场背离 → 主队xG降低，客队降得更多\n                    xg_h = xg_h * confidence_boost\n                    # 客队 × (confidence_boost ^ 1.5)，比主队降得更多\n                    xg_a = xg_a * (confidence_boost * confidence_boost)\n                else:\n                    # 市场更看好客队，但模型与市场背离 → 客队xG降低，主队降得更多\n                    xg_a = xg_a * confidence_boost\n                    # 主队 × (confidence_boost ^ 1.5)，比客队降得更多\n                    xg_h = xg_h * (confidence_boost * confidence_boost)\n\n            self.result['ab_confidence_boost'] = confidence_boost\n            self.result['ab_scoring_active'] = True\n\n        return xg_h, xg_a\n\n    # ==================== 核心 predict 方法 ====================\n    def predict(self):\n        self.result['_debug'] = {}\n        xg_h = self.params['base_xg_home']\n        xg_a = self.params['base_xg_away']\n        self.result['_debug']['xg_h_initial'] = round(xg_h, 3)\n        self.result['_debug']['xg_a_initial'] = round(xg_a, 3)\n\n        # ----- 从配置读取 xG 调整 -----\n        xg_adj = self.league_config.get('xg_adjustments', {})\n        boost = xg_adj.get('default_boost', 1.10)\n        boost_cfg = xg_adj.get('boost_conditions', {})\n        odds_h = self.odds_h\n        odds_a = self.odds_a\n        league = self.params.get('league', '')\n\n        if odds_h < 1.40:\n            boost += boost_cfg.get('deep_odds', 0.12)\n        if odds_a > 4.00:\n            boost += boost_cfg.get('big_away_odds', 0.10)\n        if self.params.get('big_ball_rate_home', 0.5) > 0.55 and self.params.get('big_ball_rate_away', 0.5) > 0.55:\n            boost += boost_cfg.get('both_attacking', 0.12)\n        if self.params.get('relegation_zone', False):\n            boost += boost_cfg.get('relegation_zone', 0.10)\n        if self.params.get('first_leg_lead', 0) >= 2:\n            boost += boost_cfg.get('second_leg_conservative', -0.20)\n        if self.params.get('forward_missing', 0) >= 2:\n            boost += boost_cfg.get('forward_missing_penalty', -0.08)\n\n        xg_h = xg_h * xg_adj.get('home_boost_factor', 1.0)\n        xg_a = xg_a * xg_adj.get('away_penalty_factor', 1.0)\n        if self.params.get('relegation_zone', False) and xg_adj.get('relegation_home_boost', 0):\n            xg_h = xg_h + xg_adj.get('relegation_home_boost', 0)\n            xg_h = max(xg_h, xg_adj.get('relegation_min_xg', 1.0))\n\n        boost = max(xg_adj.get('boost_min', 0.85), min(xg_adj.get('boost_max', 1.60), boost))\n        xg_h = xg_h * boost\n        xg_a = xg_a * boost\n        self.result['_debug']['offensive_boost'] = round(boost, 3)\n        self.result['_debug']['boost_reasons'] = []\n        if odds_h < 1.40: self.result['_debug']['boost_reasons'].append('deep_odds')\n        if odds_a > 4.00: self.result['_debug']['boost_reasons'].append('big_away_odds')\n        if self.params.get('big_ball_rate_home', 0.5) > 0.55 and self.params.get('big_ball_rate_away', 0.5) > 0.55:\n            self.result['_debug']['boost_reasons'].append('both_attacking')\n        if self.params.get('first_leg_lead', 0) >= 2:\n            self.result['_debug']['boost_reasons'].append('second_leg_conservative')\n\n        # 西甲深盘额外加成\n        if league == 'LaLiga' and self.params.get('is_top_club', False) and odds_h < 1.40:\n            la_liga_extra = xg_adj.get('la_liga_deep_odds_extra', 0.08)\n            boost += la_liga_extra\n            self.result['_debug']['la_liga_extra_boost'] = la_liga_extra\n\n        xg_max = xg_adj.get('xg_max', 5.0)\n        xg_h = min(xg_h, xg_max)\n        xg_a = min(xg_a, xg_max)\n\n        # 西甲豪门主场保底\n        la_liga_floor = xg_adj.get('la_liga_home_floor', 2.2)\n        if league == 'LaLiga' and self.params.get('is_top_club', False) and odds_h < 1.40:\n            xg_h = max(xg_h, la_liga_floor)\n            self.result['_debug']['la_liga_home_floor_applied'] = True\n            self.result['_debug']['la_liga_home_floor_value'] = la_liga_floor\n\n        # 伤病调整\n        xg_h, xg_a = self._apply_injury_adjustment(xg_h, xg_a)\n        \n        # ===== 🆕 AB 线评分（通用交叉验证） =====\n        xg_h, xg_a = self._apply_ab_scoring(xg_h, xg_a)\n        \n        self.result['_debug']['xg_h_after_injury'] = round(xg_h, 3)\n        self.result['_debug']['xg_a_after_injury'] = round(xg_a, 3)\n\n        # 通用主场保底（巴西杯等）\n        if self.league_config.get('home_floor', {}).get('active', False):\n            floor_cfg = self.league_config.get('home_floor', {})\n            if self.params.get('is_top_club', False) and self.params.get('home_win_rate', 0) > floor_cfg.get('win_rate_threshold', 0.70):\n                xg_h = max(xg_h, floor_cfg.get('min_xg', 1.5))\n                self.result['_debug']['home_floor_applied'] = True\n\n        # ===== 模块执行（含沙特新模块） =====\n        pre_modules = ['wfl', 'rdi', 'uat']\n        for module_name in pre_modules:\n            if module_name in module_registry:\n                module_func = module_registry[module_name]\n                xg_h, xg_a = module_func(self, xg_h, xg_a)\n                self.context.set('current_xg_h', xg_h)\n                self.context.set('current_xg_a', xg_a)\n                RuleEngine.apply_rules(self.context)\n\n        for module_name in MODULE_EXECUTION_ORDER:\n            if module_name in module_registry and module_name not in pre_modules:\n                module_cfg = self.modules_cfg.get(module_name, {})\n                if module_cfg.get('active', True):\n                    module_func = module_registry[module_name]\n                    #print(f\"▶️ 执行模块: {module_name}\")\n                    xg_h, xg_a = module_func(self, xg_h, xg_a)\n                    self.context.set('current_xg_h', xg_h)\n                    self.context.set('current_xg_a', xg_a)\n                    RuleEngine.apply_rules(self.context)\n        # ===== 🆕 全局 xG 上限保护（防止极端强弱对话爆炸） =====\n        xg_global_cap = xg_adj.get('xg_global_cap', 5.0)\n        if xg_h > xg_global_cap:\n            self.result['_debug']['xg_h_capped'] = round(xg_h, 3)\n            xg_h = xg_global_cap\n        if xg_a > xg_global_cap:\n            self.result['_debug']['xg_a_capped'] = round(xg_a, 3)\n            xg_a = xg_global_cap\n\n\n        self.result['xg_h_after_A'] = xg_h\n        self.result['xg_a_after_A'] = xg_a\n        self.result['_debug']['xg_h_final'] = round(xg_h, 3)\n        self.result['_debug']['xg_a_final'] = round(xg_a, 3)\n\n        # 半全场划分\n        tds_config = self.modules_cfg.get('tds', {})\n        if tds_config.get('active', False):\n            early = tds_config.get('early', [0.40, 0.60])\n            mid = tds_config.get('mid', [0.45, 0.55])\n            deep = tds_config.get('deep', [0.35, 0.65])\n            if self.round <= 3:\n                half_h, half_a = early[0], early[1]\n            elif self.odds_h < 1.30:\n                half_h, half_a = deep[0], deep[1]\n            else:\n                half_h, half_a = mid[0], mid[1]\n        else:\n            half_h, half_a = 0.45, 0.55\n\n        h_h1, h_h2 = xg_h * half_h, xg_h * half_a\n        a_h1, a_h2 = xg_a * half_h, xg_a * half_a\n        if self.params.get('var_controversy', False):\n            a_h2 *= 1.05\n\n        self.result['xg_h_half1'] = h_h1\n        self.result['xg_h_half2'] = h_h2\n        self.result['xg_a_half1'] = a_h1\n        self.result['xg_a_half2'] = a_h2\n\n        # 概率计算\n        probs = self._calc_match_probabilities(xg_h, xg_a)\n        self.result['core_prob'] = probs['best_prob']\n\n        score_corrections = self.context.get('score_corrections', {})\n        for score_key, weight in score_corrections.items():\n            if score_key in probs['score_probs']:\n                probs['score_probs'][score_key] *= weight\n        probs['sorted_scores'] = sorted(probs['score_probs'].items(), key=lambda x: -x[1])\n\n        win_h, draw, win_a = probs['win_h'], probs['draw'], probs['win_a']\n        if self.result.get('dsr_iron_draw', False):\n            draw *= 1.20\n        if self.context.get('q_attack_boost', 1.0) != 1.0:\n            win_h *= self.context.get('q_attack_boost', 1.0)\n        if self.context.get('fpa_boost', 1.0) != 1.0:\n            win_h *= self.context.get('fpa_boost', 1.0)\n\n        total = win_h + draw + win_a\n        if total > 0:\n            win_h /= total\n            draw /= total\n            win_a /= total\n\n        draw_cap = self.modules_cfg.get('dsr', {}).get('draw_cap', 0.35)\n        if draw > draw_cap:\n            draw = draw_cap\n            total = win_h + win_a\n            if total > 0:\n                win_h = win_h / total * (1 - draw_cap)\n                win_a = win_a / total * (1 - draw_cap)\n\n        self.result['prob_win_h'] = win_h\n        self.result['prob_draw'] = draw\n        self.result['prob_win_a'] = win_a\n\n        if self.params.get('handicap_line', 0) != 0:\n            probs['score_probs'], probs['sorted_scores'] = self._apply_handicap_correction(\n                probs['score_probs'], probs['sorted_scores']\n            )\n\n        matrix = self._generate_score_matrix(probs['sorted_scores'])\n        self.result['score_core'] = matrix['core']\n        self.result['score_defend'] = matrix['defend']\n        self.result['score_attack'] = matrix['attack']\n        self.result['open_attack'] = matrix['open_attack']\n\n        self._generate_strategy()\n        self._generate_tags()\n        return self.result"}