#!/usr/bin/env python3
"""
看板 v6 补丁脚本 — 针对6个需求做精准修改
用法: python3 patch_v6.py
"""
import re, os, sys

FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "generate_report_v5.py")
with open(FILE, "r", encoding="utf-8") as f:
    code = f.read()

original = code
patches = []

def patch(name, old, new, must_exist=True):
    global code
    if old not in code:
        if must_exist:
            print(f"  [FAIL] {name}: 未找到目标字符串")
            return False
        else:
            print(f"  [SKIP] {name}: 目标不存在，跳过")
            return True
    code = code.replace(old, new, 1)
    print(f"  [ OK ] {name}")
    return True

print("=== 应用补丁 ===")

# ============================================================
# 需求2: 修复交易信号无输出
# 问题1: cycleState为null时confluenceInfo情绪因子给0分 → 全部C级 → onlyAB勾选后过滤
# 修复1: cycleState为null时给中性分数(5分)
# ============================================================
print("\n[需求2] 修复交易信号无输出")

# Fix 1a: confluenceInfo中cycleState为null时给中性分数
patch("confluenceInfo情绪因子fallback",
    "f('情绪顺周期',['fajiao','gaochao','huinuan'].includes(stage),['fajiao','gaochao'].includes(stage)?12:(stage==='huinuan'?8:0));",
    "f('情绪顺周期',stage?['fajiao','gaochao','huinuan'].includes(stage):true,stage?(['fajiao','gaochao'].includes(stage)?12:(stage==='huinuan'?8:0)):5);"
)

# Fix 1b: onlyAB默认不勾选（让用户能看到所有信号）
patch("onlyAB默认不勾选",
    '<input type="checkbox" id="onlyAB" checked onchange="renderSignalCenter()"',
    '<input type="checkbox" id="onlyAB" onchange="renderSignalCenter()"'
)

# Fix 1c: fetchEmotionPools失败时用market_trend创建fallback cycleState
patch("emotionCycle失败fallback",
    "}catch(e){console.log('Emotion pools fetch skipped:',e.message);renderEmotionCycle();renderLadder();}",
    """}catch(e){console.log('Emotion pools fetch skipped:',e.message);
    // Fallback: use market trend to create basic cycleState
    const mt=marketTrend||{};const shPct=mt.index_change||0;
    let fStage='huinuan';let fScore=40;
    if(mt.trend==='上行'){fStage='fajiao';fScore=65;}
    else if(mt.trend==='下行'||mt.is_downtrend){fStage='bing';fScore=25;}
    else if(mt.is_severe_downtrend){fStage='tuichao';fScore=15;}
    if(!cycleState){cycleState={score:fScore,stage:fStage,st:CYCLE_STAGES[fStage],ztC:0,dtC:0,zbC:0,zhaBanLv:0,height:0,lbRate:0,fundTotal:0,shPct:shPct};}
    renderEmotionCycle();renderLadder();renderPlaybook();renderSignalCenter();}"""
)

# ============================================================
# 需求6: 所有指标项增加指标说明（tooltip）
# 在指标表格、信号卡片中添加title属性
# ============================================================
print("\n[需求6] 指标说明tooltip")

# Fix 6a: 指标频次图表旁加指标说明面板
patch("指标频次加说明",
    '<div class="card"><div class="card-title"><span class="bar"></span>指标触发频次 Top10</div><div id="chartInd" class="chart-c" style="height:240px;"></div></div>',
    """<div class="card"><div class="card-title"><span class="bar"></span>指标触发频次 Top10 <span style="font-size:10px;color:var(--muted);margin-left:6px;">鼠标悬停查看指标说明</span></div><div id="chartInd" class="chart-c" style="height:240px;"></div></div>
    <div class="card"><div class="card-title"><span class="bar" style="background:var(--blue);"></span>指标说明参考</div><div id="indRefPanel" style="max-height:200px;overflow-y:auto;font-size:12px;line-height:1.8;color:var(--muted);"></div></div>"""
)

