Thibaut Forest Claude Opus 5 (1M context) commited on
Commit
11cbd93
·
1 Parent(s): 534abdd

all48oof defaults to the oof arm only (THE SCORE); 'cv all48oof self' re-enables the validity-check arm and the flattery readout

Browse files
Files changed (1) hide show
  1. batteryswap_example/cv.py +184 -18
batteryswap_example/cv.py CHANGED
@@ -86,6 +86,24 @@ def load_table(dataset_path=Path('dataset')):
86
  # within-origin fleet ranks: leak-free (data <= origin only)
87
  t['r_vlast'] = t.groupby('origin_idx')['v_last'].rank(pct=True)
88
  t['r_tau'] = t.groupby('origin_idx')['tau_projected'].rank(pct=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  return t
90
 
91
 
@@ -383,9 +401,35 @@ def _blind3(t):
383
  return ['v_last', 'v_min_7', 'n_30']
384
 
385
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
386
  VARIANTS = {'base': _base, 'zone': _zone, 'vcorr': _vcorr, 'vrel': _vrel,
387
  'vrel_only': _vrel_only, 'vmin7_only': _vmin7_only,
388
- 'v_last_only': _v_last_only, 'blind4': _blind4, 'blind3': _blind3}
 
389
 
390
  ABLATION = ('base', 'vrel_only', 'vmin7_only', 'vrel', 'v_last_only')
391
 
@@ -565,7 +609,8 @@ def ablation_report(df, ns, label, ref='base'):
565
  # --------------------------------------------------------------------------
566
 
567
  def run_all48_oof(n_folds=5, seed=0, origins=range(48),
568
- dataset_path=Path('dataset')):
 
569
  """The walk-forward cost harness with BUILDING-CLEAN predictions.
570
 
571
  all48 itself is temporal, not fold-based, and its PLANNING must stay
@@ -582,9 +627,19 @@ def run_all48_oof(n_folds=5, seed=0, origins=range(48),
582
  the cold-start floor off, so the paired per-origin delta isolates the
583
  self-fleet fit effect. Levels may differ slightly from canonical all48
584
  (floor off, no warm-start state); read the DELTA, not the level.
 
 
 
 
 
 
 
 
 
585
  """
 
586
  t = load_table(dataset_path)
587
- mc.FEATURES = list(BASE_FEATURES)
588
  ctx = mc.load_context(dataset_path)
589
  dev_bldg = (pd.read_csv(dataset_path / 'train' / 'devices.csv')
590
  .drop_duplicates('device_id')
@@ -613,15 +668,40 @@ def run_all48_oof(n_folds=5, seed=0, origins=range(48),
613
  eol = c['eol'].reindex(rows.index)
614
  tr_fold = tr['device_id'].map(dev_bldg).map(bldg_fold)
615
  row_fold = rows.index.to_series().map(dev_bldg).map(bldg_fold)
616
- for arm in ('self', 'oof'):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
617
  if arm == 'self':
618
- pred = mc.SklearnRUL(CHAMPION['model']).fit(tr).predict(rows)
619
- else:
620
- parts = []
621
- for f in sorted(row_fold.dropna().unique()):
622
- m = mc.SklearnRUL(CHAMPION['model']).fit(tr[tr_fold != f])
623
- parts.append(m.predict(rows[(row_fold == f).values]))
624
- pred = pd.concat(parts).reindex(rows.index)
 
 
 
 
 
 
 
 
 
 
 
625
  plan = mc.plan_tsroute(pred, None, locs, T, CHAMPION['day_q'],
626
  travel_costs=c['travel_costs'],
627
  settings=c['settings'])
@@ -630,16 +710,90 @@ def run_all48_oof(n_folds=5, seed=0, origins=range(48),
630
  start_time=T, verbose=0)
631
  rs = mc.rank_stats(pred, plan, eol, T)
632
  recs.append(dict(overall, **rs, origin=idx, arm=arm, seed=seed))
633
- a, b = recs[-2], recs[-1]
634
- print(f"s_{idx}: self {a['total_cost']:7.1f} oof {b['total_cost']:7.1f} "
635
- f"delta {b['total_cost'] - a['total_cost']:+7.1f}",
636
- flush=True, file=sys.stderr)
 
 
 
 
637
  return pd.DataFrame(recs)
638
 
639
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
640
  def oof_report(df):
641
- for arm in ('self', 'oof'):
642
- scorecard(df[df['arm'] == arm], f'all48oof / arm={arm} / floor off')
 
 
 
 
 
643
  s = df[df['arm'] == 'self'].set_index('origin')['total_cost']
644
  o = df[df['arm'] == 'oof'].set_index('origin')['total_cost']
645
  d = (o - s).dropna()
@@ -681,9 +835,21 @@ def main():
681
  elif proto == 'screen':
682
  screen()
683
  elif proto == 'all48oof':
684
- df = run_all48_oof()
 
 
 
 
685
  df.to_csv('/tmp/cv_all48oof.csv', index=False)
686
  oof_report(df)
 
 
 
 
 
 
 
 
687
  elif proto == 'ablate':
688
  which = sys.argv[2] if len(sys.argv) > 2 else 'all48'
689
  df, ns = ablate(protocol=which)
 
86
  # within-origin fleet ranks: leak-free (data <= origin only)
87
  t['r_vlast'] = t.groupby('origin_idx')['v_last'].rank(pct=True)
88
  t['r_tau'] = t.groupby('origin_idx')['tau_projected'].rank(pct=True)
89
+ # aux usage-dynamics and Wiener-FHT columns (same merges the old
90
+ # harness __main__ did): cached parquets, data <= origin only; inert
91
+ # unless a variant/model asks for the columns
92
+ try:
93
+ from batteryswap_example.aux_features import load as load_aux
94
+ aux = load_aux(dataset_path)
95
+ dup = [c for c in aux.columns if c in t.columns
96
+ and c not in ('device_id', 'origin_idx')]
97
+ t = t.merge(aux.drop(columns=dup),
98
+ on=['device_id', 'origin_idx'], how='left')
99
+ except FileNotFoundError:
100
+ pass
101
+ try:
102
+ from batteryswap_example.fht_features import load as load_fht
103
+ t = t.merge(load_fht(dataset_path),
104
+ on=['device_id', 'origin_idx'], how='left')
105
+ except FileNotFoundError:
106
+ pass
107
  return t
108
 
109
 
 
401
  return ['v_last', 'v_min_7', 'n_30']
402
 
403
 
404
+ def _ranks(t):
405
+ # phase-8 logbag_rank, STRICT form: the within-origin rank transforms
406
+ # REPLACE the two level features (the registry's logbag_rank ADDS them).
407
+ # Ranks are the cross-fleet transfer device; the original lost the old
408
+ # scenario CV by less than that harness's label-luck floor.
409
+ f = [c for c in BASE_FEATURES if c not in ('v_last', 'tau_projected')]
410
+ return f + ['r_vlast', 'r_tau']
411
+
412
+
413
+ def _vrel_bldg(t):
414
+ # The untested arm of the vrel family: BUILDING-relative voltage. The
415
+ # phase-44 refutation covers only a fleet-UNIFORM offset (cannot reorder
416
+ # a linear model); a per-building offset does reorder across buildings,
417
+ # which is exactly what the building-OOF fit can no longer absorb.
418
+ # Deployment-legal: computed from the target fleet's own telemetry.
419
+ if 'vrel_bldg' not in t.columns:
420
+ dev_b = (pd.read_csv(Path('dataset') / 'train' / 'devices.csv')
421
+ .drop_duplicates('device_id')
422
+ .set_index('device_id')['building_id'])
423
+ b = t['device_id'].map(dev_b)
424
+ t['vrel_bldg'] = (t['v_last'] - t.groupby([t['origin_idx'], b])
425
+ ['v_last'].transform('median'))
426
+ return BASE_FEATURES + ['vrel_bldg']
427
+
428
+
429
  VARIANTS = {'base': _base, 'zone': _zone, 'vcorr': _vcorr, 'vrel': _vrel,
430
  'vrel_only': _vrel_only, 'vmin7_only': _vmin7_only,
431
+ 'v_last_only': _v_last_only, 'blind4': _blind4, 'blind3': _blind3,
432
+ 'ranks': _ranks, 'vrel_bldg': _vrel_bldg}
433
 
434
  ABLATION = ('base', 'vrel_only', 'vmin7_only', 'vrel', 'v_last_only')
435
 
 
609
  # --------------------------------------------------------------------------
610
 
611
  def run_all48_oof(n_folds=5, seed=0, origins=range(48),
612
+ dataset_path=Path('dataset'), kind=None, variant='base',
613
+ arms=('self', 'oof'), blend=None):
614
  """The walk-forward cost harness with BUILDING-CLEAN predictions.
615
 
616
  all48 itself is temporal, not fold-based, and its PLANNING must stay
 
627
  the cold-start floor off, so the paired per-origin delta isolates the
628
  self-fleet fit effect. Levels may differ slightly from canonical all48
629
  (floor off, no warm-start state); read the DELTA, not the level.
630
+
631
+ Candidate re-tests (phase 52 addendum sweep): `kind` swaps the model
632
+ family, `variant` swaps the feature set (VARIANTS key), `blend`=(kind2,
633
+ w2) rank-averages a second family in at weight w2 and then maps the
634
+ blended ORDER back onto the primary family's own p42 values - value-
635
+ preserving, so the count/pricing machinery sees the identical p42
636
+ distribution and only WHO holds each rank changes (the phase-51 lesson:
637
+ a post-hoc blend whose beliefs disagree with the count machinery fails
638
+ for that reason alone).
639
  """
640
+ kind = kind or CHAMPION['model']
641
  t = load_table(dataset_path)
642
+ mc.FEATURES = list(VARIANTS[variant](t))
643
  ctx = mc.load_context(dataset_path)
644
  dev_bldg = (pd.read_csv(dataset_path / 'train' / 'devices.csv')
645
  .drop_duplicates('device_id')
 
668
  eol = c['eol'].reindex(rows.index)
669
  tr_fold = tr['device_id'].map(dev_bldg).map(bldg_fold)
670
  row_fold = rows.index.to_series().map(dev_bldg).map(bldg_fold)
671
+
672
+ def _pred(k, arm):
673
+ if k == 'tau':
674
+ # fit-free pseudo-model: the physics timer as a rank source
675
+ # (only meaningful inside a value-preserving blend)
676
+ return pd.DataFrame(
677
+ {'p42': -rows['tau_projected'].astype(float).fillna(np.inf)},
678
+ index=rows.index)
679
+ hazard = k in ('aft', 'dthaz', 'dthaz_aux', 'logbag_open',
680
+ 'blend_dh', 'blend_dhaux')
681
+ trk = mc.censored_pairs(t, T) if hazard else tr
682
+ fold_k = (trk['device_id'].map(dev_bldg).map(bldg_fold)
683
+ if hazard else tr_fold)
684
+ make = ((lambda: mc.MODELS[k]()) if k in mc.MODELS
685
+ else (lambda: mc.SklearnRUL(k)))
686
  if arm == 'self':
687
+ return make().fit(trk).predict(rows)
688
+ parts = []
689
+ for f in sorted(row_fold.dropna().unique()):
690
+ m = make().fit(trk[fold_k != f])
691
+ parts.append(m.predict(rows[(row_fold == f).values]))
692
+ return pd.concat(parts).reindex(rows.index)
693
+
694
+ for arm in arms:
695
+ pred = _pred(kind, arm)
696
+ if blend is not None:
697
+ k2, w2 = blend
698
+ r = ((1 - w2) * pred['p42'].rank(pct=True)
699
+ + w2 * _pred(k2, arm)['p42'].rank(pct=True))
700
+ vals = np.sort(pred['p42'].values)[::-1]
701
+ pred = pred.copy()
702
+ pred['p42'] = pd.Series(
703
+ vals, index=r.sort_values(ascending=False).index
704
+ ).reindex(pred.index)
705
  plan = mc.plan_tsroute(pred, None, locs, T, CHAMPION['day_q'],
706
  travel_costs=c['travel_costs'],
707
  settings=c['settings'])
 
710
  start_time=T, verbose=0)
711
  rs = mc.rank_stats(pred, plan, eol, T)
712
  recs.append(dict(overall, **rs, origin=idx, arm=arm, seed=seed))
713
+ if len(arms) == 2:
714
+ a, b = recs[-2], recs[-1]
715
+ print(f"s_{idx}: self {a['total_cost']:7.1f} oof {b['total_cost']:7.1f} "
716
+ f"delta {b['total_cost'] - a['total_cost']:+7.1f}",
717
+ flush=True, file=sys.stderr)
718
+ else:
719
+ print(f"s_{idx}: {arms[0]} {recs[-1]['total_cost']:7.1f}",
720
+ flush=True, file=sys.stderr)
721
  return pd.DataFrame(recs)
722
 
723
 
724
+ # The re-test grid for the phase 52 addendum sweep: every prediction-side
725
+ # candidate the campaign rejected on a self-fit or scenario-grouped surface,
726
+ # re-asked on the building-OOF cost harness. Planner/appetite candidates are
727
+ # deliberately absent - that axis is closed and this harness would not
728
+ # change its answer.
729
+ OOF_CANDIDATES = {
730
+ 'logistic': dict(kind='logistic'),
731
+ 'rf': dict(kind='rf'),
732
+ 'gbm': dict(kind='gbm'),
733
+ 'zone': dict(variant='zone'),
734
+ 'vcorr': dict(variant='vcorr'),
735
+ 'vrel': dict(variant='vrel'),
736
+ 'vrel_only': dict(variant='vrel_only'),
737
+ 'vmin7_only': dict(variant='vmin7_only'),
738
+ 'v_last_only': dict(variant='v_last_only'),
739
+ 'blind4': dict(variant='blind4'),
740
+ 'blind3': dict(variant='blind3'),
741
+ 'rankavg25rf': dict(blend=('rf', 0.25)),
742
+ 'rankavg25gbm': dict(blend=('gbm', 0.25)),
743
+ # wave 2 - the A-list from the phase-52-addendum re-adjudication:
744
+ # candidates whose rejection came from a self-fit surface while a
745
+ # building-split instrument disagreed or was never consulted
746
+ 'logbagC03': dict(kind='logbag_c03'),
747
+ 'logbagC01': dict(kind='logbag_c01'),
748
+ 'logbag_rank': dict(kind='logbag_rank'),
749
+ 'ranks': dict(variant='ranks'),
750
+ 'vrel_bldg': dict(variant='vrel_bldg'),
751
+ 'logbag_aux': dict(kind='logbag_aux'),
752
+ 'aux_ucb05': dict(kind='aux_ucb05'),
753
+ 'aux_ucb10': dict(kind='aux_ucb10'),
754
+ 'dthaz': dict(kind='dthaz'),
755
+ 'rankavg25tau': dict(blend=('tau', 0.25)),
756
+ 'rankavg25fht': dict(blend=('wiener', 0.25)),
757
+ }
758
+
759
+
760
+ def oof_candidate_report(base_csv='/tmp/cv_all48oof.csv',
761
+ pattern='/tmp/cv_oofcand_*.csv'):
762
+ import glob
763
+ base = pd.read_csv(base_csv)
764
+ b = base[base['arm'] == 'oof'].set_index('origin')
765
+ print('paired vs champion oof arm (logbag/base, floor off, fold seed 0).')
766
+ print('sched/rec12 shown because a cost delta can be appetite in '
767
+ 'disguise (phase 39/51): trust a win only if sched is ~flat.\n')
768
+ hdr = (f"{'candidate':14s} {'ALL48':>7s} {'delta':>7s} {'W/L':>7s} "
769
+ f"{'panel':>7s} {'pdelta':>7s} {'pW/L':>7s} {'sched':>5s} {'rec12':>6s}")
770
+ print(hdr)
771
+ print(f"{'base(oof)':14s} {b['total_cost'].mean():7.1f} {'-':>7s} {'-':>7s} "
772
+ f"{b.loc[(b.index >= 8) & (b.index <= 41), 'total_cost'].mean():7.1f} "
773
+ f"{'-':>7s} {'-':>7s} {b['n_sched'].mean():5.1f} {b['rec12'].mean():6.3f}")
774
+ out = []
775
+ for fn in sorted(glob.glob(pattern)):
776
+ d = pd.read_csv(fn).set_index('origin')
777
+ label = d['cand'].iloc[0]
778
+ delta = (d['total_cost'] - b['total_cost']).dropna()
779
+ pan = delta[(delta.index >= 8) & (delta.index <= 41)]
780
+ out.append((delta.mean(), label, d, delta, pan))
781
+ for _, label, d, delta, pan in sorted(out):
782
+ print(f"{label:14s} {d['total_cost'].mean():7.1f} {delta.mean():+7.1f} "
783
+ f"{int((delta < 0).sum()):3d}W/{int((delta > 0).sum()):2d}L "
784
+ f"{d.loc[(d.index >= 8) & (d.index <= 41), 'total_cost'].mean():7.1f} "
785
+ f"{pan.mean():+7.1f} {int((pan < 0).sum()):3d}W/{int((pan > 0).sum()):2d}L "
786
+ f"{d['n_sched'].mean():5.1f} {d['rec12'].mean():6.3f}")
787
+
788
+
789
  def oof_report(df):
790
+ arms = [a for a in ('oof', 'self') if (df['arm'] == a).any()]
791
+ for arm in arms:
792
+ label = 'THE SCORE' if arm == 'oof' else 'validity-check arm'
793
+ scorecard(df[df['arm'] == arm], f'all48oof / arm={arm} / {label} / floor off')
794
+ if 'self' not in arms:
795
+ return
796
+ # the paired flattery term - only shown when the self arm was requested
797
  s = df[df['arm'] == 'self'].set_index('origin')['total_cost']
798
  o = df[df['arm'] == 'oof'].set_index('origin')['total_cost']
799
  d = (o - s).dropna()
 
835
  elif proto == 'screen':
836
  screen()
837
  elif proto == 'all48oof':
838
+ # THE SCORE (user directive 2026-08-22): oof arm only by default.
839
+ # `cv all48oof self` additionally runs the self arm - the validity
840
+ # check (must reproduce panel 1679.2) and the flattery-term readout.
841
+ arms = ('self', 'oof') if variant == 'self' else ('oof',)
842
+ df = run_all48_oof(arms=arms)
843
  df.to_csv('/tmp/cv_all48oof.csv', index=False)
844
  oof_report(df)
845
+ elif proto == 'oofcand':
846
+ label = sys.argv[2]
847
+ df = run_all48_oof(arms=('oof',), **OOF_CANDIDATES[label])
848
+ df['cand'] = label
849
+ df.to_csv(f'/tmp/cv_oofcand_{label}.csv', index=False)
850
+ scorecard(df, f'all48oof / cand={label} / oof arm / floor off')
851
+ elif proto == 'oofreport':
852
+ oof_candidate_report()
853
  elif proto == 'ablate':
854
  which = sys.argv[2] if len(sys.argv) > 2 else 'all48'
855
  df, ns = ablate(protocol=which)