Thibaut Forest Claude Opus 5 (1M context) commited on
Commit
645d27e
·
1 Parent(s): a6c0af5

cv: de-tune harness from champion output shape (user) - rank_stats/rec12/rec20 removed, plan-derived swaps kept; SCORE reproduces 1972.0

Browse files

The purge harness now requires only that a method yield a plan: the
pred-shape ranking metrics assumed a per-device p42 ranking, which not
every method will have. models_cv.rank_stats untouched (ad-hoc use).
CSV schema change: n_swaps replaces the rank columns; stale /tmp
candidate CSVs deleted. (Attribution note for the record: the cv.py
consolidation itself is 4f5f060 by session 59; a6c0af5 was
EXPERIMENTS.md only despite its message.)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Files changed (2) hide show
  1. EXPERIMENTS.md +16 -0
  2. batteryswap_example/cv.py +20 -21
EXPERIMENTS.md CHANGED
@@ -2940,3 +2940,19 @@ receipts are phases 51-54 and they are not to be re-argued unprompted.
2940
  The leaderboard closures (appetite, cert-first, pooling) rest on public
2941
  measurements and survive any CV change. Public best 3c7b883 at 1661.45;
2942
  private selection recommendation unchanged.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2940
  The leaderboard closures (appetite, cert-first, pooling) rest on public
2941
  measurements and survive any CV change. Public best 3c7b883 at 1661.45;
2942
  private selection recommendation unchanged.
2943
+
2944
+ **Phase 55 addendum (2026-08-22, user): harness de-tuned from the champion's
2945
+ output shape.** "cv.py is tuned towards our current model (rec12 rec20 etc),
2946
+ remove those to make sure we can use the cv on any method we try." Removed
2947
+ the `rank_stats` call and every column built on it (rec12/rec20/recall/prec/
2948
+ miss_rank/n_sched/caught/n_deaths) - all assumed the method emits a
2949
+ per-device p42 ranking. The harness now requires only that a method produce
2950
+ a PLAN; the one diagnostic kept beside the cost blocks is `swaps` (in-window
2951
+ unique batteries read from the plan itself), preserving the phase-39/51
2952
+ appetite-in-disguise guard method-agnostically. Cost path untouched: SCORE
2953
+ reproduced 1972.0 exactly post-change (106s). `models_cv.rank_stats` remains
2954
+ for ad-hoc ordering diagnostics on methods that do rank. CSV SCHEMA CHANGE:
2955
+ /tmp/cv_all48oof.csv and cv_oofcand_* now carry n_swaps instead of the rank
2956
+ columns; the pre-change stale candidate CSVs were deleted (they were
2957
+ denominated in dead instruments regardless - any candidate must re-run fresh
2958
+ through `cv oofcand <label>`).
batteryswap_example/cv.py CHANGED
@@ -17,10 +17,12 @@ Design notes, paid for in prior phases:
17
  fleet size, so holding devices out of the plan manufactures a count bias
18
  (phase 26).
19
 
20
- * Report ranking precision at the operating N beside the cost (rec12/rec20/
21
- recall/miss_rank, via `models_cv.rank_stats`): a model can hold AUC 0.947
22
- and recall 0.000 on the subgroup that drives the late cost; mean cost hides
23
- whether a change reordered the list or just cut it deeper.
 
 
24
 
25
  * Known optimism channels, accepted by user decision 2026-08-22 with the
26
  numbers in view: devices are one-shot events, so same-device pairs from
@@ -106,24 +108,20 @@ def load_table(dataset_path=Path('dataset')):
106
 
107
 
108
  def scorecard(df, label):
109
- """Three-regime scorecard with ranking precision beside the cost."""
 
 
110
  print(f'\n=== {label} ===')
111
- hdr = (f"{'block':16s} {'n':>3s} {'total':>8s} {'late':>7s} {'early':>6s} "
112
- f"{'rec12':>6s} {'rec20':>6s} {'recall':>6s} {'prec':>5s} "
113
- f"{'sched':>5s} {'miss':>5s}")
114
  print(hdr)
115
  for name, sel in BLOCKS:
116
  d = df[sel(df['origin'])]
117
  if d.empty:
118
  continue
119
- deaths, caught = d['n_deaths'].sum(), d['caught'].sum()
120
- sched = d['n_sched'].sum()
121
  print(f"{name:16s} {len(d):3d} {d['total_cost'].mean():8.1f} "
122
  f"{d['late_swap'].mean():7.1f} {d['early_swap'].mean():6.1f} "
123
- f"{d['rec12'].mean():6.3f} {d['rec20'].mean():6.3f} "
124
- f"{caught / max(deaths, 1):6.3f} {caught / max(sched, 1):5.3f} "
125
- f"{d['n_sched'].mean():5.1f} "
126
- f"{d['miss_rank'].median():5.1f}")
127
 
128
 
129
  # --------------------------------------------------------------------------