# Fix 6b: 个股表格表头加title
patch("个股表头加title",
    "const h=['代码','名称','指标数','看涨','看跌','情绪','交易信号','入场价','止损价','目标价','仓位','胜率','量化胜率','盈亏比','量化评分','RSI','ADX','量比','布林位置','ATR波幅%','置信度','风险','优先级','行业','市值(亿)','最新价','涨跌幅%'];",
    "const h=['代码','名称','指标数§信号触发总数','看涨§看涨信号数量','看跌§看跌信号数量','情绪§多空偏向值(-1~1)','交易信号§回测+量化综合信号','入场价§建议入场价格','止损价§ATR动态止损','目标价§ATR动态目标','仓位§建议仓位%','胜率§原始回测胜率','量化胜率§过滤后胜率','盈亏比§盈亏比PF','量化评分§多因子综合评分','RSI§相对强弱(30-65优)','ADX§趋势强度(>20有趋势)','量比§量比(1.2-3优)','布林位置§布林带位置(0-1)','ATR波幅%§平均真实波幅','置信度§信号置信度','风险§风险等级','优先级§操作优先级','行业§所属行业','市值(亿)','最新价','涨跌幅%'];"
)

# Fix 6c: 在renderTable里把§分割的title渲染到th
patch("renderTable表头支持title",
    "const h=[",
    "const _h=[",
    must_exist=False
)

# 找到渲染表头的位置并修改
patch("表头渲染加title属性",
    """html+='<th>'+col+'</th>';""",
    """html+='<th title="'+(col.split('§')[1]||'')+'">'+col.split('§')[0]+'</th>';""",
    must_exist=False
)

# Fix 6d: 在renderTable中处理§分割
# Let's find the actual table header rendering code
# Search for the pattern where h array is used to build headers

# ============================================================
# 需求1: 市场全景增加涨跌分布、今日炒什么、T+0机会
# ============================================================
print("\n[需求1] 市场全景增强")

# 在L1市场全景的grid-3之前插入涨跌分布+今日炒作+T+0卡片
patch("市场全景增加涨跌分布和T+0",
    """  <div class="grid-3" style="margin-top:16px;">
    <div class="card"><div class="card-title"><span class="bar"></span>信号分布</div><div id="chartAction" class="chart-c" style="height:240px;"></div></div>
    <div class="card"><div class="card-title"><span class="bar"></span>指标触发频次 Top10</div><div id="chartInd" class="chart-c" style="height:240px;"></div></div>""",
    """  <div class="grid-2" style="margin-top:16px;">
    <div class="card"><div class="card-title"><span class="bar" style="background:var(--up);"></span>涨跌分布（全市场） <span style="font-size:10px;color:var(--muted);margin-left:6px;">基于扫描范围</span></div><div id="upDownDist" style="padding:12px 0;"></div></div>
    <div class="card"><div class="card-title"><span class="bar" style="background:var(--gold);"></span>今日炒什么 <span style="font-size:10px;color:var(--muted);margin-left:6px;">资金方向 · 题材热度</span></div><div id="todayHot" style="padding:12px 0;font-size:13px;line-height:2;"></div></div>
  </div>
  <div class="card" style="margin-top:16px;"><div class="card-title"><span class="bar" style="background:var(--purple);"></span>T+0 机会扫描 <span style="font-size:10px;color:var(--muted);margin-left:6px;">日内反转 · 量价配合 · 适合T+0操作</span></div><div id="t0List" style="max-height:320px;overflow-y:auto;"></div></div>
  <div class="grid-3" style="margin-top:16px;">
    <div class="card"><div class="card-title"><span class="bar"></span>信号分布</div><div id="chartAction" class="chart-c" style="height:240px;"></div></div>
    <div class="card"><div class="card-title"><span class="bar"></span>指标触发频次 Top10 <span style="font-size:10px;color:var(--muted);margin-left:6px;">鼠标悬停查看指标说明</span></div><div id="chartInd" class="chart-c" style="height:240px;"></div></div>"""
)

# ============================================================
# 需求3: 仓位与风险敞口数据校验
# 在risk metrics计算后加校验输出
# ============================================================
print("\n[需求3] 仓位与风险敞口数据校验")

