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- 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(
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 617 |
if arm == 'self':
|
| 618 |
-
|
| 619 |
-
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 634 |
-
|
| 635 |
-
|
| 636 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 637 |
return pd.DataFrame(recs)
|
| 638 |
|
| 639 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 640 |
def oof_report(df):
|
| 641 |
-
for
|
| 642 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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)
|