Thibaut Forest Claude Opus 5 (1M context) commited on
Commit ·
fbe4c36
1
Parent(s): 30e8503
Classifier-family sweep: RF/ExtraTrees/MLP + rank-hybrid all rejected by CV
Browse filesRandomForest (default/deep/class-balanced), ExtraTrees, and a small MLP all
lose 170-820 dev points to bagged logistic. RF ranks better at some origins
(AUC 0.94 vs 0.85 at s_23) but its compressed probabilities break topsum's
count; a rank_rf hybrid preserving logbag's probability scale while using
mean-rank ordering still loses (+92). Champion unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- EXPERIMENTS.md +17 -0
- batteryswap_example/models_cv.py +153 -6
EXPERIMENTS.md
CHANGED
|
@@ -133,6 +133,23 @@ untested); `logbag_aux*` variants await their feature columns in the table.
|
|
| 133 |
**Conclusion**: every axis is flat or worse around the champion - remaining cost
|
| 134 |
is structural (fast-plunge deaths), not hyperparameters.
|
| 135 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
## Trajectory
|
| 137 |
|
| 138 |
| champion | dev | test | combined (34) | all-48 metric | public LB |
|
|
|
|
| 133 |
**Conclusion**: every axis is flat or worse around the champion - remaining cost
|
| 134 |
is structural (fast-plunge deaths), not hyperparameters.
|
| 135 |
|
| 136 |
+
### Classifier-family addendum (RandomForest and friends)
|
| 137 |
+
|
| 138 |
+
| model | dev | Δ | note |
|
| 139 |
+
|---|---|---|---|
|
| 140 |
+
| RandomForest (500, leaf 5) | 2273.1 | +199 | ranks well (AUC 0.94 at s_23 vs logbag 0.85) but probabilities too compressed for topsum's count |
|
| 141 |
+
| RF deep (leaf 1, 1000) | 2325.9 | +252 | |
|
| 142 |
+
| RF class-balanced | 2890.2 | +816 | worst config ever tested - inflated Σp42 over-includes massively |
|
| 143 |
+
| ExtraTrees | 2245.3 | +171 | |
|
| 144 |
+
| MLP (32,16, early stop) | 2768.7 | +695 | |
|
| 145 |
+
| rank_rf hybrid (RF ranking, logbag scale - Σp42 preserved) | 2166.2 | +92 | RF's ranking edge does not transfer across origins even with the scale problem removed |
|
| 146 |
+
|
| 147 |
+
Complete p42-classifier ledger: logistic-family (champion), HGB/GBM x4,
|
| 148 |
+
Weibull AFT, discrete hazard (tied), RF x3, ExtraTrees, MLP, and five blends -
|
| 149 |
+
linear + bagging wins throughout. With ~1000 positives, one dominant monotone
|
| 150 |
+
signal (voltage level), and a planner that needs a stable probability scale,
|
| 151 |
+
that is the expected outcome.
|
| 152 |
+
|
| 153 |
## Trajectory
|
| 154 |
|
| 155 |
| champion | dev | test | combined (34) | all-48 metric | public LB |
|
batteryswap_example/models_cv.py
CHANGED
|
@@ -141,6 +141,31 @@ class SklearnRUL:
|
|
| 141 |
C=self.opts.get('C', 1.0)))
|
| 142 |
self.clf = BaggingClassifier(
|
| 143 |
base, n_estimators=int(self.opts.get('bags', 15)), random_state=0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
self.clf.fit(X, y)
|
| 145 |
|
| 146 |
dying = train[train['dies42'] == 1]
|
|
@@ -158,6 +183,25 @@ class SklearnRUL:
|
|
| 158 |
return out
|
| 159 |
|
| 160 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
class RankVoltage:
|
| 162 |
"""Control: score by v_last only; p42 is a rank in [0,1], day from DayQuantiles."""
|
| 163 |
|
|
@@ -230,11 +274,14 @@ class DiscreteHazard:
|
|
| 230 |
BUCKET = 7.0
|
| 231 |
K = 6 # buckets cover days (7k, 7(k+1)], k=0..5
|
| 232 |
|
|
|
|
|
|
|
|
|
|
| 233 |
def fit(self, train_censored):
|
| 234 |
dur = train_censored['duration'].clip(upper=WINDOW_DAYS).values
|
| 235 |
event = ((train_censored['event'] == 1) &
|
| 236 |
(train_censored['duration'] <= WINDOW_DAYS)).values
|
| 237 |
-
X = train_censored[
|
| 238 |
rows, ys, ks = [], [], []
|
| 239 |
for k in range(self.K):
|
| 240 |
lo, hi = self.BUCKET * k, self.BUCKET * (k + 1)
|
|
@@ -260,7 +307,7 @@ class DiscreteHazard:
|
|
| 260 |
return self
|
| 261 |
|
| 262 |
def predict(self, rows):
|
| 263 |
-
X = rows[
|
| 264 |
n = len(rows)
|
| 265 |
haz = np.empty((n, self.K))
|
| 266 |
for k in range(self.K):
|
|
@@ -334,6 +381,79 @@ class BlendDH:
|
|
| 334 |
return out
|
| 335 |
|
| 336 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
class WeightedBagLogistic:
|
| 338 |
"""Bagged logistic with recency-weighted samples: pairs from recent origins
|
| 339 |
count more, on the theory that fleet composition and degradation mix drift."""
|
|
@@ -417,8 +537,9 @@ class LogBagC:
|
|
| 417 |
class LogBagFeats:
|
| 418 |
"""logbag with extra feature columns (aux usage-dynamics table)."""
|
| 419 |
|
| 420 |
-
def __init__(self, extra):
|
| 421 |
self.feats = FEATURES + extra
|
|
|
|
| 422 |
|
| 423 |
def fit(self, train):
|
| 424 |
from sklearn.ensemble import BaggingClassifier
|
|
@@ -426,7 +547,7 @@ class LogBagFeats:
|
|
| 426 |
base = make_pipeline(SimpleImputer(strategy='median'),
|
| 427 |
StandardScaler(),
|
| 428 |
LogisticRegression(max_iter=2000, C=1.0))
|
| 429 |
-
self.clf = BaggingClassifier(base, n_estimators=
|
| 430 |
self.clf.fit(X, y)
|
| 431 |
dying = train[train['dies42'] == 1]
|
| 432 |
self.days = DayQuantiles().fit(dying[FEATURES], dying['days_to_eol'])
|
|
@@ -456,6 +577,26 @@ MODELS = {
|
|
| 456 |
'blend_dh': BlendDH,
|
| 457 |
'logbag_aux': lambda: LogBagFeats(['rng_90', 'vstd_30', 'rng_ratio', 'gap_max_60']),
|
| 458 |
'logbag_aux2': lambda: LogBagFeats(['rng_30', 'rng_90', 'vstd_30']),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 459 |
}
|
| 460 |
|
| 461 |
|
|
@@ -809,7 +950,8 @@ def plan_tsroute(pred, included, locations, start_time, day_q,
|
|
| 809 |
floor_day - defers uncertain insurance swaps that would otherwise
|
| 810 |
pay early-replacement for weeks [floor_day 0 = off]
|
| 811 |
"""
|
| 812 |
-
|
|
|
|
| 813 |
chosen = pred['p42'].nlargest(n).index
|
| 814 |
if plan_tsroute.p_be > 0:
|
| 815 |
topups = pred.index[pred['p42'] >= plan_tsroute.p_be]
|
|
@@ -1029,7 +1171,7 @@ def walk_forward(table, scenario_ctx, origins, configs):
|
|
| 1029 |
for model_name in {c['model'] for c in configs}:
|
| 1030 |
if (model_name, idx) not in fitted:
|
| 1031 |
factory = resolve_model(model_name)
|
| 1032 |
-
if model_name in ('aft', 'dthaz', 'logbag_open', 'blend_dh'):
|
| 1033 |
train = censored_pairs(table, origin_time)
|
| 1034 |
else:
|
| 1035 |
train = training_pairs(table, origin_time)
|
|
@@ -1105,6 +1247,11 @@ def summarize(df, label):
|
|
| 1105 |
def main():
|
| 1106 |
dataset_path = Path(os.environ.get('BATTERYSWAP_DATASET_PATH', 'dataset'))
|
| 1107 |
table = load_features(dataset_path)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1108 |
try:
|
| 1109 |
from batteryswap_example.aux_features import load as load_aux
|
| 1110 |
table = table.merge(load_aux(dataset_path), on=['device_id', 'origin_idx'],
|
|
|
|
| 141 |
C=self.opts.get('C', 1.0)))
|
| 142 |
self.clf = BaggingClassifier(
|
| 143 |
base, n_estimators=int(self.opts.get('bags', 15)), random_state=0)
|
| 144 |
+
elif self.kind == 'rf':
|
| 145 |
+
from sklearn.ensemble import RandomForestClassifier
|
| 146 |
+
cw = 'balanced_subsample' if self.opts.get('bal') else None
|
| 147 |
+
self.clf = make_pipeline(
|
| 148 |
+
SimpleImputer(strategy='median'),
|
| 149 |
+
RandomForestClassifier(
|
| 150 |
+
n_estimators=int(self.opts.get('trees', 500)),
|
| 151 |
+
min_samples_leaf=int(self.opts.get('leaf', 5)),
|
| 152 |
+
max_features='sqrt', class_weight=cw,
|
| 153 |
+
random_state=0, n_jobs=-1))
|
| 154 |
+
elif self.kind == 'xtrees':
|
| 155 |
+
from sklearn.ensemble import ExtraTreesClassifier
|
| 156 |
+
self.clf = make_pipeline(
|
| 157 |
+
SimpleImputer(strategy='median'),
|
| 158 |
+
ExtraTreesClassifier(n_estimators=500, min_samples_leaf=5,
|
| 159 |
+
max_features='sqrt', random_state=0,
|
| 160 |
+
n_jobs=-1))
|
| 161 |
+
elif self.kind == 'mlp':
|
| 162 |
+
from sklearn.neural_network import MLPClassifier
|
| 163 |
+
self.clf = make_pipeline(
|
| 164 |
+
SimpleImputer(strategy='median'),
|
| 165 |
+
StandardScaler(),
|
| 166 |
+
MLPClassifier(hidden_layer_sizes=(32, 16), alpha=1e-2,
|
| 167 |
+
max_iter=1000, early_stopping=True,
|
| 168 |
+
random_state=0))
|
| 169 |
self.clf.fit(X, y)
|
| 170 |
|
| 171 |
dying = train[train['dies42'] == 1]
|
|
|
|
| 183 |
return out
|
| 184 |
|
| 185 |
|
| 186 |
+
class RankBlendRF:
|
| 187 |
+
"""logbag's probability scale with (logbag+RF) mean-rank ordering: keeps
|
| 188 |
+
sum(p42) exactly (topsum count unchanged) while letting the forest's ranking
|
| 189 |
+
reorder WHO is included - RF ranks better at some origins but its raw
|
| 190 |
+
probabilities are too compressed for count-tracking."""
|
| 191 |
+
|
| 192 |
+
def fit(self, train):
|
| 193 |
+
self.a = SklearnRUL('logbag').fit(train)
|
| 194 |
+
self.b = SklearnRUL('rf').fit(train)
|
| 195 |
+
return self
|
| 196 |
+
|
| 197 |
+
def predict(self, rows):
|
| 198 |
+
out = self.a.predict(rows)
|
| 199 |
+
mean_rank = (out['p42'].rank() + self.b.predict(rows)['p42'].rank()) / 2
|
| 200 |
+
vals = np.sort(out['p42'].values) # same multiset of values
|
| 201 |
+
out['p42'] = pd.Series(vals, index=mean_rank.sort_values().index).reindex(out.index)
|
| 202 |
+
return out
|
| 203 |
+
|
| 204 |
+
|
| 205 |
class RankVoltage:
|
| 206 |
"""Control: score by v_last only; p42 is a rank in [0,1], day from DayQuantiles."""
|
| 207 |
|
|
|
|
| 274 |
BUCKET = 7.0
|
| 275 |
K = 6 # buckets cover days (7k, 7(k+1)], k=0..5
|
| 276 |
|
| 277 |
+
def __init__(self, extra=None):
|
| 278 |
+
self.feats = FEATURES + (extra or [])
|
| 279 |
+
|
| 280 |
def fit(self, train_censored):
|
| 281 |
dur = train_censored['duration'].clip(upper=WINDOW_DAYS).values
|
| 282 |
event = ((train_censored['event'] == 1) &
|
| 283 |
(train_censored['duration'] <= WINDOW_DAYS)).values
|
| 284 |
+
X = train_censored[self.feats].values
|
| 285 |
rows, ys, ks = [], [], []
|
| 286 |
for k in range(self.K):
|
| 287 |
lo, hi = self.BUCKET * k, self.BUCKET * (k + 1)
|
|
|
|
| 307 |
return self
|
| 308 |
|
| 309 |
def predict(self, rows):
|
| 310 |
+
X = rows[self.feats].values
|
| 311 |
n = len(rows)
|
| 312 |
haz = np.empty((n, self.K))
|
| 313 |
for k in range(self.K):
|
|
|
|
| 381 |
return out
|
| 382 |
|
| 383 |
|
| 384 |
+
class LogBagUCB(LogBagFeats):
|
| 385 |
+
"""Rank by mean + k*std across the bagged estimators: a battery the ensemble
|
| 386 |
+
disagrees about gets an inclusion boost (missing it costs ~4x including it).
|
| 387 |
+
N still comes from sum of mean p42, so the count stays calibrated."""
|
| 388 |
+
|
| 389 |
+
def __init__(self, extra, k=0.5):
|
| 390 |
+
super().__init__(extra)
|
| 391 |
+
self.k = k
|
| 392 |
+
|
| 393 |
+
def predict(self, rows):
|
| 394 |
+
out = self.days.predict(rows[FEATURES])
|
| 395 |
+
X = rows[self.feats]
|
| 396 |
+
Xi = pd.DataFrame(X, columns=self.feats)
|
| 397 |
+
probs = np.stack([est.predict_proba(Xi.iloc[:, feats])[:, 1]
|
| 398 |
+
for est, feats in zip(self.clf.estimators_,
|
| 399 |
+
self.clf.estimators_features_)])
|
| 400 |
+
mean, std = probs.mean(axis=0), probs.std(axis=0)
|
| 401 |
+
out['p42'] = np.clip(mean + self.k * std, 0, 1)
|
| 402 |
+
out['p42_sum'] = mean.sum()
|
| 403 |
+
return out
|
| 404 |
+
|
| 405 |
+
|
| 406 |
+
class LogBagLog(LogBagFeats):
|
| 407 |
+
"""Same features, but log1p-compress the heavy-tailed ones so the linear
|
| 408 |
+
model is not dominated by extreme staleness/gap values."""
|
| 409 |
+
|
| 410 |
+
SKEWED = ['staleness', 'gap_max_60', 'n_30']
|
| 411 |
+
|
| 412 |
+
def _tx(self, rows):
|
| 413 |
+
X = rows[self.feats].copy()
|
| 414 |
+
for c in self.SKEWED:
|
| 415 |
+
if c in X:
|
| 416 |
+
X[c] = np.log1p(X[c].clip(lower=0))
|
| 417 |
+
return X
|
| 418 |
+
|
| 419 |
+
def fit(self, train):
|
| 420 |
+
from sklearn.ensemble import BaggingClassifier
|
| 421 |
+
X, y = self._tx(train), train['dies42']
|
| 422 |
+
base = make_pipeline(SimpleImputer(strategy='median'),
|
| 423 |
+
StandardScaler(),
|
| 424 |
+
LogisticRegression(max_iter=2000, C=1.0))
|
| 425 |
+
self.clf = BaggingClassifier(base, n_estimators=self.n_estimators,
|
| 426 |
+
random_state=0)
|
| 427 |
+
self.clf.fit(X, y)
|
| 428 |
+
dying = train[train['dies42'] == 1]
|
| 429 |
+
self.days = DayQuantiles().fit(dying[FEATURES], dying['days_to_eol'])
|
| 430 |
+
return self
|
| 431 |
+
|
| 432 |
+
def predict(self, rows):
|
| 433 |
+
out = self.days.predict(rows[FEATURES])
|
| 434 |
+
out['p42'] = self.clf.predict_proba(self._tx(rows))[:, 1]
|
| 435 |
+
return out
|
| 436 |
+
|
| 437 |
+
|
| 438 |
+
class BlendDHAux:
|
| 439 |
+
"""Mean of logbag_aux (closed windows) and DiscreteHazard-with-aux
|
| 440 |
+
(censored open windows): decorrelated training sets, shared feature set."""
|
| 441 |
+
|
| 442 |
+
def fit(self, train_censored):
|
| 443 |
+
# recover the scored origin T: censored durations end exactly at T
|
| 444 |
+
tc = train_censored
|
| 445 |
+
T = (tc['origin'] + pd.to_timedelta(tc['duration'], unit='D')).max()
|
| 446 |
+
closed = tc[tc['origin'] <= T - pd.Timedelta(days=WINDOW_DAYS)]
|
| 447 |
+
self.a = LogBagFeats(['rng_90', 'vstd_30', 'rng_ratio', 'gap_max_60']).fit(closed)
|
| 448 |
+
self.b = DiscreteHazard(['rng_90', 'vstd_30', 'rng_ratio', 'gap_max_60']).fit(train_censored)
|
| 449 |
+
return self
|
| 450 |
+
|
| 451 |
+
def predict(self, rows):
|
| 452 |
+
out = self.a.predict(rows)
|
| 453 |
+
out['p42'] = 0.5 * (out['p42'] + self.b.predict(rows)['p42'])
|
| 454 |
+
return out
|
| 455 |
+
|
| 456 |
+
|
| 457 |
class WeightedBagLogistic:
|
| 458 |
"""Bagged logistic with recency-weighted samples: pairs from recent origins
|
| 459 |
count more, on the theory that fleet composition and degradation mix drift."""
|
|
|
|
| 537 |
class LogBagFeats:
|
| 538 |
"""logbag with extra feature columns (aux usage-dynamics table)."""
|
| 539 |
|
| 540 |
+
def __init__(self, extra, n_estimators=15):
|
| 541 |
self.feats = FEATURES + extra
|
| 542 |
+
self.n_estimators = n_estimators
|
| 543 |
|
| 544 |
def fit(self, train):
|
| 545 |
from sklearn.ensemble import BaggingClassifier
|
|
|
|
| 547 |
base = make_pipeline(SimpleImputer(strategy='median'),
|
| 548 |
StandardScaler(),
|
| 549 |
LogisticRegression(max_iter=2000, C=1.0))
|
| 550 |
+
self.clf = BaggingClassifier(base, n_estimators=self.n_estimators, random_state=0)
|
| 551 |
self.clf.fit(X, y)
|
| 552 |
dying = train[train['dies42'] == 1]
|
| 553 |
self.days = DayQuantiles().fit(dying[FEATURES], dying['days_to_eol'])
|
|
|
|
| 577 |
'blend_dh': BlendDH,
|
| 578 |
'logbag_aux': lambda: LogBagFeats(['rng_90', 'vstd_30', 'rng_ratio', 'gap_max_60']),
|
| 579 |
'logbag_aux2': lambda: LogBagFeats(['rng_30', 'rng_90', 'vstd_30']),
|
| 580 |
+
'rank_rf': RankBlendRF,
|
| 581 |
+
'logbag_proj': lambda: LogBagFeats(['v_proj']),
|
| 582 |
+
'logbag_rank': lambda: LogBagFeats(['r_vlast', 'r_tau']),
|
| 583 |
+
'logbag_rankproj': lambda: LogBagFeats(['v_proj', 'r_vlast', 'r_tau']),
|
| 584 |
+
'aux_rv': lambda: LogBagFeats(['rng_90', 'vstd_30']),
|
| 585 |
+
'aux_rvg': lambda: LogBagFeats(['rng_90', 'vstd_30', 'gap_max_60']),
|
| 586 |
+
'aux_rvr': lambda: LogBagFeats(['rng_90', 'vstd_30', 'rng_ratio']),
|
| 587 |
+
'aux_all': lambda: LogBagFeats(['rng_90', 'vstd_30', 'rng_ratio', 'gap_max_60',
|
| 588 |
+
'hot_90', 'oob_90']),
|
| 589 |
+
'aux_proj': lambda: LogBagFeats(['rng_90', 'vstd_30', 'rng_ratio', 'gap_max_60',
|
| 590 |
+
'v_proj']),
|
| 591 |
+
'aux_rank': lambda: LogBagFeats(['rng_90', 'vstd_30', 'rng_ratio', 'gap_max_60',
|
| 592 |
+
'r_vlast', 'r_tau']),
|
| 593 |
+
'aux_bag30': lambda: LogBagFeats(['rng_90', 'vstd_30', 'rng_ratio', 'gap_max_60'], n_estimators=30),
|
| 594 |
+
'aux_bag50': lambda: LogBagFeats(['rng_90', 'vstd_30', 'rng_ratio', 'gap_max_60'], n_estimators=50),
|
| 595 |
+
'dthaz_aux': lambda: DiscreteHazard(['rng_90', 'vstd_30', 'rng_ratio', 'gap_max_60']),
|
| 596 |
+
'blend_dhaux': BlendDHAux,
|
| 597 |
+
'aux_ucb05': lambda: LogBagUCB(['rng_90', 'vstd_30', 'rng_ratio', 'gap_max_60'], k=0.5),
|
| 598 |
+
'aux_ucb10': lambda: LogBagUCB(['rng_90', 'vstd_30', 'rng_ratio', 'gap_max_60'], k=1.0),
|
| 599 |
+
'aux_log': lambda: LogBagLog(['rng_90', 'vstd_30', 'rng_ratio', 'gap_max_60']),
|
| 600 |
}
|
| 601 |
|
| 602 |
|
|
|
|
| 950 |
floor_day - defers uncertain insurance swaps that would otherwise
|
| 951 |
pay early-replacement for weeks [floor_day 0 = off]
|
| 952 |
"""
|
| 953 |
+
psum = pred['p42_sum'].iloc[0] if 'p42_sum' in pred else pred['p42'].sum()
|
| 954 |
+
n = int(round(plan_tsroute.alpha * psum))
|
| 955 |
chosen = pred['p42'].nlargest(n).index
|
| 956 |
if plan_tsroute.p_be > 0:
|
| 957 |
topups = pred.index[pred['p42'] >= plan_tsroute.p_be]
|
|
|
|
| 1171 |
for model_name in {c['model'] for c in configs}:
|
| 1172 |
if (model_name, idx) not in fitted:
|
| 1173 |
factory = resolve_model(model_name)
|
| 1174 |
+
if model_name in ('aft', 'dthaz', 'dthaz_aux', 'logbag_open', 'blend_dh', 'blend_dhaux'):
|
| 1175 |
train = censored_pairs(table, origin_time)
|
| 1176 |
else:
|
| 1177 |
train = training_pairs(table, origin_time)
|
|
|
|
| 1247 |
def main():
|
| 1248 |
dataset_path = Path(os.environ.get('BATTERYSWAP_DATASET_PATH', 'dataset'))
|
| 1249 |
table = load_features(dataset_path)
|
| 1250 |
+
# derived: voltage projected through the reporting gap by the device's own
|
| 1251 |
+
# slope, and leak-free within-origin fleet ranks (data <= origin only)
|
| 1252 |
+
table['v_proj'] = table['v_last'] + table['slope_lastk'] * table['staleness']
|
| 1253 |
+
table['r_vlast'] = table.groupby('origin_idx')['v_last'].rank(pct=True)
|
| 1254 |
+
table['r_tau'] = table.groupby('origin_idx')['tau_projected'].rank(pct=True)
|
| 1255 |
try:
|
| 1256 |
from batteryswap_example.aux_features import load as load_aux
|
| 1257 |
table = table.merge(load_aux(dataset_path), on=['device_id', 'origin_idx'],
|