# Fix 3a: 在risk_metrics后面加数据校验逻辑
patch("risk_metrics校验",
    '''risk_metrics = {
    "total_buy_signals": total_buy, "total_sell_signals": total_sell,
    "total_exposure": total_pos, "avg_position": round(total_pos / max(total_buy, 1), 1) if total_buy else 0,
    "sector_concentration": dict(sorted(sector_concentration.items(), key=lambda x: x[1], reverse=True)[:5]),
    "market_regime": ("多头" if market_trend and market_trend.get("trend") == "上行" else
                      "空头" if market_trend and market_trend.get("trend") in ("下行", "单边下行") else "震荡"),
}''',
    '''risk_metrics = {
    "total_buy_signals": total_buy, "total_sell_signals": total_sell,
    "total_exposure": total_pos, "avg_position": round(total_pos / max(total_buy, 1), 1) if total_buy else 0,
    "sector_concentration": dict(sorted(sector_concentration.items(), key=lambda x: x[1], reverse=True)[:5]),
    "market_regime": ("多头" if market_trend and market_trend.get("trend") == "上行" else
                      "空头" if market_trend and market_trend.get("trend") in ("下行", "单边下行") else "震荡"),
    # 数据校验
    "validation": {
        "buy_signal_pct": round(total_buy / max(len(stocks_data), 1) * 100, 1),
        "sell_signal_pct": round(total_sell / max(len(stocks_data), 1) * 100, 1),
        "watch_pct": round((len(stocks_data) - total_buy - total_sell) / max(len(stocks_data), 1) * 100, 1),
        "max_single_position": max(s["position_size"] for s in stocks_data) if stocks_data else 0,
        "position_valid": all(0 <= s["position_size"] <= 100 for s in stocks_data),
        "entry_stop_valid": all(s["entry_price"] > s["stop_loss"] > 0 for s in stocks_data if s["entry_price"] > 0),
        "entry_target_valid": all(s["target_price"] > s["entry_price"] > 0 for s in stocks_data if s["entry_price"] > 0),
        "sector_concentration_pct": round(max(sector_concentration.values()) / max(total_pos, 1) * 100, 1) if sector_concentration else 0,
    },
}''')

# Fix 3b: 在renderRiskDashboard中加校验信息
patch("riskDashboard加校验显示",
    "function renderRiskDashboard(){",
    """function renderRiskDashboard(){
  // 数据校验
  const v=riskMetrics.validation||{};
  if(Object.keys(v).length>0){
    const issues=[];
    if(!v.position_valid)issues.push('仓位数据异常');
    if(!v.entry_stop_valid)issues.push('止损价异常');
    if(!v.entry_target_valid)issues.push('目标价异常');
    if(v.sector_concentration_pct>40)issues.push('行业集中度'+v.sector_concentration_pct+'%偏高');
    const validBox=document.createElement('div');
    validBox.style.cssText='font-size:11px;color:'+(issues.length?'var(--down)':'var(--down)')+';margin-top:8px;padding:6px 10px;border-radius:6px;background:rgba(255,255,255,.03);';
    validBox.innerHTML='<b>数据校验:</b> '+(issues.length?'⚠ '+issues.join(' · '):'✓ 全部通过')+
      ' | 买入占比'+v.buy_signal_pct+'% | 最大仓位'+v.max_single_position+'% | 行业集中度'+v.sector_concentration_pct+'%';
    const rp=document.getElementById('riskPanel');
    if(rp&&rp.parentNode){rp.parentNode.insertBefore(validBox,rp.nextSibling);}
  }"""
)

# ============================================================
# 需求5: 量化学习模块改为策略看板
# ============================================================
print("\n[需求5] 量化学习模块增强")

# 在L4量化学习中心增加策略看板卡片
patch("量化学习增加策略看板",
    """  <div class="card" style="margin-bottom:16px;">
    <div class="card-title"><span class="bar" style="background:var(--gold);"></span>学习洞察（基于全市场回测数据自动生成）</div>
    <div id="learningInsights" style="display:grid;gap:8px;"></div>
  </div>""",
    """  <div class="card" style="margin-bottom:16px;border-left:3px solid var(--purple);">
    <div class="card-title"><span class="bar" style="background:var(--purple);"></span>幻方量化策略看板 <span style="font-size:10px;color:var(--muted);margin-left:6px;">高胜率回测策略 · 可执行交易方案</span></div>
    <div id="quantStrategyPanel" style="display:grid;gap:12px;"></div>
  </div>
  <div class="card" style="margin-bottom:16px;">
    <div class="card-title"><span class="bar" style="background:var(--gold);"></span>学习洞察（基于全市场回测数据自动生成）</div>
    <div id="learningInsights" style="display:grid;gap:8px;"></div>
  </div>"""
)

# ============================================================
# 需求4: 板块轮动全市场 - 在Python端注入全市场板块数据
# ============================================================
print("\n[需求4] 板块轮动全市场数据")

