"""build_strength_model_v3 兼容重建（Mac 迁移包缺原文件）
功能（按 skill 记载 + scan_daily_tb.py 调用口径）：
  rebuild(weights) — 交易完成后从 strength_baseline.csv 滚动重建 strength_model.json：
  1. 分池 percentiles：各特征 101 分位点（np.nanpercentile(qs, range(101))）
  2. 分池 raw_scores：该池全部信号的 raw_score sorted list
  3. 全表重算 raw_score + strength（v3 strength 同步段：防未完成信号行被 merge 清成 NaN）
  4. 输出 v3 兼容格式 {weights, pools: {pool: {percentiles, raw_scores}}} 覆盖 strength_model.json
  5. baseline 的 raw_score/strength 列同步写回
"""
import json
import os
import numpy as np
import pandas as pd

BASE = '/Users/xpresso/zt_app/backtest_zt_full'
FCOLS = ['f1_t0_pct', 'f2_t0_vol_ratio', 'f3_ta_shrink', 'f4_ta_drawdown', 'f5_ta_gap',
         'f6_tb_strength', 'f7_tb_gap', 'f8_chain_len', 'f9_tb_vol', 'f10_tb_ma5_slope', 'f11_tb_ma_align',
         'f12_crowd_pool', 'f13_pool_zt', 'f14_pool_avgret']

def rebuild(weights=None):
    baseline = f'{BASE}/strength_baseline.csv'
    model_file = f'{BASE}/strength_model.json'
    bdf = pd.read_csv(baseline, dtype={'buy_date': str})
    if weights is None:
        if os.path.exists(model_file):
            weights = json.load(open(model_file))['weights']
        else:
            raise RuntimeError('strength_model.json 不存在且未传 weights')
    w = weights
    pools = {}
    for pool, g in bdf.groupby('pool'):
        gs = g.dropna(subset=FCOLS)
        percentiles = {}
        for fc in FCOLS:
            vals = gs[fc].values if len(gs) else g[fc].dropna().values
            percentiles[fc] = np.nanpercentile(vals, range(101)).tolist() if len(vals) else None
        raw_scores = sorted(g['raw_score'].dropna().values.tolist())
        pools[pool] = {'percentiles': percentiles, 'raw_scores': raw_scores}
    # 全表重算 raw + strength（strength 同步段）
    def pct(pool, fc, v):
        qs = pools[pool]['percentiles'][fc]
        if qs is None or v is None or (isinstance(v, float) and np.isnan(v)):
            return 0.5
        return float(np.searchsorted(qs, v) / 100.0)
    for i, t in bdf.iterrows():
        try:
            raw = sum(w[fc] * pct(t['pool'], fc, t.get(fc, np.nan)) for fc in w if fc in FCOLS)
            rs = pools[t['pool']]['raw_scores']
            strength = float(np.searchsorted(rs, raw) / len(rs) * 100) if rs else np.nan
            bdf.at[i, 'raw_score'] = raw
            bdf.at[i, 'strength'] = strength
        except Exception:
            pass
    bdf.to_csv(baseline, index=False)
    json.dump({'weights': w, 'pools': pools}, open(model_file, 'w'))
    nan_n = int(bdf['strength'].isna().sum())
    print(f'rebuild 完成: {len(bdf)}笔, strength NaN={nan_n}, 模型已覆盖 {model_file}')
    return bdf