@@ -297,8 +295,9 @@ def run_all48_oof(origins=range(48), dataset_path=Path('dataset'), kind=None,
297
  _, _, overall = evaluate_plan(plan, locs, c['travel_costs'],
298
  c['settings'], eol_times=eol,
299
  start_time=T, verbose=0)
300
- rs = mc.rank_stats(pred, plan, eol, T)
301
- recs.append(dict(overall, **rs, origin=idx, arm='oof'))
 
302
  print(f"s_{idx}: oof {recs[-1]['total_cost']:7.1f}",
303
  flush=True, file=sys.stderr)
304
  return pd.DataFrame(recs)
@@ -345,14 +344,14 @@ def oof_candidate_report(base_csv='/tmp/cv_all48oof.csv',
345
  base = pd.read_csv(base_csv)
346
  b = base[base['arm'] == 'oof'].set_index('origin')
347
  print('paired vs champion (logbag/base, temporal purge, floor off).')
348
- print('sched/rec12 shown because a cost delta can be appetite in '
349
- 'disguise (phase 39/51): trust a win only if sched is ~flat.\n')
350
  hdr = (f"{'candidate':14s} {'ALL48':>7s} {'delta':>7s} {'W/L':>7s} "
351
- f"{'panel':>7s} {'pdelta':>7s} {'pW/L':>7s} {'sched':>5s} {'rec12':>6s}")
352
  print(hdr)
353
  print(f"{'base(oof)':14s} {b['total_cost'].mean():7.1f} {'-':>7s} {'-':>7s} "
354
  f"{b.loc[(b.index >= 8) & (b.index <= 41), 'total_cost'].mean():7.1f} "
355
- f"{'-':>7s} {'-':>7s} {b['n_sched'].mean():5.1f} {b['rec12'].mean():6.3f}")
356
  out = []
357
  for fn in sorted(glob.glob(pattern)):
358
  d = pd.read_csv(fn).set_index('origin')
@@ -365,7 +364,7 @@ def oof_candidate_report(base_csv='/tmp/cv_all48oof.csv',
365
  f"{int((delta < 0).sum()):3d}W/{int((delta > 0).sum()):2d}L "
366
  f"{d.loc[(d.index >= 8) & (d.index <= 41), 'total_cost'].mean():7.1f} "
367
  f"{pan.mean():+7.1f} {int((pan < 0).sum()):3d}W/{int((pan > 0).sum()):2d}L "
368
- f"{d['n_sched'].mean():5.1f} {d['rec12'].mean():6.3f}")
369
 
370
 
371
  def oof_report(df):
 
17
  fleet size, so holding devices out of the plan manufactures a count bias
18
  (phase 26).
19
 
20
+ * Diagnostics are PLAN-DERIVED only (cost blocks + in-window swap count).
21
+ The pred-shape ranking metrics (rec12/rec20/recall/prec/miss_rank) were
22
+ removed 2026-08-22 (user): they assumed the method emits a per-device p42
23
+ ranking, which not every method will. The harness scores anything that
24
+ yields a plan; `models_cv.rank_stats` remains for ad-hoc ordering
25
+ diagnostics on methods that do rank.
26
 
27
  * Known optimism channels, accepted by user decision 2026-08-22 with the
28
  numbers in view: devices are one-shot events, so same-device pairs from
 
108
 
109
 
110
  def scorecard(df, label):
111
+ """Three-regime cost scorecard. Plan-derived columns only, so it works
112
+ on ANY method: the swap count comes from the plan, never from the
113
+ prediction's shape."""
114
  print(f'\n=== {label} ===')
115
+ hdr = (f"{'block':16s} {'n':>3s} {'total':>8s} {'late':>7s} "
116
+ f"{'early':>6s} {'swaps':>5s}")
 
117
  print(hdr)
118
  for name, sel in BLOCKS:
119
  d = df[sel(df['origin'])]
120
  if d.empty:
121
  continue
 
 
122
  print(f"{name:16s} {len(d):3d} {d['total_cost'].mean():8.1f} "
123
  f"{d['late_swap'].mean():7.1f} {d['early_swap'].mean():6.1f} "
124
+ f"{d['n_swaps'].mean():5.1f}")
 
 
 
125
 
126
 
127
  # --------------------------------------------------------------------------
 
295
  _, _, overall = evaluate_plan(plan, locs, c['travel_costs'],
296
  c['settings'], eol_times=eol,
297
  start_time=T, verbose=0)
298
+ in_win = plan.loc[plan['day'] <= T + W, 'battery']
299
+ recs.append(dict(overall, n_swaps=int(in_win.nunique()),
300
+ origin=idx, arm='oof'))
301
  print(f"s_{idx}: oof {recs[-1]['total_cost']:7.1f}",
302
  flush=True, file=sys.stderr)
303
  return pd.DataFrame(recs)
 
344
  base = pd.read_csv(base_csv)
345
  b = base[base['arm'] == 'oof'].set_index('origin')
346
  print('paired vs champion (logbag/base, temporal purge, floor off).')
347
+ print('swaps (plan-derived) shown because a cost delta can be appetite '
348
+ 'in disguise (phase 39/51): trust a win only if swaps is ~flat.\n')
349
  hdr = (f"{'candidate':14s} {'ALL48':>7s} {'delta':>7s} {'W/L':>7s} "
350
+ f"{'panel':>7s} {'pdelta':>7s} {'pW/L':>7s} {'swaps':>5s}")
351
  print(hdr)
352
  print(f"{'base(oof)':14s} {b['total_cost'].mean():7.1f} {'-':>7s} {'-':>7s} "
353
  f"{b.loc[(b.index >= 8) & (b.index <= 41), 'total_cost'].mean():7.1f} "
354
+ f"{'-':>7s} {'-':>7s} {b['n_swaps'].mean():5.1f}")
355
  out = []
356
  for fn in sorted(glob.glob(pattern)):
357
  d = pd.read_csv(fn).set_index('origin')
 
364
  f"{int((delta < 0).sum()):3d}W/{int((delta > 0).sum()):2d}L "
365
  f"{d.loc[(d.index >= 8) & (d.index <= 41), 'total_cost'].mean():7.1f} "
366
  f"{pan.mean():+7.1f} {int((pan < 0).sum()):3d}W/{int((pan > 0).sum()):2d}L "
367
+ f"{d['n_swaps'].mean():5.1f}")
368
 
369
 
370
  def oof_report(df):