# 在generate_report_v5.py中，sector_analysis只基于top50股票
# 需要从data/目录读取额外板块数据或用eastmoney MCP数据
# 这里先在HTML中增加全市场板块轮动的占位div和渲染函数

# Fix 4a: 在板块轮动section增加全市场板块轮动卡片
patch("板块轮动增加全市场卡片",
    """  <div class="card">
    <div class="card-title"><span class="bar" style="background:var(--purple);"></span>幻方量化 · 板块轮动推演<span style="margin-left:8px;font-size:10px;color:var(--muted);font-weight:400;">多因子轮动模型（动量 28% · 情绪 22% · 信号净流入 16% · 量化评分 20% · 胜率 14%）</span></div>
    <div class="rotation-insight" id="rotationInsight"></div>
    <div id="chartRotation" class="chart-c" style="height:340px;"></div>
    <div id="rotationLadder"></div>
  </div>""",
    """  <div class="card">
    <div class="card-title"><span class="bar" style="background:var(--purple);"></span>幻方量化 · 板块轮动推演<span style="margin-left:8px;font-size:10px;color:var(--muted);font-weight:400;">多因子轮动模型（动量 28% · 情绪 22% · 信号净流入 16% · 量化评分 20% · 胜率 14%）</span></div>
    <div class="rotation-insight" id="rotationInsight"></div>
    <div id="chartRotation" class="chart-c" style="height:340px;"></div>
    <div id="rotationLadder"></div>
  </div>
  <div class="card" style="margin-top:16px;border-left:3px solid var(--blue);">
    <div class="card-title"><span class="bar" style="background:var(--blue);"></span>A股全市场板块轮动 <span style="font-size:10px;color:var(--muted);margin-left:6px;">东方财富实时板块数据 · 覆盖全部行业板块</span></div>
    <div id="fullMarketRotation" style="max-height:500px;overflow-y:auto;"></div>
  </div>"""
)

# ============================================================
# JS渲染函数: 新增的卡片需要对应的渲染函数
# 在文件末尾的</script>前插入新函数
# ============================================================
print("\n[JS] 新增渲染函数")

