# Experiment 75: fixed 30% CatBoost confirmation on seed-2026 folds 2 and 3
from catboost import CatBoostClassifier
import catboost
started75=time.time(); results75=[]; predictions75={}
ratings75=[c for c in numeric57 if c not in ['Age','Flight Distance','Departure Delay in Minutes','Arrival Delay in Minutes']]
cats75=categorical57+['route_distance_key']+['rating_category_'+c.replace(' ','_') for c in ratings75]
def cat_frame75(frame):
    result=frame.copy()
    for c in categorical57:result[c]=result[c].astype('object').fillna('__MISSING__').astype(str)
    additions={'route_distance_key':result['Flight Distance'].astype(str)}
    for c in ratings75:additions['rating_category_'+c.replace(' ','_')]=result[c].astype('object').fillna('__MISSING__').astype(str)
    return pd.concat([result,pd.DataFrame(additions,index=result.index)],axis=1)
class Cap75:
    def __init__(self):self.start=time.time();self.capped=False
    def after_iteration(self,info):
        self.capped=time.time()-self.start>=900
        return not self.capped
for fold75 in [2,3]:
    tr75,va75=folds57[fold75-1]
    enc75=TargetEncoder(target_type='binary',smooth='auto',cv=5,shuffle=True,random_state=2026+fold75)
    A75=X_65.iloc[tr75].reset_index(drop=True).copy();V75=X_65.iloc[va75].reset_index(drop=True).copy()
    A75[te_names_65]=enc75.fit_transform(keys_train_65.iloc[tr75],y57[tr75]).astype('float32')
    V75[te_names_65]=enc75.transform(keys_train_65.iloc[va75]).astype('float32')
    A75=cat_frame75(A75);V75=cat_frame75(V75)
    assert A75.columns.equals(V75.columns) and A75.shape[1]==196
    cap75=Cap75()
    model75=CatBoostClassifier(iterations=2500,depth=6,learning_rate=.035,l2_leaf_reg=10.,loss_function='Logloss',eval_metric='AUC',random_seed=2068,thread_count=4,bootstrap_type='Bayesian',bagging_temperature=.5,one_hot_max_size=10,max_ctr_complexity=2,leaf_estimation_iterations=5,verbose=250,allow_writing_files=False)
    print('75 FOLD',fold75,'START',flush=True)
    model75.fit(A75,y57[tr75],cat_features=cats75,eval_set=(V75,y57[va75]),early_stopping_rounds=150,use_best_model=True,callbacks=[cap75])
    pred75=model75.predict_proba(V75)[:,1];blend75=.7*own_oof66[va75]+.3*pred75
    old75=float(roc_auc_score(y57[va75],own_oof66[va75]));new75=float(roc_auc_score(y57[va75],blend75))
    row75=dict(fold=fold75,baseline_auc=old75,catboost_auc=float(roc_auc_score(y57[va75],pred75)),blend_auc=new75,gain=new75-old75,weight=.3,best_iteration=int(model75.get_best_iteration()),capped=cap75.capped,runtime_minutes=(time.time()-cap75.start)/60)
    model75.save_model(str(root75/f'experiment_75_catboost_fold{fold75}.cbm'));joblib.dump(enc75,root75/f'experiment_75_encoder_fold{fold75}.joblib')
    np.savez_compressed(root75/f'experiment_75_fold{fold75}.npz',id=train57['id'].iloc[va75].to_numpy(),prediction=pred75,own_blend=blend75)
    results75.append(row75);(root75/'experiment_75_progress.json').write_text(json.dumps(results75,indent=2));print('75 RESULT:',json.dumps(row75),flush=True)
    if cap75.capped:break
summary75=dict(confirmation_folds=results75,fixed_weight=.3,passed=bool(len(results75)==2 and all(r['gain']>0 and not r['capped'] for r in results75)),submitted=False,public_score=None,limitations=['Fold1 used for model and weight selection','Folds2 and3 have prior model-screening history; fresh validation still required','Early stopping uses outer validation labels'])
(root75/'experiment_75_review.json').write_text(json.dumps(summary75,indent=2));print('EXPERIMENT 75:',json.dumps(summary75),flush=True)