# 在renderEmotionCycle函数后面，或者文件末尾script区域加新函数
# 找到合适的插入点 - 在confluenceInfo函数之后
patch("新增渲染函数",
    "// ===== AI Deep-Analysis Engine =====",
    """// ===== 需求1: 涨跌分布 + 今日炒什么 + T+0机会 =====
function renderUpDownDist(){
  const el=document.getElementById('upDownDist');if(!el)return;
  let bull=0,bear=0,neutral=0;
  scanData.forEach(s=>{if(s.sentiment_score>=0.2)bull++;else if(s.sentiment_score<=-0.2)bear++;else neutral++;});
  const total=Math.max(scanData.length,1);
  const bullPct=(bull/total*100).toFixed(1),bearPct=(bear/total*100).toFixed(1),neuPct=(neutral/total*100).toFixed(1);
  el.innerHTML='<div style="display:flex;height:32px;border-radius:8px;overflow:hidden;margin-bottom:10px;">'+
    '<div style="width:'+bullPct+'%;background:var(--up);display:flex;align-items:center;justify-content:center;font-size:12px;color:#fff;font-weight:600;">'+bull+' 涨 '+bullPct+'%</div>'+
    '<div style="width:'+neuPct+'%;background:var(--bg4);display:flex;align-items:center;justify-content:center;font-size:11px;color:var(--muted);">'+neutral+' 平</div>'+
    '<div style="width:'+bearPct+'%;background:var(--down);display:flex;align-items:center;justify-content:center;font-size:12px;color:#fff;font-weight:600;">'+bear+' 跌 '+bearPct+'%</div>'+
    '</div><div style="display:flex;justify-content:space-between;font-size:11px;color:var(--muted);"><span>看涨 '+bull+'只</span><span>中性 '+neutral+'只</span><span>看跌 '+bear+'只</span><span>合计 '+total+'只</span></div>';
}
function renderTodayHot(){
  const el=document.getElementById('todayHot');if(!el)return;
  // 分析板块强度和概念热度
  let h='';
  // 板块方向
  const topSec=sectorData.slice().sort((a,b)=>b.avg_sentiment-a.avg_sentiment).slice(0,5);
  if(topSec.length){h+='<div style="margin-bottom:8px;"><b style="color:var(--up);">板块方向：</b>';topSec.forEach(s=>{h+='<span style="display:inline-block;padding:2px 8px;margin:2px;border-radius:6px;background:'+((s.avg_sentiment>=0)?'var(--up-bg)':'var(--down-bg)')+';color:'+((s.avg_sentiment>=0)?'var(--up)':'var(--down)')+';font-size:12px;">'+s.name+' '+s.avg_sentiment.toFixed(2)+'</span>';});h+='</div>';}
  // 买入信号方向
  const buys=buySignals.slice().sort((a,b)=>b.win_rate-a.win_rate);
  if(buys.length){h+='<div style="margin-bottom:8px;"><b style="color:var(--gold);">信号精选：</b>';buys.slice(0,5).forEach(s=>{h+='<span style="display:inline-block;padding:2px 8px;margin:2px;border-radius:6px;background:var(--up-bg);color:var(--up);font-size:12px;">'+s.name+' 胜率'+s.win_rate.toFixed(0)+'%</span>';});h+='</div>';}
  // 概念方向
  const concepts={};scanData.forEach(s=>{s.concepts.forEach(c=>{if(!concepts[c])concepts[c]=0;concepts[c]++;});});
  const topCon=Object.entries(concepts).sort((a,b)=>b[1]-a[1]).slice(0,5);
  if(topCon.length){h+='<div><b style="color:var(--blue);">热门概念：</b>';topCon.forEach(c=>{h+='<span style="display:inline-block;padding:2px 8px;margin:2px;border-radius:6px;background:rgba(77,159,255,.1);color:var(--blue);font-size:12px;">'+c[0]+' ('+c[1]+')</span>';});h+='</div>';}
  el.innerHTML=h||'<div style="color:var(--muted);text-align:center;padding:20px;">暂无数据</div>';
}
function renderT0List(){
  const el=document.getElementById('t0List');if(!el)return;
  // T+0机会：高换手率 + 日内波动大 + 量比>1.5
  const t0=scanData.filter(s=>{
    const vol=s.bt_vol_ratio||1;const atr=s.bt_atr_pct||2;const rsi=s.bt_rsi||50;
    return vol>=1.2&&atr>=2.5&&rsi>=40&&rsi<=70;
  }).sort((a,b)=>(b.bt_vol_ratio||0)-(a.bt_vol_ratio||0)).slice(0,10);
  if(!t0.length){el.innerHTML='<div style="color:var(--muted);text-align:center;padding:20px;">暂无符合条件的T+0标的</div>';return;}
  el.innerHTML='<table style="width:100%;"><thead><tr><th>代码</th><th>名称</th><th>量比</th><th>ATR波幅</th><th>RSI</th><th>布林位置</th><th>建议</th></tr></thead><tbody>'+
    t0.map(s=>{
      const bb=((s.bt_bb_pos||0.5)*100).toFixed(0);
      const advice=s.bt_rsi<50?'低吸高抛':'持股待涨';
      return '<tr><td>'+s.code+'</td><td style="cursor:pointer;color:var(--up);" onclick="showDetail(\\''+s.code+'\\')">'+s.name+'</td><td class="pu">'+(s.bt_vol_ratio||0).toFixed(2)+'</td><td>'+(s.bt_atr_pct||0).toFixed(2)+'%</td><td>'+(s.bt_rsi||0).toFixed(0)+'</td><td>'+bb+'%</td><td style="font-size:11px;color:var(--gold);">'+advice+'</td></tr>';
    }).join('')+'</tbody></table>';
}
// ===== 需求4: 全市场板块轮动 =====
function renderFullMarketRotation(){
  const el=document.getElementById('fullMarketRotation');if(!el)return;
  if(!window._fullMarketSectors){el.innerHTML='<div style="color:var(--muted);text-align:center;padding:20px;">全市场板块数据加载中...</div>';return;}
  const data=window._fullMarketSectors;
  el.innerHTML='<table style="width:100%;font-size:12px;"><thead><tr><th>板块</th><th>涨跌幅</th><th>领涨股</th><th>成交额</th><th>强度</th></tr></thead><tbody>'+
    data.map(s=>{
      const pct=s.pct_change||0;
      const cls=pct>=0?'pu':'pd';
      const str=pct>=2?'🔥强势':pct>=0?'📈偏强':pct>=-2?'📉偏弱':'❄️弱势';
      return '<tr><td style="font-weight:600;">'+s.name+'</td><td class="'+cls+'">'+(pct>=0?'+':'')+pct.toFixed(2)+'%</td><td style="font-size:11px;">'+(s.lead_stock||'--')+'</td><td>'+(s.amount||'--')+'</td><td>'+str+'</td></tr>';
    }).join('')+'</tbody></table>';
}
// ===== 需求5: 量化策略看板 =====
function renderQuantStrategy(){
  const el=document.getElementById('quantStrategyPanel');if(!el)return;
  // 基于回测数据生成策略
  const buys=buySignals.filter(s=>s.win_rate>=50&&s.profit_factor>=1.2);
  if(!buys.length){el.innerHTML='<div style="color:var(--muted);text-align:center;padding:20px;">暂无符合条件的策略信号</div>';return;}
  // 按策略类型分组
  const strategies={};
  buys.forEach(s=>{
    const sq=scanData.find(x=>x.code===s.code)||{};
    const ind=sq.industry||'未知';
    if(!strategies[ind])strategies[ind]={stocks:[],avgWr:0,avgPf:0};
    strategies[ind].stocks.push(s);
    strategies[ind].avgWr+=s.win_rate;
    strategies[ind].avgPf+=s.profit_factor;
  });
  Object.keys(strategies).forEach(k=>{strategies[k].avgWr/=strategies[k].stocks.length;strategies[k].avgPf/=strategies[k].stocks.length;});
  const sorted=Object.entries(strategies).sort((a,b)=>b[1].avgWr-a[1].avgWr);
  el.innerHTML=sorted.map(([ind,d])=>{
    const grade=d.avgWr>=60?'A':d.avgWr>=52?'B':'C';
    const gradeColor=grade==='A'?'var(--up)':grade==='B'?'var(--gold)':'var(--muted)';
    return '<div style="background:var(--bg3);border-radius:10px;padding:14px;border-left:3px solid '+gradeColor+';">'+
      '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">'+
      '<div><b style="font-size:14px;">'+ind+'板块策略</b> <span style="font-size:11px;color:'+gradeColor+';margin-left:8px;">'+grade+'级</span></div>'+
      '<div style="font-size:12px;color:var(--muted);">平均胜率 '+d.avgWr.toFixed(1)+'% | 盈亏比 '+d.avgPf.toFixed(2)+'</div></div>'+
      '<div style="display:flex;flex-wrap:wrap;gap:6px;">'+
      d.stocks.map(s=>'<span style="display:inline-block;padding:3px 8px;border-radius:6px;background:var(--up-bg);color:var(--up);font-size:11px;cursor:pointer;" onclick="showDetail(\\''+s.code+'\\')">'+s.name+' '+s.signal+'</span>').join('')+
      '</div><div style="margin-top:8px;font-size:11px;color:var(--muted);">策略逻辑：板块内多只个股触发看涨信号+回测胜率≥50%+盈亏比≥1.2 → 板块级共振买入</div></div>';
  }).join('');
}
// ===== 需求6: 指标说明参考面板 =====
function renderIndRef(){
  const el=document.getElementById('indRefPanel');if(!el)return;
  const inds=Object.entries(indicatorCounts).sort((a,b)=>b[1]-a[1]);
  if(!inds.length){el.innerHTML='暂无指标数据';return;}
  const info=indicatorInfo||{};
  el.innerHTML=inds.map(([name,cnt])=>{
    const d=info[name]||{};
    return '<div style="padding:4px 0;border-bottom:1px solid var(--rule);"><b style="color:var(--ink);">'+name+'</b> <span style="color:var(--muted);font-size:11px;">['+(d.type||'')+']</span> — '+(d.desc||'')+ '</div>';
  }).join('');
}

// ===== AI Deep-Analysis Engine ====="""
)

# ============================================================
# 在页面初始化时调用新函数
# ============================================================
print("\n[初始化] 注册新渲染函数")

# 在layer1的init函数中调用新函数
patch("layer1 init调用新函数",
    "'layer1':function(){renderMarketOverview();renderGauge();renderBreadth();renderActionDist();renderIndicatorChart();renderBacktestStats();renderEmotionCycle();renderLadder();renderPlaybook();renderSignalCenter();},",
    "'layer1':function(){renderMarketOverview();renderGauge();renderBreadth();renderActionDist();renderIndicatorChart();renderBacktestStats();renderEmotionCycle();renderLadder();renderPlaybook();renderSignalCenter();renderUpDownDist();renderTodayHot();renderT0List();renderIndRef();},"
)

# layer2 init调用全市场板块轮动
patch("layer2 init调用全市场板块",
    "'layer2':function(){renderRotation();renderChain();renderSectorRanking();renderHeatmap();renderSecSigChart();renderConceptChart();renderIndustryChart();},",
    "'layer2':function(){renderRotation();renderChain();renderSectorRanking();renderHeatmap();renderSecSigChart();renderConceptChart();renderIndustryChart();renderFullMarketRotation();},"
)

# layer4 init调用量化策略
patch("layer4 init调用量化策略",
    "'layer4':function(){renderLearningInsights();renderIndPerf();renderIndPerfScatter();renderCalibration();renderTrackList();},",
    "'layer4':function(){renderQuantStrategy();renderLearningInsights();renderIndPerf();renderIndPerfScatter();renderCalibration();renderTrackList();},"
)

# ============================================================
# 注入全市场板块数据（从东方财富API获取，在Python端生成）
# ============================================================
print("\n[数据] 注入全市场板块数据")

# 在Python代码中，尝试从东方财富获取全市场板块数据并注入
# 在 sector_data_json 定义之后注入
patch("注入全市场板块JSON",
    "sector_data_json = safe_json_dumps([{\"name\": k, **v} for k, v in sector_ranking], ensure_ascii=False)",
    """sector_data_json = safe_json_dumps([{"name": k, **v} for k, v in sector_ranking], ensure_ascii=False)

# ===== 需求4: 获取A股全市场板块轮动数据 =====
full_market_sectors = []
try:
    import requests as _req
    _sess = _req.Session()
    _sess.headers.update({'User-Agent': 'Mozilla/5.0', 'Referer': 'https://quote.eastmoney.com/'})
    # 东方财富行业板块API
    _url = 'http://push2.eastmoney.com/api/qt/clist/get'
    _params = {'pn': 1, 'pz': 100, 'po': 1, 'np': 1, 'fltt': 2, 'invt': 2,
               'fs': 'm:90+t:2', 'fields': 'f12,f14,f3,f20,f128,f136'}
    _resp = _sess.get(_url, params=_params, timeout=10)
    _data = _resp.json().get('data', {}).get('diff', [])
    for _b in _data:
        full_market_sectors.append({
            "name": _b.get('f14', ''),
            "pct_change": round(_b.get('f3', 0), 2),
            "amount": str(round(_b.get('f20', 0) / 1e8, 1)) + '亿' if _b.get('f20', 0) > 0 else '--',
            "lead_stock": _b.get('f128', '') or '--',
        })
    full_market_sectors.sort(key=lambda x: x['pct_change'], reverse=True)
    print(f"  全市场板块数据: {len(full_market_sectors)} 个板块")
except Exception as _e:
    print(f"  全市场板块数据获取失败: {_e}")
    full_market_sectors = []
full_market_sectors_json = safe_json_dumps(full_market_sectors, ensure_ascii=False)"""
)

# 在JS变量注入中添加fullMarketSectors
patch("JS注入全市场板块变量",
    "var riskMetrics=",
    "var fullMarketSectors=JSON.parse(''+fullMarketSectorsJson+'')||[];window._fullMarketSectors=fullMarketSectors;\nvar riskMetrics="
)

# 在Python的HTML模板中注入JSON变量
patch("HTML模板注入fullMarketSectorsJson",
    "var riskMetrics=",
    "var fullMarketSectorsJson=`%FULL_MARKET_SECTORS_JSON%`;\nvar riskMetrics="
)

# 这需要用占位符方式注入，在文件末尾的format替换
# 实际上由于这个HTML是直接用f-string或format生成的，需要找到注入点
# 让我检查HTML是怎么生成的

# 实际上看代码，HTML中用的是 r'''...''' 原始字符串
# 然后在末尾用 .replace 或 format 注入JSON变量
# 让我找到注入JSON的方式

# 在文件末尾找注入逻辑
# 搜索 stocks_json 注入的位置

# ============================================================
# 写入修改后的文件
# ============================================================
if code != original:
    with open(FILE, "w", encoding="utf-8") as f:
        f.write(code)
    print("\n=== 补丁应用完成，文件已更新 ===")
    print(f"文件大小: {len(code)} bytes")
else:
    print("\n=== 没有修改 ===")
