CYP Knowledge Dashboard

Living research memory for the OpenADMET CYP inhibition challenge · 270 findings · updated 2026-09-03 23:31

How to read this. Every card's headline states the conclusion and the → line gives the intuition — scan those to form a hypothesis fast. Open Deep dive for the mechanism, numbers, and caveats behind it. Both paths land on the same conclusion. Colour = confidence (high/medium/speculative).
🧠Methods155🧬Biology8⚛️Physics12⚗️Chemistry16🧭Meta79

🧠 Methods

modelling approaches, features, validation, ensembling
high

A 3-sample noise estimate errs in both directions, so magnitude verdicts are provisional but not systematically inflated

CYP1A2's three-seed SD came in at half the truth; CYP2D6's came in 13% above it. Unreliable, not biased -- which is a much narrower problem than it first looked.

Deep dive

Every honest-gate run printed an effect-versus-seed-noise ratio whose denominator was the standard deviation of the SAME THREE SEEDS that measured the effect. Measuring both bottleneck isoforms' deployed base arms across 12 seeds gives CYP1A2 a true SD of 0.0072 against a 3-seed estimate of 0.0037 (51%, understated) and CYP2D6 a true SD of 0.0052 against a 3-seed estimate of 0.0059 (113%, OVERstated). The first result alone suggested past verdicts were systematically flattering; the second shows they are not. The correct and narrower statement is that a 3-sample SD is unreliable in both directions -- statistical bias accounts for only about 11% at n=3, so the rest is sampling luck -- and every magnitude verdict is therefore provisional until that isoform's SD is measured properly, with no systematic correction to apply. Re-reading both isoforms against measured nulls: CYP1A2's bandl2 at -0.0206 is 2.86 SD and comfortable, its external+3D joint at -0.0104 is 1.45 SD and marginal; CYP2D6's mechanistic block at -0.0077 is 1.48 SD and marginal, which matches the 'weak' label it carried when it deployed. Both isoforms' marginal deploys land within 0.03 SD of each other, at 1.45 and 1.48. The practical outputs are a per-isoform bar for future work -- roughly 0.0072 on CYP1A2 and 0.0052 on CYP2D6 -- and a procedural note that the ratio the gates print should be read as a rough flag rather than a measurement.

honest-gateseed-variancecalibrationself-correctioneffect-sizeswarm · 2026-08-25
high

A 3-seed gate is a weak instrument in both directions - it vetoed one real effect and nearly under-sold another

The same three-seed rule that blocked three CYP3A4-TDI experiments by chance also under-measured a real CYP1A2 effect by a third. More seeds fixes both; adjusting the threshold fixes neither.

Deep dive

Two results now bracket the same weakness. On CYP3A4-TDI a single seed vetoed three consecutive experiments while the other two favoured all three, and auditing 20 seeds showed that seed was the MOST balanced split of the twenty -- the veto was chance. On CYP1A2 the external+3D block measured -0.0104 across three seeds, which the properly calibrated null of 0.0072 put at 1.44 SD, marginal enough that I explicitly weakened my earlier claim about it. Re-running at 12 seeds gives -0.0154, negative on 12 of 12, and 2.14 SD -- established rather than marginal, and the point estimate got LARGER with more data. That direction matters: an effect originally selected for looking impressive should regress toward the mean when re-measured, so growth suggests the original three seeds were unlucky rather than lucky. The general rule is that an all-must-agree rule over three draws has poor power against effects near one SD and errs in BOTH directions, rejecting real effects and certifying small ones, and the remedy in either case is more seeds rather than a looser criterion -- loosening would trade one error for the other. The practical protocol adopted: any effect landing between roughly 1 and 2 SD of its isoform's measured null should be re-run at 12 seeds before being trusted or discarded, with the outcome pre-committed both ways. A secondary observation from the same run: the super-additivity of the two blocks survived quadrupling the seeds, with neither block reaching seed-stability alone even at 12 seeds (9/12 and 8/12) while the pair is perfect at 12/12 and beats its additive prediction of -0.0126.

honest-gatepowerseed-variancereplicationpre-registrationswarm · 2026-08-24
high

A band-aware loss DEGENERATES on imputed donor rows -- and donors are 62-71% of training rows on three of four isoforms

Donor rows are appended with lo = hi = the imputed value. A zero-width band turns bandl2 into plain squared error, so the metric-matched dead zone applies only to real rows. The more donors, the less of the training set is actually fitted under the metric.

Deep dive

Re-deriving the loss gate under the LCO-resolvability rule flipped exactly one arm: CYP3A4 bandl2, -0.0050 at 3/3 seeds and 1.36x its measured seed SD, originally vetoed by an LCO delta of +0.0009 against a 0.0067 threshold. Re-measured inside the deployed stack the sign REVERSES to +0.0034 at 0/3 (control passes: l2 0.4504 vs deployed 0.4492). Removing bagging leaves +0.0025, so the interacting layer is the TDI-donor layer, not bagging. The outcome tracks donor share: 1A2 at 0.0% donors WON -0.0206 (3/3, 2.86 SD) and is the only isoform shipping bandl2; CYP3A4 at 52.4% won -0.0050; the same isoform at 62.1% lost +0.0025; 2D6 at 65.9% and 2C9 at 70.6% both lost. The within-isoform pair is the cleanest evidence -- everything else held fixed, 1,249 extra donor rows flipped the sign. Forward rule: any future band-aware loss must either give donors a real width or be tested at the donor share it will actually meet.

st-raeloss-functionbandl2donorsimputationsixth-criterionrejectedscripts/train/rederive_objective.py, objective_in_deployed.py; data/processed/objective_in_deployed_cyp3a4.json; ledger #97 · 2026-08-27
high

A block can be strongly HARMFUL because a later layer already supplies its information another way

The aux block feeds single-concentration predictions in as columns. The imputation layer feeds the same screen in as rows. Same data twice, and the column version is a noisy estimate of what the rows give directly.

Deep dive

CYP3A4's aux block gated at -0.0129 with all seeds improving, and in the deployed pipeline it measures +0.0495 at 0 of 3 seeds and 13.38 SD -- a swing of +0.062 with a sign flip, the most extreme configuration-dependence recorded in this campaign. The mechanism is redundancy by another route: the imputation layer supplies 2,571 single-concentration donor ROWS on that isoform, and the aux block supplies model predictions of the same screen as COLUMNS. Two blocks were silently dropped from shipped models by the imputation gate running its arms on 'base' alone, and both were aux-type blocks -- so the layer that dropped them was precisely the layer that made them redundant. The accidents were not random and that is why all four came out fortunate. Practical consequence: when auditing whether a passing block belongs in a stack, check whether a later layer already carries its information, because such a block does not merely fail to help, it actively hurts. The corollary for gate design is that a block gated before a data layer exists cannot be assumed to survive that layer's arrival.

redundancyimputationaux-blocksixth-criterioncyp3a4configurationscripts/train/mech_readd.py; data/processed/aux_readd.json; ledger #119 · 2026-08-28
high

A block carrying the WRONG kind of information is worse than a block carrying none

384 random columns cost CYP2D6 nothing; the same 384 columns filled with ChemBERTa cost it 0.0178.

Deep dive

Frozen ChemBERTa-77M-MLM embeddings were gated on CYP2D6's deployed pipeline (control reproduces at 0.8930). The real block is +0.0178 WORSE, 0 of 3 seeds, 3.01 SD. Because 384 columns on a 2,265-column base is a 17% widening, a permutation null was run: the block row-shuffled at two seeds, keeping width, column marginals and collinearity while destroying the molecule-to-feature link. Nulls: -0.0028 and +0.0030, mean +0.0001. A signal-free block of identical shape is FREE. So the failure is not dimensionality dilution -- it is what the embedding encodes. Working hypothesis: ChemBERTa's MLM objective rewards reconstructing masked SMILES tokens, so it encodes syntactic and scaffold similarity, while CYP2D6's signal is orthogonal to gross structural similarity (uncorrelated with all three other isoforms, driven by a protonatable-amine pharmacophore). Confident scaffold-similarity features give a tree something to split on that does not survive a scaffold split; random features give it nothing to overfit. Practical consequence: when a wide block is rejected, run the permutation null before blaming width -- and if the null is free, the fix is to change WHAT is encoded, not to reduce dimensionality.

chembertarepresentationcyp2d6permutation-nulldimensionalitydata/processed/reverify_emb_cyp2d6.json · 2026-08-25
high

A candidate measured on a co-measured subset is measuring the subset

Compounds run on several isoforms were chosen by the organisers. Any effect estimated on them inherits that choice, and a stripped baseline inflates it further.

Deep dive

Cross-task stacking looked like a real lever on the co-measured subsets: CYP2C9 <- predicted CYP3A4 gave -0.0522 ST-RAE (n=473) and CYP2D6 <- predicted CYP3A4 gave -0.0476 (n=250), both comparable to the largest levers ever deployed in this campaign. On the full training sets with the DEPLOYED recipes, both reversed sign and were rejected: CYP2C9 +0.0125 with LCO 0.6164 -> 0.6330, CYP2D6 +0.0037 with LCO 0.9215 -> 0.9247, neither seed-stable, and both base arms reproducing their deployed score exactly (0.6033 and 0.8930), so the harness was sound. Two distinct inflations were at work. First, SELECTION: compounds measured on several isoforms are not a random draw, so an effect fitted on them need not generalise to the 73% measured on one. Second, a STRIPPED BASELINE: the subset arm used a plain base with no imputation, while the deployed recipes feed ALL FOUR isoforms' single-concentration log2fc into the imputation calibration -- so the cross-isoform signal was already in the incumbent and the subset experiment merely rediscovered it. This is the absorbed-by-base pattern operating at the scale of a whole modelling family rather than one feature block. Rule: measure against the deployed pipeline on the full training set, or you will rediscover what you already have and call it an improvement.

selection-biassubsetabsorbed-by-basemultitaskstackingswarm · 2026-08-24
high

A cheap pilot with a pre-registered contrast killed a 3,000-cofold plan for about five dollars

Before scaling an expensive mechanism, buy the smallest experiment that could falsify it.

Deep dive

The cofold heme-Fe lever was the top-ranked remaining idea for CYP2D6 and was scoped as ~3,000 cofolds on Explorer. Instead: 24 compounds, deliberately stratified across the full pIC50 range so a real effect would be visible at tiny n, with the decision rule fixed BEFORE looking (Cohen's d > 0.5 on the potent-vs-inactive contrast supports scaling). Cost .80 and about 20 minutes. Result d=0.34 - below threshold - so the array is not funded. Extrapolating the full run at the same /usr/bin/bash.20/ligand and 5 samples, this saved roughly 00 of shared Boltz credit plus days of Explorer queue, and more importantly avoided building an entire feature pipeline on a mechanism that does not hold. The general lesson for the campaign: stratify the pilot across the response range rather than sampling randomly (random 24 would mostly have been inactives and shown nothing either way), and write the decision rule down first.

pilotcost-controlexperiment-designfalsificationcofold-pilot · 2026-08-23
high

A cheap screen's rule held over its measured range and broke on the first point outside it

The disagreement-direction rule fit five points perfectly across a span from -0.175 to +0.027. The first candidate scoring +0.118 was predicted to help most and made things worse -- the rule interpolates, it does not extrapolate.

Deep dive

Screening four candidate second families on CYP2D6 for one seed each returned morgan_only (LightGBM on the 2048 Morgan bits, no physchem) at a disagreement of +0.118, over 4x the incumbent ExtraTrees' +0.027 and the first candidate ever in the strongly positive regime. Its gate: +0.0092, 0/3 seed-stable, 1.78 SD worse, control reproducing -- rejected. Adding the point drops the rule's fit from spearman -1.00 / pearson -0.963 to -0.83 / -0.588. Mechanism, foreseeable in hindsight: morgan_only is a strict feature SUBSET of the deployed model, so it is a degraded copy rather than an independent view, and such a candidate can show disagreement aligned with the base's error just by being noisier in a correlated way. The screen cannot separate correcting the base from over-correcting along the base's own error axis. Restriction to a subset of the incumbent's inputs is therefore not a second family. The complementary lesson came from ridge, which scored positive (+0.068) with a solo MAE of 1.3190 against the deployed model's ~0.58: direction without competence is worthless, and the rule had been reduced to direction alone. The screen still paid for itself -- four candidates for about 25 minutes versus hours to gate them all -- because its verdicts buy a gate and never a deploy.

ensemblingscreeningextrapolationscoperefutedcyp2d6scripts/train/candidate_screen.py, candidate_gate.py; data/processed/candidate_screen_cyp2d6.json, candidate_gate_cyp2d6_morgan_only.json; ledger #114 · 2026-08-28
high

A control is only as good as the thing it controls against

The fifth gate criterion asks whether the baseline reproduces the deployed score. It silently answered yes against a model we had already replaced.

Deep dive

Two ticks ago the campaign adopted a fifth gate criterion after a -0.0402 artifact: any harness re-implementing a deployed pipeline must reproduce that pipeline's deployed score before its deltas are transferable. It was implemented as deployed_score(), which reads the FEATURE/IMPUTATION gate artifact. One tick later the objective gate (#44) added a layer ON TOP -- CYP1A2 now ships bandl2 at 0.8139 while final_cyp1a2.json still records 0.8345 for its superseded l2 pipeline. The external-feature gate then ran, its base arm scored 0.8345, the control printed REPRODUCES INCUMBENT, and it certified a -0.0118 'improvement' to 0.8214 -- which is WORSE than the 0.8139 already deployed. The control passed while comparing against a model that no longer exists. Fixed by making deployed_score() consult the objective artifact first and fall back to the recipe artifact, and by fitting BOTH arms of the gate under the objective the isoform actually ships. The general failure is that a layered pipeline has a layered incumbent, and a control anchored at one layer goes stale the moment another layer is added -- so the anchor must be derived from the full deployment path, not from whichever artifact was current when the control was written. Worth noting the rule violated here is one this campaign wrote down the previous tick: measure against the DEPLOYED pipeline. Writing a rule down does not implement it.

honest-gatecontrolincumbentlayered-pipelineself-correctionswarm · 2026-08-24
high

A dead-zone loss must apply to MEASURED rows only -- on imputed rows it discards the only signal there is

A credible band on a real row encodes measurement precision, so ignoring error inside it is free. An imputed row has no measurement; its label is the model's only handle on that chemistry, and a dead zone around it throws the gradient away.

Deep dive

#97 attributed bandl2's failure on donor-heavy isoforms to a degeneracy -- donors enter with lo = hi, collapsing bandl2 to plain squared error. Testing the fix that mechanism implies REFUTED it: widening donor bands to imputed +/- k*calibration-residual SD on the deployed CYP3A4 stack gives +0.0034 (k=0) -> +0.0040 (k=1) -> +0.0141 (k=2), monotonically worse, 0/3 seed-stable throughout, control passing. Revised account fitting both results: bandl2's benefit is the dead zone on REAL rows only, and the donor share in #97's table tracks DILUTION of those rows -- CYP3A4 fell from 47.6% real to 37.9% real when 1,249 TDI donors were added, and the sign flipped. CYP1A2, the only isoform with zero donors, is the only one shipping bandl2. The zero-width degeneracy turns out to be the least-bad treatment of donor rows, not the defect. Loss-function family closed for the three donor-bearing isoforms.

st-raeloss-functionbandl2imputationdonorsrefutedself-correctionscripts/train/donor_band_gate.py; data/processed/donor_band_cyp3a4.json; ledger #98 · 2026-08-27
high

A difference of two precise numbers can be imprecise, and no model recovers that

Both pIC50 levels are well measured and well predicted. Their difference is mostly measurement error, because subtraction cancels the shared signal and adds both noises.

Deep dive

The TDI shift is pIC50-under-preincubation minus pIC50-direct, and the binary TDI label is that shift thresholded at log10(2). Measured on identical rows and folds, the two LEVELS are healthy and the DIFFERENCE is not. CYP3A4: TDI-condition pIC50 has SD 1.132 against a mean credible-band width of 0.715 (ratio 1.58) and predicts at scaffold-CV spearman 0.717; direct pIC50 1.093 / 0.878 / 1.25 and rho 0.740; the SHIFT has SD 0.364 against a band width of 1.157 -- ratio 0.31, its measurement width more than 3x its entire population spread -- and predicts at rho 0.278. CYP2D6 is the same shape: levels at ratio 2.17 and 1.86 with rho 0.308 and 0.352, shift at ratio 0.64 with rho 0.076. Predictability collapses in lockstep with the ratio. This explains, retroactively, why five separate TDI attempts (baseline, class weighting, label-confidence weighting, structural alerts, and now shift regression) all failed on CYP2D6: the underlying continuous quantity is essentially unpredictable from structure, so the MCC 0.9011 oracle from thresholding the TRUE shift is unreachable in principle rather than merely in practice. Two rules follow. Before modelling a DERIVED quantity, check its spread against its own measurement width -- subtraction destroys information that no model can recover, and regressing two levels separately then subtracting is worst of all because it compounds two models' errors (two_reg was the weakest arm on both isoforms). And where a derived target is noise-dominated, the THRESHOLDED label is the better target, because thresholding acts as variance reduction -- the opposite of the usual intuition that binarising discards information.

tdinoise-floorderived-targetssubtractionmeasurement-errorswarm · 2026-08-24
high

A donor set can be worse alone and still worth adding

Gating a new source against the incumbent source asks the wrong question. The question is whether the union beats either, and coverage can win where quality loses.

Deep dive

CYP3A4's imputation gate compared four arms on identical rows: no imputation 0.5307, the deployed single-concentration donors 0.4773, the new TDI-condition donors alone 0.5159, and both together 0.4643. The new donors are clearly WORSE than the incumbent donors on their own -- by +0.0386, a margin the honest gate calls meaningful -- and yet adding them to the incumbent is worth -0.0130 ST-RAE, seed-stable on 3/3 with LCO improving 0.5120 to 0.5020. Had the experiment been framed as 'are TDI-condition donors better than single-conc donors', the answer would have been a confident no and the win would have been discarded. What makes them pay is COVERAGE rather than quality: 1,238 of the 1,249 TDI-condition donors are absent from the single-concentration screen entirely, so they extend the chemistry rather than re-describe it, and their individually noisier labels are outweighed by reaching compounds the incumbent never sees. This is a concrete instance of the campaign's standing rule that combinations must be measured per isoform every time -- interference, super-additivity and substitution have each held exactly once here and generalised to nothing. It also sharpens ledger #46's null: improving calibration QUALITY bought nothing on the shift-corrected isoforms, while adding ROWS on the un-shifted isoform pays. Those are different levers, and the earlier negative did not transfer across them.

imputationdonorscoveragecombination-effectscyp3a4swarm · 2026-08-24
high

A layer can DROP a block silently, because the configuration is inferred from an artifact filename

Nobody decided to remove CYP2C9's aux block. A later gate ran its arms without it, and the recipe reader then inferred it was never there.

Deep dive

recipe() reconstructs each isoform's configuration by matching artifact filenames (impute_bias_mech_emax_<iso>.json -> 'base+mech', impute_bias_emax_<iso>.json -> 'base'). CYP2C9's aux block won final_cyp2c9.json at 0.6894; the imputation gate that superseded it ran none/r2/shift on 'base' alone, so the filename carries no aux and the shipped model has cfg='base'. The drop was invisible because every individual step was correct. Re-tested properly, adding the block back on top of imputation costs +0.0539 ST-RAE, 0 of 3 seeds helped, LCO agreeing -- so the accident was fortunate, but it was still an accident. The general risk: any layer that re-gates a subset of the configuration can silently discard the rest of it, and inferring configuration from filenames makes the loss unobservable. A deployed recipe should be an explicit record, not a string parsed out of a path.

layeringrecipecyp2c9harnessauxdata/processed/reverify_aux_cyp2c9.json · 2026-08-25
high

A leave-cluster-out gain does not forecast a scaffold-CV gain - they came apart completely

I predicted CYP3A4 would benefit because its external block improved LCO. The LCO improvement happened exactly as predicted, and scaffold-CV got meaningfully worse at the same time.

Deep dive

Priors were recorded in advance for the joint external+3D gate. CYP3A4 was called the best chance of the three untested isoforms, on the grounds that its external block had improved leave-cluster-out by -0.0075 even while failing seed-stability. The prediction was refuted, and informatively: CYP3A4 is the WORST of the three rejections at +0.0156, its external block costing +0.0125 on scaffold CV, while its LCO improved exactly as forecast (0.5020 -> 0.4976 for +ext, 0.4973 for +both). The two splits did not merely disagree in magnitude, they moved in opposite directions on the same arm. So LCO and scaffold CV are measuring different things, and an LCO gain cannot be treated as a faint or noisy version of a scaffold-CV gain that better seeds would reveal. That has a direct consequence for the campaign's outstanding flag. CYP1A2 had twice shown resolvable LCO gains alongside scaffold-CV instability, and a pre-registered condition was logged for revisiting those blocks if a leaderboard signal ever suggested scaffold CV was optimistic. That condition should now be read narrowly: it applies to CYP1A2, where the two splits AGREED once the blocks were combined (-0.0104 with LCO 0.8420 -> 0.8267), and not to CYP3A4, where they actively contradict each other. The general form: when two validation schemes disagree, that is information about the schemes, and resolving it requires a third source of truth rather than a preference for whichever one currently favours deployment.

validationleave-cluster-outscaffold-cvrefuted-priorforecastingswarm · 2026-08-24
high

A lever is worth its ceiling times the fraction that survives estimation

Both TDI isoforms have a real, sizeable threshold ceiling. Neither can be captured - and on the weaker one, trying makes things worse than doing nothing.

Deep dive

Measuring the MCC-optimal decision threshold on both scored TDI isoforms gave genuine headroom in each case: CYP3A4 oracle 0.4075 against prevalence 0.3571 (+0.0504, 14% relative) and CYP2D6 oracle 0.1439 against 0.1059 (+0.0380, 36% relative -- proportionally the larger of the two). Neither survived. An inner-cross-validation estimator, choosing the cut on training folds only, captured 36.5% of the ceiling on CYP3A4 while failing seed-stability, and on CYP2D6 realised -101.8% of it: it moved the opposite direction by slightly more than the ceiling's own magnitude, making a tuned threshold WORSE than the untuned default. The thresholds explain it. On CYP3A4 the estimator picked 0.009 / 0.002 / 0.004 against oracle values of 0.011 / 0.006 / 0.017 -- the right neighbourhood, unstably. On CYP2D6 it picked 0.254 / 0.214 / 0.036 against oracle 0.043 / 0.228 / 0.053, which does not track at all. So estimation quality scales with the same properties that make the underlying model good, sample size and signal, and the isoform that most needs help is the one least able to estimate its way there. Two consequences. The fix for a variance-limited lever is never a finer search, since more grid points and tighter optimisation both increase estimation noise. And the prevalence default survives on both isoforms for a reason worth stating: it is far from optimal but it is a FIXED rule with zero estimation variance, which on weak-signal targets is worth more than headroom a tuned rule cannot reliably reach. The general form: judge a lever by its ceiling MULTIPLIED BY the fraction that survives estimation, never by the ceiling alone.

decision-rulesestimation-varianceceilingtdithresholdswarm · 2026-08-24
high

A magnitude metric normalised by range punishes you for collecting more data

The expected range of n draws grows with n. Divide an effect by the range and the same effect scores worse the more seeds you run - exactly backwards.

Deep dive

The campaign's effect_vs_seed_noise aid computed ratio = |mean(delta)| / (max(base) - min(base)) across seeds. Range is not a fixed population quantity: for normal draws its expectation is 1.70 SD at n=3, 2.32 at n=5, 3.26 at n=12 and 3.73 at n=20, so an identical effect scores roughly 1.93 times worse simply for being measured with twelve seeds instead of three. This surfaced when CYP1A2's external+3D block was re-run at 12 seeds: the effect GREW from -0.0104 to -0.0154 and its verdict FELL from 'meaningful' (ratio 1.46) to 'weak' (0.67). A metric that degrades as evidence accumulates is worse than none, because it penalises precisely the behaviour that makes results trustworthy. Fixed by dividing by the standard deviation, which estimates a fixed population quantity and is therefore comparable across experiments with different seed counts, with the verdict thresholds rescaled from (1.0, 0.3) to (2.0, 1.0) because the originals were implicitly tuned against a range denominator at n=3, itself about 1.7 SD. Both runs now read 'meaningful' at 2.83 and 2.14, and the 12-seed figure is the trustworthy one since the 3-seed denominator was that isoform's SD underestimated by half. A caveat travels with the fix: the denominator is still estimated from the supplied seeds, and ledger #61 measured 3-seed SD errors of 51 to 191 percent across the four isoforms, so with few seeds this remains a rough flag rather than a measurement and an isoform's separately measured SD should be preferred where one exists.

metricsseed-varianceeffect-sizeharness-defectself-correctionswarm · 2026-08-24
high

A metric helper that derives its own baseline from its inputs is unsafe on a subset

Every activity tranche on every isoform scored above 1.0 while the full-set scores were 0.61-0.88. A subgroup cannot all be worse than the whole unless the baseline moved -- and it had.

Deep dive

cyp.eval's weighted_st_rae computes the baseline mean mu from whatever rows it is passed. Called on a subgroup it therefore scores that subgroup against the SUBGROUP's own mean -- a baseline that already knows which tranche a compound falls in, and a far stronger one than the global mean the metric actually uses. The result is subgroup scores that are not comparable to the headline number or to 1.0. This surfaced only because the arithmetic was inconsistent on its face: tranche scores of 1.21-2.68 alongside a full-set 0.7764 on CYP1A2. Without that cross-check the write-up would have claimed the deployed models fail on every activity tranche, which is not what the numbers show. Fix: pass the baseline in explicitly (st_rae_fixed_mu carries the global mu), and keep the own-mean figure alongside for contrast rather than deleting it. The general rule generalises beyond ST-RAE to any normalised metric -- RAE, relative error, skill scores -- whose denominator is estimated from the evaluation sample. Also motivated persisting OOF predictions to data/processed/oof_<iso>.npz so subgroup analyses no longer require an hour of refitting.

methodsst-raeevaluationself-correctionbugscripts/train/mean_predictor_check.py · 2026-08-29
high

A real, headroom-ordered, four-for-four improvement can still deploy on nothing

Calibration quality is not a dial that pays continuously. Once a mean-shift correction removes the bias, a better calibration has nothing left to remove.

Deep dive

log2fc_std_error lifted out-of-fold calibration R^2 on ALL FOUR isoforms with no inversions, and the gain was perfectly inversely ordered against baseline R^2 (2D6 +0.0342, 2C9 +0.0106, 1A2 +0.0066, 3A4 +0.0043) -- the textbook signature of signal added rather than variance fitted. It deployed on zero isoforms. CYP2D6 shift 0.8930 -> 0.8923 with paired seed deltas +0.0001/-0.0030/+0.0006 and LCO regressing 0.9215 -> 0.9244; CYP2C9 shift 0.6033 -> 0.6038, deltas -0.0021/+0.0065/-0.0029, LCO 0.6164 -> 0.6279. The 'none' arm was byte-identical on both isoforms, so folds were shared and the comparison was properly paired. This REFINES ledger #41, which held that calibration quality is the lever deciding whether row imputation helps or hurts: the Emax lift was ~+0.15 and converted, a +0.03 lift converts to nothing, so there are strong diminishing returns or a threshold. The likely mechanism -- that the mean-SHIFT correction and calibration quality are SUBSTITUTES, both removing bias from imputed labels, so shifting by brute force makes extra precision redundant -- stays SPECULATIVE: its prediction that precision should help the un-shifted r2 arm replicated on 2D6 (-0.0075/-0.0067/-0.0058, sign-stable) and REVERSED on 2C9 (+0.0192/+0.0004/+0.0335). General lesson: an intermediate metric improving is not evidence the deliverable will, and 'improves the quantity a prior finding named as causal' is still a hypothesis, not a result.

calibrationimputationnegative-resultintermediate-metricssingle-concswarm · 2026-08-24
high

A row with no fitted curve is a measurement, not a missing value

The assay declined to fit a curve because nothing happened. That IS the answer -- and it is the half of the data the model most needs.

Deep dive

The PubChem CYP panel carries 46,724 rows labelled Inactive, and 38,877 of them have no pIC50. A routine dropna on the target silently discarded all of them, training any external model on ACTIVES ONLY -- blind to the active/inactive boundary, which is most of what the official assay measures. Adding them back at a censored floor 0.1 below the lowest observed potency improved BOTH self-learnability and cross-assay transfer on all four isoforms with no inversions. Transfer-rho onto official training labels: 3A4 +0.356 -> +0.592, 1A2 +0.344 -> +0.414, 2C9 +0.174 -> +0.411, 2D6 +0.230 -> +0.274. Independent corroboration that this is bias removal and not just more rows: the mean shift between external and official predictions shrinks on every isoform (3A4 +0.93 -> +0.63, 2C9 +0.49 -> +0.17, 1A2 +0.28 -> +0.13), exactly what you expect when an actives-only model stops being biased potent. This partly rehabilitates the public panel, which ledger #7 killed as a lookup for having 0/748 test InChIKey overlap: the lookup is still dead, but the learned transfer was being judged on half its data. General rule: before concluding a data source does not transfer, check what the loader dropped.

external-datacensoringtransferpubchempreflightswarm · 2026-08-24
high

A second model family ships: ExtraTrees members confirm 12/12 on both representation-bound isoforms

Bagging saturated because its members were the same model on different subsamples. Averaging in a genuinely different family breaks that correlation -- but only where the second family is good enough on its own to be worth averaging.

Deep dive

12 seeds per arm inside the deployed stack, deployed arm as the control. CYP2D6: 0.8848 -> 0.8738, delta -0.0110, 12/12 seed-stable, 2.12 SD. CYP1A2: 0.7811 -> 0.7722, delta -0.0089, 12/12, 1.24 SD. Both controls reproduce. Both effects GREW with power rather than shrinking, and CYP1A2 -- the marginal 1.05 SD case at 3 seeds -- held at 12/12. CYP3A4 and CYP2C9 stay rejected, so the competence rule holds: the second family helps only where it is competent solo (solo gaps 2D6 +0.0090 and 1A2 +0.0307 versus 2C9 +0.1179 and 3A4 +0.1396). Deployed per isoform: 3A4 0.4492, 2C9 0.5882, 2D6 0.8738, 1A2 0.7722 -- macro 0.6753 -> 0.6708. This is the first deploy since bagging and the first non-LightGBM model the campaign ships, and it lands on exactly the two isoforms that had rejected nearly every feature block.

ensemblingextratreesmodel-familycyp2d6cyp1a2deployed12-seeddata/processed/family_et_cyp2d6_12seed.json, family_et_cyp1a2_12seed.json; ledger #102 · 2026-08-27
high

A sub-threshold LCO delta is not agreement -- a signal-free block reproduces it at r=0.992

Row-shuffle a feature block so it cannot carry information, and it still 'improves' leave-cluster-out by as much as the real one.

Deep dive

Every gate in this campaign prints an 'LCO agrees' line and it has been quoted as supporting evidence. Tested against a permutation null -- the mechanistic block row-shuffled, preserving column marginals and collinearity while destroying the molecule-to-feature link -- across 4 clusterings per isoform inside the deployed pipeline. CYP2C9: the signal-free null tracks the real block at r = 0.992, the real mean -0.0194 sits at the 25th percentile of the null, and the deployed arm's own LCO ranges 0.6079-0.7479 (sd 0.0665) across four equally-valid splits, dwarfing every block effect measured on that isoform. CYP3A4: null unbiased (mean +0.0010, t=0.57), real +0.0020 at the 50th percentile. The LCO delta is largely a property of WHICH CLUSTERING was drawn. Checked against per-isoform resolvability thresholds (3A4 0.0067, 1A2 0.0081, 2D6 0.0210, and CYP2C9's first-ever measurement at 0.0570), all three recent 'LCO agrees' claims are sub-threshold and CYP2D6's -0.0393 is the only one above. LCO is NOT retired -- it discriminates when the effect is large, which is the 2D6 case -- but sub-threshold deltas must be reported as 'not resolvable'. Wired into cyp.eval.lco_resolvable() and the gate report.

lcogatingpermutation-nullharnessself-correctiondata/processed/lco_null_cyp2c9.json · 2026-08-25
high

A thresholded metric can hide a model improvement that is perfectly stable underneath it

Bagging improves CYP3A4-TDI ranking on 12 of 12 seeds; MCC registers it on 10 and calls it noise.

Deep dive

Bagged averaging was rejected on the TDI track because MCC helped on only 10 of 12 seeds at 0.78 SD. Adding threshold-free scores to the same runs -- same folds, same probabilities -- shows the model was not the problem. CYP3A4: AUROC 0.8051 to 0.8109 (+0.0058) on 12 of 12 seeds and AUPRC 0.5006 to 0.5123 (+0.0117) on 12 of 12, both perfectly seed-stable. The ranking improves every single time; the thresholded metric sees it two times out of twelve less often, because MCC is read through a cut re-estimated per fold from the predicted distribution, and shrinking that distribution moves the cut along with it. CYP2D6 is the contrast that makes the point sharper: its AUROC moves +0.0007 on 7 of 12, not seed-stable, so its ranking is not improvable either and its problem sits upstream of the decision rule. Two practical consequences. First, when a lever fails on a thresholded metric, measure the threshold-free score before concluding the model did not improve -- it costs nothing if the probabilities are already in hand. Second, a confirmed mechanism is not a deploy: bag5 remains REJECTED on MCC, which is what the challenge scores.

tdimccaurocthresholdingmetric-designmechanismdata/processed/tdi_bagged_cyp3a4.json · 2026-08-26
high

A variance-reduction lever pays off in proportion to how quiet the metric already is

Bagging deployed on all four inhibition isoforms and failed on both TDI isoforms, and the relative estimator noise differs by about four-fold.

Deep dive

Seed-bagged averaging was gated identically on both tracks. Inhibition: deployed 4 of 4 (3A4 -0.0151, 2C9 -0.0138, 2D6 -0.0140, 1A2 -0.0188). TDI: rejected 2 of 2 (3A4 +0.0098 but only 10 of 12 seeds and 0.78 SD; 2D6 -0.0020, helping on 4 of 12). The difference is measurable: CYP3A4 inhibition has a seed SD of 0.0037 on a 0.46 ST-RAE, which is 0.8% relative, while CYP3A4-TDI has 0.0125 on a 0.36 MCC, or 3.5%. An effect of comparable practical size lands comfortably outside the noise on one track and inside it on the other. A metric-specific mechanism compounds it: MCC is read through a threshold re-estimated per fold from the predicted distribution, so shrinking the probability spread partly moves the cut along with it, whereas ST-RAE scores against fixed per-compound credible bands that do not move when predictions tighten. The forward-looking rule: before spending compute on variance reduction, check the metric's relative estimator noise and whether the metric reads predictions through a re-fitted decision rule. Where it does, expect much of the gain to be absorbed.

baggingtdimccvariance-reductionmetric-designdata/processed/tdi_bagged_cyp2d6.json · 2026-08-26
high

A verdict from one task does not transfer to another, and this pair has already proved it twice

The same two blocks reversed the sign of their interaction between two isoforms of one task. Carrying either verdict to a different task would assume exactly what that disproved.

Deep dive

The external public-panel block and the 3D conformer/shape block have now been gated across all four isoforms on the inhibition track, with results that refuse to generalise: DEPLOYED on CYP1A2 at -0.0104 and super-additive, REJECTED on CYP2D6 at +0.0055 where the combination is worse than either part, and REJECTED on CYP2C9 at +0.0061. Same blocks, same harness, same seeds, opposite sign of interaction between isoforms of a single task. That record is the argument for testing them on TDI rather than reasoning about it. TDI is not a harder version of inhibition, it is a different physical event: mechanism-based inactivation requires the enzyme to convert the compound into a reactive species which then traps it, whereas pIC50 measures reversible affinity. A block can be absorbed by the base model for affinity and still carry information about whether metabolism occurs at a position that generates a reactive intermediate -- shape and planarity govern how a molecule sits over the heme, which is exactly that question. Equally, the external panel measures inhibition potency in other assays, which is only loosely coupled to inactivation propensity, so it could easily be useless here despite deploying on CYP1A2's affinity task. One implementation note worth recording because it inverts every verdict if missed: MCC is higher-is-better while ST-RAE is lower-is-better, so the seed-stability test, the interaction classifier and the deploy condition all flip sign between the two harnesses.

tditransfercombinationstask-specificityhonest-gateswarm · 2026-08-24
high

An all-seeds-must-agree gate is underpowered below one seed-SD, and the fix is more seeds in advance

Three consecutive experiments were vetoed by the same seed. That seed turned out to be the most balanced split of twenty - so the veto was chance, and the gate simply cannot see effects this small.

Deep dive

The honest gate requires an improvement on every seed. On CYP3A4-TDI that let one seed block three consecutive experiments -- the threshold estimator, the external+3D feature blocks, and predicted inhibition potency -- while the other two seeds favoured all three. Auditing the base arm across 20 seeds settled which explanation held. Seed 42 is NOT an adverse split: its base MCC of 0.3606 sits at the 45th percentile, and it has the LOWEST per-fold positive-rate spread of all twenty seeds (SD 0.0071, 0th percentile; range 0.0195, 5th percentile), with fold sizes identical across seeds. So the veto was chance, not a fold artefact. The base arm's seed-to-seed SD is 0.0125, and all three rejected effects had means of +0.0067, +0.0056 and +0.0184 -- at or below roughly one seed-SD. An all-must-agree rule over three draws has poor power in that regime: a genuine small effect will show negative on at least one seed often enough that it can be vetoed indefinitely no matter how many times it is retried. Two disciplined consequences. This does NOT reopen the three rejections, because their deltas were mostly below the noise and 'underpowered' is not evidence of a hidden positive -- retroactively passing failed results on a power argument is the same error as moving a threshold after seeing it. And the forward fix is to pre-register MORE SEEDS for future work on this isoform, at least ten, with the smallest effect worth chasing set at about 0.0125 MCC. The general rule: when a gate's veto rate looks suspicious, measure the null distribution of the quantity it gates on before either trusting or loosening it.

honest-gatepowerseed-variancetdipre-registrationswarm · 2026-08-25
high

An effect size is a property of a PIPELINE, not of a block -- normalise both on the same one

Measuring an effect in one configuration and its noise in another gives a ratio that means nothing, and it will usually flatter the noise.

Deep dive

Ledger #61 flagged three deployed effects as marginal by dividing each recorded delta by its isoform's 12-seed seed-SD. Two of the three deltas were measured on a different pipeline from the one the SD came from: CYP2D6's mechanistic block and CYP2C9's aux block both came from gate scripts scoring PLAIN RAE, UNWEIGHTED, with no imputation, while the SD was measured on the band-weighted, imputed, deployed pipeline. Re-measured where it actually ships, the 2D6 block is -0.0440 at 8.48 SD, not -0.0077 at 1.48 SD -- five times larger, and the largest per-block inhibition effect in the campaign. The block is genuinely small in the configuration it was first gated in; band weighting and imputation multiply it. scripts/train/reverify_block.py now toggles a block inside the deployed recipe and measures effect and noise together, and inhibition_seed_audit.py records the pipeline behind every historical delta and prints a warning when it is not the deployed one.

gatingeffect-sizeseed-noisecyp2d6harnessdata/processed/reverify_mech_cyp2d6.json · 2026-08-25
high

An oracle is not always an upper bound

Handing the model the true partner label can be WORSE than handing it a prediction, because the truth carries the partner assay's noise and the prediction has already averaged it away.

Deep dive

Standard practice treats an oracle -- give the model the true value of something it cannot have at inference -- as an upper bound on any realistic version. On CYP2D6 that is false in both directions. TRUE CYP3A4 label as a feature: +0.0208 ST-RAE (HURTS). PREDICTED CYP3A4 label: -0.0476 (HELPS). Same inversion with CYP1A2 (+0.0202 true vs -0.0208 predicted). The reason is that CYP2D6 is orthogonal to the other three isoforms (label correlations +0.039, -0.100, +0.077), so the partner's true value contributes only that assay's independent measurement noise -- pure harm -- while the partner's PREDICTION is a smooth, denoised structural summary that works as a derived feature. The mirror-image lesson holds where the isoforms DO correlate: there the oracle massively OVERSELLS, because out-of-fold residuals are near-orthogonal to out-of-fold predictions by construction, so a prediction cannot carry the residual where the oracle's advantage lives. CYP3A4<-CYP2C9 oracle -0.1495 realises +0.0013; CYP2C9<-CYP1A2 -0.0892 realises -0.0042 (4.7%); CYP1A2<-CYP2C9 -0.0677 realises -0.0053 (7.8%). So an oracle bounds the realistic version from ABOVE when the partner is informative and correlated, and from BELOW when it is uninformative and noisy -- run both arms, never quote the oracle alone.

oracleupper-boundmultitaskstackingmeasurement-noiseswarm · 2026-08-24
high

Auxiliary-task payoff is MONOTONIC in how learnable the aux target is - all 4 isoforms order perfectly, giving a pre-flight test

Before spending compute on an auxiliary, measure how well you can predict the AUX target from your features. Below about rho 0.6 it will hurt you under series shift.

Deep dive

Completing the single-conc sweep across all four isoforms produced a perfectly monotonic ordering between how well the aux model predicts its OWN log2fc target (Spearman, structure -> log2fc) and the leave-cluster-out delta on pIC50: 3A4 rho -0.761 -> LCO -0.0365 (DEPLOY); 2C9 -0.645 -> -0.0277 (DEPLOY); 1A2 -0.505 -> +0.0091 (REJECT); 2D6 -0.376 -> +0.0193 (REJECT). No inversions. The deploy/reject boundary sits between rho 0.645 and 0.505, i.e. around 0.55-0.60. This gives a cheap PRE-FLIGHT test for any future auxiliary: fit aux-target ~ features, check the correlation, and only proceed if it clears ~0.6 - otherwise the aux model's predictions are mostly noise and injecting them degrades exactly where the base is already weakest (out-of-series). Second lesson: CYP1A2 had the HIGHEST correlation with base error of any isoform (+0.260) and still failed 3 of 4 gates - correlating with the residual is NECESSARY but not SUFFICIENT, so gate 2 must never be used alone.

auxiliarypre-flight-testhonest-gatemonotonicsingle-concentrationsingleconc-aux · 2026-08-23
high

Averaging is the lever; subsampling is only the price paid to make the members differ

Bagging changes two things at once, and on CYP2C9 the regularisation half actively hurts while averaging more than pays for it.

Deep dive

Seed-bagged ensembles were gated with a three-arm decomposition -- deployed (single deterministic fit), sub1 (one subsampled model, regularisation only), bag5 (five averaged) -- inside each isoform's deployed pipeline with controls reproducing. Regularisation alone: 3A4 -0.0032 (stable), 2D6 -0.0056 (UNSTABLE), 2C9 +0.0081 (HARMFUL), 1A2 -0.0098. Averaging adds: -0.0119, -0.0084, -0.0152, -0.0090. Subsampling does something different on every isoform; averaging carries the effect on all four. The mechanism is metric-specific: ST-RAE charges nothing inside the credible band, band-hit rates are 19-40%, and reducing prediction variance moves borderline predictions into the dead zone -- the same route by which bandl2 won on CYP1A2 by compressing predicted SD. Deployed on 3A4 (-0.0151), 1A2 (-0.0188) and 2D6 (-0.0140); REJECTED on 2C9 at 0.33 of its seed SD. Macro 0.6910 to 0.6790 with no isoform regressing. Practical rule: when a change bundles a regularisation shift with an ensembling step, gate the two separately or the win gets attributed to whichever half is easier to believe.

baggingensemblingvariance-reductionst-raedeployeddata/processed/bagged_cyp2c9.json · 2026-08-25
high

Band weighting and features are SUPER-additive on CYP2D6 (-0.0701 vs -0.0469 predicted) but merely additive on CYP1A2

Weighting does not add information - it makes existing information usable by clearing the inactive tail out of the loss.

Deep dive

Measuring the full cross {base, +mech, +aux, +mech+aux} x {none, band weighting} under ST-RAE shows the two levers INTERACT, and the sign of the interaction differs by isoform. CYP2D6: mechanistic block alone -0.0090, band weighting alone -0.0379, additive prediction -0.0469, ACTUAL combined -0.0701 - super-additive by 0.0232, i.e. the pair delivers ~50% more than the sum of its parts. Magnitude ratio 3.05 (meaningful) and leave-cluster-out agrees (1.0299 -> 0.9649). CYP2D6 lands at 0.9304 from a 1.0005 base, the largest movement of the campaign. CYP1A2 by contrast is merely additive and slightly under: mech -0.0030 + weight -0.0350 = -0.0380 predicted vs -0.0367 actual, and the winner is the SIMPLER base|inv_width (0.8345) since base+mech|inv_width (0.8328) is inside the seed spread. MECHANISM (a hypothesis that fits, not a proven one): band weighting stops the loss being dominated by the loosely-measured inactive tail, which is exactly what was drowning out the mechanistic features. On 2D6 the basic-amine/Asp301 salt-bridge signal is real but only distinguishes POTENT binders - the compounds whose bands are tight and which the real metric actually scores. So weighting does not add information, it makes existing information usable. That predicts complementarity wherever a feature block encodes something specific to actives, and mere additivity where the block encodes a global property like lipophilicity. CONSEQUENCE FOR THE GATE: 'does this feature help?' has no isoform-independent answer, and worse, no weighting-independent answer either - a block can be worthless unweighted and valuable weighted. Every feature previously rejected under an unweighted loss deserves one re-test with weighting on before being called dead.

interactionsuper-additivecyp2d6band-weightingst-raegatingfinal-gate · 2026-08-23
high

Band weighting finally moves CYP2D6 below the mean predictor - and falsifies my own dose-response prediction

The isoform that resisted four levers responds to a change in the LOSS, not the features. And the smallest-spread isoforms gained the most, which is the opposite of what I predicted.

Deep dive

Band weighting deploys on THREE of four isoforms under ST-RAE with the full honest gate: CYP2D6 1.0005 -> 0.9626 (-0.0379, inv_width_cap); CYP1A2 0.8695 -> 0.8345 (-0.0350, inv_width); CYP3A4 0.5541 -> 0.5307 (-0.0234, inv_width_cap); CYP2C9 REJECTS (best -0.0105, fails the gate). Macro ST-RAE 0.7879 -> 0.7638. TWO THINGS MATTER HERE. (1) CYP2D6 finally MOVES. It has resisted four independent levers - richer 2D descriptors, 3x more auxiliary labels, a different task (TDI, AUROC 0.574), and cofold geometry - and sat at the mean predictor throughout. Band weighting takes it BELOW 1.0 for the first time in the campaign, and by the largest margin of any isoform. It is still far from the ~0.17 noise floor, so this is movement, not a solution, but it is the first evidence that 2D6 is not simply irreducible. CYP1A2 also accepts its first-ever deployed change, having rejected every feature block. (2) MY PREDICTION WAS WRONG. I hypothesised the payoff would scale with how much band widths VARY (3A4 p10->p90 spread 19.2x vs 2C9 5.5x) and predicted weak or no effect on 1A2 (8.0x) and 2D6 (5.6x). The opposite happened: the two SMALLEST-spread isoforms took the two LARGEST gains, and spearman(width spread, gain) = -0.2, i.e. no relationship. A better-fitting story is 'the worse the unweighted model, the bigger the gain' (2D6 1.0005/-0.0379, 1A2 0.8695/-0.0350, 3A4 0.5541/-0.0234) - but CYP2C9 breaks even that, and with n=4 isoforms this is curve-fitting a narrative to four points. Recording it as an open question rather than swapping in a second post-hoc mechanism.

band-weightingcyp2d6st-raefalsified-predictionbreakthroughband-weighted · 2026-08-23
high

Band weighting makes feature blocks ~3x more valuable on CYP2D6 - but uniformly, so no rejection reverses

Weighting amplifies every block by about the same factor. Rescaling without reordering cannot rescue anything that was already below the noise floor.

Deep dive

Ledger #32 asked whether blocks rejected under an UNWEIGHTED loss become deployable once band weighting is on. Tested by forcing the single-concentration auxiliary back into the CYP2D6 gate despite its standalone rejection (--force-aux, written to a separate artifact so the original gate is preserved). THE MECHANISM IS CONFIRMED: both feature blocks contribute roughly 3x more under weighting. Aux marginal effect goes from -0.0057 unweighted (base 1.0005 -> 0.9948, essentially nothing, which is why it was rejected) to -0.0166 weighted (0.9626 -> 0.9460) = 2.9x. Mech goes from -0.0090 to -0.0322 = 3.6x. So weighting really does make previously-invisible feature signal usable, exactly as the super-additivity result suggested. BUT NO DEPLOYMENT DECISION CHANGES, and the reason is the interesting part: the amplification is roughly UNIFORM across blocks (2.9x vs 3.6x), so it rescales every contribution without REORDERING them. Mech still beats aux under weighting just as it did without, and the best combined config (base+mech+aux|weighted, 0.9240) beats the winner (base+mech|weighted, 0.9304) by only 0.0064 against a base seed spread of 0.0230 - not resolvable, so the tie-break correctly keeps the simpler model. PRACTICAL CONSEQUENCE: a block that sat 3x below the noise floor unweighted is still below it weighted. Uniform amplification cannot rescue a rejection; only a block whose signal is DIFFERENTIALLY concentrated in tightly-measured actives could reorder, and neither of ours is. This substantially narrows #32 - blanket re-testing of rejected blocks is not worth the compute, and the expectation of reversals was wrong.

band-weightingcyp2d6amplificationledger-32negative-resultforce-aux · 2026-08-23
high

BASE on official data: macro scaffold-CV RAE 0.837 - but CYP2D6 sits at 1.001, no better than the mean predictor

Three isoforms are learnable from 2D structure; 2D6 is not. All the headroom is in one isoform.

Deep dive

scripts/train/multitask_baseline.py --source official, combined Morgan+RDKit features, scaffold 5-fold CV, per-isoform LGBM. RAE: 3A4 0.621 (n=2335), 2C9 0.824 (n=1285), 1A2 0.901 (n=1412), 2D6 1.001 (n=1493). Macro 0.837. RAE>=1.0 means the model is worthless vs predicting the training mean, so 2D6 currently contributes nothing. Against the assay noise floor (2D6 ~0.17) the 2D6 gap is the single largest block of recoverable signal in the challenge. This is consistent with 2D6 being uncorrelated/anti-correlated with the other isoforms: whatever drives 2D6 binding (basic-amine/Asp301 salt bridge, distinct pocket) is not captured by generic 2D descriptors. Priority: 2D6-specific mechanistic features (protonation state at pH 7.4, basic-nitrogen pharmacophore, heme-Fe geometry) rather than more global model capacity.

baselinecyp2d6raeheadroomeda-official · 2026-08-22
high

Better calibration does NOT always mean better imputation - Emax helps 2D6/2C9 and hurts CYP3A4

Extra calibration inputs add variance without information where the original signal was already strong. The isoform that needed help least was hurt most.

Deep dive

After Emax-calibrated imputation gained -0.0374 on CYP2D6 and -0.1242 on CYP2C9, I expected CYP3A4 to improve too since its calibration also rose (0.837 -> 0.865). It does the opposite. Against the no-imputation baseline of 0.5307: uncorrected Emax-calibrated 0.5888 (+0.0581), shift-corrected 0.5729 (+0.0422), neither seed-stable, LCO worse in both. Against the ACTUAL deployed config - log2fc-only uncorrected imputation at 0.4773 - it is 0.0956 worse. TWO REASONS FIT. First, CYP3A4's calibration gain was the smallest of the four (+0.028 vs CYP2D6's +0.151) because log2fc alone was already excellent there, so the extra columns add variance without information on the one isoform that did not need them. Second, bias correction is wrong for 3A4 on independent grounds: its donors are MORE active than its measured compounds (-0.378 log2fc, the only isoform with that sign), so forcing the imputed mean upward moves it away from truth. NET: Emax calibration deploys on 2D6 and 2C9, is rejected on 3A4 and 1A2, and 3A4 keeps its original log2fc-only imputation. That is four isoforms with four different imputation recipes, and the fourth time in this campaign a mechanism has held on some isoforms and not others.

emaxcalibrationcyp3a4per-isoformself-correctionemax-imputation · 2026-08-23
high

Bias-correcting imputed labels rescues CYP2D6 from +0.110 to -0.021 - my 'real population shift so do not correct' call was too strong

Correcting a genuine population shift still helps where the calibration is poor. But it still does not beat the deployed config, so it is promising rather than a win.

Deep dive

Ledger #36 tested whether the imputation harm is correctable. I predicted NO, because donors differ from measured compounds on the independent single-conc screen (2D6 +2.105 log2fc, 1A2 +1.004) - a real population difference, not a calibration artifact. Half right. CYP1A2: mean-shift correction gives 0.8814 vs a 0.8345 baseline, still +0.047 worse and not seed-stable - prediction held. CYP2D6: 0.9415 vs 0.9626 = -0.0211, seed-stable, leave-cluster-out 0.9863 -> 0.9758, magnitude meaningful, DEPLOY - prediction failed. Correction removes most of the damage on both (1A2 +0.5316 -> +0.0469; 2D6 +0.1102 -> -0.0211) but only 2D6 becomes useful. The two factors do not decompose cleanly: 2D6 has the WORST calibration (R^2 0.496) and the LARGEST population shift and benefits most, while 1A2 has good calibration (0.786) and a smaller shift and does not - so no rule is proposed from n=2. CRUCIAL CAVEAT AGAINST OVER-CLAIMING: this is NOT a deployment change. Shift-imputation reaches 0.9415 on base+weighting, but the deployed 2D6 config is base+mech+weighting at 0.9304, still 0.0111 better. A gain measured against its own baseline is not a gain against the incumbent, and the combined mech+shift-imputation test is what would decide it.

imputationbias-correctioncyp2d6self-correctionnot-yet-a-winimpute-bias · 2026-08-24
high

Check whether a shared hyperparameter actually binds before tuning it -- here it bound on one isoform of four

num_leaves=64 saturates every CYP3A4 tree and is never reached by the other three, so three of the four isoforms had nothing to tune.

Deep dive

The campaign used one LightGBM configuration across isoforms whose training sets differ 4.4-fold, which looks like an obvious tuning opportunity. Dumping the trees actually built settles it cheaply: CYP3A4 hits the 64-leaf cap on 100% of trees, while CYP2C9 (40.5 mean leaves), CYP2D6 (46.8) and CYP1A2 (44.1) never reach it because min_child_samples and the data bind first. A parameter that is not binding cannot be tuned in either direction, so three isoforms were excluded by measurement rather than by a null result -- and the sweep collapsed to a single pre-registered test on the one isoform where it mattered. That test then failed decisively: doubling to 128 leaves gives +0.0160 on scaffold-CV and +0.0128 on LCO, positive on 3 of 3 clusterings and above CYP3A4's 0.0067 split-noise threshold, making it resolvable contrary evidence. The likely reading is that the cap is functioning as regularisation against a hard scaffold shift, which is why LCO caught it and scaffold-CV alone would have called it merely unstable. General rule: before tuning a shared hyperparameter, dump the fitted models and check where it actually binds; the measurement is minutes and it usually shrinks the experiment.

hyperparameterscapacitylightgbmlcocyp3a4data/processed/capacity_cyp3a4.json · 2026-08-26
high

Class weighting does NOT transfer to the TDI track - weighting pays only when it corrects a loss/metric mismatch

Band weighting was not a generic trick. It worked because ST-RAE forgives error inside the band; MCC has no such quirk to exploit.

Deep dive

The inhibition track's biggest win was a LOSS change: band weighting cleared the loosely-measured inactive tail out of the objective and let active-specific features surface, super-additively on CYP2D6. Ledger #33 asked whether the same idea transfers to TDI, which is 21% positive and so plausibly dominated by negatives the way inhibition was dominated by inactives. It does NOT. CYP3A4 TDI, scored on MCC (the leaderboard's primary classification metric) at the training-positive-rate threshold, 3 seeds, scaffold CV: base 0.3571; base + scale_pos_weight(3.69) 0.3521 (-0.0050); base+alerts 0.3555 (-0.0016); base+alerts+weighting 0.3505 (-0.0066). NOTHING is seed-stable and every variant is at or below base. The structural-alert block therefore stays withdrawn even under the conditions that rescued a rejected block on the inhibition side. WHY THE ASYMMETRY, and this is the transferable part: band weighting worked because it corrected a specific MISMATCH between loss and metric - ST-RAE forgives error inside the credible band, so the unweighted loss was spending capacity on compounds the metric barely scores. MCC has no analogous forgiveness structure; it treats compounds symmetrically once a threshold is fixed, and prevalence is already handled by thresholding at the training positive rate. scale_pos_weight only shifts the probability scale, which that threshold then undoes. So weighting is not a general-purpose trick - it pays off precisely when it aligns the loss with a quirk of the scoring function, and is inert when no such quirk exists.

tdiclass-weightingmccnegative-resultloss-designtdi-weighted · 2026-08-23
high

Correlated targets, uncorrelated errors: cross-isoform influence correlation is only +0.079

CYP3A4 and CYP1A2 labels correlate at 0.42, but their per-compound contributions to ST-RAE correlate at 0.026. The model captures the shared signal, so what is left in the residual is isoform-specific.

Deep dive

ST-RAE is a ratio of sums, so a compound's effect on it is its delta-method influence (soft - theta*base)/sum(base). Measuring the correlation of those influences on co-measured compounds gives: CYP3A4-CYP2C9 +0.396 (target r 0.71, 56% retained), CYP2D6-CYP1A2 +0.066 (0.08), CYP2C9-CYP1A2 +0.059 (0.45, only 13% retained), CYP3A4-CYP1A2 +0.026 (0.42, 6% retained), CYP2C9-CYP2D6 -0.015 (-0.10), CYP3A4-CYP2D6 -0.060 (0.04). Mean pairwise influence correlation is +0.079. Two consequences. First, averaging four endpoints genuinely buys variance reduction close to the independent ideal, so the macro is a well-behaved summary rather than four copies of the same noise. Second, this explains an earlier result from a second direction: #138 found exactly one isoform pair with a significantly shared error tail, CYP3A4-CYP2C9, and that is the only pair here retaining substantial residual correlation at +0.396. The two analyses agree without having been designed to. Measured on training co-measured compounds (230-473 per pair; only 41 compounds carry all four endpoints), using a first-order approximation to a heavy-tailed statistic.

st-raemacrocross-isoformleaderboardmethodsscripts/train/macro_correlation.py · 2026-08-29
high

Coverage is not what made the CYP3A4 donors work -- label-distribution match is, and 62 percent was a prior correction

With a fixed donor budget, correlation between RAE and measured chemotype coverage is about zero, while correlation with label-distribution mismatch is +0.76. The highest-coverage arm had the worst error.

Deep dive

The campaign's account of its most distinctive CYP3A4 result was that TDI-condition donors helped through coverage rather than quality, since they were worse alone but better together with the single-concentration donors. A controlled surrogate experiment on public CYP3A4 data refutes that reading. Holding the core fixed and the donor budget fixed at 1000 rows so that only donor identity varied, and measuring chemotype coverage rather than asserting it, the correlation between RAE and coverage was approximately zero to slightly negative at 0.00 to -0.07, while the correlation between RAE and label-distribution mismatch was strongly positive at +0.76 falling to +0.38 as the core grew. The arm with the highest measured coverage had the worst RAE. With a representative core, randomly drawn donors won at every core size and every skewed stratum hurt, including the highest-quality stratum. A factorial decomposition separated the pure quality effect, which cost +0.030 shrinking to +0.012 with more data as expected, from the pure identity or coverage effect, which came out at +0.078 shrinking to +0.033 and therefore carried the wrong sign: at matched noise, different compounds were worth less rather than more. The large apparent coverage win does reappear when the core is restricted to actives, which is the campaign-like configuration, but an oracle constant-shift analysis shows roughly 62 percent of that gain is a pure prior correction obtainable with no additional data at all. The actionable rule is to compare a donor set's label distribution against the target's before adding it, and to check whether a simple calibration offset already captures most of the benefit.

methodsdonorscoveragecyp3a4imputationrefutationdata/processed/surrogate_cyp3a4_public.json · 2026-08-30
high

CYP2D6 is orthogonal to the other three isoforms - do NOT train it in one joint multitask head

Multitask only helps where targets actually correlate. 2D6 has a different pharmacophore (basic amine/aspartate) and the data says so.

Deep dive

Pearson on co-measured compounds: 3A4-2C9 = 0.71 (strong), 2C9-1A2 = 0.45, 3A4-1A2 = 0.42, but 3A4-2D6 = 0.04, 2D6-1A2 = 0.08, 2C9-2D6 = -0.10 (NEGATIVE). So the 3A4/2C9/1A2 block genuinely shares signal and should share a trunk; 2D6 should be its own model or a head with no shared gradient pressure. This refines CLAUDE.md learning #5 ('CYP is multi-task by construction, correlated targets') - true for 3 of 4, false for 2D6. Co-measured n is small (230-473) so treat magnitudes as indicative, but the 3A4-2C9 vs 2C9-2D6 gap is far too large to be noise.

multitaskcyp2d6correlationeda-official · 2026-08-22
high

CYP2D6's best model is near-degenerate - predicted SD 0.290 against a 0.272 band width, only ~9% better than the mean

ST-RAE rewards hedging toward the band centre. The gain is real and gated, but the model is close to predicting a constant.

Deep dive

CYP2D6's deployed model reached ST-RAE 0.9072, its best of the campaign, and it passed the full honest gate including leave-cluster-out. But the submitted predictions are close to degenerate and that should be visible next to the headline. Predicted SD collapses from a training SD of 0.916 to 0.434 (v03) to 0.290 (v04) - a 3.2x compression - and the predicted range is 4.02-6.15 against a training range of 1.95-7.53. The predicted spread (0.290) is now barely above CYP2D6's own median credible band width (0.272), which means the model is doing little more than placing almost every compound near the centre and relying on ST-RAE forgiving anything inside the band. Correlation between v03 and v04 2D6 predictions is only 0.669, so this is a large change in behaviour, not a refinement. PUT PLAINLY: ST-RAE 0.9072 is only about 9% better than the mean predictor, which scores 1.0 by construction. The improvement from 1.0005 is real and gated, but CYP2D6 is not solved - the model has learned that the cheapest way to score under this metric is to hedge toward the centre of a tightly-measured distribution. THE RISK: this is only correct if the test set's activity spread and band widths resemble the training set's. We never see test bands. If the blinded 750 are activity-enriched (and CYP3A4's predictions already sit +0.55 above its training mean, which hints they might be), a near-constant 2D6 prediction would be penalised exactly where a wider one would not. Logged as a caveat rather than a reason to revert: the configuration won its gate fairly, and hedging toward the band centre is a rational response to a metric that forgives band-interior error. But a headline of '2D6 improved' without this alongside would misrepresent what the model actually does.

cyp2d6calibrationdegeneratest-raecaveatsubmissionswarm · 2026-08-24
high

CYP2D6's compressed predictions are correct, not degenerate - restoring the training spread costs 48% relative ST-RAE

Scaling up a low-information prediction adds error, not information. The hedging expresses real uncertainty rather than hiding a defect.

Deep dive

Last tick I flagged CYP2D6's deployed model as near-degenerate (predicted SD 0.356 vs training SD 0.916 and a 0.272 median band width) and queued variance matching as likely-cheap insurance against an activity-enriched test set. Measured instead of argued: rescaling out-of-fold predictions around their mean by k gives ST-RAE 0.9072 at k=1.00, 0.9362 at k=1.25, 0.9845 at k=1.50 (already worse than the mean predictor), and 1.3465 at the k=2.57 that restores the training SD exactly - a +0.4392 penalty, +48.4% relative. Critically the band-hit rate FALLS as spread grows (18.5% -> 14.2%), so expansion pushes predictions outside the credible bands without improving their ordering. INTERPRETATION: the compression is not a modelling pathology, it is the correct expression of genuine epistemic uncertainty under a metric that forgives band-interior error. For an isoform the model barely understands, calibrated hedging toward the band centre is the right answer, and a wider prediction would be false confidence. Ledger #40 resolves to ACCEPT rather than variance-match. The residual risk that the blinded test is activity-enriched is real but uniform expansion is not a remedy - it fails locally and there is no mechanism by which it would succeed on a shifted distribution.

cyp2d6calibrationvariancest-raeuncertaintyself-correctionvariance-match · 2026-08-24
high

CYP2D6's deployed model beats a constant by 0.07 log units; CYP3A4's by 0.43

Macro ST-RAE 0.6708 reads like three-quarters of the way to a perfect model. Converted into accuracy, CYP2D6 is a hair's breadth from predicting a constant.

Deep dive

ST-RAE is a ratio against the mean predictor, so a score does not say how much accuracy a model actually buys. Corrupting the true labels with gaussian noise of known sd and scoring the result maps accuracy in log units onto the metric, using only labels and shipped bands -- no model fitting, no test set, no importance weighting. The sd reproducing each deployed score, versus the sd at which a predictor merely ties the mean, gives the margin: 3A4 0.58 vs 1.03 (+0.45), 2C9 0.50 vs 0.70 (+0.20), 2D6 0.66 vs 0.74 (+0.08), 1A2 0.72 vs 0.88 (+0.16). CYP2D6 buys roughly a sixth of what CYP3A4 buys. This is the mechanical reason #94's test-like reweighting pushed 2D6 past 1.0 -- there is almost nothing there to erode -- and it is a far more robust route to that conclusion than the reweighting itself, whose effective sample size on 2D6 is only 505 of 1493 rows. The unbiasedness assumption was tested rather than assumed: repeating with pred = mu + k(y-mu) + noise at k=1.0/0.8/0.6 moves every margin by at most 0.03 and changes no ordering, and the result reproduces under two RNG seeds. This does NOT license shrinking 2D6 toward the mean, which would be tuning on the deployment distribution and was closed on magnitude by #79. It licenses reporting per-isoform margin over a constant alongside macro ST-RAE.

cyp2d6st-raemetricmethodsdistribution-shiftscripts/train/margin_over_constant.py · 2026-08-29
high

CYP2D6's mechanistic block survives its first signal-free control -- but a quarter of the published effect was never mechanism

Against a row-permuted copy of itself the block wins by -0.0419 at 22.8 times the noise, on 6 of 6 seeds. Against absence it wins by -0.0559, and the -0.0140 difference is delivered by 23 columns of pure noise.

Deep dive

The campaign's largest claimed feature effect was re-measured inside the deployed CYP2D6 configuration with three arms: the real block, a row-permuted copy preserving column count and marginals while destroying compound correspondence, and the block removed entirely. Six seeds, paired within seed, fifth gate reproducing at 0.8706 against a shipped 0.8738 with a deployed-arm seed standard deviation of 0.0026. Results: absent to deployed is -0.0559 at 40.3 effect-over-noise, permuted to deployed is -0.0419 at 22.8, and absent to permuted is -0.0140 at 5.6, every contrast improving on all six seeds. Three conclusions follow. First, the block is genuinely real: it is the first block in this campaign to clear a permutation null on the primary metric, and it does so decisively rather than marginally, which matters because the same control run on CYP2C19 found a signal four times smaller and below single-size resolution. Second, roughly twenty-five percent of the historically quoted effect was never mechanism, since a block of pure noise at the same width delivers -0.0140 of the -0.0559 total, so the correct figure to quote for the block's information content is -0.0419 rather than the bundled total. Third, the width term's sign is target-dependent: it is negative here, meaning noise columns actively help, while it is approximately +0.005 on CYP3A4 and +0.0003 on CYP2C19, so it cannot be treated as a constant property of block width. Separately, the total came out at -0.0559 rather than the published -0.0440 because that figure was measured when the deployed score was 0.893, before seed-bagging and ExtraTrees were added; the effect grew twenty-seven percent as later layers arrived, which is a direct confirmation of the campaign's own rule that an effect size is a property of a configuration.

cyp2d6mechanismpermutation-nulldeployedsixth-gatedecompositiondata/processed/mech_perm_cyp2d6_deployed.json · 2026-08-30
high

CYP2D6-TDI MCC has a 40% seed-to-seed range, so three seeds resolve nothing below ~0.03

The base arm alone swings from 0.0852 to 0.1196 depending only on the fold seed. Any single-seed reading of a TDI experiment on this isoform is noise.

Deep dive

Gating the external and 3D blocks on CYP2D6-TDI produced base MCC values of 0.1196, 0.0852 and 0.1128 across three scaffold-CV seeds -- a 40% relative range on the incumbent arm, with nothing changed but the fold assignment. Against that backdrop the +3d arm gave 0.1511 on seed 42, a +0.0315 apparent gain that matched the mechanistic prediction, and then regressed on both remaining seeds to a mean delta of exactly +0.0000. The external block was worse, at -0.0145. This sets a practical floor for the TDI track on this isoform: with n=1497 and a 21.6% positive rate, three seeds cannot honestly resolve an MCC difference below roughly 0.03, which is larger than any effect the five previous TDI attempts produced. Two consequences. Future CYP2D6-TDI experiments should either pre-commit to more seeds or accept that only large effects are detectable, and any TDI result quoted from a single seed should be treated as uninformative regardless of how well it fits the story being told. The methodological failure worth recording alongside the number is that the seed-42 value was called encouraging mid-run, before the other seeds existed -- the gate's all-seeds-negative requirement exists exactly to prevent that reading, so the error was impatience with a known rule rather than ignorance of it.

tdicyp2d6seed-variancemccresolutiondisciplineswarm · 2026-08-24
high

CYP2D6-TDI's apparent headroom has no route: the shift itself is unpredictable from structure

23.5% of ceiling looks like the campaign's biggest unclaimed gap. But the ceiling assumes you can observe the shift, and regressing it from a molecular graph reaches rho 0.076.

Deep dive

The replicate-assay ceiling assumes a predictor that OBSERVES the continuous shift. #49 measured how well that shift can be regressed from structure under scaffold CV: rho 0.278 on CYP3A4 and 0.076 on CYP2D6. Simulating a predictor with exactly that correlation and cutting at the training prevalence yields MCC 0.099 and 0.029 -- so the deployed classifiers, at 0.3559 and 0.1540, beat the shift route by 3.6x and 5.3x. That independently reproduces and explains #49's verdict of winner=clf: the shift is a difference of two correlated noisy pIC50s, so subtraction cancels signal while noise adds (SNR 0.31 and 0.64 against 1.25-2.17 for the components), and the binary label retains structure the difference throws away. Consequence for planning: CYP2D6-TDI's gap to its ceiling is not a labelling-noise problem to engineer around and is not reachable via the shift; the binding constraint is that the underlying continuous quantity is nearly unpredictable from a molecular graph. A number being far from its ceiling does not by itself mean there is anything to go and get.

tdicyp2d6mccshift-regressionnegativemethodsscripts/train/tdi_noise_floor.py · 2026-08-29
high

Distance from the noise floor tells you which lever can work

The floor bounds what is achievable. How far you sit above it tells you whether your error is measurement scatter or missing chemistry - and those need opposite interventions.

Deep dive

CYP1A2 and CYP2C9 both carry a genuine replicate of the scored pIC50 in the TDI file, with 100% coverage, and inverse-variance averaging cuts the training label's measurement SD by about 30% (CYP1A2 0.153 -> 0.107, CYP2C9 0.184 -> 0.125), close to the 1/sqrt(2) an ideal replicate pair would give. On CYP1A2 that produced nothing: mean +0.0039, ivw +0.0023, both seed-unstable and noise-order against a control reproducing the incumbent exactly. The reason is arithmetic rather than experimental. CYP1A2 scores 0.8139 against a shipped noise floor of 0.163 -- five times above it -- so measurement scatter is a small share of its error budget and shrinking that share by 30% cannot move the total. The useful reframing: the inherited lesson that the noise floor is the ceiling says what is ACHIEVABLE, but the RATIO of current performance to the floor says which kind of intervention is even capable of helping. Near the floor, label quality, calibration and loss shape are the live levers -- which is consistent with band weighting and bandl2 being the only things CYP1A2 has ever accepted, since both act on how error is counted rather than on how much information the model has. Far above the floor, only representation can move it: better chemistry, better features, better structure. Current ratios are CYP2C9 2.4x, CYP3A4 2.2x, CYP1A2 5.0x, CYP2D6 5.3x -- so CYP2D6 and CYP1A2, the two bottlenecks, are precisely the two where label-side work is least likely to pay and representation work is the only remaining route.

noise-floorlabel-noisereplicatesstrategyerror-budgetswarm · 2026-08-24
high

Diversity and competence trade off directly: regularising the partner model into usefulness destroys its independence

Ridge looks diverse when it is fitting noise, and looks redundant the moment it is regularised enough to be worth blending.

Deep dive

A cross-class blend needs two conditions: the partner must be individually competent, and its errors must be weakly correlated with the incumbent's. Measured on CYP3A4 in the deployed pipeline across seven decades of ridge alpha, those conditions move in opposite directions. Competence (ST-RAE within 1.5x of the GBM's 0.4642) arrives only at alpha >= 1e3, where ridge reaches 0.5537 at 1.19x. Error correlation is 0.715 at alpha=1, 0.836 at alpha=1e3 and 0.877 at 1e4 -- so at every point where ridge is good enough to blend, it is also predicting nearly the same thing. No alpha in the grid satisfies both. The interpretation is that a weakly-regularised linear model on p greater than n is diverse because it is fitting noise, and noise-driven diversity is worthless in a blend: averaging with it adds variance without adding signal. Once regularisation forces it onto the dominant signal, it converges toward whatever the incumbent already found. The practical rule is to measure BOTH preconditions on the same grid before designing a blend, because a partner tuned to satisfy one will usually violate the other, and a single-point check can make either condition look satisfiable in isolation.

ensemblingmodel-classridgebias-diversityclosed-familydata/processed/blend_precondition_cyp3a4.json · 2026-08-26
high

Donor layers are the campaign's most valuable components: they add rows AND regularise

Removing CYP2D6's 2,883 imputed rows costs +0.0328, more than twice what deploying bagging on all four isoforms bought.

Deep dive

Three leave-one-out ablations across two isoforms now show every deployed layer earning its place, and the donor layers dominating. CYP2D6's imputation costs +0.0328 on scaffold-CV and +0.0302 on LCO to remove, positive on 3 of 3 clusterings and above that isoform's 0.0210 threshold -- the first ablation where LCO is resolvable rather than below noise, so both estimators agree independently. CYP3A4's TDI-condition donors cost +0.0261, twice the -0.0130 they were gated at. CYP3A4's band weighting costs +0.0243, unchanged from its -0.0234. For scale, the entire bagging deploy that moved macro from 0.6910 to 0.6753 was worth -0.0151 on CYP3A4. The reason donors dominate is that they do two jobs at once: they add rows on an endpoint where data is the binding constraint, and #83 showed they simultaneously regularise, since they enter as calibrated predictions at r-squared-scaled weights and pull the fit toward a smooth consensus -- which is why CYP2D6 stopped looking over-trained the moment they were restored. The corollary is the campaign's clearest structural explanation for CYP1A2: it is the only isoform where imputation was tested and decisively rejected (r-squared arm 1.3661 against none at 0.8345, because its single-concentration signal is the weakest at rho 0.376), and it remains the isoform with the least room to improve by any means found.

ablationimputationdonorscyp2d6stackdata/processed/ablate_impute_cyp2d6.json · 2026-08-27
high

Double dissociation: removing ExtraTrees costs the width term half its value and the information term almost nothing

The same manipulation that halves the benefit of noise columns leaves the mechanistic block's genuine signal 95 percent intact. The two effects come apart cleanly.

Deep dive

Disabling ExtraTrees as the only change inside the deployed CYP2D6 configuration, across three matched seeds, separates two components of the block's measured benefit that had previously been quoted as one number. The width term, defined as the improvement a row-permuted block of identical shape delivers over having no block at all, falls from -0.0133 to -0.0066, retaining half its value. The information term, defined as the real block's improvement over its own permuted copy, falls only from -0.0430 to -0.0409, retaining ninety-five percent. This is a double dissociation and it was not designed for; it emerged from a test aimed at a different question. Its importance is that it independently corroborates the permutation result through a completely different manipulation. The permutation control establishes that the block outperforms a signal-free copy of itself, which rules out the possibility that its benefit comes merely from adding columns of the right shape. The dissociation adds that the block's advantage does not depend on the ensemble's randomisation machinery either, since stripping that machinery leaves the advantage essentially untouched while visibly degrading the part that does depend on it. Two independent lines therefore agree that the roughly -0.042 information term is genuine mechanistic signal rather than an artifact of either feature width or ensembling, which is a stronger conclusion than the permutation test alone supports. A practical corollary follows for any future change of model family on this isoform: the mechanistic signal is portable across estimators and would largely survive, whereas the width bonus is substantially an ExtraTrees phenomenon and would not.

methodscyp2d6double-dissociationextratreespermutation-nullmechanismdata/processed/mech_perm_cyp2d6_deployed.json · 2026-08-30
high

Estimating a decision threshold better does not make the score better

The seed whose threshold estimate was closest to optimal lost the most MCC. How much a miss costs is fold-dependent, and that swamps how big the miss is.

Deep dive

Chasing a +0.0504 MCC ceiling on CYP3A4-TDI, three variance-reduced threshold estimators were tried after a pooled argmax failed seed-stability: a median across inner folds, and log-space shrinkage toward the fixed prevalence threshold at two pre-registered levels. The variance reduction worked exactly as intended -- median_inner recovered 44.4% of the ceiling against the pooled argmax's 36.5%, and over-shrinking at L=0.75 fell back to 23.0%, tracing a clean bias-variance curve. Every arm still failed, all on the same seed. The diagnostic explains why, and it retires the whole approach. Comparing each seed's estimated threshold to its oracle: seed 42 estimated at 0.83x the oracle -- the closest of the three -- and lost 0.0414 MCC; seed 7 at 0.41x lost 0.0198; seed 2024 at 0.22x, four and a half times off, lost only 0.0348. Threshold accuracy and MCC cost are inversely related across these folds, which means the MCC-versus-threshold curve is knife-edged on some folds and flat on others. The consequence is general: when the payoff surface for a tuned parameter varies between folds, improving the estimate cannot reliably improve the score, because the estimated quantity is not what determines the outcome. That retires the obvious next moves -- more inner folds, finer grids, better shrinkage -- since all of them improve threshold accuracy, which has just been shown not to predict the result. It also gives the untuned prevalence default a second justification beyond having zero estimation variance: it is not aiming at a target whose payoff shifts underneath it.

thresholdmccestimationfold-dependencetdiretired-approachswarm · 2026-08-24
high

Every magnitude verdict was scored against a 3-sample noise estimate, and it ran half the truth

The gate measured an effect on three seeds and measured the noise on the same three seeds. On CYP1A2 that estimate came out at 0.0037 when twelve seeds say 0.0072.

Deep dive

Every honest-gate run in this campaign printed an effect-versus-seed-noise verdict, and the noise in that ratio was the standard deviation of the SAME THREE SEEDS used to measure the effect. Measuring CYP1A2's deployed base arm across twelve seeds gives a true SD of 0.0072 against the three-seed estimate of 0.0037 -- 51% of the truth. The statistical bias of a 3-sample SD is only about 11% low (E[s] = 0.886 sigma for n=3), so most of that gap is sampling luck, which is the sharper problem: a 3-sample SD is not merely biased low, it is unreliable enough that any single instance can land anywhere. Re-reading CYP1A2's history against the measured null: bandl2 at -0.0206 is 2.86 SD and comfortable; the external+3D joint block at -0.0104 is 1.45 SD and MARGINAL, not the solid win it was first reported as; the 3D block alone at -0.0059 (0.82 SD) and the external block alone at -0.0027 (0.38 SD) both sit inside one SD and were correctly rejected. Nothing is withdrawn -- those verdicts came from a pre-registered gate whose seed-stability and leave-cluster-out criteria are separate evidence from the mean delta -- but the CONFIDENCE attached to them was calibrated against a floor half the real height, and the correction runs in the flattering direction. Two rules follow. Treat any 'meaningful' magnitude verdict as provisional until that isoform's SD has been measured properly. And set the smallest-effect-worth-chasing from a many-seed estimate: on CYP1A2 that is about 0.0072, roughly double what the old number implied.

honest-gateseed-variancecalibrationself-correctioneffect-sizeswarm · 2026-08-25
high

Every model is attenuated toward the mean, and correcting it is worth nothing under ST-RAE

Potent compounds are under-predicted on all four isoforms, monotonically. Fixing that earns credit at the extremes and pays numerator across the dead middle, and the two cancel exactly.

Deep dive

Out-of-fold bias by activity quartile shows textbook regression-to-the-mean attenuation everywhere: mean(pred-y) runs from -0.302 to +0.596 on CYP3A4, -0.358 to +0.531 on CYP2C9, -0.556 to +0.587 on CYP2D6, -0.346 to +1.139 on CYP1A2. CYP2D6 under-predicts potent compounds hardest, and on its most-active quartile the soft error is almost entirely below-band (195.4 against 12.7 above) -- the model lands under the narrow credible bands that potent compounds carry, exactly the failure a calibration exists to fix. A nested screen (calibration fitted on 4/5 of the cached OOF, applied to the held-out 1/5) gives: linear alpha 0.4438/0.5828/0.8706/0.7608 against identity 0.4451/0.5857/0.8706/0.7603, every delta inside the isoform's measured 12-seed SD (0.0037/0.0113/0.0052/0.0072); isotonic regression is WORSE on all four by 0.037-0.043. The mechanism is ST-RAE's U-shaped denominator: rows whose band contains the global mean score zero in the denominator, so undoing attenuation adds numerator across the middle with no credit available there. Scope: a screen, not a gate -- folds are random because the OOF cache carries no scaffolds; a positive result would have needed a scaffold-CV gate, a null this far inside seed noise does not.

calibrationst-raeattenuationall-isoformsnegativemethodsscripts/train/attenuation_screen.py · 2026-08-29
high

ExtraTrees amplifies the benefit of extra feature columns rather than substituting for it

A pre-registered prediction said removing ExtraTrees should make noise columns more valuable. Removing it halves their benefit instead, on every seed.

Deep dive

A hypothesis was proposed that the benefit of adding uninformative feature columns and the benefit of ExtraTrees are the same decorrelation phenomenon, which would imply the two are substitutes and that removing ExtraTrees should make noise columns more valuable because the ensemble would then be short of randomisation. The prediction was falsifiable and the decision rule was written into code before any number was produced: the hypothesis counts as supported if the noise-column benefit grows by more than 0.002 when ExtraTrees is disabled, refuted if it shrinks by more than 0.002, and unresolved in between. Measured on CYP2D6 across three matched seeds with ExtraTrees as the only change inside the deployed configuration, the noise benefit was -0.0176, -0.0105 and -0.0119 with ExtraTrees active and -0.0114, -0.0016 and -0.0069 with it disabled, means of -0.0133 and -0.0066 respectively. The benefit halves rather than growing, a change of +0.0067 that is well outside the pre-registered band, and the direction is consistent on every seed. The hypothesis is refuted and the opposite relationship holds: ExtraTrees amplifies the effect of extra columns rather than replacing it. The mechanism is straightforward once stated. ExtraTrees samples roughly 48 of 2,288 available columns at each split, so adding 23 columns materially changes its candidate pool, whereas LightGBM at a feature fraction of one already considers every column and additional ones change comparatively little. The two effects are linked through feature sampling but multiplicatively rather than as substitutes, and this also accounts for the observed cross-target pattern in which noise columns help on the isoforms where ExtraTrees helps and cost where it does not.

methodsextratreesrandomizationrefutationpre-registrationfeature-samplingdata/processed/mech_perm_cyp2d6_deployed.json · 2026-08-30
high

Feature blocks are NOT additive - on CYP3A4 the two-block model is worse than the better block alone, exactly as the PXR ensemble lesson warned

Two features that each help are not a feature that helps twice. Measure the combination; never add the deltas.

Deep dive

Joint gate on CYP3A4 (scripts/train/combined_gate.py), identical folds, 3 seeds. Scaffold-CV RAE: base 0.6215, base+mech 0.6186 (-0.0029 alone), base+aux 0.6086 (-0.0129 alone), base+mech+aux 0.6092. If the deltas were additive the two-block model would score 0.6057; it actually scores 0.6092 - WORSE than the aux block alone at 0.6086. So the mechanistic block, which genuinely helps by itself, costs a little when stacked on top of the auxiliary: they explain overlapping variance and the extra 23 columns are net dilution. This is the inherited PXR finding reproduced on CYP with a controlled comparison - 'the ultimate ensemble regressed while a robust base plus one disciplined calibration won'. Practical rule for the rest of the campaign: report per-block deltas as INDEPENDENT measurements only, and never quote a combined macro that was assembled by summing them. The campaign macro was deliberately frozen at the last verified 0.831 for several ticks for exactly this reason, and that caution is now vindicated.

additivityensemblingcyp3a4honest-gatepxr-lessoncombined-gate · 2026-08-23
high

Feature-block signal is small and target-dependent, not a constant swamped by width

A tempting synthesis said blocks carry a constant ~0.005-0.007 of signal that the width penalty either covers or eats. Both constants failed on measurement.

Deep dive

Midway through the surrogate program a unifying model was proposed: that mechanistic feature blocks carry a roughly constant quantity of real signal, and that whether a block appears to work is decided by whether the dataset's width penalty exceeds it. The attraction was that it would have reframed the campaign's long record of absorbed physics blocks from physics does not help to physics is real but arrives too wide, which implies a concrete and hopeful direction, namely narrower encodings of the same mechanism. Direct measurement refutes both of its constants. First, the width penalty is not a fixed property of block width: on CYP2C19 it is approximately zero overall at +0.0003 RAE, and at n=1500 it is -0.0018 plus or minus 0.0019, which excludes a +0.005 penalty at 3.5 standard errors. The collection where the mechanistic block did least therefore had the lowest width penalty in the whole program, so nothing was being swamped there. Second, the signal is not constant across targets: expressed in absolute pIC50 units the information gap is about -0.0010 on CYP2C19 against about -0.0044 on CYP2D6, a four-fold difference. What survives is a weaker and more honest statement. Blocks carry small, genuinely real, target-dependent information; the width penalty is a separate size-dependent cost that vanishes with enough training data; and the two must be measured separately because testing against absence confounds them. On CYP2D6 the information is real and reproducible at roughly -0.004 pIC50 across mixed, homogeneous and size-matched collections alike, while on CYP2C19 it is real but four times smaller and below the resolution of any single training size, showing as a consistent sign across five sizes at p=0.031 rather than as a significant individual result.

methodsphysicsfeature-blockssurrogaterefutationsynthesissurrogate program #145 · 2026-08-30
high

First verified macro: 0.8354 -> 0.8248 scaffold-CV, and 0.8595 -> 0.8404 under series shift

The deployed blocks help MORE out-of-series than in-scaffold - the right direction for a blinded test that is itself a series shift.

Deep dive

First joint-gated result across all four isoforms (scripts/train/combined_gate.py, identical scaffold folds, 3 seeds, base vs +mech vs +aux vs +both). Winners and per-isoform scaffold-CV RAE: CYP3A4 base+aux 0.6215->0.6086; CYP2C9 base+aux 0.8255->0.8036; CYP2D6 base+mech 0.9954->0.9878 (magnitude weak); CYP1A2 base 0.8993 (nothing qualified). MACRO 0.8354 -> 0.8248, delta -0.0106. Under leave-cluster-out the macro goes 0.8595 -> 0.8404, delta -0.0191 - nearly DOUBLE the scaffold-CV gain, which is the encouraging direction: the deployed blocks help MORE under series shift than under scaffold CV, and the blinded test is a series shift (11.5% scaffold overlap, 0% compound overlap). Two corrections to the record: the previously-quoted macro of 0.831 was never verified and was assembled by assuming additivity - the real base is 0.8354 and the real best is 0.8248. And the single-concentration auxiliary, not the mechanistic block, is doing almost all the work: it wins outright on both isoforms where it qualified, and CYP2D6's mechanistic-only gain remains noise-order. Artifact: data/processed/combined_summary.json.

macrojoint-gateverifiedleave-cluster-outcombined-gate · 2026-08-23
high

Five ensemble members is already saturation: 5 to 15 buys 0.43 SD, exactly what 1/n predicts

Tripling the ensemble removes only 13% of the member variance, and that is below every isoform's noise floor.

Deep dive

After seed-bagged averaging deployed at n=5, n=15 was tested on CYP3A4 (best-resolved) and CYP2C9 (the rejected isoform, and the best rescue candidate since its averaging component was the largest at -0.0152 while subsampling cost it +0.0081). The direction confirmed: 2C9's averaging grew to -0.0186 and its total from -0.0071 to -0.0105, 3A4's total from -0.0151 to -0.0167. But the bag15-minus-bag5 difference is -0.0016 on 3A4 (0.43 SD) and -0.0034 on 2C9 (0.30 SD), neither resolvable against the measured seed nulls. That is precisely the 1/n prediction: going from 5 to 15 members removes (1/5 - 1/15) = 13% of member variance, and 13% of an effect that was itself around 0.015 is around 0.002. bag5 is retained everywhere; the extra 3x compute buys nothing that can be distinguished from noise. CYP2C9 stays rejected at 0.93 SD -- close enough that a 12-seed re-run is the honest next step rather than a discard.

baggingensemblingsaturationcyp2c9data/processed/bagged_cyp2c9.json · 2026-08-25
high

Four criteria all computed from the same metric cannot catch a mis-transferred decision rule

A +0.0361 MCC gain passed 12/12 seeds, a 2.89 SD bar and 3/3 LCO clusterings -- and came from a threshold estimator that is 4.2x too low.

Deep dive

Bagged probabilities plus an inner-fold argmax threshold beat the shipped CYP3A4-TDI model by +0.0361 MCC on every one of 12 seeds, at 2.89 SD against the measured 0.0125 null, with LCO agreeing 3 of 3. Both halves alone had been correctly rejected (bagging +0.0098 at 10/12; the inner cut alone +0.0184 in #58), so it presented as a genuine combination effect of the kind that has worked here before. A pre-registered diagnostic then measured what none of the four criteria could see: the inner-estimated cut is 0.0068 against the outer fold's own optimum of 0.0283, so the estimator is 4.2x too low, because inner folds train on fewer rows and produce systematically lower probabilities (median 0.0013 vs 0.0017). The gain is a by-product of that mis-calibration rather than of finding the right cut, and the size of the bias depends on the inner-to-outer fold-size ratio -- which differs between CV evaluation and the final fit-on-all-data-then-predict-test setup, so it would not transfer. The structural lesson: all four honest-gate criteria are computed FROM the scored metric, so they pass identically whether a win comes from a better model or from a decision rule that is broken in a locally helpful direction. When a lever changes how predictions are converted into a score rather than the predictions themselves, at least one check must sit outside that metric.

tdithresholdinggatingestimator-biasmccdata/processed/tdi_threshold_diagnostic_cyp3a4.json · 2026-08-26
high

Four isoforms, four different winning configurations - no single architecture is right for CYP

Every isoform wants a different combination of features and loss weighting. Optimising one global model would be wrong three times out of four.

Deep dive

The full cross {base, +mech, +aux, +mech+aux} x {none, band weighting} gated under ST-RAE gives FOUR DIFFERENT winning configurations, one per isoform, every one clearing the magnitude check: CYP3A4 base+mech+aux + weighting 0.5541->0.5265 (-0.0276); CYP2C9 base+aux with NO weighting 0.7275->0.6894 (-0.0381); CYP2D6 base+mech + weighting 1.0005->0.9304 (-0.0701); CYP1A2 plain base + weighting 0.8695->0.8345 (-0.0350). MACRO 0.7879 -> 0.7452 (-0.0427), and under leave-cluster-out 0.8196 -> 0.7641 (-0.0555) - again the gain is LARGER under series shift than in-scaffold, which is the reassuring direction given the blinded test is a series shift. Three structural observations. (1) No configuration wins twice. A single global architecture would have to be wrong on at least three isoforms, which retroactively justifies gating per-isoform and refusing to optimise a macro directly. (2) Band weighting deploys on 3 of 4 but NOT on CYP2C9, where the single-concentration auxiliary alone wins - and 2C9 is the isoform whose band widths vary least among the deployers. (3) The two levers INTERACT with isoform-dependent sign: super-additive on 2D6 (-0.0701 actual vs -0.0469 additive), merely additive on 1A2, and on 3A4 every weighted config beats every unweighted config with a seed spread of only 0.0018, so weighting dominates feature choice entirely there. Worth recording that a single-seed read of 3A4 suggested features INTERFERE on top of weighting; across three seeds the full config won. Single-seed reads have now misled this campaign twice, which is the argument for keeping seed-stability as a hard gate criterion rather than a formality.

final-gateper-isoformst-raeband-weightingarchitecturefinal-gate · 2026-08-23
high

Gate a layer by removal from the final stack, not only by addition to an old one

CYP3A4's TDI donors were gated at -0.0130 and now cost +0.0261 to remove: the layer doubled in value while nobody was looking.

Deep dive

Every layer in this campaign was gated when added, against the incumbent that existed at that moment, and none had been re-tested by removal from the stack as it now stands. Layers here demonstrably interact: band weighting and imputation multiply a feature block's effect fivefold (#63), donor imputation absorbs the loss-shaping levers (#66), and donors absorb a capacity lever (#83). A greedy assembly path can therefore end on a configuration containing a layer that is redundant or harmful. Leave-one-out ablation on CYP3A4, control reproducing 0.4492 exactly: removing the TDI-condition donors costs +0.0261, twice the -0.0130 they were gated at, so that layer has grown more valuable; removing band weighting costs +0.0243 on every seed against the -0.0234 it was gated at, so that one is unchanged. Both stay. The band result also refutes a specific prior: I expected donors to have absorbed band weighting because #66 showed them absorbing precision-weighting levers, but #66 tested SWITCHING schemes and objectives, whereas removing the weighting entirely is a different operation and still matters. Donors absorb variations among precision-weighting schemes without substituting for having one. The practical rule is that a stack assembled greedily should be ablated once it stops changing, because the value of an early layer is measured against a model that no longer exists.

ablationlayeringstackcyp3a4interactionsdata/processed/ablate_tdi_donors_cyp3a4.json · 2026-08-27
high

Imputation and the single-conc auxiliary SUBSTITUTE, not stack - same data, two encodings, and the simplest config wins

Both derive from the same screen. Once one encoding is in, the other adds nothing - and stacking both costs 0.013 ST-RAE in complexity.

Deep dive

CYP3A4, full cross {base, +aux, +mech+aux} x {no-imp, imp} under ST-RAE with the winning band scheme. WINNER IS THE SIMPLEST CONFIG: base|imp at 0.4773 (base|noimp 0.5307, -0.0534), LCO 0.6110 -> 0.5120. Adding features ON TOP of imputation makes it WORSE, not better: base+aux|imp 0.4904, base+mech+aux|imp 0.4863. The imputation delta collapses as soon as the auxiliary is present: base -0.0534, base+aux -0.0001, base+mech+aux -0.0039. THE EXPLANATION IS STRUCTURAL, not statistical. Both levers draw on the SAME source - the single-concentration screen. The auxiliary encodes it as predicted log2fc FEATURES; imputation encodes it as log2fc-derived LABELS on extra rows. They are two encodings of one information source, so they SUBSTITUTE rather than stack, and the better encoding simply wins. Imputation is the better encoding here (-0.0534 vs -0.0402 for aux alone), plausibly because extra labelled rows teach the model where the response surface actually sits, whereas a predicted feature can only re-describe inputs the model already has. PRACTICAL RULE: before combining two derived signals, ask whether they trace back to the same measurement. If they do, expect substitution and pick the stronger encoding instead of stacking both - stacking cost 0.013 ST-RAE here (0.4904 vs 0.4773) for extra complexity. This is the third distinct interaction pattern observed in this campaign: features interfered with each other on 3A4, features and weighting were super-additive on 2D6, and now features and imputation substitute. There is no default assumption that survives - combinations must be measured every time.

cyp3a4imputationsubstitutioninteractionst-raesimplicityfinal3 · 2026-08-23
high

Imputation donors are silently regularising: they turn a -0.0655 over-training gap into +0.0027

CYP2D6 looks badly over-trained at 500 trees on raw data, and perfectly tuned once its 2,883 donor rows are restored.

Deep dive

A convergence diagnostic on base features showed every isoform peaking well before 500 trees and then degrading -- CYP2D6 best at 25 iterations with a -0.0655 ST-RAE gap to 500, the largest apparent unexploited effect in the campaign. Gated properly in the deployed pipeline with a pre-registered n_estimators=200, it becomes +0.0027, better on 1 of 3 seeds, with LCO -0.0002 at sign stability 0.33: the effect vanishes. The reason is that CYP2D6's deployed pipeline trains on 4,376 rows rather than 1,194, because 2,883 imputation donors are added, and those donors regularise by construction -- they are calibrated predictions entering at r-squared-scaled weights, so they pull the fit toward a smooth consensus and prevent exactly the over-fitting that 500 trees produce on raw data. The tree budget only looked wrong because the diagnostic had removed the thing making it right. This is the third independent arrival at the same rule: #63 found band weighting and imputation MULTIPLY a feature block's effect, #66 found donor imputation ABSORBS loss-shaping levers, and here donors absorb a capacity lever. A hyperparameter's correct value is a property of the configuration it sits in, so a diagnostic run on a stripped pipeline is measuring a different model.

hyperparametersimputationregularisationcyp2d6configurationdata/processed/estimators_cyp2d6.json · 2026-08-26
high

Imputation harm scales with injected variance - 15x between k=1.0 and k=0.2

The imputed labels carry no information where donors live, so what they do to a model depends on their spread, not their accuracy.

Deep dive

Scaling CYP1A2's imputed labels around their own mean (y' = mean + k*(y - mean)) sweeps the harm almost perfectly with k: k=1.0 costs +0.1427 ST-RAE, k=0.7 +0.0599, k=0.4 +0.0144, k=0.2 +0.0093 -- a 15-fold reduction, monotone across that range, with LCO tracking it from 1.0296 down to 0.8651. That is what you expect if the labels convey nothing and act only through their distribution, which the donor-region calibration result independently requires (negative R^2 on every isoform). But two boundaries matter. First, no value of k rescues CYP1A2: the best arm still loses to no imputation by +0.0093, so its 2,964-donor row supply is unusable at any variance level and that question is closed rather than open. Second, k=0.0 -- a pure constant label -- is WORSE than k=0.2 (0.8626 vs 0.8232), so the curve is U-shaped and the benefit is not pure shrinkage; a little label variation beats none. A single unified mechanism across isoforms is NOT established and should not be asserted: CYP3A4 has the strongest signal of the four, benefits most from imputation, and has the best donor-region behaviour (random-split R^2 0.8554, donors 98.1% in-range and MORE inhibitory than measured compounds), which fits genuine information transfer rather than shrinkage. The defensible reading is mixed -- information on CYP3A4, shrinkage on CYP2D6 -- with CYP1A2 getting neither because its own signal is comparatively strong and its inv_width loss already shrinks, so extra rows only dilute.

imputationvarianceshrinkagecyp1a2mechanismswarm · 2026-08-24
high

Imputing missing labels from the dense single-conc matrix makes CYP2D6 much WORSE - imputed labels are 4.8x noisier than the assay

More rows is not more information when each new label carries several band-widths of error. Scarcity beats noise at volume.

Deep dive

Ledger item 4 (sparse-matrix framing) tested in the form with real leverage: the single-concentration screen is 100% DENSE and covers 89% of train, so a compound measured only on 3A4 still has a MEASURED log2fc for 2D6. Calibrating log2fc -> pIC50 per fold and adding the imputed compounds as extra training ROWS would give 3A4 2.10x, 2C9 3.40x, 2D6 2.93x, 1A2 3.10x more data. Note this is NOT the auxiliary route that failed earlier: that predicted log2fc FROM STRUCTURE and fed it back as a feature, so it could only re-express the model's own inputs. Here the log2fc is measured, and it manufactures new LABELS. Not circular either - the labels come from an independent assay, never from our own pIC50 predictions. RESULT ON CYP2D6: badly negative. ST-RAE 0.9626 -> 1.0728 (R^2-weighted) and -> 1.0764 (flat 0.5 weight); leave-cluster-out 0.9863 -> 1.2055/1.2164. Adding 2.93x more rows pushes the isoform from below the mean predictor to well ABOVE it. WHY, quantified: imputed labels are far noisier than real ones. Residual SD of the log2fc->pIC50 calibration vs the assay's own SD is 2.1x (3A4), 2.4x (2C9), 3.1x (1A2) and 4.8x (2D6). 2D6 is worst on BOTH sides - the poorest calibration (R^2 0.496 vs 0.78 for 3A4/1A2) and the tightest real measurements (assay SD 0.134) - so imputation is least accurate exactly where extra data is most wanted. Worse for this metric specifically: a 0.650 SD imputed label is roughly 2.4x CYP2D6's entire median credible band (0.272), so imputed rows actively teach the model to land OUTSIDE the bands that ST-RAE scores. GENERAL LESSON: label noise at volume beats label scarcity only when the noise is small relative to the metric's tolerance. 1,493 clean labels beat 4,376 labels carrying several band-widths of error, and down-weighting does not rescue it because the noisy rows are 66% of the training set.

imputationsparse-matrixcyp2d6label-noisenegative-resultst-raeimpute-rows · 2026-08-23
high

Independent corroboration: the isoform that gained most WIDENED its predictions, the one that gained least compressed further

Not a test I designed - it fell out of comparing two submissions. The shrinkage-tracks-ignorance relationship holds within a single update.

Deep dive

Comparing submissions v04 and v05, which differ only in the Emax-calibrated imputation deployed on CYP2C9 and CYP2D6. CYP2C9 took the campaign's largest single-isoform gain (-0.1242, 0.7275 -> 0.6033) and its predicted SD ROSE from 0.799 to 0.911, now slightly above its training SD of 0.782. CYP2D6 gained far less (-0.0142, 0.9072 -> 0.8930) and its predicted SD FELL further, 0.290 -> 0.257, against a training SD of 0.916. CYP3A4 and CYP1A2 are byte-identical between versions (corr 1.0000) since their recipes did not change, which also confirms the per-isoform builder is not perturbing isoforms it should not touch. WHY THIS MATTERS: the earlier variance-matching work established that prediction shrinkage tracks model ignorance - CYP3A4 needed k=1.32 to restore its training spread and paid +13.3%, CYP2D6 needed k=2.57 and paid +48.4%. That was measured deliberately across two isoforms. This is the same relationship appearing unbidden in a comparison run for a different purpose: a model that learns more about an isoform stops hedging, and one that learns little hedges harder. Corroboration from an experiment not designed to test it is worth more than another deliberate replication, because there was no opportunity to fit the analysis to the expectation.

shrinkagecalibrationcorroborationcyp2c9cyp2d6submission-audit · 2026-08-23
high

Loss reweighting fixes metric misalignment, never information scarcity - three experiments now agree

Band weighting worked because ST-RAE forgives band-interior error. Where there is no misalignment to correct, or the limit is information, reweighting does nothing.

Deep dive

Three weighting experiments across both tracks now form a consistent rule. (1) BAND WEIGHTING on inhibition: large win, deployed on 3 of 4 isoforms, because ST-RAE forgives error inside the credible band so an unweighted loss spent capacity on compounds the metric barely scores - a genuine LOSS/METRIC MISALIGNMENT. (2) CLASS WEIGHTING on TDI: clean null on both scored isoforms, because MCC has no forgiveness structure and prevalence is already handled by thresholding at the training positive rate. (3) LABEL-CONFIDENCE WEIGHTING on TDI, tested this tick: also null, and for a third reason. The TDI label is close to a z-test outcome - |pic50_shift| / band_width correlates +0.695 (3A4) and +0.639 (2D6) with it - so boundary compounds have coin-flip labels and down-weighting them looked principled. It changes nothing: 3A4 best scheme +0.0045, CYP2D6 best +0.0013, neither seed-stable. The reason is that this addresses label NOISE while CYP2D6's TDI problem is an INFORMATION limit - MCC 0.106 means structure barely predicts the label at all, and no reweighting manufactures information that is absent. RULE: loss reweighting fixes misalignment, never scarcity. Diagnose which one you have before reweighting - and if the metric has no forgiveness structure and the model is near chance, do not bother.

tdiweightingmccnegative-resultunifying-ruletdi-labelconf · 2026-08-24
high

Make the harness reproduce a known value before reading any contrast

A defect renders as a confident null. If the control cannot recover a number you already know, the effect you are measuring is the harness.

Deep dive

Three defects in one tick, each one commit from being recorded as science, all caught by the same move. (1) A custom LightGBM objective starts from raw score 0, not the label mean, so it scored 10x baseline and read as 'training on the metric is catastrophic'; init_score fixed it (9.4591 -> 0.8304). (2) With that fixed, softband still lost -- but collapsing its band to a point makes it mathematically IDENTICAL to builtin l1, and it scored MAE 0.7877 against l1's 0.5919. LightGBM renews leaf values to the median for builtin l1/huber/quantile and NOT for custom objectives, so sign gradients with a constant Hessian pin every leaf near +-1 and the fit goes high-variance (pred SD 0.68 vs 0.26). The entire softband-vs-l2 contrast was leaf-value handling. (3) The objective harness re-runs each isoform's DEPLOYED recipe, so its l2 arm must recover the deployed score. It did exactly for CYP3A4 (0.4773) and CYP1A2 (0.8345) but not for the two isoforms using Emax imputation (CYP2C9 0.6924 vs deployed 0.6033) -- donor NaNs were filled from full-table medians instead of donor-subset medians. The apparent CYP2C9 l1 win of -0.0402 was measured against the wrong incumbent. Two standing controls adopted: a custom objective must reproduce its builtin twin, and any harness re-running a deployed recipe must reproduce its deployed score within 0.01 before its deltas are transferable to a submission.

harnessvalidationlightgbmobjectivemetaswarm · 2026-08-24
high

MCC on a 21% positive rate wants a far more permissive cut than prevalence matching

The MCC-optimal probability threshold sits around 0.002-0.017. Prevalence matching sits at 0.058-0.082, and the naive 0.5 is a disaster. Nobody had looked.

Deep dive

All six TDI experiments in this campaign thresholded at the training positive rate, a default that makes predicted prevalence match observed prevalence and was never questioned. Measuring it on CYP3A4-TDI: an oracle threshold chosen on the evaluation fold reaches MCC 0.4075 against the incumbent's 0.3571, so threshold choice alone is worth +0.0504, or 14% relative -- larger than any feature effect the TDI track has produced. The direction is the substantive part. MCC-optimal cuts land at 0.002 to 0.017 in probability, roughly an order of magnitude BELOW prevalence matching, because at a 21% positive rate MCC rewards recovering positives more than it penalises the false positives that come with them. That is a property of the scoring function rather than of any model, and it stayed invisible while every experiment shared one default. The incumbent is nonetheless only half-wrong: cutting at 0.5 costs -0.0616, so prevalence matching was far from arbitrary, it simply stops short. A realistic estimator -- choosing the cut by MCC on an inner cross-validation of the training fold, never touching the evaluation fold -- recovers 36.5% of the ceiling (+0.0184) and improves leave-cluster-out by +0.0382, but fails seed-stability, and the reason is visible: the inner-CV optima themselves scatter across 0.002, 0.004 and 0.009 between seeds. So the difficulty is ESTIMATING the cut, not the existence of a better one, which points specifically at a smoothed or shrunk estimator rather than at abandoning the idea.

tdimccthresholdclass-imbalancedecision-ruleswarm · 2026-08-24
high

Measure the ceiling, then measure what survives estimation - a large ceiling with poor survival is closed, not an invitation

Both TDI isoforms have threshold headroom bigger than any feature effect the track has produced. Neither can reach it, and the estimator improving as theory predicts did not change that.

Deep dive

Threshold choice on the TDI task offers +0.0504 MCC on CYP3A4 and +0.0380 on CYP2D6 at the oracle, both larger than any feature effect six TDI experiments produced. Two rounds of estimator work followed. Pooled argmax on an inner cross-validation captured 36.5% on CYP3A4 while failing seed-stability and -101.8% on CYP2D6, actively worse than the untuned default. Variance reduction then did exactly what bias-variance theory prescribes: on CYP3A4 the median across inner folds lifted capture to 44.4% and log-space shrinkage traced a clean optimum (37.3% at L=0.5, over-shrinking to 23.0% at L=0.75), while on CYP2D6 increasing shrinkage monotonically halved the damage at every step -- -101.8%, -51.1%, -27.1%, -11.8% -- converging on the incumbent as L approaches 1. That monotone ordering matters independently of the verdict, because it shows the mechanism was implemented correctly and the null is real rather than a bug. Neither isoform crossed into a deployable gain. The CYP3A4 diagnostic explains why no further estimator work would help: the seed whose threshold estimate was CLOSEST to its oracle (0.83x) lost the MOST MCC (0.0414), while a seed 4.5x off lost less, so threshold accuracy and MCC cost are inversely related across folds and the payoff surface for the estimated quantity shifts underneath it. The transferable procedure: for any tuned decision rule, measure the oracle ceiling first, then measure what fraction survives honest estimation, and treat a large ceiling with poor survival as a CLOSED question rather than a reason to estimate harder.

decision-rulesceilingestimationbias-variancetdiclosed-questionswarm · 2026-08-24
high

Model capacity is at a local optimum: more hurts where the cap binds, less does nothing where it does not

num_leaves=64 was never tuned, and testing both directions shows there is nothing to gain either way.

Deep dive

The campaign shipped one LightGBM capacity across four isoforms without examination. Dumping the fitted trees showed the 64-leaf cap binds on CYP3A4 alone (100% of trees) and never on CYP2C9, CYP2D6 or CYP1A2 (40.5, 46.8, 44.1 mean leaves, bounded by min_child_samples and the data). Both directions were then tested with LightGBM's own default of 31 as the reduced value, chosen over a search so it cannot be accused of being picked because it worked. Results: CYP3A4 at 128 leaves is +0.0160 on scaffold-CV with LCO +0.0128 positive on 3 of 3 clusterings and above its 0.0067 threshold, which is resolvable contrary evidence; CYP2D6 at 31 leaves is +0.0026 at 0.5 SD; CYP1A2 at 31 leaves is -0.0001 at 0.02 SD. The effect magnitudes track where the cap binds -- CYP3A4's is six times CYP2D6's -- which is the internal consistency check that the diagnostic was measuring something real. The practical reading is that the cap is doing useful work as regularisation against a hard scaffold shift on the one isoform with enough data to overfit, and is simply inactive elsewhere. Capacity is closed as a lever in both directions.

hyperparameterscapacitylightgbmclosed-familydata/processed/capacity_cyp2d6.json · 2026-08-26
high

Near-constant imputed labels are a regulariser, not a bug

CYP2D6's calibration collapses to a fifth of the real label spread, and imputation still deploys there. Under a band-forgiving metric, pulling predictions toward the centre is rational.

Deep dive

Measuring the imputed-label distributions across all four isoforms overturned an assumption worth stating. CYP2D6's donors sit 99.6% OUTSIDE the measured single-conc log2fc range, so its calibration extrapolates and produces labels with a standard deviation only 0.202 of the real labels' -- nearly a constant, spanning 17% of the real range. That looks like a broken calibration, and imputation is nonetheless DEPLOYED on CYP2D6, where it is worth 0.9304 -> 0.8930. The reason is the metric: ST-RAE charges nothing inside each compound's credible band, so a large block of rows carrying an approximately constant label acts as a shrinkage prior toward the band centre, which is exactly the rational prediction where the model has no discriminating signal. By contrast CYP1A2, where imputation is catastrophic (1.37, far worse than the mean predictor), has donors 98.7% INSIDE the measured range with an imputed spread of 1.068x the real one -- interpolating, realistically varied, directionally correct in its bias, and still destructive. So the usual diagnostic instinct is inverted here: the isoform whose imputed labels look worst by every distributional check is the one where they help, and the isoform whose labels look healthiest is the one where they wreck the model. Judge imputed labels by what they do under the scoring metric, not by how much they resemble real ones. CYP1A2's failure remains unexplained after donor mass, effective sample size, and extrapolation were each ruled out.

imputationregularisationst-raecyp1a2cyp2d6diagnosticsswarm · 2026-08-24
high

No label-based precondition can be evaluated on the population a donor layer is applied to

A calibrator can only be scored on compounds that have the label. Donor rows are, by definition, the compounds that do not. So a precondition measured on the labelled set says nothing reliable about the unlabelled one it will be used on.

Deep dive

Cross-isoform completion for CYP1A2 gave a decisive precondition -- adding other-isoform pIC50 labels lifted scaffold R2 for predicting held-out CYP1A2 from 0.1042 to 0.1985, better on 3 of 3 seeds -- and the gate then rejected it: 0.7688 deployed against 0.7794 with donors, +0.0106 worse on every seed at 1.47 SD, control reproducing. The precondition was measured on the 469 compounds that have a CYP1A2 label while donors are by construction those that do not, and an earlier tick had already shown calibration runs negative-R2 in the donor region on all four isoforms. The consequence generalises to every donor or imputation layer: such preconditions are structurally incapable of predicting donor-layer success, so the gate is not an expensive confirmation but the only available test. It also explains without further appeal why CYP1A2's imputation failed four separate ways while the identical machinery is worth +0.1045 on CYP2C9 -- donor-layer value is not a function of calibration quality. Sparse-matrix completion, the last untried item on the priority list, is closed on this measurement.

imputationdonorspreconditionsmatrix-completioncyp1a2rejectedscripts/train/crossiso_gate.py; data/processed/crossiso_gate_cyp1a2.json; ledger #123, #124 · 2026-08-29
high

On a noisy endpoint the SEED BLOCK is part of the configuration, not just its size

Three estimates of the same deployed CYP2D6 TDI model span 1.17 SD depending only on which twelve seeds were drawn. Matching the number of seeds is not the same as matching the seeds.

Deep dive

The same shipped model scores MCC 0.0791 on seeds 42-53, 0.0970 across a 20-seed audit, and 0.1058 on the incumbent's own twelve seeds. CYP2D6 TDI has a measured MCC seed SD of 0.0229, so the choice of seed block moves the absolute score by more than most effects this campaign measures. A gate that uses a different block from the incumbent fails the fifth criterion even when its own arms are internally consistent: here the control missed by 0.0179 against a 0.0132 tolerance, so a +0.0715 improvement at 3.12 SD is formally not transferable and does not deploy. The paired delta itself is sound -- both arms share every seed and fold, so a low block cannot manufacture the difference -- but 'the delta is fine' is precisely the argument shown insufficient when a clean delta over a wrong baseline fooled this campaign before. Practical rule: gates must reuse the incumbent's seed list, not merely the same number of seeds.

seed-noisefifth-criteriontdicyp2d6controlsharnessdata/processed/tdi_family_cyp2d6.json, tdi_seed_audit_cyp2d6.json; ledger #107 · 2026-08-28
high

On CYP3A4 the binding constraint on physics is block WIDTH, not block quality

Adding 20 to 23 arbitrary columns costs about +0.005 RAE. The real mechanistic blocks beat their permuted twins by only 0.0025 to 0.0088. The signal is real and the column budget eats it.

Deep dive

Running each feature block against its own row-permutation null on public CYP3A4 data separates two effects that are normally confounded. A permuted block preserves column count, column marginals and within-block collinearity while destroying the compound correspondence, so the difference between an absent block and a permuted block measures the pure cost of widening the feature matrix, and the difference between a permuted block and the real block measures genuine signal. On CYP3A4 the width cost of adding 20 to 23 arbitrary columns is approximately +0.005 RAE, while the real mechanistic and heme-ligation blocks beat their permuted twins by 0.0025 to 0.0088. Both blocks therefore carry real signal of roughly the same magnitude as the penalty they incur simply by being that wide, which is why they never separate from absence and appear to be worthless. The constraint is not the quality of the physics but the width in which it arrives, and the implication is that a narrow block encoding the same mechanism could win where a wide one cannot. This also explains why real-versus-absent is the wrong comparison and real-versus-permuted is the right one: the former conflates signal with width penalty. Supporting diagnosis from the same run: the strongest single mechanistic term correlates with the target at +0.218 but with the base model's error at only +0.039, which is the campaign's long-standing observation that alignment with truth is not the same as adding signal, now measured at 3010-compound resolution.

methodspermutation-nullwidth-penaltycyp3a4physicsfeature-blocksdata/processed/surrogate_cyp3a4_public.json · 2026-08-30
high

One cheap solo run predicts whether a second model family will help -- break-even is a solo gap of +0.03 to +0.05

Averaging a second family in helps only if that family is nearly as good alone. Measure it alone first: that single arm tells you the sign before you pay for the full gate.

Deep dive

Sweeping ExtraTrees' max_features on CYP3A4 cut its solo gap 63% (sqrt/~48 features +0.1395, 0.1/227 +0.0724, 0.3/680 +0.0514) -- so the MAGNITUDE of its incompetence was largely an untuned hyperparameter of mine, not a property of the family. The full gate at mf=0.3 still rejected (+0.0097, 0/3, 2.63 SD, control reproducing), exactly as the rule predicted, and the HARM fell by the same 63% as the gap -- a within-isoform pair with everything else fixed, far stronger evidence than the original cross-isoform monotonicity. Across five measured configurations: gap +0.0090 -> -0.0110 (2D6), +0.0307 -> -0.0089 (1A2), +0.0514 -> +0.0097 (3A4 mf=0.3), +0.1179 -> +0.0095 (2C9), +0.1395 -> +0.0261 (3A4 sqrt). Break-even is therefore bracketed between +0.031 and +0.051. The mechanism behind the ceiling: competence and decorrelation move against each other (max_features -> 1.0 turns ExtraTrees into a plain randomized-threshold forest), so improving the second model improves it as an ensemble member proportionally but does not convert it into one. Cost matters too -- mf=0.3 is roughly 10x the fit time for a result that is still a rejection.

ensemblingextratreesscreeningcompetencecyp3a4rejectedhyperparametersscripts/train/et_competence_sweep.py; data/processed/et_competence_cyp3a4.json, family_et_cyp3a4_mf03.json; ledger #103 · 2026-08-27
high

Optimal calibration slope flips sign with model quality: good models want expansion, weak ones want shrinkage

CYP3A4 and CYP2C9 want their predictions stretched; CYP2D6 and CYP1A2 want theirs pulled toward the mean.

Deep dive

An explicit shrinkage pred' = mean + alpha*(pred - mean) was swept from 0.70 to 1.10 on all four isoforms' deployed out-of-fold predictions. The per-isoform optima disagree in SIGN: CYP3A4 1.06 and CYP2C9 1.08 want expansion, CYP2D6 0.86 and CYP1A2 0.88 want shrinkage, with corr(best alpha, deployed ST-RAE) = -0.919. The reading is mechanical: a well-predicted model is under-confident and benefits from stretching, while a model close to the mean predictor is mostly noise and benefits from being pulled toward the mean. The practical consequence is that any selection method which pools across isoforms -- leave-one-isoform-out here -- averages opposing signals toward alpha near 1.0 and lands each isoform on the wrong side of its own optimum, which is exactly what happened (2D6's optimum 0.86, LOIO handed it 1.04, delta +0.0024). The family closes anyway on magnitude: even the in-sample optimum, fitted on the same predictions it scores and therefore an upper bound, is worth at most -0.0050 on CYP1A2 against that isoform's 0.0072 seed null. A gain smaller than the noise cannot be captured honestly by any estimator.

calibrationshrinkagest-raecross-isoformrejecteddata/processed/shrinkage_gate.json · 2026-08-26
high

Patching a symptom twice is the signal to fix the pattern

The incumbent-reproduction control has now anchored on a superseded model twice, for the same structural reason, because the first fix hardcoded one layer instead of walking them all.

Deep dive

The fifth gate criterion says a harness re-implementing a deployed pipeline must reproduce that pipeline's score. It was implemented as deployed_score(), reading the feature/imputation gate artifact. The objective gate (#44) then stacked a layer on top and the control silently anchored CYP1A2 at a superseded 0.8345 instead of the bandl2 0.8139 it actually ships -- caught, and fixed by special-casing the objective artifact. One tick later the donor gate (#50) stacked ANOTHER layer, and the identical failure recurred: the donor-weight sweep's control reported DOES NOT REPRODUCE because deployed_score() returned CYP3A4's pre-donor 0.4773 while the live model scores 0.4643. The verdict was unaffected -- the alpha=1.00 arm reproduced 0.4643 to four decimals, so the sweep was sound -- but a control that cries wolf is worse than none, because the next real failure gets waved through. The first fix treated the objective layer as a special case; the correct fix treats LAYERING as the invariant. deployed_score() now walks an ordered list of deployment layers, newest first, returning the score of the last one that actually deployed, with a comment instructing future layers to be added there rather than patched at the call site. General rule: when the same defect recurs in a new guise, the earlier fix addressed an instance rather than the mechanism, and the second occurrence is the cue to generalise rather than patch again.

controlhonest-gatelayered-pipelineself-correctiontechnical-debtswarm · 2026-08-24
high

Permutation-null bias is a property of the block's column statistics, not of the comparator

The same control reads -0.008 to -0.015 on a dense descriptor block and indistinguishable from zero on a sparse count block. Permuting a mostly-zero column just yields another mostly-zero column.

Deep dive

A zero-information control block, built by copying columns verbatim out of the base feature matrix, was used to calibrate how much a block gains simply from its permuted twin being a damaged comparator. On non-CYP surrogates that control showed a large bias of -0.008 to -0.015 RAE under LightGBM, which appeared to threaten every permutation-null verdict in the program. Repeating it inside the deployed CYP2D6 configuration gives a bias floor of -0.0023 plus or minus 0.0011 on 4 of 6 seeds at p=0.34, which is not distinguishable from zero, so the mechanistic block's raw information term of -0.0419 corrects only to -0.0396, retaining 94.5 percent. The reason for the six-fold discrepancy is the decisive part and it generalises. Column statistics differ sharply between the two blocks: the mechanistic block has a median column cardinality of 4, with 52 percent of columns taking five or fewer distinct values and a mean zero-fraction of 0.50, whereas the duplicated descriptor block has median cardinality 971, no low-cardinality columns and a mean zero-fraction of 0.04. Permuting a sparse low-cardinality column produces another sparse low-cardinality column, which can barely win a greedy split search and therefore does little damage to the null arm. Permuting a dense continuous column produces strong competing noise that does damage it, inflating the apparent advantage of whatever is being tested against it. The operational rule is that the bias floor must be measured per block rather than applied as a constant: dense continuous blocks such as learned embeddings, 3D shape descriptors and quantum-chemical features require the correction, while sparse count-based blocks such as SMARTS alerts and the mechanistic block barely do. Note also that a correction measured on a dense block and subtracted from a sparse block's comparison is conservative, so such a corrected figure is a lower bound.

methodspermutation-nullbiascontrolscolumn-statisticscyp2d6data/processed/dupe_null_cyp2d6.json · 2026-08-30
high

Prediction shrinkage tracks model ignorance on BOTH isoforms tested - compression is calibration, not a defect

The better the model, the less it hedges and the cheaper hedging is to undo. Shrinkage is the model reporting what it does not know.

Deep dive

Replication of the CYP2D6 variance-matching result on CYP3A4, deliberately run because a mechanism proposed from one isoform has failed five times in this campaign. CYP3A4 (ST-RAE 0.5313, well modelled): predicted SD 0.827 vs training 1.093, so k=1.32 restores the spread and full matching costs +0.0705 = +13.3% relative. CYP2D6 (ST-RAE 0.9072, barely modelled): predicted SD 0.356 vs 0.916, k=2.57, cost +0.4392 = +48.4%. Both the amount of compression and the cost of undoing it scale with how well the isoform is modelled. Band-hit rate falls with expansion in both cases (3A4 36.2% -> 34.7%, 2D6 18.5% -> 14.2%), so widening pushes predictions out of the credible bands without improving ordering regardless of model quality. CONCLUSION: under ST-RAE, prediction shrinkage is a calibrated response to epistemic uncertainty rather than a pathology, and the penalty for forcing a realistic-looking spread scales with ignorance. Never variance-match on this challenge; read a compressed prediction as the model reporting uncertainty. Worth flagging that CYP3A4 is where expansion looked most affordable at 13.3% - and it still buys nothing.

calibrationshrinkagest-raereplicationcyp3a4cyp2d6variance-match · 2026-08-23
high

Re-measuring at higher power is two-sided: CYP2C9's effect grew across the bar, CYP1A2's shrank away from it

If extra seeds only ever promoted effects, the re-run protocol would just be a slow way of tuning on the evaluation.

Deep dive

Ledger #62 pre-registered that effects near the deploy bar get re-run at higher power before being trusted or discarded, with the outcome committed both ways. Two cases now bracket what that buys. CYP2C9's bagged averaging read -0.0105 at 3 seeds and 0.93 SD, then -0.0138 at 12 seeds and 1.22 SD, crossing the bar and deploying. CYP1A2's reduced tree budget read -0.0032 at 3 seeds and 0.43 SD, then -0.0024 at 12 seeds and 0.33 SD, moving further below it. In both cases the effect itself never changed; only the instrument did, and it moved in opposite directions. That is the property that makes the protocol honest rather than a ratchet: a procedure that only ever revised effects upward would eventually promote anything measured often enough, which is tuning on the evaluation by a slower route. It also cautions against reading a 3-seed estimate as a lower bound. A related distinction worth holding separately: CYP1A2 improved on 12 of 12 seeds, p about 0.0002 on a sign test, so its DIRECTION is essentially certain while its MAGNITUDE is a third of the isoform's seed noise. Direction and magnitude are different claims and the deploy bar governs the second.

powerseed-noisepre-registrationgatingepistemicsdata/processed/estimators_cyp1a2.json · 2026-08-26
high

REFUTED: the screen is not redundant on CYP2D6 because of donors -- the same block is 4x worse on the isoform with none

I claimed the single-conc screen fails on CYP2D6 because imputation donors already import it. CYP1A2 has no donors, so that predicts a better result there. It is four times worse.

Deep dive

This corrects a high-confidence finding published one tick earlier. The claim was that a learned representation of the single-concentration screen hurt CYP2D6 (+0.0281) because that isoform deploys with imp=True and therefore already receives the screen through imputation donors. CYP1A2 is the only isoform with imp=False, making it the discriminating test. Gated inside apply_deployed(cyp1a2) with the control reproducing at 0.7688 against a recorded 0.7722: deployed 0.7688, plus-embedding 0.8830, delta +0.1141, better on 0 of 3 seeds. That is 4.1 times worse than on CYP2D6, the opposite of what the donor explanation predicts. The replacement account fits both isoforms and the external literature: CYP inhibition is a lipophilicity-dominated endpoint, with logP or logD the sole descriptor in roughly 80 percent of published CYP QSARs at R above 0.9, and the one controlled study testing a QM descriptor block against a lipophilicity endpoint found it degraded the model. Appending 64 learned columns to a tree that already carries 2,265 features on 1,412 compounds is dilution rather than information, and CYP1A2 suffers most because it already carries the joint block's extra columns. The generalisable methodological point is that the original explanation was consistent with every observation available when it was written and still wrong, because the isoform that discriminates it had not been run: a causal claim published from a single isoform is a hypothesis, not a finding.

cyp1a2cyp2d6single-concself-correctionrefutationmethodsscripts/train/gate_embed_block.py · 2026-08-29
high

Rescue a rejected block by testing its combination, not by relaxing the gate

When two blocks each miss on the same criterion, the tempting move is to decide that criterion mattered less. The honest move is to ask whether they pass together under the rules as written.

Deep dive

CYP1A2 produced the same signature twice from unrelated feature families: the external public-panel block improved leave-cluster-out by -0.0127 and the 3D conformer/shape block by -0.0116, both exceeding CYP1A2's measured resolvable LCO threshold of 0.0081, while both failed scaffold-CV seed-stability and were rejected. That is exactly the situation where a campaign talks itself into moving the goalposts -- arguing after the fact that LCO deserves more weight than seed-stability because the blinded test is a scaffold shift, which happens to be true and is therefore especially seductive. The discipline that keeps this honest is to separate two different questions. Whether the GATE is right is a question to settle in advance, on general grounds, never while staring at a result you would like to deploy. Whether a COMBINATION passes the existing gate is a fresh empirical question that costs nothing in integrity, because the criteria are unchanged and the answer can still be no. The campaign's own record supports asking it: interference, super-additivity and substitution have each held exactly once here and generalised to nothing, so combination behaviour is genuinely unpredictable per isoform and must be measured rather than inferred from single-block deltas. If the joint arm fails too, the blocks stay rejected and the LCO pattern remains a flagged near-miss with a pre-registered condition for revisiting -- which is a perfectly good outcome, and better than a deployment obtained by rewriting the rules.

honest-gatecombinationsdisciplinecyp1a2goalpost-movingswarm · 2026-08-24
high

Row imputation FLIPS SIGN by isoform: -0.053 on CYP3A4 (biggest gain of the campaign) vs +0.110 on CYP2D6

Imputation pays exactly when the imputed label's error is small relative to the credible band the metric forgives.

Deep dive

Row imputation from the dense single-concentration matrix does NOT have a single sign - it flips by isoform, and the flip tracks imputed-label noise almost exactly. CYP3A4 (calibration R^2 0.777, imputed residual SD 0.524 vs assay SD 0.253 = 2.1x noise ratio, 2.10x more rows): ST-RAE 0.5307 -> 0.4773 (-0.0534, DEPLOY, magnitude meaningful) and leave-cluster-out 0.6110 -> 0.5120 (-0.099). That is the LARGEST single gain of the entire campaign, and the series-shift gain is nearly double the in-scaffold one. CYP2D6 (R^2 0.496, residual SD 0.650 vs assay SD 0.134 = 4.8x noise ratio, 2.93x more rows): ST-RAE 0.9626 -> 1.0728 (+0.110) and LCO 0.9863 -> 1.2055. Same procedure, opposite sign, and a swing of 0.16 ST-RAE between the two isoforms. THE MECHANISM SURVIVES AND IS NOW BIDIRECTIONAL EVIDENCE: what matters is how noisy the imputed label is RELATIVE to the metric's tolerance. On 3A4 an imputed label carries 0.524 SD against a median credible band of 0.379 - same order, so imputed rows still point the model at roughly the right place while tripling the data. On 2D6 it carries 0.650 SD against a 0.272 band - about 2.4 band-widths of error, so imputed rows teach the model to land outside the region ST-RAE actually scores. PREDICTION for the two untested isoforms, from their noise ratios: CYP2C9 (2.4x, band 0.526) should GAIN, and CYP1A2 (3.1x, band 0.328) is the genuinely uncertain case sitting between the two regimes. Both are now running as a real test rather than a confirmation.

imputationcyp3a4cyp2d6sign-flipst-raeself-correctionimpute-rows · 2026-08-23
high

Seed noise is monotone in training-set size, so the effect-size bar differs 3x between isoforms

CYP2C9 needs an effect three times larger than CYP3A4 to clear the same bar. Nobody knew that, and it means CYP2C9 experiments have been the least powered all along.

Deep dive

Measuring each isoform's deployed base arm across 12 scaffold-CV seeds gives seed-SDs that are perfectly monotone in training-set size: CYP2C9 with 1,285 rows at 0.0113, CYP1A2 with 1,412 at 0.0072, CYP2D6 with 1,493 at 0.0052, and CYP3A4 with 2,335 at 0.0037. That is a threefold spread in the noise floor across isoforms of the same campaign, which means the smallest effect worth chasing is isoform-specific and had been treated as if it were not. The three-seed estimates every gate printed were 0.0216, 0.0037, 0.0059 and 0.0020 -- errors of 191%, 51%, 113% and 54%, two overstating and two understating, so the unreliability is symmetric and there is no systematic correction to apply. Re-reading the whole deployed model against measured nulls: CYP3A4 band weighting 6.32 SD, CYP3A4 TDI-condition donors 3.51 SD, CYP1A2 bandl2 2.86 SD, CYP2C9 single-conc aux 1.95 SD, CYP2D6 mechanistic block 1.48 SD, CYP1A2 external+3D 1.44 SD -- three comfortable, three marginal, none withdrawn. The gate has been sorting correctly regardless: every deployed effect exceeds every rejected one in SD terms bar a single 1.34-SD near-miss. One caution learned in passing: the monotone SD-versus-n ordering does NOT support a scaling law. Fitting a power law to three of the points gave a slope of -5.13 against the -0.5 that sampling noise predicts, and extrapolating it would have missed CYP3A4's true SD by a factor of seven. Three points over a 16% range in n can order isoforms without being able to scale them.

seed-varianceeffect-sizeper-isoformcalibrationhonest-gateswarm · 2026-08-24
high

ST-RAE is almost entirely a test of the potency extremes; the middle of the range is pure downside

A row whose credible band contains the global mean has a denominator of exactly zero. It can only add numerator, never denominator. Between 15% and 37% of rows are like that.

Deep dive

ST-RAE's denominator is the mean predictor's soft error, and it is strongly U-shaped in potency. Bottom and top deciles carry 45-58% of ALL denominator mass (3A4 45.3, 2C9 58.3, 2D6 56.4, 1A2 54.8) while middle deciles carry essentially zero (3A4 deciles 4-6: 0.3 / 0.0 / 3.6). Rows whose band contains the global mean have a denominator of exactly 0 -- 357/2335 on 3A4 (15.3%), 470/1285 on 2C9 (36.6%), 279/1493 on 2D6 (18.7%), 321/1412 on 1A2 (22.7%). Those rows cannot help the score and can only hurt it. Two consequences. First, the same RMSE converts into very different ST-RAE on different isoforms, because the fraction of dead rows varies 2.4x -- so cross-isoform score comparisons are not accuracy comparisons. Second, the metric rewards getting the actives and the inactives right and is indifferent to the middle, which is where the band-weighting and bandl2 levers already deployed get their leverage. Computed from labels and shipped credible bands alone, no model fitting.

st-raemetricmethodsall-isoformsscripts/train/margin_over_constant.py · 2026-08-29
high

ST-RAE's dead zone is not being exploited: the deployed levers win outside the bands, not inside them

bandl2 shrinks predictions 16% yet lands inside the credible bands no more often than plain l2 does.

Deep dive

ST-RAE charges nothing for a prediction inside a compound's credible band, so a lever could improve it by shrinking toward band centres rather than predicting better. Both deployed inhibition levers shrink spread, making the campaign's 0.6910 to 0.6753 move a fair target for this suspicion. Audited with band-blind metrics plus the in-band rate of predictions. Bagging: real on both isoforms tested, with MAE, RMSE and Spearman all improving and shrinkage of only 1-4% (3A4 SD ratio 0.988, 2D6 0.960). bandl2 on CYP1A2: ST-RAE -0.0206 and MAE -0.0061 improve, Spearman flat at -0.0045, RMSE worsens +0.0111, predicted SD shrinks to 0.843 -- and the in-band rate is UNCHANGED at 22.1% versus 21.7%. Since the in-band count does not move, the ST-RAE gain comes from shorter out-of-band distances: being less wrong when wrong, which is what the metric is defined to reward. The MAE/RMSE divergence names the cost precisely -- shrinkage helps the many mid-range compounds and hurts the extremes. Practical rule: to test whether a metric's forgiveness region is being gamed, measure how often predictions land in that region, not whether proxy metrics happen to agree.

st-raedead-zonebandl2baggingmetric-gamingauditdata/processed/deadzone_objective_cyp1a2.json · 2026-08-26
high

State a precondition, then MEASURE it before running -- ridge alone was worse than the mean predictor

The blend's failure was determined before any gate ran, by a thirty-second check I wrote down and skipped.

Deep dive

A 50/50 GBM-plus-ridge blend was pre-registered on CYP3A4 to buy bias diversity, which bagging cannot supply because its members share a tree's inductive bias. The pre-registration explicitly named the condition -- where two models are individually COMPARABLE and their errors are weakly correlated, the average beats both -- and then tested the blend without measuring either half. The gate returned the largest negative in the campaign: +0.0458 scaffold-CV and +0.0699 LCO, resolvable at ten times threshold. The decomposition shows exactly one of the two conditions failed: error correlation was 0.274, genuinely low, so diversity WAS available; but ridge alone scored 2.1142 against the GBM's 0.5548, worse than predicting the mean, because alpha=1.0 on 2265 standardised features with 1868 rows is p greater than n and barely regularised at all -- its predicted SD was 2.013 against a label SD of 1.093. Blending a good model with one that bad at equal weight can only destroy. The transferable habit is narrow and cheap: when a pre-registration names a precondition, measure it as its own step before the experiment. Fitting the second model alone costs seconds and either validates the design or makes the run unnecessary.

ensemblingmodel-classpre-registrationridgeself-correctiondata/processed/blend_cyp3a4.json · 2026-08-26
high

Stratifying residuals on the true label measures shrinkage, not opportunity -- it refuted a load-bearing claim

The campaign believed base under-predicts cationic CYP2D6 ligands by +0.211 log at 4.8 sigma. Measured properly it is +0.0141 at p=0.567, and within potency deciles it reverses sign.

Deep dive

Two Queens independently hit the same statistical trap and it invalidates a claim the campaign had been building on. Any regression model shrinks predictions toward the mean, so residual correlates with the true label by construction: Queen C measured residual-versus-truth at r = +0.915 with slope +0.822 on held-out CYP2D6 data. Consequently any subgroup that is more potent than average will appear to be systematically under-predicted, and that appearance is a property of shrinkage rather than a missing feature. The campaign's claim that the base model under-predicts cationic CYP2D6 ligands by +0.211 log units at 4.8 sigma was exactly this artifact. Re-measured on 8,961 public CYP2D6 compounds the cationic-versus-neutral residual difference is +0.0141 with t = 0.57 and p = 0.567, and stratifying within potency deciles reverses the sign to -0.2355. Cationic ligands are simply more potent, by +0.28 log units in that key. The decisive practical test is that applying an in-fold cationic offset correction buys exactly -0.0000 RAE. Queen A reached the same conclusion independently on CYP2C19, where potency-decile eta squared of 0.63 collapsed to 0.0016 on ionisation and 0.0104 on chemotype once strata were chosen independently of the label. The rule adopted is to stratify residual analyses only on label-independent variables such as chemotype, molecular weight, source and ionisation class, and to report potency-decile structure explicitly as shrinkage rather than as an opportunity. A related casualty: on CYP2D6 logD7.4 turns out to be an ionisation proxy rather than a lipophilicity signal, since protonation controlling for clogP gives +0.267 while logD controlling for protonation gives +0.034 at p=0.186.

methodsstatisticsshrinkagecyp2d6refutationfailure-analysisdata/processed/surrogate_cyp2d6_public.json · 2026-08-30
high

TDI baseline established: CYP3A4 AUROC 0.804 but CYP2D6 only 0.574 - 2D6 is structure-opaque on BOTH tracks, not just regression

The 2D6 problem is not a regression problem. The same isoform is near-chance on a completely different task with different labels and different chemistry driving it.

Deep dive

First TDI models (scripts/train/tdi_baseline.py, LGBM classifier on Morgan+RDKit, scaffold 5-fold CV, 3 seeds). CYP3A4: n=3583, 21.3% positive, AUROC 0.804 / AUPRC 0.503 / MCC 0.30, holding at AUROC 0.775 under leave-cluster-out - a genuinely useful model. CYP2D6: n=1497, 21.6% positive, AUROC 0.574 / AUPRC 0.284 / MCC 0.08, LCO 0.578. AUPRC 0.284 against a 0.216 base rate is close to no lift. This matters strategically: TDI is mechanism-based (reactive metabolite covalently kills the enzyme) and is driven by metabolic liability, NOT binding affinity - a completely different problem with different labels. 2D6 being near-chance on BOTH tracks means the issue is not the pIC50 objective or the RAE metric; whatever determines 2D6 chemistry is not encoded in 2D structure at all. Strengthens the case that 2D6 needs 3D/mechanistic representation rather than any further 2D modelling.

tdicyp2d6cyp3a4baselinestructure-opaquetdi-baseline · 2026-08-23
high

TDI class weighting is a clean null on BOTH scored isoforms - the alert block stays dead

The conditions that rescued a rejected block on the inhibition track do nothing here, because MCC has no forgiveness structure to exploit.

Deep dive

TDI, scored on MCC (the leaderboard's primary classification metric) at the training-positive-rate threshold, 3 seeds, scaffold CV, testing {base, base+alerts} x {none, scale_pos_weight}. CYP3A4 base 0.3571: balanced 0.3521 (-0.0050), +alerts 0.3555 (-0.0016), +alerts+balanced 0.3505 (-0.0066). CYP2D6 base 0.1059: balanced 0.1059 (+0.0000), +alerts 0.1006 (-0.0053), +alerts+balanced 0.1046 (-0.0013). NOTHING is seed-stable on either isoform and nothing clears the magnitude check; every variant is at or below base. The structural-alert block stays withdrawn even under the exact conditions that were expected to rescue it. Also worth stating plainly: CYP2D6's TDI model is at MCC 0.106, which is close to useless, and no weighting or alert variant changes that - consistent with 2D6 being structure-opaque on the TDI track as well as the inhibition track. WHY THE INHIBITION INSIGHT DID NOT TRANSFER: band weighting worked because it corrected a MISMATCH between loss and metric - ST-RAE forgives error inside the credible band, so an unweighted loss spent capacity on compounds the metric barely scores. MCC has no such forgiveness structure; it treats compounds symmetrically once a threshold is fixed, and prevalence is already handled by thresholding at the training positive rate, so scale_pos_weight merely shifts a probability scale that the threshold then undoes. The rule to carry forward is that loss weighting pays off when it aligns the loss with a quirk of the scoring function, and is inert otherwise - not that weighting is generally worth trying.

tdimccclass-weightingnegative-resultcyp2d6tdi-weighted · 2026-08-23
high

TDI is scored on MCC, not AUROC - and tuning the threshold for MCC makes it WORSE once validated nested

Threshold selection is a fitting step. An optimum found on the data you score it on is not a gain, it is a leak.

Deep dive

Two discoveries about the TDI track. FIRST, the leaderboard's classification metrics are MCC, Accuracy, Precision, Recall, F1 - and the config comment states the primary/sort metric is listed first, i.e. MCC. AUROC is NOT scored at all, yet the campaign selected and reported the TDI model on AUROC. MCC is threshold-dependent, so the decision threshold matters as much as the ranking: on CYP3A4, MCC moves from ~0.30 at a naive 0.5 cut to 0.3545 at the training-positive-rate cut - a ~18% relative swing from thresholding alone. SECOND, and this is the part that saved a mistake: tuning the threshold to maximise MCC on out-of-fold predictions LOOKED like a further free +0.0323 on 3A4. Validated properly - threshold chosen only on each fold's TRAIN part, then applied to the held-out fold - the gain INVERTS: 3A4 0.3606 -> 0.3000 (-0.0607) and 2D6 0.1196 -> 0.0127 (-0.1069). The chosen thresholds do not transfer at all (sd 0.138 on 3A4, and 2D6's mean optimal threshold lands at 0.935), because a model is overconfident on its own training rows so the probability scale shifts between fit and inference. DECISION: keep the training-positive-rate threshold. It is distribution-matched by construction and depends only on prevalence, which is a stable quantity, instead of trying to locate an optimum on a shifted probability scale. GENERAL LESSON, and this is the third time it has bitten this campaign: threshold selection is a FITTING step. It needs nested validation exactly like feature selection and model selection do. An 'optimal' threshold measured on the data it was chosen from is not evidence.

tdimccthresholdnested-validationnegative-resultleakagetdi-threshold · 2026-08-23
high

TDI's first deploy came from changing the MODEL, after a dozen experiments changed only inputs and losses

Every previous TDI attempt fed a LightGBM classifier different features, weights, thresholds or targets. Swapping the classifier for ExtraTrees raised CYP2D6's MCC by 46%.

Deep dive

On the incumbent's own seed block, with the control reproducing exactly (deployed 0.1058 vs the recorded 0.1058), a plain ExtraTrees classifier scores MCC 0.1540 against 0.1058 (+0.0482, 11 of 12 seeds, sign-test p=0.0032, 2.10x the measured 0.0229 seed SD) with AUROC 0.6253 vs 0.5719 improving on 12/12. It beats the averaged ensemble arm (0.1487), so the simpler single-model arm ships -- model selection, not ensembling. The effect SHRANK when measured on the correct seed block (+0.0672 -> +0.0482), which is exactly what the reproduce-the-incumbent control exists to catch, and it still cleared every bar with none waived. Shipped as 12_tdi_et2d6.csv, validated PASS, with a per-isoform reader so CYP3A4 keeps LightGBM (ExtraTrees is less competent there). Scope stated plainly: AUROC 0.6253 is modest and MCC 0.154 is a low number that is merely much better than 0.106. The transferable diagnosis: when a dozen input-side levers all fail on one endpoint, the estimator is the untested variable.

tdicyp2d6extratreesmodel-selectiondeployedfirst-deploydata/processed/tdi_family_cyp2d6.json; submissions/12_tdi_et2d6.csv; ledger #108 · 2026-08-28
high

The blinded test is a hard scaffold shift - only 11.5% of test scaffolds exist in train, 0% compound overlap

Validate on scaffold-held-out folds or your local number is fiction. This is the PXR series-shift trap again.

Deep dive

750 test compounds span 546 Bemis-Murcko scaffolds; only 11.5% of those appear among the 4,526 train scaffolds, and InChIKey overlap with train is exactly 0. So the test is a genuine out-of-scaffold draw, not a random split - random CV will be badly optimistic (in PXR it was ~0.1 RAE optimistic). Mandates cyp.eval.scaffold_kfold_indices / leave_cluster_out_indices for every gate, plus a never-touched holdout. Also means the inactive-dominated training distribution (79% of 3A4 below pIC50 5, median 4.27) may not match the test's activity mix.

scaffold-shiftvalidationgeneralisationeda-official · 2026-08-22
high

The cost of adding useless feature columns falls to zero as training data grows -- it is a size effect, not a dataset property

A signal-free block of the same width costs +0.0034 RAE at n=100 and about zero by n=3000 on a fixed collection. Two Queens reached this from different directions after one of them proposed the wrong explanation and withdrew it.

Deep dive

Adding k uninformative columns to a feature matrix carries a measurable cost, and quantifying it separates two things that the campaign's usual comparison confounds. Testing a block against its absence measures signal plus width penalty together; testing it against a row-permuted copy of itself, which preserves column count, marginals and within-block collinearity while destroying the compound correspondence, isolates signal alone. Measured on a fixed CYP2C19 collection while varying only training size, the width penalty runs +0.0034 at n=100, about -0.0005 at 300, +0.0008 at 1000, -0.0018 at 1500 and -0.0003 at 3000, so it decays to nothing as the model acquires enough data to reject noise splits. On CYP2D6 an apparent five-fold difference in width penalty between a mixed collection and a single-assay homogeneous subset was initially attributed to assay composition, but the two subsets differed in size as well, and a matched-size control settled it: subsampling the mixed collection to exactly the homogeneous subset's size gave +0.0047 plus or minus 0.0033, statistically indistinguishable from the homogeneous +0.0065 and clearly above the full-size mixed +0.0013. Size is the driver and the composition interpretation was withdrawn. The deeper methodological lesson is the more valuable one: the original composition claim rested on two single collection draws with no error bar on the collection itself, and the spread across three equivalent draws ran 0.0022 to 0.0084, a four-fold range that spans the entire gap being interpreted. A point estimate carries no evidence about a sampling unit whose noise has not been measured. Note also that collection size at fixed training size and training size at fixed collection are related but not identical axes, and should not be merged without saying so.

methodspermutation-nullwidth-penaltystatisticsself-correctionsurrogatedata/processed/reconcile_width_penalty_cyp2c19.json + surrogate_cyp2d6_public.json · 2026-08-30
high

The CYP2D6 mechanistic block passes the honest gate on 3 of 4 isoforms - and is correctly REJECTED on CYP1A2

Gate per isoform, not globally. A block that helps three targets can still be noise on the fourth.

Deep dive

Same 23-feature block, identical gate (marginal-over-base, corr-with-error, seed-stability over seeds 42/7/2024, leave-cluster-out). Mean scaffold-CV RAE delta: 3A4 -0.0029, 2C9 -0.0109, 2D6 -0.0077, 1A2 -0.0021. 1A2 FAILS seed-stability - seed 7 regressed +0.0075 - so it does not deploy there despite a negative mean. Revealing control: block-ALONE RAE is 0.784 on 3A4 and 0.911 on 2C9 (real standalone signal, driven by logP r=+0.60 with target - the known promiscuity axis) but 1.156 on 2D6 and 1.081 on 1A2 (no standalone signal). Yet 2D6 gets the second-LARGEST marginal gain despite the block being useless alone there - evidence the salt-bridge geometry contributes something the base genuinely lacks for 2D6 specifically. Deployed on 3A4/2C9/2D6; new macro approx 0.831 (from 0.837).

honest-gateper-isoformcyp1a2deploycyp2d6-mechanistic · 2026-08-23
high

The deployed models LOSE to the mean predictor mid-range on all four isoforms -- and no rule can reach it

ST-RAE is a ratio of sums, so it splits exactly over potency terciles. The split says the model beats a constant on actives and inactives and loses to it in the middle. But the model cannot tell which of its own predictions are mid-range any better than chance, so the free score is visible and untouchable.

Deep dive

Decomposing the deployed OOF (share sums to the isoform's score; local = that tercile's break-even against the mean predictor, >1.0 = worse than a constant): actives 0.37/0.54/0.73/0.60, inactives 0.32/0.52/0.86/0.82, MIDDLE 1.40 (3A4) / 7.84 (2C9) / 4.89 (2D6) / 2.56 (1A2) -- 8-28% of each isoform's entire score. The cause is structural: ST-RAE's denominator IS the mean predictor's soft error, and the label mean sits inside mid-potency bands almost by definition, so the metric allots the middle tercile only 1-9% of its budget while the model still errs there. The deployable lever -- emit the training mean where the model's own prediction lands mid-tercile -- was REJECTED 4/4 (3A4 +0.0086, 2C9 +0.0060, 1A2 +0.0040, all 0/3 seed-stable; 2D6 -0.0018 at 0.34 SD). Why: predicted-middle captures true-middle at only 0.563/0.425/0.413/0.356 against a 0.333 chance floor, and 44% (3A4) to 64% (2D6) of that bin is truly ACTIVE or inactive -- the narrow-band compounds #95 proved are unforgiving. The isoform with the worst score has the LEAST identifiable middle (2D6, 0.356 = chance), so the mid-range deficit is not a separate defect to patch: it IS the accuracy deficit seen through the metric, and only a better model closes it.

st-raeerror-decompositioncyp2d6abstentionrejectedoracle-conditionedscripts/train/error_locus.py, mid_band_abstain.py, tercile_purity.py; data/processed/error_locus.json; ledger #96 · 2026-08-27
high

The external 14k public panel shares ZERO chemistry with the challenge test set - read-across is dead as a lever

We assumed abundant public CYP data meant coverage was solved. It is not: there are no neighbours to read across from.

Deep dive

Official test = 750 OCNT-* compounds. Overlap with data/external/cyp_external_wide.parquet (14,055 InChIKeys, PubChem NCGC + ChEMBL): 0/748 on FULL InChIKey AND 0/748 on the skeleton (first block); formats verified identical (27-char). Train overlap only 2.9%. Contradicts CLAUDE.md premise #4 ('coverage is NOT the wall for CYP - external data is a real lever'). External data may still help as PRETRAINING/representation learning, but nearest-neighbour read-across has no support. Ledger item #7 must be re-scoped.

externalcoverageread-acrossnegative-resulteda-official · 2026-08-22
high

The ExtraTrees layer reverses sign on a test-like distribution -- and the control shows it is the layer, not the measure

A layer that wins on the training distribution can lose on a shifted one. The way to tell that apart from a measure that dislikes everything is to run the same check on a different layer -- and bagging goes the opposite way.

Deep dive

Re-scoring deployed and layer-ablated out-of-fold predictions under an activity-matched reweighting, 3 seeds: the ExtraTrees layer reverses on both isoforms where it ships -- CYP1A2 from -0.0075 to +0.0443, CYP2D6 from -0.0098 to +0.0365, four to six times the size of the gain it replaces and in the opposite direction. The essential control rules out the alternative reading: on identical weights, seeds and machinery, ablating BAGGING instead shows its benefit GROWING under the same shift, CYP1A2 -0.0214 to -0.0554 and CYP2D6 -0.0100 to -0.0469. So the shifted measure is not penalising averaging in general; it is penalising this specific layer. This is the first ranking flip in the campaign -- a configuration that wins on the training distribution and loses on a test-like one -- and it sits in the shipped model. It is NOT being acted on yet: the honest gate is defined on scaffold-CV where ET passed 12/12 at 1.24-2.12 SD with an exact control, so removing it means changing the deploy criterion, which requires pre-registration plus retroactive application to every deployed layer. Caveats bound the level but not the ranking: weights clipped at 5x, activity-model error unmeasured, ESS 843 and 505.

covariate-shiftst-raeextratreesranking-flipdeployed-stackcontrolsscripts/train/shift_check_et.py; data/processed/shift_check_et.json, shift_check_bagging.json; ledger #116 · 2026-08-28
high

The fix for a sub-threshold LCO reading is more clusterings, not a weaker criterion

CYP1A2's objective layer looked unsupported on one clustering and is supported 9-for-9 on nine.

Deep dive

An audit of all ten deployed layers against the #64 split-noise thresholds flagged exactly one: CYP1A2's bandl2 objective, LCO -0.0053 against a 0.0081 threshold (0.65x). Re-measured over 9 clusterings, the paired delta is -0.0163, sd 0.0113, negative on 9 of 9, sign stability 1.00 -- 2.01x the threshold. The gate's own clustering reproduces exactly (-0.0053) and was simply the low end of the spread. The economics matter: that layer sits below CYP1A2's external+3D joint layer and has no imputation, so nine clusterings cost roughly one joint-gate seed. Running LCO once was never justified by cost. Standing practice: when an LCO delta falls below its isoform's threshold, re-measure the paired contrast over many clusterings before recording anything about it -- and include the gate's own (k, seed) pair explicitly, since resampling k and seed together does not generally reproduce it. After this, every deployed layer has resolvable LCO support; the #64 finding changed how the criterion is checked without costing the campaign a single deploy.

lcogatingauditcyp1a2harnessdata/processed/lco_remeasure_objective_cyp1a2.json · 2026-08-25
high

The honest gate cannot see a defect shared by both of its arms

A gate compares two arms of the same harness, so anything wrong with the harness cancels out of the comparison while quietly corrupting the baseline.

Deep dive

CYP2C9 l1 scored -0.0402 against l2, seed-stable on 3/3, surviving leave-cluster-out (0.7001 -> 0.6732), magnitude 'meaningful'. It passed all four honest-gate criteria and was entirely an artifact: the harness filled donor NaNs from full-table medians where the deployed pipeline uses donor-subset medians, which diverge most on the sparse Emax columns. Both arms inherited the defect, so the DELTA looked clean while the BASELINE was wrong -- the l2 arm scored 0.6924 against a deployed 0.6033. Correcting one expression made the l2 arm reproduce 0.6033 exactly, and l1 flipped to +0.0154 WORSE. Seed-stability, LCO and effect-vs-noise are all internal to the harness and structurally cannot catch this; only reproducing a known external value can. FIFTH GATE CRITERION for any script that re-implements a deployed pipeline: its baseline arm must reproduce the deployed score within 0.01 before any delta is transferable. Enforced in code -- objective_match.py prints a [control] line and writes reproduces_incumbent into the artifact, and make_submission.py refuses to read a winner past a False. Verified: CYP2C9 and CYP2D6 now both drift +0.0000.

honest-gatevalidationharnessfalse-positivemetaswarm · 2026-08-24
high

The imputation bias is a REAL population difference, not a calibration artifact - donors differ on the independent screen too

The organisers chose which compounds got full curves, so 'measured' is a selected subpopulation. Imputation imports that selection, and correcting the mean would assert something the raw data contradicts.

Deep dive

Row imputation helped CYP3A4 (-0.0534, the campaign's largest single gain) and hurt CYP2D6 (+0.110) and CYP1A2 (+0.5316). I attributed that to systematic BIAS in the imputed labels (imputed minus real mean: 3A4 +0.41, 2C9 -0.30, 2D6 -0.74, 1A2 -1.56) but could not tell whether the bias was a CALIBRATION artifact - fixable by shifting - or a REAL population difference, in which case shifting would inject error rather than remove it. The discriminating evidence is INDEPENDENT of our models: compare donors and measured compounds on the single-concentration screen itself, a direct measurement neither the calibration nor the pIC50 model touches. Mean log2fc, measured vs donor: CYP3A4 -1.688 vs -2.066 (shift -0.378, donors MORE inhibitory); CYP2C9 -0.791 vs -0.647 (+0.144); CYP1A2 -1.323 vs -0.319 (+1.004); CYP2D6 -1.797 vs +0.307 (+2.105, donors barely inhibit at all). So donors ARE genuinely different compounds - the shift is real, not a calibration failure. And the sign lines up exactly with the outcome: CYP3A4 is the ONLY isoform where donors are MORE active than the measured set, and the only one where imputation helps. WHY THIS HAPPENS: the organisers chose which compounds got full dose-response curves, plausibly using the single-conc screen itself, so 'measured' is a selected subpopulation and 'donor' is what was left over. Imputation therefore imports a population shift, not just measurement noise. CONSEQUENCE for ledger #36: mean-shift correction should NOT rescue the failing isoforms, because forcing donor labels onto the measured-compound mean asserts something the independent screen contradicts. Running that correction on 1A2 and 2D6 as a falsification test rather than assuming - this campaign has had five predictions overturned already.

imputationselection-biaspopulation-shiftcyp3a4cyp2d6diagnosticimpute-bias · 2026-08-24
high

The magnitude bar is calibrated on the wrong distribution for shift-amplified blocks

A block whose value is five times larger on a test-like distribution is judged by dividing its training-distribution delta by its training-distribution seed noise. Both halves of that ratio come from the distribution the model will not be scored on.

Deep dive

An enriched CYP2D6 pharmacophore encoding improves the deployed model on 12 of 12 seeds (sign test p = 0.0002) by a mean of -0.0040, which is 0.76 times the isoform's measured seed SD and therefore under the magnitude bar, so it was rejected. Its shift-corrected gain is -0.0209, 5.3 times the unweighted one and consistent with the 4.9x amplification independently measured for the mechanistic block it extends. The bar divides the unweighted delta by the unweighted seed SD, so for exactly the features that matter most on an active-enriched test set it systematically under-reads. The shifted delta cannot simply be substituted, because the seed SD under the shifted weighting has never been measured and a shifted magnitude ratio is therefore not computable. That measurement is a twelve-seed job and is the prerequisite for even asking whether such blocks clear the bar on the distribution that matters; until it exists no criterion should change, since pre-registration and a retroactive sweep both require the denominator. Two blocks now sit certain-in-direction and sub-bar on the two isoforms furthest from their noise floors.

honest-gatemagnitudecovariate-shiftcyp2d6pharmacophorecriteriascripts/train/pharm_gate.py; data/processed/pharm_gate_cyp2d6.json; ledger #126 · 2026-08-29
high

The MCC resolution floor is a property of the isoform, not of the metric or the task

Same metric, same harness, same three seeds: one isoform's base arm varies by 4% and the other by 40%. Quoting a shared TDI sensitivity would misstate both.

Deep dive

Running identical feature-block gates on both scored TDI isoforms produced base-arm MCC across three scaffold-CV seeds of 0.3485-0.3623 on CYP3A4 (about 4% relative) and 0.0852-0.1196 on CYP2D6 (about 40%). Nothing differed but the isoform: same script, same seeds, same fold construction, same metric. So the roughly 0.03 MCC resolution floor recorded for CYP2D6 last tick is NOT a property of MCC, and not a property of the TDI task -- it follows from that isoform's sample size (1,497 versus 3,583) together with whether the model has real signal at all (CYP2D6-TDI sits near chance at 0.106, CYP3A4-TDI is genuinely predictive at 0.357). It would have been easy and wrong to file the earlier number as a TDI-track constant. Practical consequence: CYP3A4-TDI experiments can honestly resolve effects around 0.01, CYP2D6-TDI cannot resolve below roughly 0.03, and any future TDI claim should state which isoform's floor it is being judged against. The general form is that a resolution floor must be measured per target rather than per metric, because it is set by the data available for that target rather than by the scoring function -- the same reason ledger #25 had to measure leave-cluster-out variance separately for each isoform rather than assuming one threshold.

tdimccresolutionseed-varianceper-isoformswarm · 2026-08-24
high

The metric implementation checks out on all eight invariants, and its one silent path is dormant

Every deploy decision in the campaign routes through st_rae, and only one of its properties had ever been tested.

Deep dive

cyp.eval.st_rae computes every number in this campaign and was documented as a verbatim port of the official scorer, with a single property verified when written (mean predictor = 1.0). An audit of the invariants the definition implies now passes all eight: mean predictor exactly 1.000000000000000, perfect prediction 0.0, any prediction inside its band 0.0, a prediction exactly AT the band edge 0.0, monotone in distance outside the band, exactly linear in that distance (2x gives 2.0000x), symmetric above versus below, and above 1.0 for a worse-than-mean prediction. Separately, the one way a port can diverge without any invariant firing is silent row dropping: st_rae drops rows whose bands are non-finite, which would score a subset locally while the leaderboard scores all 750. That behaviour is confirmed, and its exposure is zero -- across all 6,525 training rows there are no non-finite bands and none inverted with lower above upper. The limitation is worth naming: this verifies the implementation against the DEFINITION, not against the backend's source, so a divergence in the definition itself would be invisible to invariants derived from that same definition. What is now established is that no arithmetic or edge-case bug sits between the documented formula and the campaign's numbers.

metricst-raeverificationauditscripts/strae_audit.py · 2026-08-27
high

The model family behaves like the prior, and mechanism does not -- replicated on two independent targets

ExtraTrees' advantage decays monotonically as training data grows, which is what a prior does. The mechanistic block is flat in n, which is what an ordinary weak feature does.

Deep dive

Two Queens ran the same learning-curve experiment on independent targets with frozen scaffold-disjoint answer keys. On CYP2C19 (11,286 compounds, 3,000-compound key, 10 resamples) the ExtraTrees advantage over the deployed LightGBM base ran -0.1296, -0.0663, -0.0458, -0.0403, -0.0266 across n = 100, 300, 1000, 1500, 3000. On CYP2D6-public (8,961 compounds, 3,138-compound key, 12 to 20 resamples) it ran -0.1175, -0.0891, -0.0499, -0.0421, -0.0239, -0.0130 across n = 100, 300, 1000, 1500, 3000, 5000, with every point improving on 12 of 12 or 20 of 20 resamples. Monotonic decay with data is the defining signature of a prior: it substitutes for information the data does not yet contain, so its value must shrink as data accumulates. The mechanistic block shows no such behaviour on either target, oscillating without trend in a narrow band and never growing as data shrinks, which makes it an ordinary weak feature rather than a constraint standing in for data. The practical consequence is large. At the challenge's own training size of about 1500 compounds, the CYP2D6 counterfactual is base 0.9274 improving to 0.8808 with the best arm, a margin of -0.0466 at 20 of 20 resamples and 4.00 SD, and that margin decomposes into -0.0421 from the estimator switch and only -0.0045 from the mechanistic block. Roughly ninety percent of the available improvement is model-family choice. On CYP2C19 the largest feature-block effect was seventeen times smaller than the estimator swap. This does not say mechanism is worthless: on CYP2D6 every mechanistic block beat its own row-permutation null under both model families, so the signal is real where the pharmacophore is real, and on CYP2C19 no block ever separated from its null. It says mechanism is small and flat while family choice is large and data-dependent.

methodsestimatorlearning-curvesurrogateextratreespriordata/processed/surrogate_cyp2c19.json + surrogate_cyp2d6_public.json · 2026-08-30
high

The official label matrix is 73% single-isoform - multitask must use a masked loss, not a dense 4-vector

The data looks like a 4-task matrix but is really four mostly-disjoint datasets stacked.

Deep dive

4,905 train compounds carry only 6,525 compound x isoform measurements: fill rate 33%, mean 1.33 isoforms/compound, 73.3% measured on exactly ONE isoform, only 0.84% on all four. Per-isoform n: 3A4 2335, 2C9 1285, 2D6 1493, 1A2 1412. Consequence: any joint model MUST mask missing targets (scripts/train/chemprop_multitask.py already does); dense-target formulations or naive imputation will fabricate labels for two-thirds of the matrix. It also caps how much multitask can borrow - the co-measured overlap is only 230-473 compounds per pair.

multitasksparsitymasked-losseda-official · 2026-08-22
high

The permutation null is a biased comparator: 21 duplicated columns beat their own null more than any real block

A block built by copying columns verbatim out of the base matrix carries zero new information, yet it wins against its row-permuted twin by -0.008 to -0.015 RAE under LightGBM -- larger than any mechanistic block measured.

Deep dive

The row-permutation null was adopted mid-program as the correct comparator for a feature block, on the reasoning that permuting rows preserves column count, marginals and within-block collinearity while destroying the compound correspondence, so the difference isolates information from width. A deliberate zero-information control shows the comparison is biased. The control block, called dupe, consists of 21 columns copied verbatim out of the base feature matrix; it adds nothing, and its own RAE equals the base model's to four decimal places. Yet dupe beats its own permutation null by -0.0080 on hERG at n=1500, -0.0114 on hERG at n=3000, and -0.0142 to -0.0146 on aromatase, on 9 or 10 of 10 resamples with p at or below 0.011. Those margins exceed every real mechanistic block measured on either target. The mechanism is that the bias lives in the null arm rather than the treatment arm: permuted columns are pure noise that can win greedy split searches and actively degrade the model, whereas duplicated real columns are harmless and simply ignored. Crucially the bias is estimator-dependent. Under LightGBM it is worth -0.008 to -0.015 RAE; under ExtraTrees on hERG the null is nearly unbiased, with et_dupe at exactly +0.0000 on 5 of 10 resamples, and there the real blocks do clear the floor but only by -0.002 to -0.004 and not in mechanism order. The consequence for the program is that no verdict of the form block beats its permutation null should be believed until a matched zero-information block has been run in the same configuration and its margin subtracted. This is a sixth-gate result about a gate: whether the control is valid is itself a property of the configuration it runs in.

methodspermutation-nullcontrolsbiasextratreessurrogatedata/processed/surrogate_herg_kcnh2.json + surrogate_aromatase_cyp19a1.json · 2026-08-30
high

The same effect read 0.49, 0.93 then 1.22 SD as calibration and power improved -- nothing about it changed

Two of those three readings would have discarded CYP2C9's bagging effect, and the effect was identical throughout.

Deep dive

CYP2C9's bagged-averaging effect was measured three times. First at bag5 over 3 seeds, scored against a 3-sample SD of 0.0216: -0.0071, ratio 0.33, dismissed as indistinguishable from noise. Then rescored against the MEASURED 12-seed null of 0.0113: ratio 0.93, just under the bar and inside the re-run band. Then re-measured at 12 seeds: -0.0138, 12 of 12 negative, ratio 1.22, deployed. The effect did not change; the instrument did. Two independent errors compounded in the same direction -- a 3-sample SD that happened to run 1.9x high, and 3-seed power that understated the effect itself. Higher power also shrank the confounder: the subsampling cost fell from +0.0081 to +0.0023, so most of what looked like a real regularisation penalty was also sampling noise. The rule this supports: when an effect lands within a factor of two of a gate's threshold, the threshold is not the thing to argue about -- the measurement is. Re-measure with a calibrated null and adequate seeds before either trusting or discarding, which is exactly what ledger #62 pre-registered and what would have been skipped if the first reading had been taken at face value.

seed-noisepowercalibrationcyp2c9gatingdata/processed/bagged_cyp2c9.json · 2026-08-25
high

The same feature block rejects under LightGBM and passes under ExtraTrees on the same folds

The MBI alert block scored -0.0115 MCC on 2/12 seeds with LightGBM and +0.0072 on 10/12 with ExtraTrees, identical data and splits. The estimator was the entire result.

Deep dive

The mechanism-based-inactivation structural alert block was gated on CYP2D6-TDI twice with everything held constant except the classifier. Under LightGBM: MCC 0.1058 to 0.0943, delta -0.0115, better on 2 of 12 seeds, seed-stability False. Under ExtraTrees, which is the deployed CYP2D6 TDI classifier since #108 replaced LightGBM there: MCC 0.1552 to 0.1624, delta +0.0072, better on 10 of 12 seeds, sign-test p=0.019. Both controls are near-exact, the LightGBM arm reproducing the pre-ExtraTrees 0.1058 and 0.5719 to four decimals and the ExtraTrees arm reproducing the shipped 0.1540. Reporting the first run as the verdict would have rejected a block that works, which is the sixth gate criterion paying for itself: an effect is a property of a configuration, so a block must be measured with the estimator it would actually ship alongside. The practical rule is that when a campaign changes model family on one endpoint, every previously-rejected block for that endpoint becomes untested rather than settled, because the rejection was measured on a configuration that no longer exists. CYP3A4, where LightGBM IS still deployed, gave +0.0018 on 8 of 12 seeds with sign-test p=0.194 and is rejected on stability.

methodstdiestimatorsixth-gatecyp2d6extratreesscripts/train/gate_tdi_alerts_et.py · 2026-08-30
high

The same two blocks are super-additive on one isoform and substitutive on another

Combination behaviour is not a property of the blocks. It is a property of the block-pair-and-isoform together, and it reverses sign between isoforms.

Deep dive

The external public-panel block and the 3D conformer/shape block were run as base / +ext / +3d / +both on two isoforms, each under its own full deployed recipe. On CYP1A2 the pair is SUPER-ADDITIVE: -0.0027 and -0.0059 alone, -0.0104 together, beating the -0.0086 that additivity predicts, and seed-stable on all three seeds when neither part is. On CYP2D6 the same pair is SUBSTITUTIVE: +0.0004 and +0.0027 alone, +0.0055 together -- worse than either block on its own. Same two blocks, same harness, same three seeds, opposite sign of interaction. This turns the campaign's standing rule from a caution into an observation: combination behaviour cannot be transferred across isoforms, and single-block deltas predict neither the magnitude nor the direction of the joint effect. Mechanistically the contrast is coherent rather than mysterious. On CYP1A2 the blocks supply non-overlapping ignorance -- other assays' read on molecules the training set covers thinly, plus planarity and shape for a narrow planar binding slot. On CYP2D6 the 3D block has nothing to contribute: ledger #54 measured its shape descriptors flat against pIC50 at |rho| < 0.01 and showed CYP2D6 cares THAT a basic nitrogen exists rather than WHERE it sits, so combining two weak blocks there only spends model capacity. Practical consequence: a block pair that deploys on one isoform must still be gated separately on every other, and the cheap assumption that a win generalises is exactly the assumption this pair falsifies.

combinationsinterferencesuper-additiveisoform-specifichonest-gateswarm · 2026-08-24
high

The single-conc plate effect is an artefact of pooling enzymes

Plate means look like they span 1.75 log2fc units. But plate encodes enzyme, so that spread is the enzyme difference wearing a batch-effect costume.

Deep dive

The single-concentration file has 36 plate_ids and the pooled per-plate mean log2fc ranges -2.042 to -0.288, a spread of 1.754 against a within-plate sd of 0.911 -- which reads as a serious batch effect in the auxiliary signal feeding the imputation calibration, and would justify plate-correcting log2fc before calibration. It is not real. The 36 plates are 9 per enzyme x 4 enzymes, and WITHIN enzyme the between-plate variance component is CYP1A2 0.6%, CYP2C9 0.9%, CYP3A4 0.9%, CYP2D6 2.2% of total variance. There is no batch effect worth correcting, and plate-correcting pooled log2fc would have removed genuine between-isoform signal while claiming to remove noise. Recorded because the confound was self-inflicted -- the grouping variable silently carried a second factor -- and the same trap exists anywhere an ID column encodes more than it names. Separately, the audit confirmed the single-conc file has ZERO missing values in all 12 columns, so the dropped-inactives bug found in the public PubChem panel has no analogue here.

batch-effectconfoundsingle-concauditmetaswarm · 2026-08-24
high

The single-conc screen carries large transferable signal for CYP2D6 as an auxiliary task

Used as imputation donors it is extra rows of the same task. Used as a shared trunk it reshapes the representation, and that is worth -0.1333 ST-RAE on 3/3 seeds -- on a model too weak to ship.

Deep dive

The screen (4,376 compounds x 4 isoforms = 17,504 measurements, same chemistry as train) has only ever been used as imputation donors. A shared-trunk MLP with a pIC50 head and a 4-isoform single-conc head, ablated by aux_weight alone on identical folds and seeds, gives single-task 1.1057 versus auxiliary 0.9724 on CYP2D6: delta -0.1333, better on 3/3 seeds, which is 25.6x the isoform's seed SD and 5.7x its leaderboard bootstrap SD. LightGBM cannot express a shared trunk, which is why this framing was never tested. CYP2D6 was chosen because it is the representation-bound isoform at 5.25x its noise floor with label, calibration and loss levers all spent. The result does NOT ship, and the reason is the absolute level rather than the delta: the mean predictor scores 1.0, the single-task MLP 1.1057 (worse than a constant), the auxiliary MLP 0.9724 (barely better), and the deployed LightGBM stack 0.8738 -- so the auxiliary task rescues a bad model and still trails what already ships by 0.0986. This is the sixth gate criterion applied reflexively: an effect is a property of a configuration, so a large delta measured on a non-competitive vehicle does not transfer. Leakage control verified non-vacuous, dropping 315-342 of 4,376 auxiliary compounds per fold for sharing a Murcko scaffold with validation. The actionable follow-up is distillation: take the aux-trained trunk embedding as a feature block for the deployed stack and gate it honestly.

cyp2d6auxiliary-taskrepresentationsingle-concneuralmethodsscripts/train/aux_task_mlp.py · 2026-08-29
high

The single-conc screen is already in the CYP2D6 model via donors, so re-adding it as features hurts

An auxiliary-task MLP gained -0.1333 from the screen, but that MLP had no imputation donors. The deployed model already has the screen, and giving it again as 64 learned columns costs +0.0281.

Deep dive

Distilling the aux-trained trunk's 64-dim embedding into a feature block for the deployed LightGBM stack, gated inside apply_deployed(cyp2d6): deployed 0.8704/0.8773/0.8676 mean 0.8718 against +embedding 0.8939/0.9045/0.9011 mean 0.8998. Delta +0.0281, better on 0/3 seeds, stability False, control reproducing the incumbent at tolerance 0.0100. That is worse by more than five times the isoform's seed SD. The explanation is that CYP2D6 deploys with impute=True, so the single-concentration screen already contributes rows through imputation donors; the auxiliary-task MLP of #140 had no donors at all, so its large gain was largely the MLP gaining access to information the deployed stack already holds rather than discovering new signal. Both encodings now agree: the aux block as four prediction COLUMNS was previously rejected on this isoform (delta -0.0037, all_seeds_improve false, LCO +0.0193), and a learned REPRESENTATION is rejected harder. The screen is not an untapped source for CYP2D6. This also confirms a prediction made in advance: #140's write-up stated that an effect measured on a non-competitive MLP does not transfer to the deployed pipeline, and it did not.

cyp2d6single-concrepresentationnegativemethodssixth-gatescripts/train/gate_embed_block.py · 2026-08-30
high

The single-concentration screen covers 89% of train and 0% of test - useful only as a learned auxiliary, never as a feature

Check overlap before designing the feature. This is the second official data source that looks like a lookup table and is not one.

Deep dive

4,376 single-conc compounds cover 4,375 of 4,905 train compounds (89.2%) but 0 of 750 TEST compounds. So the measured log2fc can never be read off at inference time - the same trap that killed external read-across, but disguised, because the train-side coverage looks excellent. The only legitimate use is to train a model on log2fc and apply that MODEL to test compounds (transfer, not lookup - the labels are independent experiment, so this is not circular). Worth noting for architecture: the single-conc matrix is 100% DENSE (4,376 x 4) against 0.333 fill for pIC50, and its cross-enzyme correlations independently REPRODUCE the pIC50 finding on 3x more data - 3A4-2C9 +0.72, but 2D6 lowest against everything (+0.16 / +0.24 / +0.33). That corroborates 2D6 orthogonality, which previously rested on only 230-473 co-measured pairs.

single-concentrationcoverageleakagecyp2d6singleconc-aux · 2026-08-23
high

The single-concentration screen helps exactly where the isoform is already structurally learnable - it amplifies signal, it cannot create it

More labels of a quantity you also cannot predict from structure is not more information. The auxiliary inherits the same wall.

Deep dive

17,504 single-conc measurements (4,376 compounds x 4 enzymes, log2 fold-change at 49.5 uM) used as a LEARNED auxiliary: predict log2fc from structure, feed the 4 predictions as features. Scaffold-disjoint per fold to prevent leakage (89% of pIC50 train compounds also appear in single-conc). Results track structural learnability almost perfectly. DEPLOY: 3A4 mean delta -0.0129, leave-cluster-out -0.0365 (largest gain of the campaign), aux predicts its own log2fc at rho -0.761; 2C9 -0.0220 / LCO -0.0277, rho -0.645. DO NOT DEPLOY: 2D6 - fails seed-stability AND actively HURTS under leave-cluster-out (+0.0193), and the aux predicts 2D6 log2fc at only rho -0.376. So 2.93x more labels for 2D6 (4,376 vs 1,493) bought nothing, because log2fc for 2D6 is as structurally opaque as pIC50 for 2D6. General rule: an auxiliary trained on the SAME features as the base can only redistribute information, never add it - it pays off only when the auxiliary target is itself learnable from those features.

single-concentrationauxiliarycyp2d6honest-gatetransfersingleconc-aux · 2026-08-23
high

The solo-competence screen ranks CONFIGURATIONS of one family, not families against each other

Within ExtraTrees, how good the second model is alone predicted exactly how much averaging it in helped or hurt. DART is better alone than ExtraTrees ever was on CYP3A4 -- and hurts more. The one-number rule does not cross family boundaries.

Deep dive

On CYP3A4, control reproducing: ExtraTrees at max_features=sqrt has a solo gap of +0.1395 and costs +0.0261; at max_features=0.3 a gap of +0.0514 and costs +0.0097; DART at 1000 trees has a SMALLER gap of +0.0440 and costs +0.0147 (0/3 seed-stable, 3.97 SD). DART is off the ExtraTrees curve entirely. The mechanism vindicates the original diversity-versus-competence trade-off: DART is competent precisely because it shares the deployed model's library, features and hyperparameters, and that is exactly what makes it undiverse -- it buys competence with the diversity that made averaging worth doing. The screening instrument survives with its scope corrected: use it to choose a configuration within a family, never to rank candidate families. Stated prediction, untested and queued: DART's predictions should correlate with the deployed model's substantially more than ExtraTrees', whose disagreement correlated with the base model's error at only +0.024.

ensemblingdartextratreescompetencediversityscoperefutedscripts/train/dart_family_gate.py; data/processed/dart_family_cyp3a4_t1000.json; ledger #111 · 2026-08-28
high

The ST-RAE error tail is not a shared blind spot, so there is no cross-isoform lever in it

If the same compounds failed on every isoform, fixing one chemotype would improve four endpoints. They do not: the only significant overlap is the most correlated target pair, and the orthogonal pair overlaps below chance.

Deep dive

Following #137's finding that leaderboard variance dwarfs recent mean gains, the natural target became the heavy tail generating that variance. Concentration first: the worst decile of compounds carries 42.3% (CYP3A4), 47.0% (CYP2C9), 36.4% (CYP2D6) and 36.0% (CYP1A2) of total soft error against the 10% a uniform error would give, and 19.8-42.1% of compounds score exactly zero by landing inside the band. Then overlap, computed only over co-measured compounds and tested by exact hypergeometric against a Bonferroni threshold of 0.0083 for six pairs: CYP3A4-CYP2C9 gives 16 observed against 5.3 expected, ratio 3.04, p<0.0001 -- and it is the only significant pair. It is also the campaign's most correlated target pair at r=+0.71. The orthogonal pair CYP2C9-CYP2D6 (r=-0.10) overlaps BELOW chance at ratio 0.67. Ranking all six by target correlation against overlap ratio gives spearman +0.829 with an exact permutation p of 0.0583 over all 720 orderings -- suggestive, not significant, and reported as such. The reading is that shared tail failure is what correlated labels predict rather than evidence of a shared representational weakness: if two endpoints' true values correlate, a compound hard for one is hard for the other for reasons unrelated to featurisation. No cross-isoform tail lever exists to pursue.

st-raetailcross-isoformnegativemethodsleaderboardscripts/train/tail_structure.py · 2026-08-29
high

The TDI label is a threshold on a number we never modelled

is_TDI is not a category. It is 'did this continuous shift exceed 2x', and the shift ships in the data with its own error bars.

Deep dive

The TDI training file carries CYP<ISO>_pIC50_TDI_condition -- the pIC50 measured under preincubation -- with conf_high, conf_low and std, for all four isoforms, next to the direct-inhibition pIC50. The binary label is simply their difference thresholded at log10(2) = 0.301. Thresholding the TRUE shift reproduces the shipped label at MCC 0.7410 on CYP3A4 and 0.9011 on CYP2D6, against classifiers scoring 0.30 and 0.08. On CYP2D6, where 99.7% of labelled rows carry a finite shift, the shift-to-label map is near-deterministic, so essentially ALL of the model's failure lies in predicting the shift rather than in the thresholding -- which reframes 'CYP2D6 TDI is structure-opaque' as 'we have been asking a classifier to rediscover a magnitude we deliberately discarded'. Five TDI attempts (baseline, class weighting, label-confidence weighting, structural alerts, and the alert-conditioned variant) all classified the binary label. To a classifier a compound with shift 0.30 and one with shift 2.59 are the same row, and one at 0.31 is a coin flip it is asked to learn as certain -- ledger #43 had already measured that the label is close to a z-test outcome (|shift|/band-width predicts it at spearman +0.752 / +0.640) without drawing the modelling conclusion. The asymmetry that makes the regression framing non-free: the shift needs BOTH pIC50s, and on CYP3A4 only 2,334 of 3,583 labelled rows have one, so it is a richer target on ~65% of the rows.

tdiregressionthresholded-labellabel-constructionmccswarm · 2026-08-24
high

The TDI track has a measured ceiling at last: MCC 0.56 on CYP3A4, 0.66 on CYP2D6

is_TDI is a threshold on a continuous quantity the drop ships with its own uncertainty. Ask what a second run of the same assay would score against the shipped labels -- no model can beat a direct re-measurement.

Deep dive

shift = pIC50_TDI_condition - pIC50_direct_inhibition, sd_shift = sqrt(std_TDI^2 + std_direct^2), and the 2x threshold recovers from the data as 0.300 against log10(2)=0.301 (verified, not assumed). Drawing a replicate measurement and thresholding it gives MCC 0.5629 [0.537, 0.586] on CYP3A4 and 0.6559 [0.616, 0.689] on CYP2D6. Against deployed 0.3559 and 0.1540 that is 63.2% and 23.5% of ceiling. 45.7% of CYP3A4 compounds and 27.1% of CYP2D6 compounds sit within one standard deviation of the threshold and are close to coin flips on repeat measurement, which is what caps the metric. The ceiling is optimistic by construction because the observed shift is treated as the latent truth, so the replicate carries one noise draw rather than two -- the honest floor is at or below these numbers, the safe direction for a bound meant to decide whether to keep investing. Practical reading: CYP3A4-TDI has far less headroom than MCC 0.36 sounds like, and the inhibition track's noise-floor discipline now extends to the half of the challenge that lacked it.

tdimccnoise-floorcyp3a4cyp2d6methodsscripts/train/tdi_noise_floor.py · 2026-08-29
high

The width penalty is real but small at the widths that matter: +0.007 at 19-23 columns, +0.042 at 384

Signal-free columns cost CYP3A4 almost nothing at 4, a bit at 19-23, and a lot at 384 -- and every block this campaign rejected sits at the cheap end.

Deep dive

Signal-free blocks (row-shuffled embedding columns) added to CYP3A4's deployed pipeline at increasing width, control reproducing at 0.4643 with seed SD 0.0020: K=4 +0.0017 (0.85 SD), K=19 +0.0067 (3.34 SD), K=23 +0.0069 (3.44 SD), K=64 +0.0192, K=128 +0.0279, K=384 +0.0420 (20.97 SD). Monotone, with LCO rising in step from 0.5020 to 0.5437. The real block widths in this campaign are: external panel 4, 3D conformer 19, mechanistic 23, ChemBERTa 384. So a previously-claimed +0.027 to +0.039 handicap on rejected blocks was measured at a width no rejected block has, and is corrected to about +0.007. That is small but not nothing -- roughly half a typical deployed effect here - so the practical rule is to gate a narrow block against a WIDTH-MATCHED NULL rather than the bare base, which costs one extra arm and removes the ambiguity entirely. Applying the correction retrospectively changes no decision: 3A4's joint block goes +0.0156 to +0.0087 (still rejected), 2C9's +0.0061 to -0.0008 (neutral), and CYP2D6 cannot be corrected this way because its own 384-column null was FREE, so its width curve is likely flat.

dimensionalitypermutation-nullgatingwidth-penaltyself-correctiondata/processed/width_penalty_cyp3a4.json · 2026-08-25
high

Two blocks that each fail seed-stability can be jointly stable, super-additively

If each block covers compounds the other misses, the union has fewer seed-sensitive failure modes than either part - so stability is a property of the combination, not an average of the pieces.

Deep dive

On CYP1A2, the external public-panel block gives -0.0027 and the 3D conformer/shape block -0.0059, and NEITHER is seed-stable, so both were correctly rejected alone. Together they give -0.0104, seed-stable on all three seeds (0.8084 < 0.8109, 0.8038 < 0.8180, 0.7983 < 0.8128), with LCO 0.8420 -> 0.8267 and magnitude 'meaningful'. The joint delta EXCEEDS the -0.0086 that simple additivity predicts, so this is super-additivity rather than averaging: each block is covering compounds the other misses, which reduces the seed-sensitive failure modes of the union below those of either part. The mechanism fits -- the external block supplies other assays' read on molecules the training set covers thinly, while the 3D block supplies planarity and shape for CYP1A2's narrow planar binding slot. Those are different kinds of ignorance and they do not overlap. Two lessons. First, seed-stability is a property of a CONFIGURATION, not an attribute a block carries around, so a block failing it alone says nothing about whether it fails in company -- which is a sharper version of the campaign's standing rule that combinations must be measured per isoform every time. Second, and more uncomfortable: both blocks had a resolvable leave-cluster-out gain and a tempting story about the blinded test being a scaffold shift, so rescuing either by reweighting the criteria was available and would have shipped a WEAKER model while corrupting the gate. The disciplined route was also the better-performing one.

combinationsseed-stabilitysuper-additivecyp1a2honest-gateswarm · 2026-08-24
high

Two hand-rolled layer updates fought and mis-reported the macro while the model was right

The single source of truth for a layered pipeline's live score already existed. Writing a second one next to it guaranteed they would disagree.

Deep dive

Two ticks ago the incumbent-reproduction control fired a false alarm because deployed_score() lagged a deployment layer, and the fix was to make it walk an ORDERED LIST of layers newest-first, with a comment telling future layers to register there. This tick added a new layer -- the joint external+3D block on CYP1A2 -- and instead of registering it, the submission builder got its own bespoke score-overwrite block, sitting immediately before the objective layer's equally bespoke one. They then overwrote each other in sequence: the joint block set CYP1A2's score to 0.8035, the objective block reset it to 0.8139, and the META recorded score_before_objective: 0.8035 as the fingerprint. The reported expected macro came out 0.6936 instead of 0.6910. The MODEL was correct throughout -- joint_ext_3d: True and n_external_models: 4 confirm both blocks were fitted and applied -- so this was a reporting defect, not a scientific one, caught by noticing the macro had not moved despite a deployed win. Two things worth keeping. Reporting defects are easy to wave through precisely because the predictions are fine, and a submission whose META misstates its own expected score is a trap for every later comparison. And the deeper failure is that the generalisation already existed and was not used: writing a second implementation of a rule beside the first guarantees they diverge the moment either changes. make_submission now calls deployed_score() as the single source of truth and the per-layer overwrites are deleted.

reportinglayered-pipelinesingle-source-of-truthself-correctionswarm · 2026-08-24
high

Two layers with the SAME mechanism are not substitutes -- bagging and ExtraTrees both earn their place

Bagging averages subsampled fits of one model; ExtraTrees averages across a second model family. Both reduce variance, so one might have made the other redundant. Removing either hurts, on every seed.

Deep dive

Leave-one-layer-out inside the deployed stack, 3 seeds, both controls reproducing. CYP2D6: -bagging costs +0.0100 (3/3, 1.93 SD) and -et costs +0.0098 (3/3, 1.88 SD). CYP1A2: -bagging costs +0.0214 (3/3, 2.98 SD) and -et costs +0.0075 (3/3, 1.05 SD). So subsampled averaging within one family and averaging across two families reduce different parts of the variance and the stack keeps both. The asymmetry is informative in its own right and confirms the competence rule a third time: the two layers are worth the same on CYP2D6 but bagging is worth nearly 3x ET on CYP1A2, tracking ET's solo competence gap (+0.0090 vs +0.0307) -- the more competent the second family, the more of the stack's variance reduction it carries. That rule was established by adding ET, sharpened within an isoform via its hyperparameter, and now holds on removing it.

ensemblingablationbaggingextratreesredundancystack-hygienescripts/train/layer_ablation_et.py; data/processed/layer_ablation_et_cyp2d6.json, _cyp1a2.json; ledger #109 · 2026-08-28
high

Under ST-RAE, shrink-to-mean is a WIDE-BAND strategy -- and the blinded test is enriched for narrow bands

A prediction inside the credible band scores zero, so pulling toward the mean pays only where the band is wide enough to catch you. On actives the band is ~0.2 wide and the mean is ~0.9 away, so there is nothing to catch you there -- and the test set is enriched for actives.

Deep dive

Sweeping the shrinkage coefficient a in pred'=mu+a*(pred-mu) over the deployed OOF, scored under a test-like activity reweighting, moved the optimum DOWN on 0 of 4 isoforms -- refuting my prediction that models losing to the mean predictor are over-confident. CYP2D6 moved 0.85 -> 1.10 (interior; the grid runs to 1.15) and at a=0.5 degrades 1.0300 -> 1.1012. The mechanism is visible in the labels alone, with no model involved: on the active tercile the credible band is ~0.2 wide while mu sits ~0.9 away, so mu is outside the band for 0.00 of actives on ALL FOUR isoforms, while inactive bands are 0.96-2.05 wide. ST-RAE's dead zone is what makes shrinkage pay, and on the population the shift up-weights that dead zone is unreachable from the mean. So CYP2D6 falling below the mean predictor is an ACCURACY failure on actives, not a calibration failure -- and every future calibration, blending, damping or ensemble-averaging lever inherits the same asymmetry: it buys forgiveness on inactives and pays for it on actives.

st-raecalibrationcovariate-shiftcyp2d6refutedscripts/train/shift_ranking_flip.py; data/processed/shift_ranking_flip.json; ledger #95 · 2026-08-27
high

We have been optimising the WRONG metric - the leaderboard scores ST-RAE, which forgives any prediction inside the credible band

The target is the experimental band, not the point estimate. And CYP2D6 has the tightest bands, so it gets the least forgiveness.

Deep dive

The leaderboard metric is ST-RAE (rae_soft_threshold_absolute_error), not plain RAE. Source: OpenADMET/CYP-Challenge-Tutorial evaluation/config.py + custom_scoring_functions.py, which the tutorial states is ported from the challenge backend. Mechanics: soft_error = max(y_pred - conf_high, 0) + max(conf_low - y_pred, 0), so a prediction landing anywhere INSIDE the per-compound credible band scores exactly zero; the denominator softens the mean predictor the same way, so 1.0 still means 'no better than the mean'. The training data already ships conf_low/conf_high, so this is scoreable locally. Re-scoring our own out-of-fold predictions: MACRO base RAE 0.8349 -> ST-RAE 0.7879, and base+mech 0.8287 -> 0.7780. Every reported number in this campaign has been ~0.05 PESSIMISTIC. Band-hit rates (fraction scoring a free zero): 3A4 36.9%, 2C9 39.5%, 1A2 22.2%, 2D6 19.2%. THREE CONSEQUENCES. (1) CYP2D6 gets the LEAST relief and is actually WORSE under the real metric (0.9912 vs 0.9873 plain) because its assay bands are the tightest (median width 0.27 vs 3A4's 0.38) - there is no measurement slack to hide in, so the 2D6 problem is harder than we have been reporting, not easier. (2) The metric CHANGES RANKINGS: on CYP3A4 the mechanistic block beats base on plain RAE (0.6195 vs 0.6206) but exactly TIES on ST-RAE (0.5548 vs 0.5548) - its entire 3A4 gain was in the region the real metric forgives. Gating on plain RAE can therefore deploy features that do nothing on the leaderboard. (3) The optimisation target is the BAND, not the point estimate - where the assay was uncertain there is free slack, where it was precise precision is mandatory. All future gating must use ST-RAE as the primary metric. Implemented as cyp.eval.st_rae (verified: mean predictor scores exactly 1.0, perfect scores 0.0) plus cyp.eval.band_hit_rate.

st-raemetricscoringleaderboardcyp2d6campaign-widest-rae-discovery · 2026-08-23
high

We weight imputed rows by an R-squared of 0.96 that is actually negative where they live

The calibration is scored on compounds that have a measured label. Donors are, by definition, compounds that do not - and there the same calibration is worse than predicting the mean.

Deep dive

Row imputation weights each donor by its calibration R^2, measured in-domain on compounds that carry both a single-conc log2fc and a measured pIC50 -- 0.9674 on CYP3A4, 0.9826 on CYP2C9. Donors are by construction compounds the organisers declined to run a dose-response on, so they occupy a different region of log2fc space. Reaching that region using measured compounds only (rank by distance from the donor median log2fc, fit the calibration on the far half, evaluate on the near half) gives NEGATIVE R^2 on ALL FOUR isoforms: CYP3A4 -0.2803, CYP2C9 -0.8786, CYP2D6 -0.1817, CYP1A2 -0.1511, against random-split R^2 of 0.8554 / 0.7500 / 0.5934 / 0.8270. The number used to weight donors is therefore not merely optimistic, it has the wrong sign where it is applied. Two consequences. First, imputation cannot be working by supplying accurate labels, because the labels are demonstrably worse than a constant in the donor region -- and it still helps three of four isoforms, so it must be working as a REGULARISER, a block of rows pulling predictions toward the centre, which under ST-RAE is the rational prediction wherever the model has no discriminating signal. Second, the honest way to set donor weights is a held-out estimate in the donor region rather than an in-domain fit; the current scheme happens to work because the weight it produces is roughly the right SIZE for shrinkage, not because it measures label quality. This also refutes concept shift as the explanation for CYP1A2's failure: the mapping breaks down everywhere, and CYP1A2's breakdown is the mildest of the four.

imputationcalibrationregularisationdonor-weightscovariate-shiftswarm · 2026-08-24
high

Weighting training rows by inverse credible-band width beats every feature block so far - and a control proves it is about precision, not potency

The assay measured actives tightly and inactives loosely, so the real metric grades them differently. Put that in the loss, not the features.

Deep dive

Band width is not random: spearman(band_width, pIC50) = -0.93 (3A4), -0.90 (2C9), -0.89 (1A2), -0.56 (2D6). Inactive compounds were measured imprecisely (3A4 p90 width 2.52 log units) and potent ones tightly (p10 0.13). Since ST-RAE only penalises distance OUTSIDE the band, the real metric effectively forgives the inactive tail and demands precision on actives - the opposite of what an unweighted fit optimises, given 79% of 3A4 training rows sit below pIC50 5. Band widths ship with the TRAINING data but are never available for the blinded test, so they belong in the LOSS, not the features: weight each row by how much its error will actually cost. No inference-time dependency and no leakage. CYP3A4 result (ST-RAE, scaffold CV, 3 seeds, full honest gate): unweighted 0.5541; inv_width_cap 0.5307 (-0.0234, DEPLOY); inv_width 0.5329 (-0.0212, DEPLOY); inv_sqrt_width 0.5409 (fails leave-cluster-out). THE CONTROL IS THE POINT: 'active_focus', which upweights potent compounds via a sigmoid on pIC50 WITHOUT using band information at all, gives only -0.0080 - about a third of the band-weighted gain. So the effect is genuinely about measurement precision, not merely about caring more about actives. Note the mechanism is NOT more predictions landing inside bands (band-hit barely moves, 36.9% -> 36.6%); it is smaller distances outside the band where the band is narrow enough to matter. At -0.0234 this is roughly 3x larger than anything the mechanistic block or the single-concentration auxiliary delivered on 3A4 under the real metric.

st-raesample-weightingband-widthcyp3a4loss-designband-weighted · 2026-08-23
high

When a dozen input-side levers all fail on one endpoint, suspect the ESTIMATOR

CYP2D6's TDI model had been fed new features, new losses, new thresholds and new weights a dozen times, always with the same classifier underneath. Swapping the classifier nearly doubled its MCC.

Deep dive

On CYP2D6 TDI at 12 seeds, a plain ExtraTrees classifier on identical features and folds scores MCC 0.1506 against the deployed LightGBM's 0.0791 (+0.0715, 3.12x the measured seed SD of 0.0229, better on 11 of 12 seeds), with AUROC 0.6237 vs 0.5692 improving on 12/12. Notably et_only BEATS the averaged +et5 arm (0.1464), so this is model selection rather than ensembling. It is REJECTED under the honest gate as written, which requires every seed to improve -- both arms fail on the same seed by -0.0039, or 0.17 SD -- and is reported as rejected rather than relaxed. CYP3A4 behaves oppositely and consistently with the competence rule: ET is less competent there (solo gap -0.0255) and averaging it in hurts (-0.0077, 0/3), so the #103 screen transfers across tracks and across metrics from ST-RAE to MCC. The durable lesson is the diagnosis: twelve TDI experiments varied inputs and losses and none varied the model class, on the endpoint where the deployed model sits closest to chance.

tdicyp2d6extratreesmodel-selectionestimatorrejectedscripts/train/tdi_family_gate.py; data/processed/tdi_family_cyp2d6.json; ledger #105 · 2026-08-28
high

Width is not the cost -- splittability is: 1,889 dead columns cost +0.0006, 384 dense ones cost +0.0420

LightGBM already ignores inert features for free, so removing them buys nothing; what hurts is a column good enough to attract a split but carrying no signal.

Deep dive

Between 73% and 83% of the 2265-column base matrix receives zero splits on every isoform (3A4 1665, 2C9 1889, 2D6 1848, 1A2 1876). With bagging now deployed at feature_fraction=0.8, each tree samples ~1812 columns of which three quarters are dead, which looked like a real dilution cost -- especially since #69 had measured signal-free columns degrading ST-RAE by +0.0017 at K=4 rising to +0.0420 at K=384. Pruning to the used columns inside each fold, selected on training rows only, gives +0.0006 on CYP2C9 with 1 of 3 seeds improving and LCO +0.0016 well under its 0.0570 threshold. The reconciliation: #69's penalty came from permuted ChemBERTa columns, which are dense, continuous and high-variance, so they readily win splits while carrying no information. The columns pruned here are dead because the split-gain criterion already declined them -- 66 constant, 552 active in fewer than 5 rows. LightGBM filters inert features at no cost, so there is nothing to recover. The forward-looking rule is unchanged in substance but sharper in scope: sparse or degenerate columns can be left alone, while any DENSE block should still be compressed before gating, because those are the ones that absorb spurious splits.

featuresdimensionalitylightgbmwidth-penaltyrejecteddata/processed/feature_prune_cyp2c9.json · 2026-08-26
medium

A second model family has THREE measurable properties, and the cheapest one was never used

Competence alone failed to predict whether averaging a family in helps. Measuring residual correlation and the direction of disagreement as well explains both the successes and the failures -- and the direction is the cheapest thing to measure.

Deep dive

On CYP3A4, one seed, one member per family: ExtraTrees has residual correlation 0.894 with the deployed model and DART 0.979, so independent-error content (1 - r^2) is 0.201 versus 0.042 -- ExtraTrees carries 4.8x more independent error to average away. DART is simultaneously the more competent model (solo MAE 0.5508 vs 0.6761), confirming by a non-ST-RAE route that it buys competence with diversity. The unplanned result: the correlation between a family's disagreement and the base model's error is -0.175 for ExtraTrees here, while the same diagnostic on CYP2D6 -- where ExtraTrees deploys -- gave +0.024. Near zero means orthogonal disagreement and pure variance reduction; negative means the family pulls in the direction that worsens the base's error. So the same family has opposite-signed disagreement on different isoforms and the sign tracks whether it helps. Three properties, then: competence, independent-error content, and direction of disagreement -- and the third needs one seed and no gate. Medium confidence: one seed, one isoform, one member per family, and the +0.024 figure came from five members on a different isoform, so the comparison is directional. The honest next step is the same diagnostic on CYP2D6 and CYP1A2 where ExtraTrees deploys.

ensemblingdiversitycompetencediagnosticsscreeningcyp3a4scripts/train/family_diversity.py; data/processed/family_diversity_cyp3a4.json; ledger #112 · 2026-08-28
medium

A second model family helps in proportion to its SOLO competence -- and only on the representation-bound isoforms

Averaging two models helps when both are good and they disagree for different reasons. Measure how good the second one is on its own, and you can predict the sign of the ensemble before building it.

Deep dive

ExtraTrees averaged as equal-weight members alongside the deployed LightGBM bag, 3 seeds, all controls passing. The outcome is monotone in how far ExtraTrees lands from the deployed model SOLO: CYP2D6 gap +0.0090 -> ensemble -0.0098 (3/3, 1.88 SD); CYP1A2 gap +0.0307 -> -0.0075 (3/3, 1.05 SD); CYP2C9 gap +0.1179 -> +0.0095 (0/3); CYP3A4 gap +0.1396 -> +0.0261 (0/3, 7.06 SD). The two that gain are exactly the two isoforms previously identified as representation-bound (1A2 at 4.99x its noise floor, 2D6 at 5.25x), which have rejected nearly every feature block -- the second family helps precisely where the primary model extracts least. Mechanism confirmed and it is NOT new signal: corr(ET - LGBM, LGBM residual) = +0.024 on 2D6, so the disagreement is orthogonal to the base's error and this is ensemble decorrelation, exactly the headroom predicted by bagging's measured saturation (1/n explains only 13% of member variance). Medium confidence pending a 12-seed confirmation, since CYP1A2 sits at 1.05 SD. This also refutes the earlier closure of the blend family: competence was correctly named as the binding constraint, but the family was closed after sampling one badly-chosen point (a ridge on 2,265 mostly-binary features).

ensemblingextratreesmodel-familycyp2d6cyp1a2decorrelationdeploy-pendingscripts/train/family_gate.py, family_diagnose.py; data/processed/family_et_*.json; ledger #100 · 2026-08-27
medium

A wide block's width penalty tracks how GOOD the model already is, not how many rows it has

384 random columns cost CYP3A4 0.0394 and CYP2D6 nothing -- and CYP2D6 has more training rows than CYP1A2, which pays 0.0273.

Deep dive

Permutation nulls (a row-shuffled 384-column ChemBERTa block: same width, marginals and collinearity, no molecule-to-feature link) were run on all four isoforms inside their deployed pipelines. Null cost vs deployed ST-RAE: CYP3A4 +0.0394 at 0.4643, CYP2C9 +0.0375 at 0.6033, CYP1A2 +0.0273 at 0.8036, CYP2D6 -0.0028 at 0.8930. Monotone with no inversions; corr(null cost, deployed ST-RAE) = -0.857. The rows-per-feature explanation was pre-registered and is REFUTED by a clean control: CYP2D6 and CYP2C9 have identical 4,376-row training sets (real plus imputed donors) and null costs of -0.0028 versus +0.0375, and corr(null cost, rows) is only +0.167. The mechanism that fits: random columns dilute an INFORMATIVE feature set, and CYP2D6's model sits near the mean predictor so it has almost nothing to dilute. Consequence beyond this block: every wide block ever rejected on 3A4, 2C9 or 1A2 was charged an unmeasured width penalty of +0.027 to +0.039, which is larger than almost every effect this campaign has measured -- those blocks competed against a handicap nobody had quantified. Caveats: n = 4 isoforms, so the monotone ordering is stronger evidence than the correlation coefficient, and CYP2C9's +0.0375 is only 1.74 SD because its own seed noise is large (0.0216). AMENDED 2026-08-25 (#69): the sentence 'every wide block ever rejected on 3A4, 2C9 or 1A2 was charged an unmeasured width penalty of +0.027 to +0.039' was wrong in MAGNITUDE and in scope. That figure was measured at 384 columns, and no block this campaign rejected is remotely that wide -- external panel 4, 3D conformer 19, mechanistic 23. The measured width curve on CYP3A4 is +0.0017 at K=4, +0.0067 at K=19, +0.0069 at K=23, +0.0192 at K=64, +0.0279 at K=128, +0.0420 at K=384. The real handicap on those rejections is about +0.007, not +0.035. It is still nonzero (3.3-3.4 SD) and about half a typical deployed effect, so the practical fix is to gate narrow blocks against a width-matched null; but applying the correction retrospectively overturns no decision.

dimensionalitypermutation-nullgatingwidth-penaltydata/processed/reverify_emb_cyp3a4_perm11.json · 2026-08-25
medium

Adding ExtraTrees made the bandl2 objective stop paying -- a later layer superseding an earlier one

Both layers shrink prediction spread: ExtraTrees by averaging a decorrelated family, bandl2 by a dead zone that charges nothing inside the credible band. Once the first is present, the second has less left to do.

Deep dive

Measured on CYP1A2 with identical seeds and only the baseline stack differing: without ExtraTrees, removing bandl2 costs +0.0085 on 3/3 seeds at 1.18 SD and the layer clearly earns its place; with ExtraTrees present -- the actual deployed configuration -- removing bandl2 IMPROVES the score by 0.0063 on all three seeds at 0.87 SD. A swing of -0.0148 attributable entirely to a layer sitting above it. This is the first measured instance of a later layer superseding an earlier one inside the deployed stack, as distinct from the four blocks that were silently dropped and turned out to be redundant with the imputation layer. It is not being acted on: 0.87 SD is under the magnitude bar, so the direction is consistent across every seed but the size does not clear the criterion, and it is recorded as the stack's first identified redundancy candidate worth 12 seeds if CYP1A2 is revisited. Medium confidence on three seeds. Measured alongside it: CYP2C9's imputation costs +0.1045 at 9.25 SD to remove, the most load-bearing layer in the campaign, more than the entire gap between CYP2C9 and CYP3A4.

ablationredundancybandl2extratreescyp1a2cyp2c9imputationscripts/train/loo_untested.py; data/processed/loo_untested.json; ledger #120 · 2026-08-28
medium

All four isoforms show the same shape: LCO improves while scaffold-CV does not

The split that simulates the blinded test likes these blocks; the split the gate is built on does not.

Deep dive

CYP1A2's external-panel and 3D blocks each improved leave-cluster-out by more than the isoform's resolvable LCO threshold while failing scaffold-CV seed-stability -- and jointly they passed the gate as written, at -0.0154 over 12 seeds. The mechanistic block has now produced the same signature on every other isoform, in each one's deployed pipeline with the control reproducing: CYP3A4 LCO -0.0006 with scaffold-CV +0.0022 (1 of 3 seeds), CYP2C9 LCO -0.0080 with scaffold-CV +0.0017 (2 of 3), CYP1A2 LCO -0.0067 with scaffold-CV -0.0013 (1 of 3, 0.26 SD). That is four isoforms and two unrelated feature families showing LCO and scaffold-CV disagreeing in the same direction, and the sign of the LCO delta is negative in every single case. This is recorded, NOT acted on: all three new cases fail the gate and stay rejected. It matters because the blinded test is a hard scaffold shift (546 test scaffolds, 11.5% seen in training), so LCO is the split that resembles it -- but rescuing a block by promoting LCO over seed-stability after seeing the result is exactly the after-the-fact criterion change the campaign has refused twice. The legitimate move, if one is ever warranted, is a pre-registered joint test like the one that passed on CYP1A2.

lcoscaffold-cvgatinggeneralisationdata/processed/reverify_mech_cyp3a4.json · 2026-08-25
medium

Assembled the missing middle rung: only 2 of 6 methods replicate their sign across independent abundant targets

Five single-target learning curves are five chances to be fooled by one dataset. The shared arm set was mandated so rankings could be compared ACROSS targets -- but the comparison was never made.

Deep dive

Cross-target paired deltas vs base (RAE, negative = better), assembled from the five surrogate artifacts at two rungs. At the challenge's own n=1500: base+et 5/5 targets better (mean -0.0246, sign p=0.031) and mech 5/5 better (mean -0.0028, p=0.031) while its permutation null is 1/3; ET SOLO is only 4/5 (fails on CYP3A4-public, +0.0231) and precision-weighting is 1/4. At the abundant rung mech still replicates 5/5 but base+et drops to 4/5 and ET solo to 3/5 -- the ensemble's advantage shrinks with n, exactly as the small-data-prior story predicts. So the campaign's two deployed levers behave differently under replication: the ET ENSEMBLE generalises across targets, the ET SWAP does not, matching the shipped model (ET on 2D6/1A2, rejected on 3A4/2C9). CAVEATS carried in the artifact: aromatase's pool tops out at n=1500 so its abundant column is degenerate (4 targets clear rung 2, not 5); hERG and aromatase share a Queen and codebase so treat effective units as 4; and precision-weighting is a protocol SLOT filled by three different operations, so its 1/4 is not one technique failing.

surrogatevalidationreplicationextratreesmechanismdata/processed/surrogate_rung2_crosstarget.json · 2026-09-03
medium

CYP1A2 has rejected five independent levers - and none of the usual explanations fit

Not scarcity, not calibration, not unlearnability. 1A2 has more data than 2C9, better calibration than 2C9, and 2C9 just took the biggest gain of the campaign.

Deep dive

CYP1A2 has now rejected every lever this campaign has put to it except band weighting: the mechanistic block (seed 7 regressed, +0.0075), the single-concentration auxiliary (LCO +0.0091), uncorrected row imputation (+0.5316, catastrophic), bias-corrected imputation (+0.0469), and now Emax-calibrated bias-corrected imputation (+0.1087, not seed-stable). Its joint-gate winner is plain base plus weighting, and in the joint gate its ENTIRE within-noise set was all four weighted configs (seed spread 0.0127) - meaning no feature choice was resolvable there at all. This is not a run of bad luck; five independent levers with different mechanisms have failed on the same isoform. WHAT MAKES 1A2 DIFFERENT is not obvious and the usual explanations do not fit. It is not data scarcity - 1A2 has 1,412 compounds, more than CYP2C9's 1,285, and 2C9 just took the campaign's largest single gain (-0.1242). It is not poor calibration - 1A2's log2fc+Emax calibration reaches R^2 0.837, HIGHER than 2C9's 0.763 which flipped from reject to a large win. It is not that the isoform is unlearnable - base ST-RAE 0.8345 is respectable and far from the mean predictor, unlike CYP2D6. The one structural fact that stands out is the population shift: 1A2 donors sit +1.004 log2fc above measured compounds on the independent screen, versus 2C9's +0.144 - so 1A2's donor pool is far less exchangeable with its measured set even though its calibration is better. That is a hypothesis, not a conclusion, and it is recorded as such: it is exactly the kind of single-isoform story that has been overturned five times here. PRACTICAL POSITION: stop spending ticks on CYP1A2 feature and data levers. It is the second-cheapest isoform to leave alone (0.8345 against a 0.163 noise floor) and every attempt has cost compute for nothing.

cyp1a2negative-resultpatternopen-questionemax-imputation · 2026-08-23
medium

CYP1A2's ExtraTrees layer moves half an active band to buy ~1% of score

ST-RAE only charges for predictions outside the credible band, so what matters is how far a layer moves predictions relative to band width. On CYP1A2 that ratio is half a band -- for the smallest gain of any deployed layer.

Deep dive

Comparing the submissions built before and after the ExtraTrees layer: CYP2D6's median prediction moves 0.0347 against a mean active band of 0.223 (0.16x) for a 0.0110 ST-RAE gain, while CYP1A2's moves 0.1075 against a 0.219 band (0.49x) for a smaller 0.0089 gain -- three times the movement for less return, making it the stack's highest movement-per-gain layer. The ablation agrees from the other side: removing ET costs CYP1A2 only +0.0075 against bagging's +0.0214, while on CYP2D6 the two contribute equally. This is exposure, not measured harm. The blinded test set was found to be more active with credible bands roughly half as wide as training's, so the 0.49x ratio there is plausibly a full band, and a large movement for a small measured gain means the gain is an average over the training distribution while the movement is the exposure to a different one. The layer passed 12 of 12 seeds at 1.24 SD and is not being removed on a speculative concern; the correct test is to re-score the ExtraTrees arms under the shift-corrected reweighting, which is queued.

st-raecyp1a2extratreesriskcovariate-shiftdeployed-stackscripts/submission_delta_check.py; submissions/10_bagged_all4.csv, 11_et_family.csv; ledger #115 · 2026-08-28
medium

CYP2D6 is still at the mean predictor after mechanistic features - 1.001 to 0.993 - so 2D6 needs 3D, not more 2D

A gated win that does not move the isoform off RAE 1.0 is a signpost, not a solution.

Deep dive

The 2D6 salt-bridge/basic-amine hypothesis produced a real, seed-stable, leave-cluster-out-surviving gain - but only -0.008 RAE, leaving 2D6 at ~0.993 against a 0.170 noise floor. The block explains a sliver of the residual and nothing more. Reading: the discriminating information for 2D6 is geometric rather than compositional - WHICH atom sits 5-7 A from the heme iron in a bound pose, not whether a basic nitrogen exists somewhere in the molecule. That is exactly what a 2D topological block cannot represent and what cofolding (ligand + HEME + receptor, min ligand-to-Fe distance, pocket engagement) can. Next lever for 2D6 should be structural, not more descriptors. Do NOT keep adding 2D blocks expecting a different answer.

cyp2d6headroomcofoldnext-stepcyp2d6-mechanistic · 2026-08-23
medium

CYP2D6 sits at parity with a constant under distribution shift, not below it

#94 reported 2D6 at 1.0300 on a test-like reweighting. Sweeping the arbitrary knobs shows the cells that cross 1.0 are exactly the cells that throw away two-thirds of the effective sample.

Deep dive

A clip/bins sweep of the shift-corrected evaluation (3 seeds, deployed config) puts CYP3A4 (0.578-0.592), CYP2C9 (0.606-0.616) and CYP1A2 (0.856-0.877) below 1.0 in 0 of 12 cells each. CYP2D6 exceeds 1.0 in 9 of 12 -- but at clip=3, which retains the most effective sample (ESS 815-871 of 1493 rows), it scores 0.9792-0.9839, just BELOW 1.0. The crossing cells are clip=5 (ESS 502-519), clip=10 (ESS 260-355) and unclipped (ESS 146-315). So the claim that the deployed CYP2D6 model loses to a constant on the deployment distribution is an artifact of how much weight concentration one tolerates; the defensible statement is parity, 0.98-1.05. This converges with an independent route that shares no machinery: converting ST-RAE into accuracy puts CYP2D6 only 0.07 log units better than a constant, which is exactly what hovering at parity under a modest shift looks like. Neither result licenses shrinking 2D6 toward the mean -- selecting shrinkage on a test-like score is tuning toward the blinded distribution, and #79 closed that family on magnitude.

cyp2d6distribution-shiftst-raemethodsimportance-weightingscripts/train/mean_predictor_check.py · 2026-08-29
medium

CYP2D6's shipped predictions span 1.56 log units against CYP3A4's 4.16

The attenuation measured in the diagnostics is visible in the file we would actually upload: 2D6's predictions are compressed into a narrow band around the mean.

Deep dive

In submission 11_et_family.csv the predicted ranges are CYP2D6 4.44-6.00 (span 1.56), CYP1A2 3.92-6.21 (2.29), CYP3A4 2.27-6.43 (4.16), CYP2C9 2.70-6.74 (4.04), against training labels spanning roughly 1.9-7.5 on every isoform. This is the same fact three independent measurements have now reported: #133 put CYP2D6 at 0.07 log units of accuracy over a constant against CYP3A4's 0.45, #135 measured its attenuation span at 1.143 with the largest under-prediction of potent compounds (-0.556), and here the shipped file shows the compression directly. A model whose output range is a third of its label range is close to predicting the mean, which is what the margin analysis concluded from a different direction. TDI positive rates in 12_tdi_et2d6.csv match training prevalence closely (0.2173 against 0.2164 for CYP2D6, 0.2133 against 0.2132 for CYP3A4), consistent with the prevalence-matched threshold that #58 kept for its zero estimation variance.

cyp2d6attenuationsubmissionmethodsscripts/official_validator.py · 2026-08-29
medium

Diversity as a DIRECTION predicts ensembling; diversity as a MAGNITUDE predicts backwards

Ask not how much a second model disagrees with the base, but which way. If its disagreement points along the base's error it corrects; if it points against, it amplifies. The size of the disagreement says nothing.

Deep dive

Measuring corr(second-family prediction minus base prediction, base residual) on one seed with one member, then comparing against every ensembling verdict the campaign has reached: 2D6 ExtraTrees +0.027 -> -0.0110 (deploys), 1A2 ET -0.014 -> -0.0089 (deploys), 2C9 ET -0.067 -> +0.0095, 3A4 DART -0.077 -> +0.0147, 3A4 ET -0.175 -> +0.0261. Spearman -1.00 and pearson -0.963, monotone across four isoforms AND two model families, and it places DART correctly where the competence gap had inverted. By contrast residual correlation -- the intuitive diversity magnitude -- ranks backwards across isoforms (spearman -0.40, wrong sign): CYP2D6 has the HIGHEST residual correlation at 0.973 and is where ExtraTrees deploys, while CYP3A4 has the lowest at 0.894 and is where it fails worst. Practically this is the cheapest screen available: one seed, one member, no gate arms, roughly five minutes, versus hours for a full three-arm gate. Medium confidence -- five points, one seed each, ensemble deltas from runs of differing seed counts, and no candidate has yet occupied the strongly-positive regime the rule predicts would help most.

ensemblingdiversityscreeningdiagnosticscheap-screencross-familyscripts/train/family_diversity.py; data/processed/family_diversity_cyp{3a4,2d6,1a2,2c9}.json; ledger #113 · 2026-08-28
medium

Donor imputation absorbs the loss-shaping levers: both band weighting and bandl2 die once donors are added

Imputation already downweights uncertain rows by calibration r-squared, so a second precision-weighting scheme has little left to do.

Deep dive

Two loss-shaping levers were re-tested inside the CURRENT deployed pipelines, both with controls reproducing exactly. CYP2C9 inv_sqrt_width band weighting was -0.0105 and seed-stable 3/3 on the pre-imputation base; with emax-shift imputation it REVERSES to +0.0040, 0 of 3 seeds. CYP3A4 bandl2 was -0.0050 and seed-stable 3/3 before the TDI-condition donor layer; with it, -0.0011 and no longer seed-stable. Both are levers that reweight or reshape the loss according to measurement precision, and both stopped working once a donor layer existed. Proposed mechanism, NOT established: imputed donor rows carry no credible bands (they collapse to points) and enter at weight r2 * mean(w), which is itself a precision-weighting scheme, so changing the band scheme mostly shifts the real-versus-donor weight balance rather than reweighting real rows. This is the mirror image of CYP2D6, where band weighting and imputation MULTIPLIED the mechanistic block's effect five-fold. Both directions say the same thing: a recipe element's value is a property of the configuration it sits in, not of the element, so the order layers are gated in matters and an early rejection is not durable evidence.

imputationband-weightingbandl2layeringcyp2c9cyp3a4data/processed/reverify_recipe_cyp2c9_inv_sqrt_width.json · 2026-08-25
medium

Every deployed model is strong where ST-RAE pays and weak where it does not; CYP2D6 is the exception

Scored by activity quartile against the global mean, the second-most-active tranche exceeds 1.0 on all four isoforms -- and that is exactly the near-zero-denominator region where a row can only add numerator.

Deep dive

Quartiles by predicted activity, least to most active, ST-RAE against the GLOBAL baseline mean (3 seeds, deployed config): CYP3A4 0.330/0.595/1.544/0.202, CYP2C9 0.472/1.028/2.128/0.383, CYP2D6 0.709/1.172/1.209/0.816, CYP1A2 0.560/0.848/2.164/0.737. Tranche 2 exceeds 1.0 everywhere, which is the middle of the potency range where the denominator is near zero; the extremes, carrying 45-58% of all denominator mass, score 0.20-0.47 on the three healthy isoforms. This is the mechanism behind band weighting being the campaign's largest lever -- it directs capacity to where the denominator lives. CYP2D6 is the exception: it is the only isoform failing to excel on the most-active tranche (0.816 against CYP3A4's 0.202) and its profile is nearly flat, which localises the weakness behind its 0.07 log-unit margin over a constant. CAVEAT, and it matters: ranking the four isoforms by most-active-tranche score reproduces their margin ranking exactly, but n=4 gives an exact permutation p of 0.083 two-sided (scipy's reported 0.000 is an asymptotic artifact invalid at this n), and the two quantities are largely mechanically linked since both derive from the same OOF predictions under the same metric. This is a localisation of where CYP2D6 loses, not independent evidence that it loses.

st-raecyp2d6methodsdiagnosticsband-weightingscripts/train/mean_predictor_check.py · 2026-08-29
medium

Only one deployed layer reverses under shift-correction -- the other five get better

If a shifted evaluation made every layer look bad, it would say nothing about any of them. Five of seven layers improve under it, by 1.4x to 4.9x, and the two that reverse are the same layer.

Deep dive

Sampling layer kinds rather than one isoform: CYP2D6 mechanistic block -0.0442 -> -0.2155 (grows 4.9x), CYP1A2 bagging -0.0214 -> -0.0554 (2.6x), CYP2D6 bagging -0.0100 -> -0.0469 (4.7x), CYP3A4 band weighting -0.0108 -> -0.0208 (1.9x), CYP3A4 TDI-condition donors -0.0075 -> -0.0107 (1.4x) -- against ExtraTrees at -0.0098 -> +0.0365 and -0.0075 -> +0.0443. So reversal is rare across a loss change, a data layer, a feature block and an ensemble layer, and ExtraTrees is the sole anomaly on both isoforms where it ships. The single largest number was incidental to the question: CYP2D6's mechanistic block amplifies nearly fivefold under the shift, the biggest layer effect measured in the campaign, which matches the biology since that isoform binds basic amines through an anionic pocket, the block encodes protonation state and the basic-N pharmacophore, and the blinded test set is enriched for actives. A layer gated as a modest win on training CV is the stack's most valuable component on a test-like distribution. Medium confidence: three seeds, weights clipped at 5x, activity-model error unmeasured, ESS 505 to 1907 -- the level of a shifted score is unreliable and only the ranking is being read.

covariate-shiftst-raedeployed-stackmechanistic-blockcyp2d6extratreesscripts/train/shift_sweep.py; data/processed/shift_sweep.json; ledger #117 · 2026-08-28
medium

Optimal shrinkage is not predicted by attenuation magnitude: the most attenuated models want more shrinkage

CYP2D6 and CYP1A2 are the most attenuated and want alpha below 1; CYP3A4 and CYP2C9 are the least attenuated and want alpha above 1. The naive reading of the bias diagnosis points the wrong way.

Deep dive

Selecting the linear coefficient in pred' = mu + alpha*(pred-mu) inside nested folds gives alpha 1.06 (CYP3A4), 1.12 (CYP2C9), 0.94 (CYP2D6), 0.92 (CYP1A2). Ranked by attenuation span the order is CYP1A2 1.486 > CYP2D6 1.143 > CYP3A4 0.898 > CYP2C9 0.889, so the two MOST attenuated isoforms are exactly the two wanting shrinkage rather than expansion. The reason is that alpha trades bias against variance and CYP2D6 and CYP1A2 are the weaker models -- damping prediction variance pays more than correcting the bias does. The practical warning is that a bias diagnosis does not license a bias correction; the sign of the optimal fix depends on how much signal the model has, not on how biased it looks. These values independently reproduce #79's per-isoform optima (1.05 / 1.10 / 0.85 / 0.85) measured on a different harness, which is stronger evidence than either run alone -- and both agree the effect is too small to deploy.

calibrationshrinkagemethodscyp2d6cyp1a2replicationscripts/train/attenuation_screen.py · 2026-08-29
medium

Row imputation deploys ONLY on CYP3A4 - and the harm tracks systematic label BIAS, not noise (my noise rule is refuted)

Donor compounds are not exchangeable with measured ones: the organisers chose what to run full curves on, so imputation transfers a population shift.

Deep dive

All four isoforms, row imputation from the dense single-conc matrix, gated under ST-RAE. Only CYP3A4 deploys: 0.5307 -> 0.4773 (-0.0534, LCO 0.6110 -> 0.5120). CYP2C9 improves scaffold-CV slightly (-0.0119) but REGRESSES under leave-cluster-out (0.7332 -> 0.7401) so it fails the gate. CYP2D6 +0.110. CYP1A2 is catastrophic at +0.5316 (0.8345 -> 1.3661; LCO 0.8473 -> 1.5585). MY NOISE-RATIO RULE IS REFUTED: 1A2 has a BETTER noise ratio than 2D6 (3.1x vs 4.8x) and does far more damage, so the ordering is non-monotonic and the rule cannot predict. WHAT ACTUALLY ORDERS THE HARM is systematic BIAS - the imputed labels are shifted relative to the real ones. Imputed mean minus real mean: 2C9 -0.30 (neutral), 3A4 +0.41 (gains), 2D6 -0.74 (harmful), 1A2 -1.56 (catastrophic). The two smallest shifts are the two non-harmful isoforms and the two largest are the two harmful ones. LIKELY CAUSE, offered as a hypothesis not a conclusion: donor compounds are NOT exchangeable with measured ones. The organisers chose which compounds got full dose-response curves, plausibly using the single-concentration screen itself, so donors are systematically weaker - imputation therefore transfers a POPULATION SHIFT, not merely measurement error. CYP3A4 has by far the highest measured fraction (2,335 of 4,905 = 48% vs 26-30% for the others), i.e. the least selective split and the smallest bias. IMPORTANT CAVEAT: bias ordering four points post-hoc is exactly the kind of story that has failed five times in this campaign. It is logged as an observation, NOT as a deploy rule. The defensible practical conclusion is narrower: imputation must be gated per isoform empirically, and a cheap pre-flight check of imputed-vs-real mean shift is worth computing before spending the compute.

imputationselection-biascyp1a2cyp3a4refuted-ruleper-isoformimpute-rows · 2026-08-23
medium

Scaffold CV and leave-cluster-out disagree, and the blinded test is the second one

A block can fail in-scaffold validation and still improve resolvably under series shift. The test set is a series shift.

Deep dive

The public-panel feature block was rejected on CYP3A4 and CYP1A2 for failing scaffold-CV seed-stability, and that rejection stands. But its leave-cluster-out score improved on BOTH isoforms and under BOTH objectives -- CYP3A4 0.5120 -> 0.5045, CYP1A2 0.8420 -> 0.8293 under l2 and again under bandl2. Ledger #25's multi-clustering tool was run to decide whether those are noise: CYP1A2's absolute LCO is very noisy (mean 0.9229, SD 0.0165, range 0.9051-0.9436) but its PAIRED deltas are 4x tighter at SD 0.0040, giving a smallest resolvable delta of 0.0081. The block's -0.0127 exceeds that; on CYP3A4 the threshold is 0.0067 and the block gave -0.0075, which also exceeds it. So both gains are resolvable. This is the first lever in the campaign where the two validation regimes disagree in a resolvable way, and it matters because the blinded test is a hard scaffold shift -- 546 test scaffolds with only 11.5% seen in training and zero compound overlap -- so LCO is the closer analogue of the actual scoring conditions while scaffold CV is what the gate is built on. The gate is NOT being moved after seeing this. The pre-registered condition for revisiting: if a leaderboard signal ever indicates scaffold CV is optimistic relative to the blind test, this block is the first candidate to reconsider. Caveat: the 0.0081 threshold was measured on the base-vs-mech contrast, so it proxies the external contrast's paired-delta spread rather than measuring it directly.

leave-cluster-outscaffold-cvseries-shiftvalidationexternal-dataswarm · 2026-08-24
medium

The donor weight BINDS where donors carry signal and goes FLAT where they only regularise

A scalar can be optimal because it is the right value, or optimal because nothing depends on it. Measuring the spread across the whole grid against the isoform's own seed noise tells the two apart -- and they call for different next moves.

Deep dive

Sweeping the donor sample weight alpha over a pre-registered {0.5, 1.0, 2.0} inside the deployed stack, both controls passing: CYP3A4 has a real interior optimum at alpha=1.0, losing +0.0030 (1/3) at 0.5 and +0.0037 (0/3) at 2.0. CYP2D6 does not -- alpha=0.5 gives -0.0013 (2/3) and alpha=2.0 gives -0.0024 (3/3) at only 0.47 SD. Spread across the whole 4x range against each isoform's measured seed SD: 3A4 1.00 (binds), 2C9 0.55 (flat), 2D6 0.47 (flat). Proposed mechanism, with the prediction it makes: the weight binds where donors carry SIGNAL and goes flat where they act as REGULARISATION. CYP3A4 is the only isoform with TDI-condition donors, pearson 0.947 with the target; single-conc donors are the ones shown to be negative-R2 in the donor region. Prediction -- any single-conc-only isoform is flat -- and the free check (CYP2C9) confirms it. That is 2 flat vs 1 binding on a post-hoc grouping, hence medium confidence: a hypothesis with one confirmed prediction, not an established fact. Practical consequence: on CYP2D6 this closes a lever rather than tuning one, on the isoform with the largest remaining gap.

donorsimputationsample-weightcyp2d6cyp3a4rejectedknob-sensitivityscripts/train/donor_weight_gate.py; data/processed/donor_weight_deployed_cyp2d6.json, _cyp3a4.json; ledger #99 · 2026-08-27
medium

The pre-flight bar ranked backwards: headroom beat transfer quality

How well an external source predicts the target does not tell you where it will help. Where the model is already strong, there is nothing left to add.

Deep dive

Ledger #45 pre-registered a bar for spending a gate on the public panel -- rho_self >= 0.6 AND rho_transfer >= 0.45 -- extending the #13 rule that an auxiliary pays off only when its own target is learnable. CYP3A4 cleared it comfortably (self +0.703, transfer +0.592); CYP1A2 missed on transfer (+0.705, +0.414) and was run anyway as a declared test OF the bar. The ranking inverted: CYP3A4 gave +0.0042 and was not seed-stable, while CYP1A2 gave the larger and seed-stable effect (-0.0118 against its then-baseline). CYP3A4 is the strongest isoform in the campaign (ST-RAE 0.4773) with the most training data (2,335 rows vs 1,412), so it has the least room for an external opinion to add anything, while CYP1A2 is data-poor. That is the same headroom-ordering seen in ledger #46, where a calibration lift was perfectly inversely ordered against baseline R^2. So transfer quality predicts whether an external model is any good; HEADROOM predicts whether that goodness can pay. The #13 pre-flight rule should be read as a necessary condition, not a ranking. Both isoforms were ultimately REJECTED once the gate was run against the correct incumbent, so this is a lesson about how to choose what to test, not about a deployed win.

pre-flightexternal-dataheadroombarrefutedswarm · 2026-08-24
medium

The PXR physics wins (AIMNet2, DFT-D4, DBSTEP, strain, SOAP) port directly and should rank HIGHER here, because CYP reactivity is atomic.

Reuse the proven physics blocks, but expect Fukui/charge/heme-distance to matter more than they did for PXR's activation task.

Deep dive

In PXR these were honestly-gated residual wins of ~−0.001 to −0.01 RAE each: AIMNet2 (learned-NNP charges/forces), DFT-D4 (dispersion/polarizability), DBSTEP (sterics/3D shape), MMFF strain, SOAP, OrbMol. Semi-empirical xTB alone was absorbed; learned NNPs escaped the sink.
For CYP, atomic reactivity is mechanistic (physics cards), so atomic Fukui indices and partial charges — cheap from AIMNet2/xTB — plus the cofold heme-Fe distance should rank above generic physchem. Hooks are documented in scripts/features/featurize_cyp.py.
Process rule unchanged: deploy a block only if it improves marginal-over-base, correlates with the base model's error, is seed-stable, and survives leave-cluster-out. Alignment-with-truth ≠ adds-signal.

physics-featuresaimnet2gatingseed · 2026-08-12
speculative

A multitask model over the four isoforms (masked loss) is the right BASE, not a marginal add — CYP is jointly-labelled and the isoforms share a promiscuity axis.

Start from one shared-representation model with four heads; single-task per-isoform models throw away the correlation you are handed for free.

Deep dive

Unlike PXR (single target, where multitask was a marginal aux-head win), CYP arrives as a 4-target matrix. The shared lipophilic/heme-ligating cause (chemistry card) means a shared trunk transfers real signal, and masked loss lets partially-labelled compounds train every observed head.

  • Baseline built: scripts/train/multitask_baseline.py (per-isoform LGBM on shared features) — proven end-to-end on external data.
  • GNN: scripts/train/chemprop_multitask.py (4-head D-MPNN, masked loss).
  • The TDI heads (3A4, 2D6) can hang off the same trunk as classification outputs.

Validate per isoform and macro; watch for one isoform's abundant data dominating the shared trunk (weight the loss by inverse label count if so).

multitaskbase-modelmasked-lossseed · 2026-08-12
speculative

Band weighting compresses CYP2D6 predictions (sd 0.54 to 0.43) - correct under ST-RAE, but only if test bands resemble train bands

The compression is the metric working as intended. It becomes a liability only if the test set was measured more precisely than the training set.

Deep dive

Comparing v01 (plain-RAE-gated) with v02 (ST-RAE-gated, band-weighted) per isoform, predicted standard deviation: 2D6 0.539 -> 0.434, 1A2 0.711 -> 0.647, 3A4 0.972 -> 1.009, 2C9 unchanged (its winner is identical in both, pearson 1.0000). Cross-submission correlations: 2C9 1.000, 3A4 0.981, 1A2 0.932, 2D6 0.897 - so band weighting changed CYP2D6's predictions most, which is also where it bought the most (-0.0701). The COMPRESSION is expected and is the metric working as intended: weighting concentrates the loss on tightly-measured potent compounds and effectively stops paying for the sprawling inactive tail, so the model stops predicting extreme low values it was previously being penalised into. Under ST-RAE those inactives have wide bands, so a compressed prediction still lands inside them and scores zero. THE RISK WORTH NAMING: this is only correct if the TEST bands resemble the TRAIN bands. We never see test bands. If the organisers measured the test set more precisely - narrower bands - then v02's compression would be penalised exactly where v01 would not, and v02 could score WORSE despite winning every local gate. Train band widths vary a lot by isoform already (median 0.27 on 2D6 vs 0.38 on 3A4), so this is not an idle worry. Mitigation if a leaderboard signal ever becomes available: submit v01 and v02 in separate windows and compare, since they are the same pipeline differing only in the loss. Both files are validated and retained rather than one overwriting the other, specifically to keep that comparison possible.

calibrationst-raeband-weightingsubmissionriskfinal-gate · 2026-08-23
speculative

External data is a REAL lever here (unlike PXR) — the public CYP space is huge, so read-across / auxiliary-head pretraining should genuinely move the needle.

The wall that stopped PXR (no public data near the test) is down for CYP; pull hard on PubChem + ChEMBL and use them as heads, not just pretraining.

Deep dive

PXR's #1 finding was that coverage, not representation, was the wall — almost no public data sat near its test. CYP flips this: the PubChem NCGC qHTS panel alone gives ~13k compounds across the isozymes; ChEMBL adds thousands more.

  • Pre-staged: data/external/cyp_external_wide.parquet — 14k compounds, all 4 isoforms, 740 fully covered (read-across backbone).
  • Use it as: (a) auxiliary multitask heads (the RyeCatcher-style mechanism that lifted in PXR), (b) read-across features (nearest-neighbour public pIC50), (c) pretraining for the D-MPNN.

Honest caveat that still applies: gate everything on the never-tuned holdout with corr-with-error; abundant data ≠ automatic gain if it is off-manifold. But the *prior* here is much more favourable than PXR.

external-datacoverageread-acrosspubchemseed · 2026-08-12
speculative

Submitted CYP3A4 predictions sit 0.55 log units above its training mean - the opposite of shrinkage bias

Predictions drifting AWAY from the training mean is unusual. Either the test set is activity-enriched or the model extrapolates - both matter for RAE.

Deep dive

Comparing the submission's predicted distribution against the training distribution per isoform: cyp3a4 train_mean 4.096 pred_mean 4.646 shift +0.549; train_sd 1.093 pred_sd 0.972 | cyp2c9 train_mean 4.581 pred_mean 4.779 shift +0.198; train_sd 0.782 pred_sd 0.793 | cyp2d6 train_mean 4.784 pred_mean 4.605 shift -0.179; train_sd 0.916 pred_sd 0.539 | cyp1a2 train_mean 4.955 pred_mean 4.958 shift +0.002; train_sd 1.030 pred_sd 0.711. Two patterns. (1) CYP3A4 predictions sit +0.55 log units ABOVE its training mean. Shrinkage normally pulls predictions TOWARD the training mean, so a large upward shift is the opposite of the expected bias - it suggests either the 750 test compounds are genuinely enriched for actives relative to the inactive-dominated training set (79% of 3A4 train sits below pIC50 5, median 4.27), or the model extrapolates upward on unseen scaffolds. A challenge organiser would plausibly enrich the test set to make it scoreable rather than mirror an 80%-inactive screen. (2) Predicted standard deviations are much SMALLER than training ones across the board - the classic regression-to-the-mean compression. This matters for RAE specifically, because RAE's denominator is the TEST set's own mean absolute deviation: if the test spread is wider than our predictions, compression costs us directly. CANNOT be verified without labels, so logged as an observation, not a finding - but it argues for checking calibration/variance-matching before the final deadline rather than only chasing point accuracy.

calibrationdistribution-shiftraesubmissioncombined-gate · 2026-08-23

🧬 Biology

isoform function, active-site biology, induction, TDI mechanism
high

Band weighting and imputation amplify a block that HAS signal -- they do not manufacture it

The 5x multiplier on CYP2D6's mechanistic block looked like it might be a property of the pipeline. Offered the same block on the other isoforms, inside the same kind of pipeline, it does nothing.

Deep dive

CYP2D6's mechanistic block is worth -0.0440 at 8.48 SD in its deployed band-weighted, imputed pipeline, against -0.0077 in the unweighted plain-RAE gate it was first measured in. Re-offered on the other isoforms inside their own deployed pipelines, with controls reproducing to four digits (3A4 0.4643 vs 0.4643, 2C9 0.6033 vs 0.6033): CYP3A4 +0.0022 worse (1.10 SD, 1 of 3 seeds), CYP2C9 +0.0017 worse (0.08 SD, 2 of 3 seeds). Both rejected. So the amplification is not a generic effect of weighting or imputation on any block -- it is specific to a block carrying real signal for that isoform, which is what the mechanism predicts: CYP2D6 binds basic amines via an anionic pocket (Asp301/Glu216) and the block encodes protonation state at pH 7.4, while the other isoforms have no such pocket. The practical rule: a stale rejection is worth re-testing in the deployed pipeline only where there is a mechanistic reason to expect signal, not as a blanket sweep.

cyp2d6mechanismband-weightingimputationgatingdata/processed/reverify_mech_cyp2c9.json · 2026-08-25
high

CYP1A2 wants flat polyaromatics (narrow planar slot); CYP2C9 wants acidic/H-bonding substrates (Arg108) — the two most 'shape-specific' isoforms.

1A2 and 2C9 should give the cleanest models — their pockets select hard on planarity and charge, so simple descriptors separate actives well.

Deep dive

CYP1A2: a narrow, planar active site selecting flat polycyclic aromatics / heteroaromatics (PAH-like). Induced by AhR (smoking) — the same xenosensor family as the AhR aux-head we tested for PXR. Probes: phenacetin O-deethylation, caffeine N3-demethylation. Inhibitors: α-naphthoflavone, fluvoxamine.
CYP2C9: active site with a cationic Arg108 that anchors anionic / H-bond-accepting substrates (carboxylic acids, acidic NSAIDs). Metabolizes warfarin (S), diclofenac, phenytoin. Inhibitors: sulfaphenazole. Polymorphic (*2, *3).
Modelling consequence: for 1A2 emphasise planarity / aromatic-ring count / fraction-sp3 (low); for 2C9 emphasise acidic groups / formal charge / H-bond donors. These two isoforms are where a plain descriptor model should already do well — a good place to validate the pipeline.

1a22c9planaracidicseed · 2026-08-12
high

CYP2D6 binds basic amines via an anionic pocket (Asp301/Glu216) — a protonatable nitrogen ~5–7 Å from the oxidation site is the pharmacophore.

For 2D6, a basic N and an aromatic ring is almost the whole story — model the ionization state explicitly and it will pay off.

Deep dive

CYP2D6 has a small, acidic active site. The key interaction is a salt bridge between a protonated basic nitrogen on the ligand and Asp301 / Glu216, orienting an aromatic ring over the heme.

  • Pharmacophore: basic N + flat aromatic, with oxidation typically 5–7 Å from the charged N.
  • Highly polymorphic (poor → ultrarapid metabolizers) — but that is a *human-variation* axis, not a per-compound one, so it doesn't change our SMILES→pIC50 map.
  • Not inducible (unlike 3A4/1A2/2C9) — so induction/xenosensor data won't transfer here.
  • Probes: dextromethorphan (O-demethylation), bufuralol. Strong inhibitors: quinidine, paroxetine (TDI).

Feature tip: compute the dominant protonation state at pH 7.4 and an explicit "basic N present" flag — the prior campaign found protonation was otherwise absorbed, but for 2D6 it is mechanistic.

2d6polymorphismbasic-amineseed · 2026-08-12
high

CYP2D6's compound set is 3x enriched in basic amines - the mechanism shows up in the data

If CYP2D6 binds protonated amines through an anionic pocket, its measured compounds should be amine-rich. They are, and the other three isoforms are not.

Deep dive

Applying a validated pH-7.4 basic-nitrogen SMARTS (sp3 amine, excluding amides, anilines, nitriles, sulfonamides and already-charged N; checked against nicotine, propranolol and quinidine as positives and sulfonamide, aniline and acetamide as negatives) to each isoform's measured training compounds gives: CYP2D6 37.4% carrying a basic centre, versus CYP3A4 13.7%, CYP1A2 13.1% and CYP2C9 12.8%. CYP2D6 is close to 3x the other three, which are indistinguishable from each other. That is an independent, data-side confirmation of the mechanistic story the campaign has been assuming from the literature -- CYP2D6 binds basic amines via Asp301/Glu216 in an anionic pocket, so amine-bearing chemistry is what gets tested against it. Two practical consequences. First, it validates targeting the basic-amine pharmacophore at CYP2D6 specifically rather than adding it everywhere: on the other three isoforms the feature is undefined for roughly seven compounds in eight, so it can carry little regardless of merit. Second, it sets a ceiling on how much a pharmacophore feature can contribute even on CYP2D6, since 62.6% of its compounds have no basic centre at all and fall back to imputed values -- meaning any 3D block tested there is carrying most of its weight through shape descriptors rather than through the mechanistically targeted columns.

cyp2d6basic-aminemechanismchemistrycoverageswarm · 2026-08-24
high

CYP3A4 is the promiscuous giant — huge flexible pocket, ~50% of drug metabolism, and it is the isoform most shaped by the prior PXR work.

Expect the weakest structure–activity signal and the strongest lipophilicity/bulk trend of the four; it inhibits almost anything greasy enough.

Deep dive

CYP3A4 has the largest and most malleable active site of human CYPs — it can bind two substrates at once and shows homotropic/heterotropic cooperativity. Consequences for modelling:

  • SAR is flatter — potency tracks lipophilicity, size, and heme-ligating groups more than a precise pharmacophore.
  • PXR induces CYP3A4 — the enzyme we just spent a campaign predicting the *inducer* for. Any PXR-activation signal we built is mechanistically upstream of 3A4 levels (not the same as direct 3A4 *inhibition*, but related chemistry).
  • Probe substrates: midazolam (1'-OH), testosterone (6β-OH). Classic inhibitors: ketoconazole, ritonavir (also a potent TDI).

Practical: 3A4 will likely have the most training data and the least crisp model — lean on the multitask heads and lipophilicity features.

3a4active-siteinductionseed · 2026-08-12
high

Shared fold does not imply shared SAR: MAO-A and MAO-B correlate at 0.257 on 2,188 co-measured compounds

Two isozymes of the same enzyme with the same cofactor, measured on the same compounds, agree about as poorly as CYP3A4 and CYP2D6 do. Cross-target transfer never separated from its null at any training size.

Deep dive

The surrogate program tested whether a model trained on one target carries usable information about another, using donors chosen for recognition similarity rather than convenience. The donor models were competent in their own right, scoring 0.61 to 0.84 scaffold-holdout RAE with Spearman 0.49 to 0.73, so any failure to transfer is not a failure of the donors. Their prediction columns nonetheless correlate with the hERG label at only 0.088, 0.082 and 0.061, and the transfer arm never separated from its permutation null at any training size, giving -0.0022 at n=1500 on 7 of 10 resamples. Same-class donors from the monoamine oxidase family were indistinguishable from cross-class donors. The sharpest form of the result is that MAO-A and MAO-B, two isozymes of the same enzyme sharing a cofactor and co-measured on 2,188 compounds, correlate at Pearson 0.257, which is comparable to the campaign's own measured CYP3A4 to CYP2D6 correlation of 0.04 and well below its CYP3A4 to CYP2C9 value of 0.71. Structural and mechanistic similarity between targets therefore does not imply similar structure-activity relationships. A second observation reinforces it: compound overlap between public target datasets is essentially nil, with hERG and aromatase sharing 2 compounds and hERG and MAO-B sharing 18. That means the campaign's earlier finding that read-across has no support is not a peculiarity of the blinded challenge series but a general property of how public medicinal chemistry is organised, since different targets are simply screened against different compound collections.

biologytransferread-acrossmaohergcross-targetdata/processed/surrogate_herg_kcnh2.json · 2026-08-30
high

The four isoforms decompose into exactly two channels, and CYP2D6 loads 0.000 on the shared one

Eigendecomposing the pairwise-complete label correlations gives a shared promiscuity channel carrying 3A4/2C9/1A2 and a second channel that is almost purely 2D6.

Deep dive

Pairwise-complete correlations of pIC50 across isoforms (no imputation; 230-473 co-measured compounds per pair) give eigenvalues 2.059/1.032/0.631/0.278, i.e. variance shares 0.515/0.258/0.158/0.069 against 0.25 each for independence. Component 1 loads CYP3A4 0.607, CYP2C9 0.617, CYP1A2 0.501 and CYP2D6 0.000; component 2 loads CYP2D6 0.969 with at most 0.185 elsewhere. So a rank-2 factorisation of the form pIC50_iso = mu_iso + sum_k rho_iso,k sigma_k(molecule) is structurally supported, with 16 isoform-side scalars estimated from all four endpoints pooled and all scaffold dependence pushed into the shared compound-side channels. This quantifies the campaign's existing qualitative finding that CYP2D6 should get its own model: its loading on the shared channel is zero to three decimals, not merely small. IMPORTANT TEMPERING: these are LABEL correlations, whereas the deployed models' ERROR correlations were measured at mean +0.079 (3A4-1A2 label 0.42 versus influence 0.026), meaning the shared factor is already largely extracted. The structure being real does not imply that imposing it adds signal, so any rho-sigma head must clear marginal-over-base like anything else. Self-correction: a first pass used EM matrix completion with k=4 on a 4-column matrix, where the low-rank constraint is vacuous, and produced one-isoform-per-component loadings that were an artifact of that setup.

biologymultitaskcyp2d6rank-structurephysics-scorerdata/processed/isoform_rank_structure.json · 2026-08-29
medium

TDI (the classification track) is mechanism-based inhibition — a reactive metabolite covalently kills the enzyme, so it is driven by METABOLIC LIABILITY, not binding affinity.

Model TDI from reactive-metabolite structural alerts + whether the compound is even a substrate — not from pIC50; the two tracks need different features.

Deep dive

Time-dependent inhibition means inhibition increases after preincubation with NADPH: the enzyme turns the ligand into a reactive species that then covalently modifies the heme, the apoprotein, or forms a metabolic-intermediate (MI) complex. Scored for CYP3A4 and CYP2D6 only.

  • It is a metabolism phenomenon → correlates with being a good substrate + bearing a bioactivatable group, not with reversible potency.
  • 3A4 is especially TDI-prone (large pocket accepts many substrates → many reactive metabolites).
  • Therefore the TDI classifier should use: structural alerts (see chemistry cards), predicted site-of-metabolism near a labile group, and substrate-likeness — distinct from the pIC50 regressor's feature set.

Data caveat: TDI is a binary, imbalanced label; use AUROC/AUPRC and class weighting, and expect the positive rate to be low.

tdimechanism-based3a42d6seed · 2026-08-12

⚛️ Physics

heme catalysis, QM/electronic structure, geometry, energetics
high

Deposited CYP crystals do not all use the HEM ligand code - matching only HEM silently reports holo structures as apo

A hardcoded three-letter code is a silent data-loss bug: you do not get an error, you get a smaller validation set that looks legitimate.

Deep dive

Building the cofold reference frame, CYP2C9 structures 1OG5 and 1OG2 reported zero heme atoms and were dropped as apo. They are not apo - they use the CCD code HEC (heme C) rather than HEM (protoporphyrin IX / heme B), with the iron present and parseable (1OG5 Fe at -24.114, 79.313, 29.662). Widening the accepted set to {HEM, HEC, HEA, HEB, HDD, DHE, SRM, VER, 1CP, MH0} took CYP2C9 from 1 usable reference structure to 3. Kept the distinction explicit in cyp_targets.py: HEME_CCD='HEM' is what we ASK Boltz to co-fold, HEME_CCD_ALIASES is what we ACCEPT when parsing experimental structures. Reference frame is now 4/4 isoforms with a locatable heme iron - the precondition for trusting any cofold-derived distance.

cofoldhemereference-framesilent-bugstructure-agent · 2026-08-23
high

OpenProtein returns diffusion samples as models in ONE CIF - and with 5 requested, all five come back byte-identical

The atom-count anomaly and the sample-count anomaly were one fact. But the samples genuinely do not vary, which kills the pose-spread signal.

Deep dive

Resolving the two anomalies that paused the OpenProtein cofold run, both traced to how results are packaged. OpenProtein packs every diffusion sample into ONE CIF as separate models (pdbx_PDB_model_num). Chain layout confirmed: A = protein (3,938 atoms/model), B = query ligand (comp LIG1, 13 atoms/model for a 13-heavy-atom SMILES), C = HEM (43 atoms/model). Parsing the file as a single structure silently POOLS the models - which is exactly why the parser reported '65 ligand atoms' (13 x 5) and '215 heme atoms' (43 x 5), and why it matched every model's ligand against whichever model's Fe was read last. So 'fold() returned 1 result' and '65 atoms' were the same fact, not two bugs. Fixed by splitting on pdbx_PDB_model_num, which restores the intended one-distance-per-sample readout. THEN A REAL FINDING, and a more consequential one: with diffusion_samples=5 the five models are BYTE-IDENTICAL - maximum coordinate difference 0.0000 A across all 13 ligand atoms, and the heme Fe sits at exactly (-2.78, -2.68, 4.87) in every model. So the request is returning one structure replicated five times, not five independent samples. CONSEQUENCES: (1) cross-sample POSE SPREAD is unavailable, and that was the single lead the untemplated pilot produced - fe_dist_std was the only quantity with any ordering against potency (spearman +0.289), and inactives showed 2.7x the pose variance of potents. (2) Any per-ligand aggregate over 'samples' is currently an average of one number. (3) It is unclear whether we are being charged/queued for 5x the compute we actually receive. Testing whether use_potentials=True is collapsing the diffusion to a deterministic optimum, since that is the cheapest discriminating explanation. The primary pre-registered readout - fraction of poses within 2.5 A of the iron - still works with one pose per ligand, so the 24-ligand experiment is not blocked by this; only the pose-spread feature is.

openproteincofolddiffusion-samplespose-spreadsilent-bugopenprotein-cofold · 2026-08-23
high

Oxidation happens at the heme Fe(IV)=O 'Compound I' — so site-of-metabolism = a position that is both geometrically ACCESSIBLE to the iron and electronically ACTIVATED.

The right physics feature is a per-atom score = reactivity (Fukui / H-abstraction energy) gated by distance-to-Fe from a pose — not a whole-molecule scalar.

Deep dive

Catalysis proceeds through Compound I (a high-valent iron-oxo porphyrin radical cation) that abstracts a hydrogen atom or adds oxygen. Two factors set the site of metabolism (SOM):

  • Electronic activation — how easily a position is oxidised: hydrogen-atom BDE, Fukui indices (f⁻ for H-abstraction / aromatic oxidation), local partial charge. Learned NNPs (AIMNet2) or xTB give these cheaply.
  • Geometric accessibility — can that atom actually reach the Fe? Comes from a cofold/docking pose → distance-to-heme-Fe (our boltz_cofold_cyp.py extracts exactly this).

The product reactivity × accessibility is the mechanistic SOM signal. This is the key reason atomic-reactivity descriptors matter *more* for CYP than they did for PXR (which was activation, not chemistry-at-an-atom).

hemecompound-isomfukuiseed · 2026-08-12
high

Structure-based physics cannot beat the deployed model here: FEP at its ceiling scores worse than our incumbent

RAE converts kcal/mol directly into the metric. Prospective FEP at 1.64 kcal/mol implies macro RAE 1.72 -- worse than predicting the mean -- and even the experimental reproducibility ceiling of 0.91 kcal/mol implies 0.95 against our deployed 0.6708.

Deep dive

MAE-to-mean on the training labels is 0.896/0.593/0.631/0.751 log units for 3A4/2C9/2D6/1A2, so at 1.364 kcal/mol per log unit a method's published accuracy converts straight into an RAE floor. Prospective FEP+ (12 targets, 19 series, RMSE 1.64 kcal/mol) implies macro RAE 1.72; the retrospective benchmark median of 1.08 implies 1.13; the 0.91 kcal/mol experimental reproducibility floor -- a ceiling nobody has reached -- implies 0.95. MM/GBSA at ~2.5 kcal/mol implies 2.62. All are worse than the deployed 0.6708 and most are worse than the mean predictor. The cause is that this dataset has a narrow dynamic range (pIC50 SD 0.78-1.09) while physics error is ~1 log unit, comparable to the entire label spread. Recalibrating a physics score linearly onto pIC50 gives best-case RAE sqrt(1-r^2), so matching the deployed numbers needs r >= 0.893 on 3A4 and 0.742 macro, which nothing achieves prospectively on a scaffold-shifted set. Five independent structural blockers compound this: RBFE is inapplicable because 546 test scaffolds contain no congeneric series with reference values; MM force fields cannot represent the Fe coordination bond that type-II inhibitors form; CYP3A4's active site expands over 80 percent on binding; CYP3A4 crystallises with two ketoconazoles and multiple binding modes; and CYP1A2 has exactly ONE PDB structure with one ligand while 2C9 and 2D6 have 14-15, none better than 2.0 A. Boltz-2's own paper states the affinity module does not handle cofactors including ions and water and will be unreliable when they occupy the pocket -- for a heme enzyme the cofactor IS the binding site. Cost is also prohibitive: ABFE for 3,000 systems is roughly 288,000 A100-hours, about 8 years of wall clock at Explorer's 4-concurrent GPU cap.

physicsfepdockingboltz2structurenegativedecisiondeep-research: binding free energy stream · 2026-08-29
high

Templating does NOT fix cofold geometry - and the earlier 0/24 was a harness artifact, not a model limit

Both arms coordinate 6/24. The hosted API's zero came from its settings, not from Boltz being unable to coordinate the iron.

Deep dive

Both arms run on OpenProtein boltz-2, use_potentials pinned False, same 24 CYP2D6 ligands, 1 pose each. Templated 6/24 within 2.5 A of the heme iron; untemplated 6/24. Identical, so the template hypothesis is rejected - and templating slightly WORSENS distances (median 4.051 vs 3.862 A). Crucially the hosted-API pilot gave 0/24 with a 2.80 A minimum, while these arms give 25% coordinating with a 1.953 A minimum: that gap is VENUE AND SETTINGS, not chemistry. Comparing templated-OpenProtein against untemplated-hosted-API - the original plan - would have shown 6/24 vs 0/24 and produced a confident 'templating fixes the geometry' conclusion that was pure harness. The within-venue design caught it. STRONGEST RESULT: even where the model does coordinate the iron, min-Fe distance does not track potency - spearman +0.107 and +0.150, near zero and the WRONG SIGN (the mechanism predicts potent compounds sit closer), with potent-vs-inactive Cohen d of 0.069 and 0.203. This closes heme-Fe distance as a CYP2D6 feature on evidence that is no longer confounded by the model never coordinating at all.

cofoldtemplatecyp2d6harness-artifactnegative-resultclosedcofold-op-arms · 2026-08-24
high

The strongest reversible CYP inhibitors coordinate the heme iron directly (Type II binding) — a lone-pair nitrogen (azole/pyridine) ligating Fe is a potency cliff.

A sp2 N with an accessible lone pair pointed at the iron flips a weak binder into a nanomolar inhibitor — flag heme-ligating motifs as a first-class feature.

Deep dive

CYP inhibitors bind in two regimes:

  • Type I: substrate-like, sits in the pocket, shifts the spin state — moderate, lipophilicity-driven.
  • Type II: an electron-rich N (imidazole, triazole, pyridine, sometimes primary amine) directly coordinates the heme Fe, giving a strong, red-shifted complex and often sub-µM inhibition. Azole antifungals (ketoconazole, fluconazole, itraconazole) are the archetype.

Modelling: a substructure flag for accessible heme-ligating heteroatoms (unhindered imidazole/triazole/pyridine N) is a mechanistic, high-value feature — and its *geometry* (can the N reach the Fe?) again comes from the cofold pose. Expect a discontinuity: adding a coordinating azole can move pIC50 by 2–3 log units, an activity cliff the 2D model will miss without the flag.

heme-ligationazoletype-iiseed · 2026-08-12
high

Untemplated Boltz-2 cofold does NOT discriminate CYP2D6 binders - zero of 23 poses reach true Fe coordination and confidence is 0.95 for everything

The model answers 'where would this sit if it bound', not 'does this bind'. A confident pose for a non-binder is still a confident pose.

Deep dive

Pilot: 24 CYP2D6 train compounds spanning pIC50 1.95-7.53 (8 inactive / 8 mid / 8 potent), Boltz-2.1, protein+HEME+ligand, 5 samples each, 23 parseable poses, ~.80. Min ligand-atom to heme-Fe distance vs pIC50: Pearson -0.170, Spearman -0.001. Potent vs inactive contrast only 3.38 A vs 3.71 A (Cohen's d 0.34) - correct direction, negligible magnitude. Two diagnostics explain the null. (1) ZERO of 23 poses reach true Type II coordination: minimum Fe distance across the whole set is 2.80 A, median 3.50 A, while Fe-N coordination requires ~2.0-2.3 A. Boltz is packing ligands at van der Waals contact in the pocket, never coordinating the iron. (2) structure_confidence is 0.935-0.955 for EVERY compound, with standard deviation 0.00 within potency bands - it is equally confident about inactives, so confidence carries no discriminative information. CONSEQUENCE: do not scale this protocol to the ~3,000-cofold array; the pilot exists precisely to prevent that spend. CAVEAT: n=23 rules out a large effect, not a small one, and this tests ONE engine with ONE untemplated protocol.

cofoldboltzheme-fecyp2d6negative-resultpilotcofold-pilot · 2026-08-23
high

use_potentials shifts the heme-Fe distance by 3.1 A - ten times larger than the potent-vs-inactive signal it is meant to measure

When an arbitrary config flag moves the measurement further than the biology does, cross-venue comparison is meaningless.

Deep dive

Tested whether use_potentials=True was collapsing boltz-2's diffusion sampling. It is NOT: both settings return 5 byte-identical models, so diffusion_samples is simply not producing independent samples through the OpenProtein path. The hosted Boltz API DID vary its samples on the same ligand set - the untemplated pilot produced non-zero fe_dist_std (inactives sd 1.26, potents 0.47) - so the two venues behave differently for the same nominal model. TWO CONSEQUENCES, the second more serious than the first. (1) Pose spread cannot be computed on OpenProtein, so the pilot's only lead (fe_dist_std, spearman +0.289 vs potency) is unavailable there. (2) THE READOUT IS MORE SENSITIVE TO A CONFIG FLAG THAN TO THE BIOLOGY. For one ligand, use_potentials=False gives a min-Fe distance of 4.635 A and use_potentials=True gives 7.740 A - a 3.1 A shift from a single boolean. The entire effect we are trying to detect is the potent-vs-inactive contrast, which in the untemplated pilot was 3.38 vs 3.71 A, i.e. 0.33 A. So an arbitrary API default moves the measurement roughly TEN TIMES further than the signal does. That makes any cross-venue comparison invalid: the templated OpenProtein run cannot be compared against the untemplated hosted-API pilot, because venue and flag differences swamp the template effect. CORRECTED PLAN: run BOTH arms (untemplated and templated) on OpenProtein with use_potentials fixed at False - the documented default and the setting matching the hosted YAML which did not specify potentials. Within-venue comparison is the only valid design. This doubles the compute but the alternative is a number that cannot be interpreted. GENERAL LESSON: before comparing two conditions, check that the measurement is more sensitive to the condition than to the harness.

cofolduse-potentialsconfoundingexperiment-designopenproteinopenprotein-cofold · 2026-08-23
medium

Cofolding ligand·HEME·isoform and reading the min ligand→Fe distance is a mechanistic feature 2D descriptors cannot see — and it is better-motivated here than the PXR cofold win.

If the pose puts a reactive/coordinating atom on the iron, that is direct evidence of inhibition or metabolism; treat the Fe-distance as an orthogonal residual signal.

Deep dive

In the PXR campaign the Boltz-2 cofold interaction embedding was the one featurizer that escaped 2D saturation. For CYP the physical story is tighter: binding and site-of-metabolism both happen at the iron, so the geometry of the ligand over the heme is *directly* the observable of interest.
Our scripts/cofold/boltz_cofold_cyp.py folds protein + HEM cofactor + ligand and extracts, per diffusion sample: the minimum ligand-atom→Fe distance and its across-sample fluctuation, plus confidence.
Use it as an honestly-gated residual/aux feature on the multitask base (corr-with-error, seed-stable, survives leave-cluster-out) — not standalone. Cost: ~3,000 cofolds (750 test × 4 isoforms); scale on Explorer. Open question the swarm should test: does Fe-distance help *inhibition*, *TDI*, or both, and for which isoforms?

cofoldheme-fe-distancegeometryseed · 2026-08-12
medium

CYP2D6's salt bridge is measurable in bound poses and does not track potency there

The feature that carries CYP2D6 measures a distance in BONDS. Measured properly, in angstroms, inside the pocket, on poses we already had -- it points the wrong way.

Deep dive

The four columns carrying 85% of CYP2D6's mechanistic block (#125) measure a basic-N to aromatic distance topologically; the mechanism they proxy is a salt bridge to Asp301/Glu216 in space. Mined from the 24 cofold poses already on O: at zero GPU cost (numbering verified against UniProt P10635; the ligand-to-heme-Fe control reproduces #19's null at pearson -0.016). The salt-bridge distance correlates with potency in the WRONG direction (+0.331 spearman over 24 poses; +0.519 restricted to the 11 genuinely basic ligands), and restricting to basic centres made it stronger rather than weaker, so the ligand-N proxy is not the explanation. Against a permutation null the strongest result is p=0.044 -- among 8 comparisons computed, where 0.4 hits are expected by chance. I proposed that the bimodal potencies were congeneric clusters and would have dismissed the result on that basis; checking showed all 11 have distinct Murcko scaffolds, so they are genuinely independent units and the dismissal was wrong. Consistent with #54 (2D6 cares THAT a basic nitrogen exists, not where it sits) -- now shown for bound-pose geometry, not only single-conformer geometry. Bounds: n=11, one pose per ligand, poses never validated against a 2D6 crystal structure, 24-ligand set built to prove a pipeline. Practical result: #38's readout is free once poses exist; what GPU must buy is a pose set with basic centres and within-series potency spread.

cyp2d6cofoldstructurenegativeself-correctionscripts/cofold/mine_saltbridge.py · 2026-08-29
medium

The CYP2D6 cofold poses are physically sound, so geometry's failure is not a pose artifact

Median salt bridge 4.16 A with 10 of 22 under 4.0 A, heme-Fe at 3.86 A. The ligands really do sit in the anionic pocket. The geometry is right and still does not predict potency.

Deep dive

A real N...O salt bridge is 2.5-4.0 A. Mined from the 24 OpenProtein cofold poses on O:, ligand-N to Asp301/Glu216 carboxylate runs min 2.44 / median 4.16 / max 10.72 A, with 10 of 22 forming a genuine bridge and 15 under 6.0 A; ligand-to-heme-Fe sits at median 3.86 A, coordination range. This validates the cofold pipeline for pharmacophore work and eliminates pose quality as the explanation for #131's null. A mechanistic alternative was also tested: a ligand cannot simultaneously bridge Asp301/Glu216 and coordinate the heme, so the two distances should trade off -- they do, in the predicted direction (spearman -0.260 over 22 poses, -0.455 over the 11 basic ones), but at p=0.24/0.16 this is not resolvable at the available n, and heme coordination does not separate potency at any cut (MWU p=0.396/0.562/0.668). Combined with the power analysis, the reading is that bound-pose geometry on CYP2D6 is untested rather than refuted -- but testing it costs 269-462 cofolds for an effect the size of one already rejected.

cyp2d6cofoldstructuresalt-bridgenegativescripts/cofold/mine_saltbridge.py · 2026-08-29
speculative

Partial template-cofold data shifts CYP2D6 ligands 0.7 A closer to the heme iron - but n=4, so the lever stays UNTESTED

The template appears to fix the geometry the first pilot diagnosed. Four poses cannot establish that, and the run died on depleted credits.

Deep dive

The template-guided retry is INCOMPLETE - only 4 of 24 jobs submitted before Boltz credits ran out - so no verdict is available on whether templating fixes the geometry. But the 4 poses that did complete point the OPPOSITE way to my comparator's (now-removed) automatic conclusion. Median min-ligand-to-Fe distance moves 3.535 -> 2.835 A, mean 3.616 -> 3.099 A, and the fraction within 3.0 A goes 0.29 -> 0.75. So the 3QM4 template does appear to pull ligands substantially closer to the heme iron, which is precisely the geometric failure the untemplated pilot diagnosed. What has NOT changed is the pre-registered primary readout: still 0 poses reaching the <2.5 A of true Type II coordination, though 0/4 carries essentially no information about that rate. Confidence remains flat and uninformative (0.947-0.952 templated vs 0.935-0.955 untemplated), consistent with the earlier finding that Boltz confidence does not discriminate binders. STATUS: the structural lever is NOT closed and NOT open - it is UNTESTED, pending ~20 more jobs (about $4) once credits are restored. Worth completing precisely because the partial signal is encouraging and because a distance distribution shifting by 0.7 A is a real geometric change, not noise in the way a tertile contrast would be.

cofoldtemplatecyp2d6hemeincompletecofold-template · 2026-08-23

⚗️ Chemistry

pharmacophores, structural alerts, SAR, physchem drivers
high

Alert enrichment and predictive utility are different: OR 5 on 1.5% of compounds moves nothing

Cyclopropylamine is 53.7% TDI-positive against a 21.3% base rate at p=1.4e-7. It covers 1.5% of compounds and recovers 5.6% of the positives.

Deep dive

Fisher-exact enrichments on the challenge TDI labels are strong and reproduce independently: cyclopropylamine 53.7% positive on CYP3A4 (n=54, OR 4.41, p=1.4e-7), methylenedioxy 57.1% on CYP2D6 (n=21, OR 4.97, p=3.8e-4), benzofuran 56.0% on CYP2D6 (n=25, OR 4.77, p=1.6e-4). There is also a genuine isoform dissociation: cyclopropylamine is null on CYP2D6 (OR 1.32) and methylenedioxy is null on CYP3A4 (OR 1.12), the latter contradicting the textbook account in which methylenedioxyphenyl carbenes form CYP3A4 metabolic-intermediate complexes. But the chemistry being real does not make it useful. Used as classifiers in their own right the significant alerts give: CYP2D6 coverage 4.5%, precision 0.456, recall 0.096, MCC 0.1268; CYP3A4 coverage 2.6%, precision 0.462, recall 0.056, MCC 0.0993. They identify only 5 to 10 percent of the positives. Meanwhile the full 23-column block fires on 99.3% of compounds because it also contains near-universal patterns like aryl halide, pyridine nitrogen and tertiary alkylamine, so the block is simultaneously near-constant in aggregate and too sparse where it is informative. That bounds the whole family at a fraction of one seed standard deviation regardless of how the SMARTS are refined, and it is why the measured effect is +0.0072 rather than the larger number the enrichments suggest.

chemistrytdistructural-alertscoveragembiscripts/features/tdi_alerts.py · 2026-08-30
high

CYP2C9 and CYP1A2 have a second measurement of the scored label, and it is a true replicate

Preincubation is supposed to change the IC50 - that is what TDI means. On two of the four isoforms it changes essentially nothing, which turns the TDI-condition value into a duplicate reading.

Deep dive

The TDI file ships pIC50_TDI_condition next to pIC50_direct_inhibition for the same compound and the same enzyme, differing only by a preincubation step. The mean shift that step produces splits the isoforms cleanly in two: CYP3A4 +0.339 and CYP2D6 +0.267 are real experimental differences, while CYP2C9 +0.0094 and CYP1A2 +0.0296 are indistinguishable from zero, with pearson 0.962 and 0.986 against the direct value. On those two isoforms the second column is therefore a replicate measurement of the quantity the leaderboard scores, and coverage is complete -- 1,285 of 1,285 on CYP2C9 and 1,412 of 1,412 on CYP1A2 carry both. Combining them by inverse-variance weighting, using the shipped per-measurement _std columns, reduces the training label's measurement SD from 0.184 to 0.125 on CYP2C9 and from 0.153 to 0.107 on CYP1A2 -- close to the 1/sqrt(2) an ideal replicate pair would give, which is itself evidence the two readings are near-independent. One precision worth stating, because it is easy to overclaim: this does NOT lower the scored noise floor. That floor is set by the TEST labels' own error, which no amount of training-side averaging touches. What it lowers is the noise the model LEARNS FROM, so any gain has to come from a cleaner signal rather than an easier target -- and evaluation is deliberately kept on the shipped direct pIC50 and its bands so that stays true.

replicateslabel-noisemeasurement-errortdi-conditionnoise-floorswarm · 2026-08-24
high

CYP2D6 cares THAT there is a basic nitrogen, not WHERE it sits

Presence of a basic centre predicts CYP2D6 potency at rho +0.30. Its distance from the aromatic system, in angstroms, predicts almost nothing - and molecular shape predicts nothing at all.

Deep dive

Correlating each 3D conformer feature against CYP2D6 pIC50 on the compounds where it is defined separates the mechanism sharply. The COUNT of basic centres -- a graph property, not a geometric one -- gives spearman +0.299 across all 1,493 compounds, the strongest single feature examined. The geometric versions of the same pharmacophore are weak: basic-N to molecular centroid -0.167, to aromatic centroid mean -0.148, minimum -0.131, each on the ~550 compounds carrying a basic nitrogen. And the pure shape descriptors are flat to three decimal places: asphericity +0.007, NPR1 -0.006, plane-of-best-fit -0.008, radius of gyration -0.005, spherocity +0.004. So CYP2D6's amine requirement is a presence/absence property rather than a geometric one, at least at the resolution a single ETKDG conformer provides, and overall molecular shape is irrelevant to it. That explains why the 3D block was correctly rejected there rather than merely unlucky: the presence term is already captured by the deployed mechanistic block, so the 3D block was only ever contributing geometry, and the geometry is near-zero. Two consequences. It bounds cheap conformer-based 3D for CYP2D6 specifically -- the answer is not more descriptors from one conformer. And it sharpens what a real structural experiment would have to deliver: if geometry matters at all it must be BOUND-pose geometry inside the anionic pocket, which is ledger #38's cross-model pose pool, not anything computable from the ligand alone.

cyp2d63dpharmacophoreshapenegative-resultstructureswarm · 2026-08-24
high

CYP2D6's signal is the basic-N to aromatic TOPOLOGICAL distance -- 4 columns carry 85% of the block

The most valuable layer in the campaign turns out to be four numbers: whether a molecule has both a basic nitrogen and an aromatic ring, and how many bonds apart they are. Which kind of amine it is, and its charge, matter far less.

Deep dive

Ablating CYP2D6's 23-column mechanistic block by mechanism group, three seeds, scored both unweighted and shift-corrected: pharmacophore geometry (has_basic_and_aromatic plus min, max and mean basic-N to aromatic topological distance, 4 columns) costs +0.0376 unweighted at 7.24 SD and +0.1835 shifted, which is 85% of the whole block on both measures. Charge state (5 columns) contributes about 5%, and amine identity (7 columns), generic physchem (4) and acidic groups (3) contribute nothing that clears their own seed noise. This matches CYP2D6's binding mode -- a basic nitrogen salt-bridging Asp301 or Glu216 while an aromatic ring stacks in the hydrophobic pocket, so what matters is the SPACING between those two pharmacophore points rather than the amine's substitution class. It also sharpens an earlier conclusion in the opposite direction: a previous tick found CYP2D6 cares that a basic nitrogen exists and not where it sits, but that used 3D distances from a single ETKDG conformer, whereas the topological distance is the single most valuable feature in the deployed stack. Where the nitrogen sits matters enormously; one unminimised gas-phase pose is simply the wrong instrument. The practical consequence is that bound-pose geometry becomes the best-motivated open item rather than merely the most expensive one.

cyp2d6mechanistic-blockpharmacophorefeature-attributionstructurescripts/train/mech_group_ablation.py; data/processed/mech_group_ablation_cyp2d6.json; ledger #125 · 2026-08-29
high

Every 3D descriptor we have computed used the neutral species, which does not exist at pH 7.4 for a basic amine

standardize_smiles returns propranolol, quinidine and nicotine at net formal charge 0. CYP2D6 recognises the protonated cation against Glu216/Asp301, so the 3D block described the wrong molecule on the isoform that needs it most.

Deep dive

Verified directly: standardize_smiles returns net formal charge 0 for propranolol (secondary amine, pKa 9.5), quinidine (tertiary, pKa 8.6) and nicotine. At pH 7.4 all three are predominantly cationic. This matters because the CYP2D6 pharmacophore is a protonated nitrogen ion-pairing with Glu216 and/or Asp301, and neutralising both residues abolishes substrate recognition. Any conformer ensemble, partial charge, electrostatic potential, Fukui index or co-folded pose computed from the neutral form describes a species that is not present in the assay, and the error is largest exactly on the isoform where 38.7 percent of measured compounds carry a basic centre versus roughly 13 percent elsewhere. This yields a concrete, testable explanation for an existing result rather than a new feature proposal: the 3D conformer block was REJECTED on CYP2D6 (+0.0027, seed-unstable), and it was computed on neutral structures. The fix is a preprocessing step -- enumerate microstates and select the dominant species at pH 7.4 -- after which the existing block is re-gated unchanged. Note the honest counterweight: the campaign already measured that CYP2D6 cares THAT a basic nitrogen exists (rho +0.299) far more than WHERE it sits (-0.13 to -0.17), so the payoff is not a new geometric feature but a correction to the electronic structure every other block consumes. Open tools with measured accuracy: Uni-pKa (MAE 0.631 on multi-site drug-like), QupKake (MAE 0.55/0.39), MolGpKa (0.835, ~10 ms CPU), Dimorphite-DL for enumeration.

chemistryprotonationpkacyp2d6preprocessingbugdeep-research: QM + CYP2D6 streams; verified locally · 2026-08-29
high

Lipophilicity drives CYP3A4 and 2C9 potency and has ZERO relationship to CYP2D6

clogP correlates with pIC50 at +0.571 on CYP3A4 and +0.469 on CYP2C9, but +0.002 on CYP2D6 -- p=0.95, no relationship at all.

Deep dive

Measured directly on the challenge training data: clogP-pIC50 Pearson is +0.571 (CYP3A4, n=2335, p=5e-202), +0.469 (CYP2C9, n=1285, p=4e-71), +0.217 (CYP1A2, n=1412, p=1.5e-16) and +0.002 (CYP2D6, n=1493, p=0.95). This corrects an explanation published one tick earlier: when the learned single-conc embedding was rejected on both CYP2D6 and CYP1A2, the replacement account offered was that CYP inhibition is a lipophilicity-dominated endpoint and extra learned columns are therefore dilution. That holds for CYP3A4 and CYP2C9, is weak on CYP1A2, and is simply false for CYP2D6, where potency has no lipophilicity component whatsoever. The correct statement is that CYP2D6 potency is set by charge and pharmacophore geometry alone, which is independently consistent with three existing campaign results: CYP2D6 sits 5.25x above its noise floor and was classified representation-bound, its measured compounds are 38.7% basic amines against roughly 13% for the other isoforms, and the base model systematically under-predicts cationic CYP2D6 ligands by +0.211 log units at 4.8 sigma. The generalisable point is that a single explanation offered for a result seen on two isoforms can be right for the wrong reason on one of them, and the per-isoform check is cheap enough that there is no excuse for skipping it.

chemistrylipophilicitycyp2d6cyp3a4self-correctionverified locally against the agent's measurement · 2026-08-29
high

MBI structural alerts fire on 76-84% of a drug-like library, so they cannot carry a 21% signal

A warhead list describes what CAN be bioactivated, and on real compounds that is almost everything.

Deep dive

Mechanism-based inactivation alerts -- furan, thiophene, benzofuran, thiazole, terminal alkyne, methylenedioxyphenyl, aniline, hydrazine, thiourea and others -- were built as a 22-column block, smoke-tested against six named inactivators (furafylline, ethinylestradiol, paroxetine, ticlopidine, tienilic acid, ritonavir) and three negative controls, then gated on both scored TDI isoforms at 12 seeds. Rejected on both: CYP3A4 MCC -0.0005 helping on 3 of 12 seeds, CYP2D6 -0.0118 on 2 of 12. The reason is base rate. At least one alert fires on 76.5% of CYP3A4 compounds and 83.8% of CYP2D6 compounds, and on CYP3A4 P(TDI positive given any alert) is 0.213 against 0.215 for no alert -- literally no information. CYP2D6 separates slightly, 0.222 against 0.190, and its strongest individual alerts are the mechanistically correct ones (benzofuran 0.109, methylenedioxyphenyl 0.103, furan 0.082), but no single alert reaches a usable correlation. The contrast that makes this precise is CYP2D6's mechanistic block for inhibition, worth -0.0440, which encoded protonation state at pH 7.4 -- a property that SPLITS a drug-like library roughly in half rather than describing three quarters of it. The rule: before gating a knowledge-derived feature, check its base rate against the label prevalence. A mechanistically correct descriptor that fires on most compounds is nearly a constant.

tdistructural-alertsmechanismbase-raterejectedscripts/features/tdi_alerts.py · 2026-08-27
high

Protonation state is NOT invisible to Morgan fingerprints - the base model already absorbs it

The obvious hypothesis (fingerprints see atoms, not charge) is wrong. Correlating with the target is not the same as adding signal.

Deep dive

Tested a CYP2D6 mechanistic block (basic-amine SMARTS excluding amide/aniline/sulfonamide N, net charge at pH 7.4, basic-N-to-aromatic topological distance). On 2D6, net_charge_pH74 has Spearman +0.294 with the TARGET but only -0.058 with the BASE MODEL'S ERROR. That gap is the whole point of the honest gate: the base already extracts the charge signal from substructure, so a feature that looks strongly predictive in isolation contributes almost nothing marginally. The features that actually correlate with base ERROR on 2D6 were generic (arom_rings +0.135, logp +0.125) plus the basic-N-to-aromatic distances (-0.11). Lesson: always regress candidate features against the residual, never only against the label.

honest-gateprotonationcyp2d6absorbedcyp2d6-mechanistic · 2026-08-23
high

TDI has a well-known structural-alert list — furan, thiophene, terminal alkyne, aniline, methylenedioxyphenyl, thiazolidinedione — bioactivated to reactive species.

A handful of SMARTS alerts + 'is it a substrate' gets you a surprisingly good TDI classifier; these groups are the mechanistic positives.

Deep dive

Mechanism-based inhibition needs a group the enzyme can turn reactive. High-value alerts:

  • Methylenedioxyphenyl (MDP) → carbene forming a stable MI-complex (classic 3A4 TDI: e.g. paroxetine).
  • Furan / thiophene → epoxide / γ-ketoenal.
  • Terminal alkyne → ketene / heme adduct.
  • Aniline / hydrazine / thiourea → nitroso / reactive N,S species.
  • Thiazolidinedione, tertiary amines (dealkylation), certain heterocycles.

Build these as binary SMARTS features for the TDI heads (3A4, 2D6). Caveat: an alert is necessary-ish but not sufficient — the group must actually be metabolised (accessible, and the compound a substrate), so combine alerts with predicted SOM/substrate-likeness. Expect false positives from alerts alone.

tdistructural-alertsreactive-metaboliteseed · 2026-08-12
high

The canonical TDI structural-alert list barely enriches for CYP3A4 (max lift 1.19x) but works well for CYP2D6 (MDP 2.64x) - inverse to where the base model is strong

Textbook alerts are not a free classifier. They pay off only where the learned model is weak - and there the sample sizes are too small to deploy.

Deep dive

Tested 25 bioactivation SMARTS (furan, thiophene, terminal alkyne, aniline, methylenedioxyphenyl, thiazolidinedione, hydrazine, nitroaromatic, epoxide, quinone, catechol...) as a block over the base. CYP3A4 enrichment vs a 21.3% base rate is weak: furan 1.19x (n=126), MDP/benzodioxole 1.17x (n=72), aniline 1.13x, most others ~1.0x - and the block adds only +0.0008 AUROC. CYP2D6 is the opposite: MDP/benzodioxole 2.64x (n=21), furan 1.87x (n=47), catechol 1.71x (n=46), thiophene 1.41x (n=98) - real, mechanistically sensible lifts (MDP is bioactivated to a carbene that forms a metabolic-intermediate complex with the heme), but the block still FAILS seed-stability and n is tiny. This partially falsifies the seed expectation that 'a handful of SMARTS alerts gets you a surprisingly good TDI classifier' - for 3A4 the learned model already absorbs them; for 2D6 they carry signal the model lacks but there are too few examples to deploy. Actionable: for 2D6 TDI specifically, alert-conditioned priors or targeted data on MDP/furan/catechol chemotypes is a better bet than more generic capacity.

tdistructural-alertsenrichmentcyp2d6mechanismtdi-baseline · 2026-08-23
high

The deployed CYP2D6 pharmacophore is measured in bonds, not angstroms

A salt bridge to Asp301 happens across physical space. Two molecules with the same bond count can present that nitrogen entirely differently once folded.

Deep dive

CYP2D6's binding is dominated by a protonated basic nitrogen forming a salt bridge with Asp301/Glu216, positioned relative to an aromatic system. The deployed mechanistic block encodes exactly that pharmacophore and it passed its gate -- but it computes the basic-centre-to-aromatic distance TOPOLOGICALLY, as a count of bonds along the molecular graph. That is a proxy for the physical distance, and a lossy one: conformational preference, ring geometry and substitution can place two nitrogens the same number of bonds from an aromatic ring at very different separations in space. The broader point is structural: every feature currently in the campaign's inhibition models -- Morgan fingerprints, the 217 RDKit 2D descriptors, the mechanistic block -- is a function of the molecular GRAPH. There is no genuinely three-dimensional information anywhere in the deployed feature set, which matters because ledger #53 showed CYP2D6 and CYP1A2 are the two isoforms where only representation can help. Worth recording independently of how the 3D gate turns out, because it identifies a specific and cheap gap rather than a general wish for better features: the same pharmacophore already believed to matter, measured in the units the physics actually uses. A caveat travels with it -- a single ETKDG conformer is not the bound pose, and CYP active sites are notably plastic, so a null here would bound the value of cheap 3D without saying anything about docking or co-folding.

cyp2d6pharmacophore3drepresentationfeature-gapswarm · 2026-08-24
high

The drop ships a per-measurement assay std - the RAE floor is ~0.16-0.25, so stop optimising well before RAE 0

The challenge hands us the noise floor directly instead of making us estimate it. Use it as the stopping rule.

Deep dive

Columns CYP<ISO>_pIC50_direct_inhibition_std give per-compound assay SD. Converting to an irreducible RAE floor (E|noise| = sd*sqrt(2/pi), divided by the mean-abs-deviation denominator of RAE): 1A2 0.163, 2D6 0.170, 3A4 0.212, 2C9 0.247 using MEAN sd. Using MEDIAN sd (0.07-0.14) the floor is roughly half that - the mean is inflated by the poorly-determined inactive tail, so treat 0.16-0.25 as a conservative floor and ~0.08-0.12 as optimistic. Either way a model reporting local RAE far below ~0.15 is fitting noise or leaking. This operationalises CLAUDE.md learning #8 with real numbers.

noise-floorraeassay-uncertaintyeda-official · 2026-08-22
high

The Emax file carries TDI labels for the two isoforms the challenge does not score

CYP1A2 and CYP2C9 have is_TDI labels shipped, even though the TDI leaderboard only scores CYP3A4 and CYP2D6.

Deep dive

Auditing the Emax file -- the last of the four training files never read column by column -- turned up 30 columns rather than the 5 the loader uses. Two things sit there unused. First, CYP<ISO>_is_TDI is present for ALL FOUR isoforms (CYP1A2 n=1,414, CYP2C9 n=1,285, CYP2D6 n=1,498, CYP3A4 n=3,584), whereas the TDI training file carries only the two scored isoforms. The challenge scores TDI on CYP3A4 and CYP2D6 alone, so the CYP1A2 and CYP2C9 labels are not directly submittable, but they are 2,699 extra binary labels on the same chemistry and remain available as auxiliary targets. Second, EmaxVsPosCtrl is shipped under BOTH the direct-inhibition and the TDI-preincubation condition, with credible bands on each, and the TDI-condition version inherits the same extended coverage as the TDI-condition pIC50 -- CYP3A4 n=3,583 versus 2,335 direct, exactly the 1,249-compound gap that ledger #50 turned into a deployed win. Checked and found not worth pursuing on its own: Emax under the TDI condition has essentially ZERO marginal correlation with the direct pIC50 (pearson -0.003) and only -0.116 with the residual left by the TDI-condition pIC50, so as a second calibration input for those donors it has very little to add on top of a proxy already correlating at 0.947.

data-auditemaxtdiunused-columnsswarm · 2026-08-24
high

The TDI-condition pIC50 is the closest proxy for the inhibition label in the whole drop

It is the same assay on the same compound, differing only by a preincubation step -- so it tracks the target far more tightly than any screen-derived surrogate.

Deep dive

Every auxiliary the campaign has used for the inhibition track is a DIFFERENT readout requiring a learned nonlinear calibration: single-concentration log2fc, Emax, the public PubChem panel. The TDI file carries CYP<ISO>_pIC50_TDI_condition, which is a pIC50 on the same enzyme and the same compound with only a preincubation step added. Measured against the direct-inhibition pIC50 on compounds having both: pearson 0.947 (CYP3A4, n=2,334), 0.962 (CYP2C9, n=1,285), 0.986 (CYP1A2, n=1,412), 0.896 (CYP2D6, n=1,493), with near-constant offsets (mean shift +0.339 / +0.009 / +0.030 / +0.267). Its practical value is coverage, and that is entirely CYP3A4's: 1,249 compounds carry a TDI-condition pIC50 with no direct pIC50, versus 0 on CYP2C9, 1 on CYP1A2 and 4 on CYP2D6. And 1,238 of those 1,249 are absent from the single-concentration screen too, so they are new donors rather than better-calibrated existing ones -- a 48% increase over CYP3A4's 2,571 deployed donors. Separately ruled out: the TDI file's direct-inhibition columns are not extra labels, since all 6,525 already appear in the inhibition training set on every isoform. The generalisable point is that a challenge shipping several files may repeat the SAME endpoint under a different condition in a file named for a different task, and that near-duplicate can be a better imputation source than any purpose-built surrogate.

data-inclusionimputationtdi-conditioncyp3a4coverageswarm · 2026-08-24
high

Two orthogonal drivers set reversible CYP inhibition: a heme-ligating N-heterocycle (specific, huge effect) and raw lipophilicity (nonspecific, especially 3A4).

Score every compound on both axes — 'does it have an unhindered azole/pyridine N?' and 'how greasy is it?' — and you capture most of the reversible-inhibition variance.

Deep dive
  • Heme-ligating heterocycles — imidazole, 1,2,4-triazole, pyridine, benzimidazole: strong, often pan-CYP inhibition via Type II Fe coordination (see physics). Steric hindrance around the N attenuates it.
  • Lipophilicity (logP/logD) — drives nonspecific inhibition, most pronounced for CYP3A4's large greasy pocket; high-logP compounds are promiscuous inhibitors.
  • Isoform-specific overlays: 2D6 basic amine + aromatic; 2C9 acid/H-bond; 1A2 flat polyaromatic (see biology).

Practical feature set: azole/pyridine SMARTS flags, logD7.4, aromatic-ring count, MW, plus the isoform-specific groups. This is a strong, interpretable baseline before any QM/cofold.

pharmacophoreazolelipophilicityseed · 2026-08-12
medium

Inhibition across the four isoforms is correlated but not redundant — greasy/azole compounds hit everything, while charge/shape decides the selective cases.

Multitask helps because of the shared promiscuity axis, but keep per-isoform heads because selectivity lives in the charge/shape residual.

Deep dive

A large fraction of variance is a shared promiscuity axis (lipophilic, heme-ligating → inhibits all four). That is exactly why multitask learning helps: the isoforms share this common cause.
But the *interesting* compounds are selective: an acid that hits only 2C9, a basic amine selective for 2D6, a flat aromatic selective for 1A2. That selectivity is the charge/shape residual orthogonal to the promiscuity axis.
Implication: a multitask base captures the shared axis; the honest per-isoform gains will come from features encoding the isoform-specific chemistry (protonation for 2D6, acidity for 2C9, planarity for 1A2). The swarm should check the cross-isoform correlation matrix in the real data early — it sets how much multitask can borrow.

cross-isoformselectivitysarseed · 2026-08-12

🧭 Meta

process, validation discipline, what transfers, pitfalls
high

"All seeds must improve" gets STRICTER as you add seeds -- the magnitude ratio does not

Requiring 3/3 at three seeds and 12/12 at twelve are not the same standard. Collecting more evidence should not raise the bar, but under unanimity it does.

Deep dive

A 3.12-SD effect on CYP2D6 TDI -- the largest the campaign has measured, with AUROC agreeing on 12/12 -- fails the honest gate at 11/12, on a single seed where it loses by 0.17 SD. That is a tie inside noise being treated as a contradiction. A binomial sign test on 11/12 gives p = 0.0032. The unanimity criterion is a PROXY for seed-stability whose strictness scales with the seed count, while the magnitude-vs-measured-noise ratio has no such dependence; #70b had already noted that 3/3 is a weak instrument at the low end, and this is the same defect at the high end. The result is reported as REJECTED rather than relaxed, because the precedent here is explicit: a gate relaxed to rescue a result with a good story is how campaigns fool themselves, and the one time this project resisted that temptation the disciplined route also performed better. Any change is queued with two mandatory conditions -- pre-registered before application, and re-derived retroactively across every past rejection -- since a rule changed while looking at one favourable result is not a rule.

honest-gateseed-stabilitystatisticsepistemicsqueuedledger #105 · 2026-08-28
high

"Already optimal" and "inert" are different findings and were being reported as one

A sweep that retains the incumbent looks like the same result every time. But whether deviations cost anything decides what to do next: tune more finely, or stop looking here entirely.

Deep dive

Ledger #52 retained alpha=1.0 for the donor weight and read as a routine null. Re-measured in the deployed stack with valid controls, CYP3A4 loses about 1 seed SD in BOTH directions from alpha=1.0 -- the value is load-bearing -- while CYP2D6 spans 0.0024 across a 4x range against a 0.0052 seed SD, so nothing there depends on it. Same verdict line, opposite implications: one says the knob is set correctly, the other says the knob is not connected. Reporting the SPREAD across the grid relative to the isoform's measured seed noise separates them and costs nothing, and it converted a routine retention into a closed lever on the bottleneck isoform. Two further audit findings from the same run: #52's CYP3A4 artifact carried reproduces_incumbent:false, so a conclusion had been drawn from a measurement its own control rejected (the conclusion happened to be right), and CYP2D6 -- 2,883 donor rows, 65.9% of its training set, the isoform with the largest gap -- had never been swept at all.

epistemicshyperparametersnull-resultshonest-gateledger #99 vs #52 · 2026-08-27
high

A "binds vs inert" verdict describes the GRID you sampled, not the parameter

The statistic is max-minus-min across the grid. Choose a grid on one side of the optimum and you measure a slope; choose one spanning it and you measure a plateau. Same parameter, opposite conclusions.

Deep dive

Sweeping the ExtraTrees member count on CYP2D6, the pre-registered upward grid {5,10,15} gave spread 0.0055 = 1.06 SD and the verdict 'the count BINDS -- the mix is a live parameter'. Extending into the direction my hypothesis had never sampled, {1,2,3} gave spread 0.0036 = 0.69 SD and 'the count is INERT'. Same parameter, same isoform, same seeds, opposite verdicts. The full curve explains it -- 1: 0.8750, 2: 0.8724, 3: 0.8714, 5: 0.8717, 10: 0.8745, 15: 0.8773 -- flat below 5 and rising above it, so n_et=5 sits inside a plateau with one real edge. The spread statistic (introduced to separate 'already optimal' from 'inert') stays useful but must always be reported with its range. Corollary worth keeping separate from the no-fishing rule: extending a grid into a direction never sampled is a design fix that a one-sided hypothesis owes, whereas refining around a winner is fishing; the first must still be pre-registered before running.

epistemicshyperparametersgrid-designknob-sensitivityself-correctionscripts/train/et_count_gate.py; data/processed/et_count_cyp2d6.json, et_count_cyp2d6_down.json; ledger #104 · 2026-08-28
high

A branch that rebuilds a feature matrix from the original silently discards every earlier block

The joint branch ended with XX = hstack([X, ...]) rather than hstack([XX, ...]). Any block added above it vanished, and the gate would have scored a bit-identical no-op and called it a null.

Deep dive

The EMBED_TRUNK hook added in #141 appends a learned representation to XX inside the fold loop. The joint-block branch further down then rebuilt XX from X, the pristine feature matrix, discarding anything added before it. CYP2D6 deploys with joint=False so #141 was unaffected, but CYP1A2 deploys with joint=True -- so testing the same block there would have trained on features identical to the baseline, produced a delta of exactly zero, and been written up as evidence that the representation does not help. That is the same silent-drop class the campaign has hit repeatedly: #120 lost the ExtraTrees layer to a knob the harness did not read, #123's shape guard returned a score bit-identical to baseline for a layer that never ran, and recipe() once dropped CYP2C9's aux block by inferring configuration from filenames. Two fixes applied together, because the code fix alone would not catch the next instance: the branch now composes (XX = hstack([XX, ...])), and the gate refuses to emit a verdict when the treatment arm is bit-identical to the control on every seed, exiting non-zero with an explicit harness-defect message rather than reporting a null. The general rule: when a delta is exactly zero to machine precision, the default hypothesis is that the treatment never ran, not that it was neutral.

metaharnesssilent-dropself-correctionguardsscripts/train/shrinkage_gate.py · 2026-08-30
high

A control calibrated on seed noise cannot detect a layer worth less than that noise

The fifth-gate control asks whether a harness reproduces the deployed score within a tolerance set by seed noise. A layer contributing less than that tolerance can be entirely absent and the control still says 'reproduces'.

Deep dive

The deployed OOF path applies the ExtraTrees layer through a module knob rather than through recipe(), so calling it bare silently omits a shipped layer. A leave-one-out run did exactly that: CYP1A2's baseline came back at 0.7764, which is precisely the minus-ET arm measured in an earlier tick, and the control reported 'reproduces' because its tolerance is 0.0100 while ExtraTrees' whole contribution on that isoform is 0.0089. The consequence was not cosmetic -- with the correct baseline the bandl2 objective's verdict flips from +0.0085 (earns its place, 3/3 seeds, 1.18 SD) to -0.0063 (removing it helps, 0/3, 0.87 SD), a swing of -0.0148. So every layer whose effect is smaller than the control tolerance is invisible to the control by construction, and on a noisy isoform that can be most of the stack. Practical mitigation: harnesses should assemble the deployed configuration from ONE source of truth rather than a mix of recipe() and module knobs, and an ablation harness should assert which layers it believes are active before measuring anything.

harnesscontrolsfifth-criterionseed-noiseconfigurationscripts/train/loo_untested.py; ledger #120 · 2026-08-28
high

A correct verdict reached by a broken criterion is still a broken criterion

Both rejections stand -- but neither was decided by evidence, and next time the coin could land the other way.

Deep dive

CYP3A4's bandl2 was rejected because LCO moved +0.0009, which is 0.13x that isoform's 0.0067 split-noise threshold. CYP2C9's band weighting was rejected because LCO moved +0.0041, or 0.07x its 0.0570 threshold. Re-tested properly in the current pipelines over 6 clusterings each, both still reject -- but on scaffold-CV seed-stability, not on LCO, and in both cases the multi-clustering LCO was itself unresolvable (sign stability 0.67 and 0.50). So the original decisions were right by luck. The reason this is worth logging rather than shrugging off: a criterion that decides by coin flip will eventually flip the other way and admit something that should have been rejected, and the campaign would have no way to tell which case it was in. The value of #65's threshold check is not that it overturns past decisions -- so far it has overturned none -- but that it distinguishes decisions backed by evidence from decisions backed by nothing.

gatinglcoepistemicsself-auditdata/processed/reverify_recipe_cyp3a4_bandl2.json · 2026-08-25
high

A correlation table written up as a mechanism -- and the experiment that caught it

#97 measured a real, reproducible relationship between donor share and a loss function's success, then attached a causal story to it. The story was wrong, and only running the experiment it implied revealed that.

Deep dive

#97's table (donor share 0% / 52.4% / 62.1% / 65.9% / 70.6% against bandl2 winning or losing) was correct and replicable, including a clean within-isoform pair where 1,249 extra donor rows flipped the sign with everything else fixed. The mechanism attached -- zero-width donor bands collapse bandl2 to l2 -- made a sharp prediction: widen the donor bands and bandl2 recovers. #98 tested it and got the opposite, monotonically (+0.0034 -> +0.0040 -> +0.0141). The correlation survived; the cause did not. Two features of the situation made this easy to miss: the mechanism was visible in the source code (lt = concat([lt, yi]) really does set lo = hi), and it fit every row of the table. What it lacked was a prediction anyone had checked. The follow-up that caught it happened to also be the obvious next lever, which will not always be true -- so the rule is to state the prediction a proposed mechanism makes AT THE TIME OF WRITING IT UP, not when it becomes convenient to test.

epistemicsmechanism-vs-correlationself-correctionledger #97 vs #98 · 2026-08-27
high

A default that is always overridden is never tested -- and it overwrote a committed submission

make_submission.py's -o defaulted to a hardcoded v03 path from when v03 was current. Eight submissions all passed -o explicitly, so nobody ever exercised the default -- until one run did.

Deep dive

Running the builder bare silently replaced committed submission v03 (macro 0.7329) with v11's predictions. Restored from git, nothing lost because it had been committed. The convention -- one CSV per experiment, never overwrite, bump the number -- was written in CLAUDE.md and enforced only by the caller's habit of passing -o. Fixed both directions: the default is now the next free NN_ slot computed from the directory, and any existing output path is refused unless --force; both verified. The general rule: a rule worth stating in CLAUDE.md is worth an assertion in the script that implements it, because correctness that depends on someone remembering is not correctness. Same shape as the layer-registration guard one level down.

harnesssubmissionsdefaultsconventionguardscripts/train/make_submission.py; ledger #102 · 2026-08-27
high

A family closed on a trade-off was closed after sampling ONE point on it

Naming the right variable is not the same as measuring it. #87 said diversity and competence trade off, tested a single low-competence model, and closed the whole family.

Deep dive

The blend family was closed with the conclusion that diversity and competence trade off directly -- correct as a statement, and drawn from one arm: a ridge regression on 2,265 mostly-binary features, which is close to the lowest-competence model available on this data. ExtraTrees occupies the corner that was never sampled (tree-based, so competent on these features; bagged with random split thresholds, so decorrelated from a boosted ensemble), and it improves two of four isoforms with the outcome monotone in solo competence -- confirming #87's variable while refuting its closure. The cheap guard, adopted: when closing a family on a trade-off, state which REGION of the trade-off was actually sampled, so the closure carries its own scope. This is the same shape as #97 -> #98 (a correlation written up as a mechanism) but one level up: a mechanism written up as a boundary.

epistemicsfamily-closurescopeself-correctionledger #100 vs #86/#87 · 2026-08-27
high

A fixed rule does not fix the artifacts written under the old one -- second occurrence

deployed_score returned 0.5962 for CYP2C9 because a stale JSON still said DEPLOY, weeks after the rule that said so was corrected.

Deep dive

bagged_gate.py originally had no magnitude requirement in its deploy condition, so it printed DEPLOY for CYP2C9 at -0.0071 while its own magnitude line called that indistinguishable from noise (0.33 of a 0.0216 seed SD). The rule was fixed; the artifact was not. Because deployed_score walks bagged_<iso>.json as the newest layer, the stale verdict sentence was read as a live deploy and CYP2C9's noise-level effect was one submission build away from shipping. It was caught by a routine re-check of deployed_score before building, not by noticing the bug. This is ledger #65's lesson recurring in a different script three weeks later, which suggests the lesson needs a mechanism rather than a memory: any gate whose verdict is READ by the submission path should either store the inputs needed to re-derive its verdict (these did, which is why rederive_bagged.py could fix it without refitting) or record the rule version it was scored under. A related defect in the same script: its else-branch reported every non-deploy as a scaffold-CV failure, so a CONTROL failure on CYP1A2 printed 'REJECT -- scaffold-CV does not hold (delta -0.0138, seed-stable True)', a self-contradictory sentence that would have been read as a real rejection.

harnessartifactsreproducibilityself-correctiondata/processed/bagged_cyp2c9.json · 2026-08-25
high

A gate can say DEPLOY while the submission path has no way to express the layer -- check both directions

The gate and the builder are each individually correct; the gap is that neither knows about the other. Nothing reports a problem, and the shipped model quietly lacks the thing that was just gated.

Deep dive

This has now happened twice (#62, a stale joint-gate artifact that would have dropped a block passing 12/12; #77, an aux block lost because recipe() infers configuration from filenames) and was primed to happen a third time: the pending ExtraTrees layer had no reader in either deployed_score() or make_submission. scripts/check_layers.py checks BOTH directions -- every registry entry referenced by the score walk AND a builder reader, and every artifact on disk recording a deploy registered -- with a non-zero exit so it can gate a rebuild. On its first run it found tdi_bagged_threshold_cyp3a4.json still carrying verdict DEPLOY for a configuration ledger #76 had killed with a fifth check, with nothing on disk recording the overturn; it ships nothing today only because the TDI track has no submission path yet. Also verified: registering the new layer moved no current value (0.4492/0.5882/0.8790/0.7847 unchanged) and the reader deliberately requires the 12-seed artifact, so the marginal 3-seed result cannot ship by accident.

harnesssubmissionstale-artifactshonest-gateguardscripts/check_layers.py; ledger #101 · 2026-08-27
high

A gate fix that is not propagated is a gate fix that will be undone by the next script

The magnitude bar added in #70b lived in one script for eleven ticks while four others deployed without it.

Deep dive

In #70b bagged_gate was found to lack a magnitude requirement and printed DEPLOY for a CYP2C9 effect its own output called indistinguishable from noise. The condition was added there and nowhere else. Eleven ticks later capacity_gate, estimators_gate, feature_prune_gate and reverify_recipe were still deploying on improves-plus-seed-stable-plus-LCO-not-contrary, and estimators_gate duly printed DEPLOY on 3 of 4 for a CYP1A2 effect at 0.43 SD against a measured 0.0072 null. It was caught by reading the magnitude line rather than the verdict, which is not a reliable control. Three of the four had produced no false positive only because their candidates failed on other criteria first -- luck, not design. The structural point is that these gates are generated by copying one another, so a defect fixed in the parent propagates to nothing while the parent's descendants inherit the original flaw. Two habits follow: when a gate criterion changes, grep every gate for the condition rather than fixing the one in hand, and prefer a shared helper over a copied conditional so that the criterion has one definition. The same reasoning applies to the fifth-criterion control, which is currently reimplemented in each script.

gatingtoolingself-correctionpropagationdata/processed/estimators_cyp1a2.json · 2026-08-26
high

A guard caught me writing DEPLOY into an artifact for something I had decided not to deploy

My gate script emitted verdict DEPLOY whenever three checks passed, but the honest gate has four criteria and the script only tests two of them. check_layers.py refused the artifact.

Deep dive

The new TDI alerts gate wrote verdict DEPLOY into data/processed/tdi_alerts_et_cyp2d6.json whenever the control reproduced, the delta was positive and seed-stability held. Those are two of the four honest-gate criteria plus the fifth-gate control; correlates-with-base-error and leave-cluster-out are not tested by that script at all. check_layers.py flagged the artifact as UNREGISTERED, because a DEPLOY verdict records a shipping decision that the submission path cannot express, and the layer was deliberately not added to the submission path since the intention was never to ship it. The prose write-up was already correct -- it stated plainly that only two criteria had been tested and the effect was 0.31 of the measured seed SD -- so the defect was purely that the machine-readable artifact disagreed with the human-readable conclusion. That is exactly the divergence that has burned this campaign before, when a corrected harness left a stale winner in an artifact that downstream code kept reading. Fixed by making the verdict vocabulary honest rather than by relaxing the guard: the script can now emit only REJECT or PASSES-2-OF-4, never DEPLOY, and the comment in the source records why. The general rule is that a gate script must not be able to express a verdict stronger than the criteria it actually evaluates.

metaguardshonest-gateself-correctiontoolingscripts/check_layers.py · 2026-08-30
high

A guard that proves WIRING is not a guard that proves EFFECT

One check confirms a deployed layer has a reader in the submission builder. It cannot confirm the built file changed. Comparing consecutive submissions does, and it costs seconds.

Deep dive

Submission v10 predates the ExtraTrees layer and v11 follows it, ET deployed on CYP2D6 and CYP1A2 only, and both builds are deterministic -- so CYP3A4 and CYP2C9 must be identical and the other two must differ, with each possible violation meaning something opposite: an ungated model moved, or the gated layer never reached the file. Result: CYP3A4 and CYP2C9 bit-identical at 0 compounds changed, CYP2D6 and CYP1A2 changed on 749 of 750 each, the single exception per isoform being a compound where the models agree below the CSV's 4-decimal rounding. This is the only check in the campaign that verifies the shipped artefact against the gated decision at the level of the numbers rather than the plumbing, and it needs no model and no refit. Generalises to any deploy: after shipping a layer that is scoped to a subset of targets, diff the new artefact against the previous one and assert both the expected changes AND the expected non-changes -- the non-changes catch the more dangerous defect.

harnesssubmissionsintegrityguarddeterminismscripts/submission_delta_check.py; ledger #115 · 2026-08-28
high

A guard that skips is a guard that lies -- a silent no-op is indistinguishable from a null result

A shape check written as defensive coding quietly disabled an experimental layer. Its score came back bit-identical to the baseline, which reads exactly like the layer having run and achieved nothing.

Deep dive

An experimental donor layer was guarded by 'if donor_columns == training_columns: proceed'. The condition was false, so the layer never ran, and the isoform scored 0.7721 against a deployed 0.7721. That output is identical to what a genuinely useless layer would produce, and the conclusion drawn from it would have been a null result for an experiment that never executed. The only thing that caught it was the four-decimal match to the baseline being too exact. Replacing the skip with a raised error immediately produced the real diagnosis: 2265 donor columns against 2288 training columns, because that isoform's joint block adds 23 per-fold columns from external-model predictions and 3D features that the donor rows cannot express. The rule: every conditional wrapped around an experimental layer must fail loudly rather than skip, because a silently skipped layer produces an unfalsifiable null. This is the same family as an earlier finding that a reader of gate artifacts must crash on a corrupt file rather than treat it as absence -- absence and failure look identical downstream unless something refuses to continue.

harnesssilent-failureguardsnull-resultsexperimental-layersscripts/train/shrinkage_gate.py CROSSISO_DONORS; ledger #123 · 2026-08-29
high

A hardcoded output filename let one isoform's gate silently overwrite another's artifact

The embedding gate always wrote gate_embed_block_cyp2d6.json regardless of which isoform ran, so the CYP1A2 run destroyed the CYP2D6 result and produced a file named cyp2d6 whose isoform field said cyp1a2.

Deep dive

The gate script derived every configuration from its --isoform argument but hardcoded the output path to gate_embed_block_cyp2d6.json. Running it on CYP1A2 therefore overwrote the CYP2D6 artifact in place, leaving a file named for one isoform containing another's payload, and no artifact at all under the CYP1A2 name that the write-up cited. Both were recovered: the CYP2D6 result was restored from the commit that produced it, and the CYP1A2 payload was rescued from the overwritten file and refiled under its correct name. The delta values confirm the recovery, CYP2D6 at +0.0281 and CYP1A2 at +0.1141, matching what each run reported to its log. This is the same defect class as the campaign's earlier finding that recipe() infers configuration from artifact filenames, but pointing the other way: there the filename determined behaviour, here the filename lied about content, and both break because filename and payload are allowed to disagree. The fix derives the path from the isoform actually gated. The generalisable rule is that any artifact whose name encodes a parameter must construct that name from the same variable the run used, and it is worth asserting the payload's own isoform field matches its filename before trusting either.

metatoolingartifactsdata-lossself-correctionscripts/train/gate_embed_block.py · 2026-08-30
high

A model-selection rule must enforce the same honest gate as the features it selects among - mine did not, and it silently re-admitted a rejected block

The gate protects features. Nothing was protecting the step that CHOOSES between feature sets - so a block that failed its own gate walked back in through selection.

Deep dive

Writing the joint gate (ledger #15) I made the winner rule 'beats base on scaffold-CV mean AND under leave-cluster-out'. On CYP1A2 that selected base+mech - the very configuration whose mechanistic block had been REJECTED earlier for failing seed-stability (seed 7 regressed +0.0075). The block failed the feature gate, then passed the selection step, because selection only checked two of the four criteria. Fixed: the winner rule now requires the full gate - beats base on mean AND leave-cluster-out AND improves on EVERY seed AND the effect exceeds noise (effect/seed-noise >= 0.3) - and breaks ties toward the SIMPLER configuration (fewest blocks first, then best mean), so complexity is never bought for a tie. CYP1A2 now correctly falls back to plain base. GENERAL POINT: any place a pipeline picks among alternatives - feature sets, hyperparameters, ensembles, thresholds - is itself a fitting step and needs the same discipline as the models. Selection is where quietly-rejected things come back.

model-selectionhonest-gateself-correctionoverfittingcombined-gate · 2026-08-23
high

A plausible explanation for a flagged check is not a substitute for re-measuring it

The verifier flagged CYP2C9 by a hair, and a ready explanation was available -- the known seed-42 outlier. Accepting it would have been reasonable and would have skipped the one step that could tell the difference.

Deep dive

Verifying the deployed configuration at a single seed, three isoforms reproduced and CYP2C9 missed at 0.6118 versus a deployed 0.5882, a difference of 0.0236 against a 0.0226 tolerance. An earlier tick had already established that seed 42 is an unlucky draw on that isoform, so the mismatch had an obvious and probably correct story attached. It was re-measured instead: at three seeds CYP2C9 gives 0.5970 with a difference of 0.0088 against a 0.0130 tolerance and reproduces, with seed 42 sitting 1.31 SD from the mean. The story was right, and it is now a measurement rather than an inference -- and the operational consequence only appears once it is measured, namely that single-seed verification cannot be trusted on the isoform with the largest per-seed noise, so the verifier now defaults to three seeds. The general point: when a check fires and you already know why, the cost of confirming is usually one run, and the alternative is a habit of explaining away exactly the signals the check exists to raise.

epistemicsverificationseed-noisedisciplinescripts/train/deployed_config.py; ledger #121 · 2026-08-28
high

A reader of gate artifacts must fail LOUDLY on a corrupt file -- absence and corruption mean opposite things

An empty artifact and a missing artifact look the same to a defensive reader, but they should not: missing means the layer was never gated, corrupt means the result exists and was lost.

Deep dive

CYP1A2's 12-seed run completed all 24 fits and then hit ENOSPC on write, leaving family_et_cyp1a2_12seed.json at 0 bytes. make_submission.extra_trees() does json.load(open(fp)), which raises on an empty file, so the build would have crashed -- and that is the GOOD outcome. Had the reader been written defensively with a try/except returning 0, the build would have SUCCEEDED and silently shipped the un-improved model, which is the #62 failure mode exactly. Recovery discipline matters too: the artifact was rebuilt by PARSING the run's own stdout rather than re-typing numbers, with the derived verdict asserted to match the verdict line the script printed before it failed (delta, seed count, SD ratio, and the DEPLOY word), and reconstructed_from recording the provenance -- a hand-entered artifact is indistinguishable from a fabricated one. Space was freed by rclone move of a regenerable model cache to O:, not deletion, after an earlier tick destroyed a dataset that was not mine while clearing the same disk. D: reaching 0 bytes has now corrupted output twice (a source file, then a gate artifact) and is a blocker needing a human: data/ is 34 MB and the repo 79 MB, while AppData at 48.5 GB and .windsurf at 21.5 GB are the consumers.

harnessenospccorruptionfail-loudprovenanceblockerledger #102 · 2026-08-27
high

A reproduce-the-incumbent control with a FIXED tolerance can fire on seed noise

The fifth gate demands a re-implementation land within 0.01 of the deployed score. CYP2C9's own seed SD is 0.0113 -- larger than the tolerance -- so a correct pipeline can fail its own control by drawing an unlucky seed.

Deep dive

error_locus.py at one seed scored CYP2C9 at 0.6118 against a deployed 0.5882 and printed DOES NOT REPRODUCE; the same pipeline at three seeds gives 0.5970 and reproduces, so the 0.6118 was a 2.1-SD single draw. Fix: cyp.eval.control_tolerance(iso, n_seeds) returns max(0.01, 2*SD/sqrt(n)) -- CYP2C9 needs 0.0226 at one seed and 0.0130 at three, while CYP3A4 stays at the 0.01 floor -- so the bar tightens as seeds accumulate and never demands more precision than the pipeline can deliver. A control that fires on noise gets ignored, which is exactly the failure mode the fifth gate exists to prevent. Caveat inherited by #94 and #95: both scored CYP2C9 at a single seed, so their 2C9 rows carry roughly 2 SD of noise.

honest-gatefifth-criterionseed-noiseharnesssrc/cyp/eval.py control_tolerance(); ledger #96 · 2026-08-27
high

A robust single model beats a finely-tuned kitchen-sink stack on small, series-shifted tests — resist over-ensembling.

Every extra tuned lever is a liability on a 750-compound blind set; prefer one strong multitask base + one disciplined calibration.

Deep dive

In PXR the 'ultimate ensemble' (stack every validated lever with subset-tuned weights) regressed versus the robust base plus one calibration. On a small shifted test, each additional tuned weight overfits.
Corollary for CYP:

  • Build a strong multitask base; add features only when they pass the honest gate.
  • Prefer few, well-justified members with stable weights over many finely-tuned ones.
  • When in doubt between a complex stack and a robust single model, ship the robust single model.

The 750-test is small enough that variance in the metric can hide real differences — don't chase decimal places that leave-cluster-out can't confirm.

ensemblingrobustnessover-stackingseed · 2026-08-12
high

A second block was silently dropped by filename-inferred configuration

recipe() reads the configuration off artifact filenames. When a later gate runs its arms on 'base' alone, it writes a filename with no block in it -- and the block vanishes from the shipped model without any decision being made.

Deep dive

The mechanistic block passed its own gate on CYP3A4 (-0.0029), CYP2C9 (-0.0109) and CYP2D6 (-0.0077) with all seeds improving, and ships on CYP2D6 alone. The cause is that 3A4 and 2C9's imputation gates ran their arms on base alone and wrote impute_bias_emax_<iso>.json, so the filename-inferred configuration lost the block. This is the second separate instance of the pattern, after CYP2C9's aux block, and it left a stale claim in the project instructions for weeks. Measured consequence this time: restoring the block fails the honest gate on both isoforms (2C9 -0.0053 at 0.47 SD, 3A4 -0.0011 at 2/3 and 0.29 SD), so the accident was fortunate twice -- which is luck, not a process. The durable lesson is that inferring state from filenames makes silent drops invisible by construction, and the only reliable detectors are periodic audits comparing what gated against what ships.

harnessconfigurationsilent-dropstale-claimsauditscripts/train/mech_readd.py; ledger #118 · 2026-08-28
high

All 13 submissions pass the real validator, and the compound identity check it omits

Nothing has ever been uploaded, so no file had ever been confirmed acceptable by the thing that decides. A format defect would cost the campaign while every model result stayed valid.

Deep dive

The official logic was transcribed from the public Space (config.py, submission.py) into scripts/official_validator.py rather than inferred from prose. All 13 submissions PASS, correctly typed by track (11 regression, 2 classification): 750 rows, required identifier and endpoint columns present with exact names, no NaN or inf in regression endpoints, binary values in classification endpoints. Extra columns are allowed by the official reader, so carrying SMILES plus Molecule_Name plus endpoints is fine. The official validator checks row COUNT but never compound IDENTITY, so a mismatched Molecule_Name set would join wrongly and silently mis-score; verified separately that v10, v11 and v12 match the test set's ID set in the same order, with identical SMILES and zero duplicates. Live challenge state read from config rather than assumed: CURRENT_PHASE=1 (portal open), CLASSIFICATION_ACTIVE=True, STRUCTURE_TRACK_LIVE=False, HOURS_BETWEEN_SUBMISSIONS=12, ACTIVITY_DATASET_SIZE=750, STRUCTURE_DATASET_SIZE=184. The campaign's output is submittable; the remaining blocker is purely that a human must upload.

metasubmissionvalidationblockerscripts/official_validator.py · 2026-08-29
high

An amplification RATIO on a near-zero base carries no information

One layer's effect grew 4.9x under a shifted evaluation and another grew 5.1x. The first was a real effect getting much bigger; the second was five times almost nothing.

Deep dive

Re-scoring the mechanistic block under shift-correction gave amplification ratios of 4.9x on CYP2D6, 5.1x on CYP3A4 and 0.7x on CYP2C9, which reads as a general property of the block until the magnitudes are put beside them: CYP2D6 goes -0.0442 to -0.2155 while CYP3A4 goes -0.0011 to -0.0056, thirty-eight times smaller. I was one sentence from publishing that the amplification was CYP2D6-specific and tracked the anionic-pocket biology; the ratios refuted that reading, and the correct statement is that the MAGNITUDE is 2D6-specific while the ratio is not informative anywhere the base effect is negligible. General rule: never report a ratio without its numerator, and never generalise from a ratio whose denominator is inside the noise -- printing it next to a real one is an active invitation to over-generalise.

epistemicsstatisticsratioscovariate-shiftself-correctiondata/processed/mech_readd.json; ledger #117, #118 · 2026-08-28
high

Any estimator starting from raw score 0 shows the init_score defect, not just LightGBM

An MLP scored ST-RAE 3.32 against a mean-predictor 1.0. It was not a null result: predicted mean 3.288 against label mean 4.820, with the spread already correct.

Deep dive

The campaign documented that custom LightGBM objectives start from raw score 0 rather than the label mean, and that fixing it with init_score moved CYP1A2 softband from 9.4591 to 0.8304. That was recorded as a LightGBM quirk. It is not. A two-layer MLP on CYP2D6, the first neural model the campaign has run, reproduced the identical failure: ST-RAE 3.3229 on the single-task arm and 6.5051 on the auxiliary arm, both catastrophic against a mean predictor of 1.0. Diagnosing before interpreting showed a pure OFFSET rather than a fitting failure -- predicted mean 3.288 against a label mean of 4.820, with predicted spread 0.891 against 0.904, so the network had learned the shape and was still climbing the intercept after 120 epochs. Training on the centered target and adding the training mean back is the direct analogue of init_score, and it moved predicted mean to 4.700. The general rule, now in CLAUDE.md: before believing any catastrophic score from a new model family, compare predicted mean against label mean, because an offset with correct spread is this defect rather than evidence that the approach fails. Had the first numbers been written up as a result, the conclusion would have been that auxiliary-task learning is catastrophically bad for CYP2D6, which they do not show.

metainit-scoreneuraldebuggingself-correctionscripts/train/aux_task_mlp.py · 2026-08-29
high

Auditing gated-vs-shipped found four silent drops; a drop is only 'accepted' once it has a number

One guard checks that a layer is wired into the submission path. Nothing checked that a block which passed its gate is still in the configuration -- and four had quietly fallen out.

Deep dive

recipe() infers configuration from artifact filenames, so when an imputation or objective gate runs its arms on 'base' alone it writes a filename with no block in it and the block disappears without any decision. gated_vs_shipped.py compares each block gate's verdict against what recipe() actually reports, in both directions, and found four drops: the mechanistic block on CYP2C9 and CYP3A4, and the aux block on both. Three had been examined; CYP3A4's aux had not, and measuring it required first teaching the deployed OOF path to express that block at all -- which is exactly why it had gone unmeasured. It came back at +0.0495, 0 of 3 seeds, 13.38 SD worse. The design rule that makes the audit useful rather than noisy: a drop may be marked ACCEPTED only with the measurement that justifies leaving it, since otherwise it is an unexamined accident with a nicer name, and anything unlisted exits non-zero. All four now carry numbers and the audit passes clean.

harnessauditsilent-dropconfigurationguardscripts/gated_vs_shipped.py; ledger #119 · 2026-08-28
high

AUROC is not a TDI leaderboard metric, and TDI submissions must be binary

The campaign has reported AUROC on every TDI experiment and believed hard booleans were forfeiting it. The Space's own config says MCC, Accuracy, Precision, Recall, F1 -- and the validator rejects anything non-binary.

Deep dive

Fetched from hf://spaces/openadmet/cyp-challenge. CLASSIFICATION_METRIC_DISPLAY_NAMES is {MCC, Accuracy, Precision, Recall, F1} with MCC primary and sorting; AUROC and AUPRC are never computed. CLAUDE.md stated 'Metrics: AUROC / AUPRC / MCC' and has been corrected in place. Every AUROC figure in the ledger, including #108's headline 'AUROC 0.5719 to 0.6253 on 12/12', describes a quantity the leaderboard does not use -- though #108's deploy stands because it gated on MCC (0.1058 to 0.1540, 11/12 seeds, sign-test p=0.0032). Separately, the submission format REQUIRES binary values: the Submit tab specifies 'a bool prediction for TDI' and submission.py rejects with 'contains non-binary values' unless every entry is in [0, 1, True, False]. Submitting probabilities to improve a ranking metric would have been rejected outright, so the existing hard True/False file is correct and the instinct to fix it would have broken a working submission. Regression metrics confirmed as ST-RAE (primary), MAE, R2, Spearman rho, Kendall tau.

metasubmissiontdimccaurocself-correctionscripts/official_validator.py · 2026-08-29
high

Before spending seeds on a borderline result, compute whether ANY n could resolve it

Two blocks sat just above the magnitude bar. One would take about 48 seeds to settle and the other about 3468 -- so the second is not marginal, it is undecidable by that criterion, and no amount of compute changes that.

Deep dive

Bootstrapping over seeds, resampling each seed's deployed and alternative scores together so the delta and the seed SD stay coupled as they are in the real estimate, gives the ratio's own confidence interval: 1.18 with CI 0.82 to 2.49 for one block and 1.01 with CI 0.84 to 1.50 for the other. Extrapolating the half-width as one over the square root of n, the first needs about 48 seeds and the second about 3468. That distinction matters operationally because the two had been recorded identically as marginal and queued, when in fact one is an affordable experiment and the other is a question the criterion cannot answer. The calculation costs nothing -- it reads stored per-seed numbers and fits no models -- and should precede any decision to re-run a borderline gate with more seeds. It also corrects an earlier estimate that propagated only the denominator's uncertainty and therefore reported intervals up to twice too narrow; the conclusion was unchanged but the confidence in it was overstated.

epistemicsstatisticsbootstrapmagnitudecost-as-verdictscripts/train/ratio_resolvability.py; data/processed/ratio_resolvability.json; ledger #129 · 2026-08-29
high

Boltz credits are exhausted (402) - and the failure was invisible because my submit loop stored '}' as a job ID

A success count is not evidence of success unless the thing being counted is validated. 'submit DONE 24/24' was 83% wrong.

Deep dive

Boltz returns 402 payment_required: 'All credits depleted. Purchase prepaid credits or enable on-demand billing to continue running jobs.' The untemplated CYP2D6 pilot (24 jobs, ~$4.80) plus 4 templated jobs exhausted the shared pool. Only indices 0-3 of the 24 templated submissions went through; 20 never submitted. TWO BUGS THIS EXPOSED, both now fixed. (1) The submit loop took the LAST LINE of CLI stdout as the job id. On success --transform id prints a bare id, but on an API error the CLI prints a JSON object whose last line is '}' - which was stored as the job id. Since '}' is truthy, the run reported 'submit DONE: 24/24 jobs' while 20 had actually died on 402. Fixed: ids must start with 'sab_pred_', failures print the error, and the summary warns when ok < requested. (2) The comparator emitted a hard verdict - 'the structural route is CLOSED for 2D6' - from 0/4 templated poses. That is exactly the underpowered-conclusion error logged earlier for the Cohen-d threshold: a 0/n result at n=4 is consistent with any true coordinating rate below roughly 50%. Fixed with a MIN_N=12 guard that returns INCONCLUSIVE instead. GENERAL LESSON: a success COUNT is not evidence of success unless the thing counted is validated. 'submit DONE 24/24' was the most confident-looking line in the run and was 83% wrong.

blockerboltzcreditssilent-bugvalidationcofold-template · 2026-08-23
high

Configuration assembled from many sources is a defect class, not a bug

The shipped model was defined by a recipe function, direct artifact reads, and eight module globals. Each was correct; no single caller could reconstruct the whole. A harness that used one of the three measured something else and its control said fine.

Deep dive

The deployed configuration came from recipe() for cfg, band, imputation, emax and shift; from direct artifact reads inside oof_for for TDI donors, the joint block and bagging; and from eight module globals for ExtraTrees, DART, the LightGBM member switch, generic candidate models, two disable flags and two donor knobs. Calling oof_for bare therefore picked up the recipe-driven layers and not the knob-driven ones, which is exactly how an earlier tick lost the ExtraTrees layer from a leave-one-out baseline and flipped a verdict by -0.0148 -- with the fifth-gate control reporting 'reproduces' because its tolerance is wider than that layer's whole contribution. The fix is not more care at each call site but one function returning the entire configuration, plus a context manager that sets every knob from it, so an ablation harness enters it and turns exactly one thing off and the baseline is the shipped model by construction. Validated by running the deployed OOF inside that context for every isoform and requiring it to reproduce the deployed score; all four do.

harnessconfigurationsingle-source-of-truthdefect-classablationscripts/train/deployed_config.py; ledger #120, #121 · 2026-08-28
high

Correlated repeats are not independent samples: pooling them inflated my own t by 2x

Four permutations evaluated on the same split mostly measure that split, not four independent draws.

Deep dive

The permutation-null script first reported the LCO bias at t=2.45 over 16 arm-clusterings and declared an estimator artifact. But the 16 are 4 clusterings x 4 permutations, and permutations sharing a split share that split's difficulty almost entirely -- on CYP2C9 one clustering (k12_s1, deployed LCO 0.7479 against a 0.61-0.63 typical) drove the whole mean, with all four permutations moving -0.045 to -0.060 together. Averaging within each clustering first gives the correct independent unit, n=4: t=1.12, not significant. The inflation is roughly sqrt(n_repeats), so it grows as you add repeats -- the same perverse direction as the range-normalisation bug found earlier, where collecting more evidence made the statistic worse. Both scripts now print the naive and correct values side by side so the discrepancy is visible rather than silent. General rule for this campaign: before computing a t or an SD, state what the independent unit is; repeats within a split, a fold, or a seed are not it.

statisticsharnessindependenceself-correctiondata/processed/lco_null_cyp2c9.json · 2026-08-25
high

CYP1A2 refutes the '3x amplification' mechanism I logged from CYP2D6 alone - the factor is isoform-specific and can be below 1

Four isoforms is enough to TEST a mechanism and never enough to ESTABLISH one. Replicate before claiming.

Deep dive

CYP1A2 was run as a replication of the CYP2D6 'weighting amplifies feature blocks ~3x' result. It FAILS to replicate. On 1A2 the mechanistic block's marginal contribution SHRINKS under weighting (-0.0030 unweighted -> -0.0017 weighted, 0.6x) and the auxiliary HURTS in both conditions (+0.0039 -> +0.0064; positive means worse than base). Compare CYP2D6, where aux went 2.9x and mech 3.6x. So the amplification factor is ISOFORM-SPECIFIC and can be below 1 - it is not a general property of band weighting, and I should not have stated it as a mechanism from a single isoform. The 2D6 finding is now marked corrected. WHAT DOES SURVIVE both isoforms: no rejected block reversed, and the winner was unchanged in both cases (2D6 base+mech|weighted, 1A2 base|weighted). The practical conclusion for ledger #32 - blanket re-testing of rejected blocks is not worth the compute - stands on those two outcomes, not on the amplification story. NOTE ON 1A2 SPECIFICALLY: its entire within-noise set is all four WEIGHTED configs (seed spread 0.0127), meaning no feature choice is resolvable there at all and weighting alone carries everything. That is consistent with 1A2 having rejected every feature block ever put to it. METHOD NOTE: this is the fourth time this campaign that a pattern read off one isoform did not generalise to the others. Four isoforms is enough to test a mechanism and never enough to establish one; per-isoform replication should be the default before any mechanistic claim is logged.

self-correctionreplicationband-weightingcyp1a2overgeneralisationforce-aux · 2026-08-23
high

Do not break a tie with an estimator whose noise you have not measured - leave-cluster-out is single-pass here, so it does not get a vote

The temptation is strongest exactly when the tie is real: reach for another number. But an unmeasured number is not evidence.

Deep dive

On CYP3A4 the joint gate left base+aux (scaffold-CV 0.6086) and base+mech+aux (0.6092) separated by 0.0006 - well inside the 0.002 base seed spread, so indistinguishable. Leave-cluster-out separates them by 0.0045 the other way, favouring the two-block model, and LCO is the closer analogue of the blinded test (11.5% scaffold overlap, 0% compound overlap). I nearly let it decide. But LCO here is computed in ONE pass over a single k-means clustering, so its own variance is completely unquantified - using a 0.0045 margin from it is the same error as the cofold pilot's pre-registered d>0.5 threshold at n=8. Decision: on a within-noise scaffold-CV tie, prefer the SIMPLER configuration (PXR: a robust single model beats a tuned stack on small series-shifted tests). The LCO preference is RECORDED in the artifact as rather than acted on, and ledger #25 queues multi-seed LCO (vary the clustering seed and k) to find out whether that margin is resolvable at all. If it is, the rule should change - but on evidence, not on convenience.

model-selectionleave-cluster-outvariancetie-breakself-correctioncombined-gate · 2026-08-23
high

Even the campaign's largest single-isoform effect is below leaderboard resolution

Macro is the mean of four isoforms, so a single-isoform delta moves macro by a quarter of itself. Clearing the 0.0135 macro bootstrap sd from one isoform needs 0.054 -- larger than anything ever measured here.

Deep dive

The macro score is the mean of the four per-isoform ST-RAEs, so a block improving one isoform by d moves macro by d/4. Against the measured macro bootstrap sd of 0.0135 (#139), a single-isoform effect must exceed 0.054 to be visible on the leaderboard. Ranking every large deploy the campaign has made against that bar: the CYP2D6 mechanistic block, the largest per-block inhibition effect ever measured here at -0.0440 and 8.48 SD, contributes 0.0110 macro; CYP3A4 band weighting -0.0234 gives 0.0059; CYP2C9 single-conc aux -0.0220 gives 0.0055; CYP1A2 bandl2 -0.0206 gives 0.0052; CYP2D6 ExtraTrees -0.0110 gives 0.0027. Every one is below leaderboard resolution. This reframes what counts as a worthwhile target: a feature that helps only one isoform cannot be seen no matter how mechanistically satisfying it is, so the only viable shapes are a block that moves three or four isoforms simultaneously, or a stack of several independently-gated blocks whose contributions add. It also explains why the campaign total (0.1662, 12.3 sd) is overwhelming while every recent increment is invisible: the total is a sum of many sub-resolution parts. Three independent research streams converged on this same arithmetic before it was checked locally.

metaleaderboardstrategyarithmeticphysics-scorerdeep-research convergence + local verification · 2026-08-29
high

First honest inventory: 39 reachable tools never used, 0 of 6 physics blocks implemented, and our own code was advertising them as available

Before innovating, know what is already on the table. Nobody had ever enumerated the targets or the tooling, so the campaign could not tell reachable-and-unused from unavailable.

Deep dive

Two probed inventories now ship as a Targets tab. TOOLING: 73 tools, every access claim verified at runtime (import, PATH, CLI version, API whoami) -- 57 verified-working, 18 used by this campaign, 39 reachable and never touched. OpenProtein is live and serves 11 fold models (boltz-2, alphafold2, protenix-v2, rosettafold-3, esmfold) and 25 embedding models (ESM-2, ESM-C up to 6B, ProtT5-XL, ProteinMPNN, PoET). The headline unused asset is the Boltz-2 AFFINITY head -- a genuine (protein,ligand) to affinity model on credentials already proven to work, where the campaign consumed only poses. TabPFN v8 runs locally on CPU in 1.7s and needs no API key. PLIP and ProLIF both import, so the poses already paid for on O: can become typed contact fingerprints for zero credits. SELF-CHECK FAILURE: CLAUDE.md learning 7 says the PXR physics blocks have hooks in featurize_cyp.py. They do not -- QM_HOOKS was a list of six strings with no implementation, no artifact and no gate, and the script printed 'optional QM/3D blocks available'. Renamed QM_HOOKS_TODO with the print corrected. 0 of 6 are used; only DBSTEP is installed. TARGETS: 30 proteins, 30/30 UniProt+ChEMBL+PDB, 29/30 Open Targets, all 30 sequence-aligned. Heme ligand present in 100% of entries for all 22 P450s and 0% of the 8 non-P450s -- an unplanned control on the structure filter.

inventorydashboardtoolingpcmtargetsdata/processed/target_intelligence.json,data/processed/tool_inventory.json · 2026-09-03
high

Fixing a harness does not fix the artifacts it already wrote

The broken run's verdict sits on disk, and downstream scripts keep reading it long after the bug is gone.

Deep dive

Two joint-gate defects were fixed last tick, but joint_cyp1a2.json still held what the broken run printed: winner='base', reproduces_incumbent=false. deployed_score() walks that file as the newest layer and make_submission.joint_blocks() reads winner=='+both' to decide whether the shipped CYP1A2 model gets the external and 3D blocks -- so the repo's on-disk state said the block was not deployed while submission v08 ships it and the 12-seed re-run had shown it holds 12/12. Rebuilding from that state would have dropped a passing block and looked like a clean rebuild. Re-deriving the verdict from the stored seeds under the corrected rule flips it to +both and restores deployed_score('cyp1a2') to 0.8036 and the macro to 0.6910. Re-deriving is legitimate ONLY because the measurements were sound and just the scoring rule was wrong: the rule is fixed in code first, applied mechanically, and the superseded verdict is kept in the artifact.

harnessartifactsreproducibilitycyp1a2data/processed/joint_cyp1a2.json · 2026-08-25
high

Four checks in a row over-matched: my acceptance rules keep being broader than the property I care about

Each one raised a false alarm, and the habit of ignoring false alarms is what let ast.parse pass a zero-byte file.

Deep dive

Four consecutive self-written checks mis-fired, all the same shape. #76's diagnostic returned DEGENERATE because a conjunction over positive-rate and precision fired on its weakest term, while precision held at 1.92x the base rate. #77's returned DEAD-ZONE EXPLOITATION on a conjunction over MAE and Spearman, printing a sentence its own numbers contradicted -- it cited an improvement as evidence against improvement. #78's submission validator reported a file as REJECTED on a plausible-pIC50 range that appears nowhere in the recovered spec. #88's integrity sweep flagged objective_twincheck_cyp2d6.json for missing per_seed and winner, because the prefix objective_ swallowed a twin-check artifact with an entirely different schema. In every case the matching or acceptance rule was broader than the property actually being tested, and in every case the error was a false alarm rather than a false pass. False alarms are the safer direction but they are not free: they train you to skim the checker's output, and that is exactly how ast.parse came to be trusted when it reported PARSES OK on a file truncated to zero bytes by a full disk. Two habits that would have caught all four: derive the matching rule from the exact thing being described rather than a prefix or a conjunction of proxies, and test the checker against a known-good case before trusting its verdict on an unknown one.

diagnosticstoolingself-correctionfalse-alarmsscripts/integrity_check.py · 2026-08-27
high

Generating a script by string-patching another: syntax validity proves nothing about semantic completeness

The patch silently failed to match, ast.parse passed, and my own leftover-scan was looking for the wrong variable names.

Deep dive

capacity_gate.py was produced by string-substituting a copy of reverify_recipe.py. One replacement did not match, because the source had shifted when the measured-SD lookup was wired into it two ticks earlier. The generated file parsed cleanly, and the leftover-check written specifically to catch this scanned for alt_obj, w_alt and args.objective but not for the bare obj that actually survived. The run crashed with NameError on the first fold. That was the GOOD outcome: a stale name that happened to remain bound in scope would have run to completion and produced numbers measuring a different contrast than the one claimed, which is precisely how the CYP2C9 l1 artifact survived several ticks. Three practical consequences for this campaign, which now generates scripts this way routinely. First, ast.parse is a syntax check and says nothing about whether every intended substitution landed. Second, a leftover-scan must be derived from the names being REMOVED rather than hand-listed, or it drifts out of sync with the patch. Third, the strongest cheap guard is the one already in place everywhere else here -- a control arm that must reproduce a known deployed score, which would have caught a silently-wrong contrast even if it ran.

codegentoolingself-correctioncontrolsscripts/train/capacity_gate.py · 2026-08-26
high

I attributed to a model family what was 63% my own untuned hyperparameter

#100 said ExtraTrees is incompetent on CYP3A4 and closed the question. But the competence number came from one line I wrote without gating it, and changing that line moved the number most of the way.

Deep dive

The claim was that a second model family helps only where it is competent solo, with CYP3A4 out of reach at a +0.1395 gap. That gap came from max_features='sqrt' -- ~48 of 2,265 features -- chosen for decorrelation and never gated. Sweeping it reached +0.0514, cutting 63% of the effect I had attributed to the family. The conclusion happened to survive (the gate still rejects, and the harm falls proportionally with the gap so it never crosses break-even), but it survived for a reason I had not established at the time. This is the third instance of the same shape in four ticks: #97 wrote up a correlation as a mechanism, #100 closed a family after sampling one point on a trade-off, and here a configuration's property was reported as a family's. The common guard: before attributing a result to a THING, ask which of your own unexamined choices could produce the same number -- and say which ones you did not vary.

epistemicsattributionhyperparametersself-correctionledger #103 vs #100 · 2026-08-27
high

I committed the stripped-pipeline trap one tick after citing it as the reason not to

#86's explanation was measured on base features while the experiment it explained ran with 3,820 donor rows.

Deep dive

#83 established that a diagnostic run on a stripped pipeline measures a different model: CYP2D6 looked badly over-trained at 500 trees on base features (-0.0655) and perfectly tuned once its donors were restored (+0.0027). One tick later, #86 rejected a GBM+ridge blend and I wrote a separate script to explain why -- using base features only, while the gate it was explaining had trained its ridge on donor-augmented data. The explanation reported ridge at 2.1142 with error correlation 0.274; measured in the actual deployed pipeline, ridge at the same alpha scores 0.6897 with correlation 0.715. The blend's rejection stands, but the stated cause was wrong: it failed because the partner is 1.49x worse AND 0.715-correlated, not because it was catastrophically broken. Two things made this easy to miss. The diagnostic was a throwaway script rather than a mode of the gate, so nothing forced it to share the gate's data construction, and its answer was plausible enough not to invite checking. The fix applied here is structural rather than a resolution to be careful: the precondition sweep is now a MODE of the gate itself, and the donor augmentation both paths need lives in one shared helper, so a diagnostic cannot silently diverge from the experiment it explains.

diagnosticspipelineself-correctiontoolingdata/processed/blend_precondition_cyp3a4.json · 2026-08-26
high

I pre-registered a decision threshold the pilot could not possibly resolve - power analysis must come BEFORE the decision rule

A pre-registered threshold feels rigorous and is worthless if the design cannot resolve it. Check resolvability, not just honesty.

Deep dive

The CYP2D6 cofold pilot fixed 'Cohen d > 0.5 supports scaling' before looking - good practice - but with 8 inactive vs 8 potent. Detecting d=0.5 at 80% power needs roughly 64 per group. Consequences, visible once the 24th pose landed: d moved from 0.34 (n=23) to 0.490 (n=24) on ONE compound; the bootstrap 95% CI is [-0.707, +1.385], spanning moderate-negative to large-positive; P(d>0.5|data)=0.499, an exact coin flip against my own threshold; and leave-one-out d ranges [+0.155, +0.636], so a single compound spans most of the decision range. I reported 'a clean null' - that was wrong, and so would 'supports the mechanism' have been. The pilot resolved nothing about the contrast. What DOES survive is independent of n: Spearman -0.059 across the full 24-compound pIC50 range (no monotonic relationship), 0 of 24 poses reaching true Fe coordination (min 2.80 A vs the required ~2.0-2.3 A), and structure_confidence flat at 0.935-0.955 across every compound. Those are the real evidence, and they still say do not fund the array. RULE ADOPTED: any pilot with a pre-registered effect-size threshold must state the n required to resolve it, or report a diagnostic that does not depend on the contrast.

power-analysisexperiment-designself-correctionpilotmethodologycofold-pilot · 2026-08-23
high

Leave-cluster-out has SD 0.018 on absolutes but only 0.003 on paired deltas - quote deltas, never absolutes

The split difficulty cancels when you compare two configs on the SAME split. Absolute LCO numbers are nearly meaningless at the precision we were quoting them.

Deep dive

scripts/train/lco_variance.py resamples the CLUSTERING (k-means seed x k in {8,10,12,14,16}) - every such choice is an equally valid series split - and recomputes LCO. Results over 8 clusterings. CYP3A4: absolute LCO mean 0.6402, SD 0.0175, range 0.6219-0.6700 (spread 0.048); paired delta (base+mech - base) mean -0.0026, SD 0.0034; sign stable in 75% of clusterings. CYP2D6: absolute LCO mean 1.0317, SD 0.0176, range 0.9990-1.0556 (spread 0.057); paired delta mean -0.0283, SD 0.0105; sign stable in 100%. TWO CONCLUSIONS. (1) ABSOLUTE LCO is far too noisy to quote precisely - SD ~0.018 and a spread up to 0.057 means a single reported LCO figure can move by more than most of the effects we are chasing. Previous ticks quoted LCO values to four decimals; that was overprecision and the numbers should carry +/-0.02. (2) PAIRED DELTAS are ~5x more stable than absolute values (3A4: 0.0034 vs 0.0175), because the same split is applied to both configs and split difficulty cancels. So gating on deltas is sound even though the absolutes are not. Smallest resolvable LCO delta (2 SD): 3A4 0.0067, 2D6 0.0210. TWO THINGS THIS SETTLES. The CYP3A4 tie-break from last tick - base+aux vs base+mech+aux separated by 0.0045 on LCO - sits BELOW the 0.0067 resolvable threshold, so declining to let LCO decide it was correct, and now provably so rather than by caution. And the CYP2D6 mechanistic block, which scaffold-CV magnitude called 'weak' (0.389), has an LCO delta of -0.0283 that EXCEEDS its 0.0210 threshold with 100% sign stability - it is a resolvable effect under series shift even though it is noise-order in-scaffold. That asymmetry matters because the blinded test is a series shift.

leave-cluster-outvarianceresolvabilitygatingself-correctionlco-variance · 2026-08-23
high

Macro leaderboard sd is 0.0135: three deploys are visible, everything since is not

#137 left this as a factor-of-two range, which was the difference between band weighting being clearly visible and being marginal. Measuring the cross-isoform error correlation closes it.

Deep dive

With mean pairwise influence correlation +0.079, var(macro) = (1/16) sum_ij rho_ij sd_i sd_j gives a macro bootstrap sd of 0.0135 -- essentially the independent bound of 0.0122 and nowhere near the fully-correlated 0.0243. Scoring the campaign against it: total move 0.837 to 0.6708 = 0.1662 = 12.3 sd, overwhelming. Band weighting on CYP3A4 (0.0234) 1.73 sd, single-conc aux on CYP2C9 (0.0220) 1.63 sd and bandl2 on CYP1A2 (0.0206) 1.53 sd are genuinely visible. Everything since is not: TDI-condition donors 0.96 sd, ExtraTrees on CYP2D6 0.81, external+3D joint on CYP1A2 0.77, ExtraTrees on CYP1A2 0.66, and the entire v10-to-v11 submission move 0.33. The picture is slightly kinder than the pessimistic end of #137's range but the shape is unchanged, and the v10-versus-v11 choice stays dissolved rather than decided. The pre-registered falsifiable prediction sharpens from a range to a point: the downloadable leaderboard CSV should show MA-ST-RAE std of approximately 0.0135, with per-endpoint std approximately 0.022-0.026.

metaleaderboardstrategypredictionbootstrapscripts/train/macro_correlation.py · 2026-08-29
high

My own pre-registered rule said DEGENERATE; the honest reading is narrower

The rule fired on positive-rate alone, but precision held at 1.92x the base rate -- that is an operating-point shift, not a degenerate classifier.

Deep dive

The diagnostic's decision rule was written before the numbers: legitimate required the predicted positive rate within 1.5x of the true 21.3% AND precision at least 1.5x the base rate. The result was 40.2% positive (1.89x, fails) at precision 0.410 (1.92x base rate, passes), so the rule returned DEGENERATE. That word overstates what was found. A degenerate operating point means precision collapsing toward the base rate while recall runs to 1; here precision stayed nearly double the base rate and the arm traded precision 0.498 to 0.410 for recall 0.499 to 0.765, which is a real higher-recall operating point that MCC legitimately prefers. The rule conflated two distinct failure modes and only the milder one occurred. The verdict does not change -- the arm is rejected on the threshold-gap evidence, which is independent and damning -- but the stated REASON matters for the record, and overriding a pre-registered rule because the answer is unwelcome is the after-the-fact criterion change this campaign has refused repeatedly. The fix is to write diagnostic rules with each failure mode tested separately rather than as a conjunction that reports the wrong one.

pre-registrationdiagnosticsself-correctionepistemicsdata/processed/tdi_threshold_diagnostic_cyp3a4.json · 2026-08-26
high

Never hardcode the marginals of a significance test: check they reproduce their own expected count

I typed worst-decile counts into a hypergeometric test by eye instead of computing them. The numbers did not even reproduce the expected value printed one command earlier.

Deep dive

Testing whether error tails overlap across isoforms needs the worst-decile marginals ON THE CO-MEASURED SUBSET, which differ from the full per-isoform decile sizes. The first pass hardcoded them as wA=66, wB=52 for CYP3A4-CYP2C9. Those imply an expected overlap of 66*52/473 = 7.3, while the preceding command had printed 5.3 from the real data; the true marginals are 47 and 53. Every p-value in that pass was therefore computed from invented inputs. The defect is detectable without rerunning anything, purely by checking that the test's own inputs reproduce a quantity already on screen -- which is the general safeguard: any statistic assembled by hand from a previous output must be made to regenerate that output before it is believed. The conclusion happened to survive recomputation, since CYP3A4-CYP2C9 is significant on the real marginals too, but a surviving conclusion does not redeem a fabricated test. The fix is structural rather than a note to be careful: scripts/train/tail_structure.py computes every marginal it uses in the same pass that reports the p-value, so the two cannot drift.

metastatisticsself-correctionprocessscripts/train/tail_structure.py · 2026-08-29
high

One validation set, 164 decisions: the campaign has been fitting its methodology to scaffold-CV

Every honest-gate verdict has been measured against the same scaffold-CV on the training data. After 164 logged experiments that is not a held-out set, it is a set we have optimised against.

Deep dive

The campaign's validation discipline has a structural gap that no individual gate can see. Scaffold-CV on the challenge training data is the sole arbiter for all 164 logged experiments, so it has absorbed 164 comparisons worth of multiple-testing exposure and now functions as a training set for methodology rather than an estimate of generalisation. The blinded test set is the only genuinely untouched holdout and has never been scored against. A second and equally serious limitation is that every negative result can only say that a change failed to improve cross-validation; it can never say what the right choice would have been, because the answers are never visible. The remedy adopted is a surrogate-target program: CYP targets with abundant public data are subsampled down to the challenge's own training size of about 1500 compounds and scored against thousands of held-out compounds whose labels are known, giving many independent test sets instead of one reused one and making the counterfactual measurable. The central experimental design is a learning-curve crossover. A mechanistic prior constrains the function class using knowledge that is not present in the data, so if it carries value that value must increase as data shrinks, since a flexible model given unlimited data learns the mechanism from examples and renders the prior redundant. Marginal-over-base is therefore measured as a function of training size across 100, 300, 1000, 1500, 3000 and 10000 compounds. If mechanism-informed methods only win below 1500 they cannot help this challenge and the campaign should stop investing in physics; if they win at or above 1500, physics is underexploited and the size of the gap is quantified. What transfers from a surrogate is the ranking of methods, not effect sizes and not models, because the challenge test series shares zero of 748 InChIKeys with public data.

metavalidationmethodologysurrogatelearning-curvemultiple-testingdocs/SURROGATE_PROTOCOL.md · 2026-08-30
high

OpenProtein fold() returns Structure objects, not CIF - str() writes a repr that reads downstream as 'no pose'

A defect that masquerades as a scientific null is the dangerous kind. Check atom counts and sample counts before trusting any new venue.

Deep dive

Two extraction bugs found while wiring the OpenProtein cofold path, both of which produce output that LOOKS like a scientific result rather than a defect. FIRST: fold() returns openprotein.molecules.Structure objects, not CIF text. Writing str(item) yields the Python repr '<openprotein...Structure object at 0x...>' - a 72-byte file that parses to zero atoms and is reported downstream as 'no parseable pose'. That is indistinguishable from a genuine folding failure, and at scale it would have produced a clean-looking '0/24 poses recovered' conclusion about the structural lever. The fix is Structure.to_string('cif'). Caught because idx 0 reported 'no parseable pose' on the very first ligand, which was worth stopping the run for rather than waiting to see whether later ones worked. SECOND, still under investigation: after the fix the parser reports 65 ligand atoms for a 13-heavy-atom query SMILES, and fold() returned 1 result rather than the requested 5 diffusion samples. Both would silently corrupt the readout - a wrong atom selection makes the min-Fe distance meaningless, and 1-vs-5 samples changes what a per-ligand aggregate even means. Not proceeding with the 24-ligand run until both are explained. GENERAL POINT, and it is the recurring one in this campaign: a pipeline that reports 'no result' is far safer than one that reports a plausible WRONG number, and every new venue integration should be sanity-checked on atom counts and sample counts before it is trusted - not just on whether it returns without raising.

openproteinsilent-bugcofoldvalidationextractionopenprotein-cofold · 2026-08-23
high

Our ChEMBL puller silently capped every target, and recorded no provenance -- so re-deposits were invisible

The staged file held exactly 3,000 activities per isoform while CHEMBL289 alone has 26,911. It also stored no document or assay id, which is why nobody could see that most of the data was one re-deposited assay.

Deep dive

Two independent defects in scripts/external/pull_cyp_external.py were exposed by the surrogate program and both are now fixed. First, the pull was capped per target, defaulting to 4000 and evidently run at 3000, so the staged external parquet held exactly 3,000 activities per isoform; a direct pull of CHEMBL289 for CYP2D6 returned 26,911. Every downstream statement about external data volume was therefore made against an arbitrarily truncated sample, and the truncation left no trace in the output. Second and more consequential, the puller stored only molecule id, SMILES, value and type, with no document_chembl_id or assay_chembl_id. Without those, a re-deposited assay is indistinguishable from independent literature. Three Queens hit this separately: on CYP2C19, 12,690 of 13,056 usable ChEMBL rows were CHEMBL1741323 or CHEMBL1613777, the NCGC qHTS campaign re-deposited from PubChem, leaving only 1,829 rows of genuine multi-lab literature, and on CYP2D6, CHEMBL1201862 contributed 5,665 rows of the same panel so that a combined PubChem-plus-ChEMBL set was 68 percent one assay with cross-source Pearson 0.992 and replicate standard deviation around 1e-06. The danger is specific rather than cosmetic: a re-deposit counted as a replicate reads one measurement as two agreeing measurements, which collapses the apparent assay noise and would corrupt any precision-weighted loss or noise-floor estimate built from it. The fix removes the cap by default, makes any truncation print a loud incomplete-pull warning, and pulls document and assay identifiers so provenance can be deduplicated.

metadataprovenancechembltoolingbugscripts/external/pull_cyp_external.py · 2026-08-30
high

Our external CYP3A4 data was two different assays stacked: AID 884 and 885 both mapped to cyp3a4

28,230 rows over 12,800 unique compounds, and pIC50 barely separates Active from Inactive (5.30 vs 5.18). That reads as a noisy public label but is an inhibition assay and a second assay averaged together.

Deep dive

scripts/external/pull_cyp_external.py mapped both PubChem AID 884 and AID 885 onto the isoform key cyp3a4. The two are different assays that report the same Potency field, so the staged panel holds 12,800 unique compounds as 28,230 rows, a ratio of 2.21, and build_wide() then takes a median across both, placing AID 885 potency inside the CYP3A4 inhibition column of cyp_external_wide.parquet. The diagnostic symptom, verified independently, is that pIC50 stops discriminating the outcome classes: Active 5.3044, Inactive 5.1819, Inconclusive 5.0341, a gap of only 0.12 log units where a clean inhibition assay separates sharply. This is a dangerous failure mode because it presents as evidence that public labels are uninformative rather than as a merge error, and it would silently corrupt any noise estimate or read-across drawn from that column. The clean subset is AID 884 alone, giving 12,784 unique InChIKeys. Practical blast radius is limited because read-across was already abandoned after the finding that the blinded test shares zero of 748 InChIKeys with public data, so the wide table is not in the deployed path, but any future use of the external CYP3A4 column would have been wrong. The mapping is fixed so AID 885 now carries its own key and cannot merge silently, and a contamination notice records that the staged parquets predate the fix and need regeneration. Separately, ChEMBL re-deposit contamination in the same external file is isoform-dependent and much worse elsewhere: CYP3A4 is clean at 3.5 percent overlap with the panel, while CYP2C9 is 15.3 percent, CYP2D6 15.1 percent and CYP1A2 34.3 percent.

metadatapubchemcontaminationbugexternalscripts/external/pull_cyp_external.py · 2026-08-30
high

Reading a ranking off an unfinished partition is reading noise

Three of five ablation groups had finished and I named the leader. The two outstanding groups included the one that turned out to dominate by tenfold.

Deep dive

A five-group ablation runs sequentially, so partial output is a prefix of the loop order, not a sample of the ranking. After three groups the leading effect was charge state at +0.0035, and I reported it as the carrier. The completed run put pharmacophore geometry at +0.0376 unweighted and +0.1835 shifted, ten times larger and 85% of the block. Nothing was wrong with the three measurements; the error was treating an ordering determined by how the loop was written as evidence about magnitude. The cost here was only a retracted sentence, but the same reflex applied to a sweep that gets abandoned early would produce a confident and permanent wrong answer. Rule: do not rank anything until the partition is complete, and when reporting mid-run say which arms are outstanding rather than which is currently ahead.

epistemicspartial-resultsablationself-correctionledger #125 · 2026-08-29
high

Recalibrating every past rejection resurrects nothing -- the bad instrument cost no real effect

The 3-sample SD inflated some confidences and deflated others, but no rejection crosses the bar when rescored against the measured null.

Deep dive

Every rejection in this campaign was scored as a 3-seed delta over a 3-sample SD estimated from those same three seeds, and #72 showed that instrument turning a real effect (CYP2C9 bagging) from 0.49 SD to 1.22 SD purely through better calibration and power. Rescoring all 19 improving-but-not-deployed arms against the measured 12-seed nulls: nothing is resurrected. No arm both improves on every seed and crosses the bar. The recalibration was not neutral -- CYP3A4's ratios fell by 1.08 on average because its 3-sample SDs ran low and inflated confidence, CYP2D6's rose 0.15 -- but the only seed-stable arm to fall below the bar was bagged_cyp3a4/sub1 (1.61 to 0.87), a decomposition arm that was never shipped. All nine seed-stable deployed effects stay at or above the bar. The audit also had to distinguish two things that look identical in an artifact: an arm rejected as noise, versus an arm BEATEN by a better arm in the same multi-arm gate. CYP2D6's active_focus band flagged as a mover but had lost to inv_width_cap by five-fold in the same run; only an arm that is the best in its own artifact and still did not deploy is a genuine candidate.

auditseed-noisecalibrationrejectionsdata/processed/rejection_audit.json · 2026-08-26
high

Run the control before believing the finding -- it cost one job and decided the interpretation

A deployed layer looked bad under a shifted measure. Before concluding anything, ask whether the measure makes everything look bad. One extra run answered it and pointed the opposite way.

Deep dive

The ExtraTrees layer's contribution reversed sign under an activity-matched reweighting on both isoforms where it ships. That result has two completely different readings: the layer does not transfer, or the shifted measure penalises variance reduction generally and no averaging layer would survive it. Running the identical procedure on the bagging layer -- same weights, same seeds, same code path -- settled it: bagging's benefit GREW 2.6 to 4.7 times under the same shift. Without that control the finding would have been unreadable, and with it the conclusion is specific and the machinery is validated at the same time. The general rule: when a diagnostic makes a deployed component look bad, the first question is whether the diagnostic makes everything look bad, and the cheapest way to find out is to point it at a component you expect to survive. A control that passes is what makes the failure credible -- the same evidence pattern that made an earlier gate-rule change credible, where the sweep's value came from the cases it declined to move.

epistemicscontrolsdiagnosticscovariate-shiftdata/processed/shift_check_bagging.json; ledger #116 · 2026-08-28
high

Seed NOISE is a property of a configuration too -- the recorded SDs went stale and two verdicts hinged on them

Effect sizes were long known to depend on the configuration they were measured in. The seed SD they are divided by is measured the same way, and nobody re-measured it after two variance-reducing layers were added.

Deep dive

Measuring the deployed model at 12 seeds gives CYP2D6 a seed SD of 0.0039 against a recorded 0.0052 and CYP1A2 0.0056 against 0.0072 -- both recorded values roughly 1.3x too large and both taken before the ExtraTrees layer existed. Bagging and ExtraTrees each reduce prediction variance, so a less seed-variable current stack is exactly what should be expected, which is what makes the discrepancy credible rather than suspicious. The consequence is not academic: two blocks rejected in the preceding ticks were rejected against that stale denominator, and on the deployed configuration's own noise both clear the 1.0 bar -- a bandl2 removal moves from 0.92 to 1.18 and a pharmacophore addition from 0.76 to 1.03. Nothing was deployed on this. Updating a denominator retroactively changes past verdicts, so it requires the same pre-registration and full retroactive sweep that any criterion change does, including measuring the remaining two isoforms so that only the ones already looked at are not the ones corrected. Separately, the shifted seed SD was measured for the first time and does not scale with the shifted deltas: noise grows 2.28x on CYP2D6 while that block's shifted effect grows 5.3x, so the unweighted bar really does under-read shift-amplified blocks.

honest-gateseed-noisemagnitudestale-artifactssixth-criterioncovariate-shiftscripts/train/shifted_seed_sd.py; data/processed/shifted_seed_sd.json; ledger #127 · 2026-08-29
high

Stability and magnitude answer different questions -- and here they disagree maximally

Removing a layer improved CYP1A2 on twelve seeds out of twelve, sign-test p = 0.0002. The same effect is 0.92 times the isoform's seed noise, under the magnitude bar. Both readings are correct.

Deep dive

Twelve-seed measurement with an exact control: removing the bandl2 objective from CYP1A2 gives a mean delta of -0.0066 with every seed improving, per-seed deltas from -0.0135 to -0.0035, at 0.92 SD. The three-seed estimate of -0.0063 barely moved, so more power confirmed the effect is real and confirmed it is small -- which is the outcome the magnitude bar exists to distinguish. Stability asks whether a direction is an artifact of which seeds were drawn; magnitude asks whether the effect is large enough to justify changing a shipped model. At p = 0.0002 and 0.92 SD they disagree as sharply as they can, and the layer was kept: macro would have moved 0.6708 to 0.6692. One asymmetry is worth recording without acting on it -- the bar was designed to stop marginal ADDITIONS riding on noise, while a REMOVAL also buys simplicity, and the inherited lesson from the prior campaign is that a robust simple model beats a finely-tuned stack on a series-shifted blind test. Whether removals deserve a different criterion is a real question, but answering it by relaxing the bar for a result sitting just underneath it is the failure mode this project has repeatedly declined.

honest-gateseed-stabilitymagnitudecriteriacyp1a2bandl2scripts/train/bandl2_redundancy.py; data/processed/bandl2_redundancy_cyp1a2.json; ledger #122 · 2026-08-29
high

State the minimum detectable effect BEFORE running a pilot, not after it returns null

#131 spent a tick producing a null that was arithmetically certain from its sample size alone. The experiment could not have succeeded, and nothing in the honest gate would have caught that.

Deep dive

The four honest-gate criteria, the fifth (reproduce the incumbent) and the sixth (measure the effect where it ships) all police how an effect is MEASURED. None of them asks whether the design could detect the effect at all. #131 correlated a bound-pose salt-bridge distance against potency on 11 basic ligands and logged the null as evidence about geometry; Fisher-z puts the minimum detectable |rho| at n=11 at 0.758 -- larger than any correlation in this campaign, so the null was guaranteed by construction. At n=22 the bound is 0.567 and at n=24 it is 0.545. This also prices the pose track: resolving an effect the size of the quantity's own TOPOLOGICAL analogue (|rho| 0.13-0.17 over 1,493 compounds, #54) needs 269-462 poses, or 437 charging the 8 comparisons #131 made. Ledger #21 already required a stated resolvable-n for any pilot with an effect-size rule; #131 skipped it and the omission cost a full tick. The corollary for reading past work: a null from a small pilot is only evidence if its MDE is smaller than the effect being sought, and most pilot nulls in a campaign like this are not.

metapowerstatisticscofoldcyp2d6self-correctionscripts/cofold/pose_power.py · 2026-08-29
high

Test a relaxed rule by counting the chances it DECLINED, not the results it rescued

A new rule that only rescues the result which motivated it looks self-serving -- unless you also count how many other results it could have rescued and did not. That number is the evidence.

Deep dive

Replacing the all-must-agree stability criterion with a one-sided sign test (p<=0.05, unanimity retained below n=5 where no outcome can reach it) was pre-registered before any artifact was re-scored, with the ordering problem disclosed: the rule was formulated after seeing one result sit at 11/12. The retroactive sweep across every stored gate found 163 re-derivable comparisons at n=3 (untouched by construction) and 17 at n=12, the only regime the rule reaches, and flipped exactly 2 -- both arms of the motivating result. Taken alone that reads as self-serving. The evidence that it is not: three other n=12 arms improve over base and would newly pass the stability test, and the rule rescued none of them, because the untouched magnitude bar blocked all three (two at 0.79 SD, one failing the sign test at p=0.194). The sharpest is a result whose mechanism had been positively confirmed in an earlier tick and whose effect was simply too small -- the new rule still rejects it. So the relaxation is confined to stability and the other criteria did the blocking work every time they were asked.

honest-gateseed-stabilitypre-registrationepistemicsstatisticsscripts/train/rederive_stability.py; data/processed/rederive_stability.json; ledger #106, #107 · 2026-08-28
high

The alternative submission already existed -- checking beat rebuilding

Deciding whether to drop a layer looked like it required a gate-rule change and a rebuild. It required neither: the previous submission is exactly the current one minus that layer, and an earlier integrity check had already proved it.

Deep dive

The ExtraTrees layer deployed on CYP2D6 and CYP1A2 only, and an earlier check had verified that the submission built before it is bit-identical on CYP3A4 and CYP2C9 and differs only on the two ET isoforms. That makes the earlier file exactly the current model minus ExtraTrees -- already format-validated, already on disk. So a decision framed as 'change the deploy criterion, then rebuild' collapses to 'choose between two files that both already exist', and the choice can be handed to a human as a judgment about which distribution to bet on rather than taken unilaterally by relaxing a gate. The general lesson: before building an ablated artefact, check whether a previous artefact already IS the ablation -- versioned outputs from a scoped deploy frequently are, and an integrity check that diffs consecutive versions tells you for free.

submissionsintegritydecision-framingefficiencyscripts/submission_delta_check.py; ledger #115, #117 · 2026-08-28
high

The Boltz blocker was credits, not compute - boltz-2 runs free via OpenProtein, and Explorer already has OpenFold3/Protenix/RF3/Chai

A 402 from one venue is not a dead lever. The same model was reachable under different credentials the whole time.

Deep dive

The Boltz 402 was an API CREDIT limit, not a compute limit — the same model is reachable through other venues, and the structure track was never actually blocked. Verified availability, 2026-08-23. REACHABLE NOW: boltz-2 via OpenProtein (session.fold.get_model('boltz-2')), whose fold() accepts templates=, constraints=, use_potentials= (Boltz-2 physics potentials) and diffusion_samples — this is the credit-free path and ledger #19 is running on it. OpenProtein also serves alphafold2, boltz-1, boltz-1x, esmfold, esmfold2, esmfold2-fast, minifold, protenix, protenix-v2, rosettafold-3. On Explorer (H200 x4 nodes, A100, V100; 8h gpu partition, 2-day short partition) there is already a full PXR-era stack under /scratch/shenoy.am: OpenFold3 0.4.1 installed in of3_ft/env, plus intfold, rf3 (RoseTTAFold3), protenix_ft, chai, af3_explorer, boltz caches and MSAs. NOT AVAILABLE, checked rather than assumed: OpenDDE exists on HF as aurekaresearch/OpenDDE (all-atom diffusion co-folding, Apache-2.0, Jun 2026) but is not installed anywhere here; OrbMol exists as Orbital-Materials/OrbMol and orbmol-v2 (May 2026) and is likewise not installed; 'OpenBind-0' and 'DeCaf'/'PEARL' return nothing on the HF hub and are not present on Explorer or OpenProtein, so they cannot be planned around without a source. API GOTCHAS worth recording for anyone using OpenProtein for cofolding: (1) Ligand is constructed as Ligand(smiles=...) / Ligand(ccd=...), not via Ligand.smiles() — those attributes exist but are None. (2) fold() builds an MSA query that rejects any Complex containing non-protein chains, so a holo-crystal TEMPLATE must be reduced to protein-only via complex.get_protein('A') — the heme still enters the prediction as a ligand chain of the query itself. (3) For a Protein template the mapping argument is a plain target-chain string, not a dict; dict mappings are only valid for Complex templates.

openproteinexplorerboltzopenfold3model-availabilityunblockedvenue-audit · 2026-08-23
high

The campaign total is 7-14 bootstrap sd; every deploy since band weighting is under one

Both statements are true at once, and the tension between them is the finding: the work was real, and the marginal work stopped being visible on the leaderboard some time ago.

Deep dive

Against a macro bootstrap sd of 0.0122-0.0243, the campaign's total move -- base macro 0.837 to deployed 0.6708, a delta of 0.1662 -- is 6.8 to 13.6 standard deviations. That is overwhelming and settles that the modelling programme worked. But scoring the individual deploys against the same yardstick: band weighting on CYP3A4 (0.0234) is 0.96-1.92 sd, single-conc aux on CYP2C9 (0.0220) 0.91-1.81, bandl2 on CYP1A2 (0.0206) 0.85-1.69, TDI-condition donors (0.0130) 0.54-1.07, ExtraTrees on CYP2D6 (0.0110) 0.45-0.90, external+3D joint on CYP1A2 (0.0104) 0.43-0.86, ExtraTrees on CYP1A2 (0.0089) 0.37-0.73, and the entire v10-to-v11 move (0.0045) just 0.19-0.37. Practical consequence: the pending v10-versus-v11 submission choice, logged as a human judgement about which distribution to bet on, is a coin flip at a third of one bootstrap sd and needs no further deliberation. Pre-registered falsifiable prediction, checkable as soon as anyone submits, since the downloadable leaderboard CSV publishes the std: MA-ST-RAE std approximately 0.012-0.024, per-endpoint ST-RAE std approximately 0.022-0.026.

metaleaderboardstrategysubmissionpredictionscripts/train/leaderboard_resolution.py · 2026-08-29
high

The four honest gates test DIRECTION but not MAGNITUDE - a retroactive audit found one deployed block was pure noise and two more are noise-order

Passing four sign tests is not the same as having an effect. Divide the effect by the seed spread before calling anything a win.

Deep dive

The TDI alert block passed all four gates on CYP3A4 with +0.0008 AUROC - while the base AUROC itself varied by 0.011 across seeds. The effect was 7% of its own noise floor. Added cyp.eval.effect_vs_seed_noise (ratio = |mean delta| / (max-min of base across seeds); >=1.0 meaningful, 0.3-1.0 weak, <0.3 noise) and audited every gated experiment retroactively. Results: MEANINGFUL - 3A4 single-conc aux 6.45, 2C9 single-conc aux 1.55, 3A4 mechanistic 1.43. WEAK (same order as noise, keep but do not call a win) - 2C9 mechanistic 0.765, CYP2D6 mechanistic 0.389. NOISE (should never have been deployed) - CYP3A4 TDI alert block 0.077. Two corrections follow: the TDI alert block is withdrawn, and last tick's report that the mechanistic block moved CYP2D6 from 1.001 to 0.993 was OVERSTATED - at ratio 0.389 that shift is the same size as run-to-run variation and 2D6 should be treated as unmoved. Deliberately kept as a reporting aid rather than a fifth pass/fail: a small real effect can be worth carrying, it just must not be described as a win.

honest-gatemagnitudeself-correctionmethodologymagnitude-audit · 2026-08-23
high

The label matrix is too sparse for collaborative filtering to be the frame

Matrix completion needs co-observation. With 73% of compounds measured on exactly one isoform and only 41 on all four, there is almost no matrix to complete.

Deep dive

The inhibition label matrix is 4,905 compounds x 4 isoforms at 33% fill, but the fill is not spread evenly: 3,596 compounds (73.3%) carry exactly ONE isoform, 1,039 carry two, 229 carry three, and only 41 carry all four. Pairwise co-measurement is correspondingly thin -- 3A4-2C9 n=473, 2D6-1A2 n=427, 3A4-1A2 n=297, 2C9-1A2 n=295, 3A4-2D6 n=250, 2C9-2D6 n=230. Collaborative filtering across four columns needs compounds observed on several columns to tie the factors together, and that is exactly what is missing, so the matrix-completion framing of the sparsity problem is the wrong frame here regardless of method quality. The correlation structure also rules out a single shared trunk: 3A4-2C9 +0.705 is the only strong pair, 2C9-1A2 +0.451 and 3A4-1A2 +0.415 are moderate, and CYP2D6 is orthogonal to all three (+0.039, -0.100, +0.077) -- consistent with 2D6 binding basic amines through an anionic pocket the other three do not have. Any joint model must therefore be {3A4, 2C9, 1A2} with 2D6 separate, which is ledger #5's conclusion arrived at from the matrix side.

multitasksparsitymatrix-completionlabel-matrixswarm · 2026-08-24
high

The leaderboard resolves 0.022-0.026 ST-RAE, 2.3-5.9x coarser than the seed noise every gate used

Every gate asked whether a change survives a reseed. The leaderboard asks whether it survives resampling 750 compounds. Those are different questions and the second is far less forgiving.

Deep dive

The Space's leaderboards.py publishes every activity metric as a mean/std pair -- the live table drops the std, the downloadable CSV keeps it -- and config.py describes bootstrap-results.parquet holding one row per (Sample, Endpoint) with a synthetic macro pseudo-endpoint per bootstrap sample. So the backend bootstraps over compounds, and there are only 750. Bootstrapping ST-RAE at n=750 over cached OOF predictions (4000 resamples) gives per-isoform sd 0.0220 (CYP3A4), 0.0264 (CYP2C9), 0.0233 (CYP2D6), 0.0254 (CYP1A2) -- respectively 5.9x, 2.3x, 4.5x and 3.5x the measured 12-seed SDs of 0.0037/0.0113/0.0052/0.0072 that every honest gate validated against. Macro sd is 0.0122 assuming independent draws to 0.0243 assuming perfect correlation, likely nearer the upper half since CYP3A4/2C9/1A2 co-measure at r 0.42-0.71 and only CYP2D6 is orthogonal. Seed noise remains the correct null for 'is this model change real'; it is simply not the null for 'will this change my rank'. Limit: the bootstrap runs on training compounds via OOF while the test is a scaffold shift, though the magnitude is driven by n=750 and the metric's heavy-tailed denominator, which transfer.

metaleaderboardst-raebootstrapstatisticsscripts/train/leaderboard_resolution.py · 2026-08-29
high

The magnitude bar has never had error bars -- any ratio between 0.8 and 1.25 is indistinguishable from it

Every gate verdict divides an effect by a seed SD estimated from 12 seeds. That denominator is itself an estimate with about 20 percent uncertainty, so ratios near 1.0 cannot distinguish pass from fail.

Deep dive

Re-measuring seed SDs on the deployed configuration found all four recorded values too large -- CYP3A4 0.0037 against 0.0023, CYP2C9 0.0113 against 0.0103, CYP2D6 0.0052 against 0.0039, CYP1A2 0.0072 against 0.0056 -- because all predate the ExtraTrees layer and both bagging and ExtraTrees reduce prediction variance. The correction was swept over 152 stored comparisons and flipped four, three of which are not live decisions, so it is conservative and was adopted. But the standard error of an SD estimated from n seeds is sd over the square root of 2(n-1), about 20 percent at n=12. Propagating that, a block measured at ratio 1.03 has a plausible range of 0.85 to 1.30 and one at 1.18 spans 0.97 to 1.50 -- both straddle the bar, so neither was deployed. The general consequence is that every ratio this campaign has recorded carries roughly 20 percent uncertainty from its denominator alone, so anything between about 0.8 and 1.25 is not resolvable. Large effects are unaffected -- a block at 8.48 SD stays comfortably clear -- but the marginal calls were never as sharp as they read, in both the accepting and the rejecting direction. The bar resolves effects; it does not resolve near-misses.

honest-gatemagnitudeseed-noiseuncertaintystatisticsscripts/train/rederive_sd_correction.py; data/processed/rederive_sd_correction.json; ledger #128 · 2026-08-29
high

The measured 12-seed nulls existed for weeks and no gate ever used them

Every magnitude verdict since #61 estimated the noise from the same three seeds as the effect, and the error runs about 2x in both directions.

Deep dive

Ledger #61 ran each isoform's deployed recipe across 12 seeds and published the nulls -- 3A4 0.0037, 2D6 0.0052, 1A2 0.0072, 2C9 0.0113 -- explicitly because a 3-sample SD is unreliable. Nothing then consumed them. Measured against the bagging results: CYP3A4's 3-seed SD was 0.0020 against a true 0.0037, inflating its ratio 1.9x (the reported 7.55 SD is honestly 4.09); CYP2C9's was 0.0216 against a true 0.0113, deflating its ratio 1.9x (0.49 SD becomes 0.93, which is the difference between dismissing an effect and re-running it). 2D6 and 1A2 moved less but in opposite directions too. No deploy decision changed, which is the reassuring part, but every reported confidence was wrong. Fixed with cyp.eval.measured_seed_sd(), which reads the audit artifact and is preferred wherever it exists, with the 3-sample spread kept only as fallback. The generalisable point: publishing a calibration is not the same as wiring it in, and a measurement that nothing consumes will quietly fail to change anything. When a campaign measures a constant it intends to gate on, the same change should make the gate read it.

seed-noisecalibrationharnessgatingself-correctiondata/processed/bagged_cyp3a4.json · 2026-08-25
high

The QM program is affordable: the whole descriptor stack is hours, not allocations

Measured rather than assumed: 6,897 unique molecules, median 45 atoms including hydrogens. A 30-conformer ensemble is 62 minutes on this box and the full xTB Fukui block is 4.7 hours.

Deep dive

Measured costs for the complete molecule set (6,897 unique parsable SMILES across train, test, single-conc, TDI and Emax; mean 25.4 heavy atoms, median 45 all-atom, max 126): RDKit 30-conformer ETKDGv3+MMFF ensemble 62 min on 8 threads, benchmarked on real molecules from this set rather than extrapolated; DFT-D4 per-atom C6/C8/polarizability under 5 CPU-minutes total; GFN2-xTB optimisation plus Fukui, IP/EA and electrophilicity about 75 core-hours, which is 4.7 h on this box's actual 16 threads or roughly 1 h wall on an Explorer short-partition array; AIMNet2 charges 10-40 GPU-minutes on one A100; GPU4PySCF wB97X-D/def2-SVP single points with SMD water 12-19 GPU-hours. Nothing on the recommended list needs a new allocation. What is explicitly ruled out on cost: CREST/CENSO conformer ensembles at 48,000-1,000,000 core-hours, DFT geometry optimisation at ~1,100 GPU-hours, and DFT Hessians at ~1,500. Three standing assumptions were wrong and are corrected: this box has 16 logical CPUs not 4-6; only 22 of 6,897 molecules carry a formal charge, so the pH-7.4 protonated microstate must be generated rather than read from the data; and ANI-2x is disqualified for 192 molecules because it lacks Br, I and P. Windows blockers that force Explorer for parts of the stack: aimnet, orb-models, tblite, dftd4 and PySCF have no working Windows or Python 3.14 wheels, so QM work needs a 3.12 venv on Linux, while RDKit conformers and morfeus steric descriptors run locally fine.

metacomputefeasibilityqmexplorerphysics-scorerdeep-research: compute feasibility stream · 2026-08-29
high

The same attribution error, caught BEFORE the claim this time

A screen said DART was a poor second family. But DART drops trees while boosting, so the tree budget inherited from the base model under-trains it -- the number was my hyperparameter, not the family. Asking that question before writing the claim down is the whole difference.

Deep dive

An earlier tick published ExtraTrees' incompetence on CYP3A4 as a property of the family, and a later tick had to walk 63% of it back after sweeping a max_features value that had never been gated. The guard adopted then was: before attributing a result to a THING, ask which of your own unexamined choices could produce the same number. Applied here, DART's first screen showed a solo gap of +0.0861 -- worse than ExtraTrees -- and the budget sweep then gave +0.0440 at 1000 trees, nearly halving it. Same error, same shape, caught before the claim rather than a week after. The substantive consequence is real too: at 1000 trees DART is the most competent second family the campaign has found on CYP3A4, better than ExtraTrees' best there (+0.0514), and it lands inside the unresolved band of the competence rule. Cost remains a verdict in its own right -- 2000 trees was abandoned at roughly 50k trees per seed -- so the budget cannot simply be raised until the gap closes.

epistemicsattributionhyperparametersself-correctiondartscripts/train/dart_family_gate.py; data/processed/dart_family_cyp3a4_screen.json, _screen_t1000.json; ledger #110 · 2026-08-28
high

The same hardcoded-output-path defect appeared in BOTH submission builders

One builder overwriting a committed submission looked like an accident. Finding the identical defect in the second one, days later, makes it a pattern in how these scripts were written.

Deep dive

make_submission.py defaulted -o to a hardcoded 03_imputed_strae.csv from the era when v03 was current, and a bare run silently overwrote that committed submission. The TDI builder had the same shape: its output path was hardcoded to 01_tdi_base.csv, so a bare --submit would have clobbered the existing TDI submission the same way. Both are now auto-numbered to the next free NN_ slot and refuse an existing path. The convention -- one CSV per experiment, never overwrite, bump the number -- was written in CLAUDE.md and enforced only by every caller happening to pass an explicit path. A rule worth stating in project instructions is worth an assertion in each script that implements it, and 'each' is the operative word: fixing the first instance did not fix the second, because the defect was in the pattern rather than in one file.

harnesssubmissionsdefaultsconventionguardpatternscripts/train/make_submission.py, scripts/train/tdi_baseline.py; ledger #102, #108 · 2026-08-28
high

The search space is exhausted under this criterion -- measured, not felt

Auditing all 152 stored comparisons: 97 are settled negatives, 27 settled positives, 16 provably undecidable at any sane cost, and every remaining affordable item is already deployed or superseded. Nothing live is left that the criterion could decide.

Deep dive

For each stored arm that improves over its base, bootstrapping the ratio over seeds and extrapolating the seeds needed for its lower bound to clear 1.0 classifies the whole campaign. Ten arms came back affordable at 60 seeds or fewer, which would have made a headline about remaining opportunities -- but checking each against the live deployed configuration showed all ten are historical: four are already-deployed layers, one is the deploying gate itself, one was superseded by a deployed-pipeline re-measurement that reversed its sign, two are old-configuration gates since re-measured, and two are sub-arms of a decomposition whose other branch shipped. The substantive consequence is that further marginal experiments on the existing feature, loss and ensemble space cannot move the campaign -- not because they were tried and failed, but because the criterion lacks the resolution to decide what remains. Progress requires a genuinely new signal rather than more gates on the current space. An important caveat is that the classifier reads stored artifacts and cannot itself distinguish live from historical, so the filtering was manual and any future run must repeat it rather than trust the tally.

metaexhaustionresolvabilitycampaign-stateplanningscripts/train/resolvability_audit.py; data/processed/resolvability_audit.json; ledger #130 · 2026-08-29
high

The sixth criterion can PREVENT a deploy, not just enlarge one -- a re-derived verdict is a claim, not a deploy

Re-deriving an old verdict under corrected rules is legitimate, but the numbers it re-scores were measured in whatever configuration existed then. Layers added since can reverse the sign.

Deep dive

rederive_objective.py correctly flipped CYP3A4's bandl2 arm to DEPLOY: -0.0050, 3/3 seed-stable, 1.36x measured seed SD, vetoed originally by a sub-threshold LCO wiggle that #64 says is not evidence. Everything about that re-derivation was right on its own terms. But the numbers came from a run with neither the TDI-donor layer nor bagging, and inside the deployed stack the effect is +0.0034 at 0/3 -- a regression. Previously the sixth criterion had only been seen ENLARGING an effect (2D6's mechanistic block, -0.0077 where first gated, -0.0440 where it ships); this is the first time it stopped a deploy. The guard is now written into rederive_objective.py's own output: a flip prints 'A FLIP IS NOT A DEPLOY' and names the re-measurement script.

honest-gatesixth-criterionrederivationharnessscripts/train/rederive_objective.py; ledger #97 · 2026-08-27
high

Third tick running, a rule I wrote over-fired -- separate the requirement from the heuristic

A validator that mixes a real spec requirement with a plausibility check will report the heuristic's opinion with the requirement's authority.

Deep dive

Three consecutive ticks produced an over-claiming rule of mine. #76's diagnostic returned DEGENERATE on a positive-rate conjunction while precision held at 1.92x the base rate. #77's returned DEAD-ZONE EXPLOITATION on a conjunction over MAE and Spearman that fired on its weakest term, in a sentence its own numbers contradicted. #78's submission validator reported a superseded file as FAIL and WOULD BE REJECTED on a plausible-pIC50 range that is nowhere in the recovered spec -- the official validator checks columns, exactly 750 rows, NaN/inf and file type, and nothing about values. The shape is consistent: a check that bundles a hard requirement with a soft plausibility test inherits the requirement's authority for the heuristic's opinion, and the failure mode is always over-claiming rather than missing something. Two fixes now applied across those scripts: test the mechanism directly where it is measurable rather than ANDing proxies, and tier the output so that only genuine violations use the language of failure. The third instance was caught before it was reported, which is the only reason it is a note rather than a correction.

validationdiagnosticsself-correctionsubmissionsscripts/validate_submission.py · 2026-08-26
high

Three harness defects in two ticks, all in parameter plumbing -- and the silent one was the dangerous one

Two crashed at construction and cost minutes; the third ran clean and answered a different question than the one asked.

Deep dive

Editing LightGBM parameter assembly across several gate scripts produced three distinct failures in quick succession. First, capacity_gate.py was string-patched from reverify_recipe.py and one replacement silently failed to match because the source had shifted two ticks earlier; ast.parse passed and the leftover-scan checked for the wrong variable names, so it surfaced only as a NameError at run time. Second, adding a --leaves override to bagged_gate.py passed it through the per-member dict while BASE already carried num_leaves, giving LGBMRegressor two values for one keyword. Third, and the only one that did not fail: that same override applied to BOTH arms, so the run measured the bagging contrast at reduced capacity rather than measuring capacity, and produced a clean table answering a question nobody asked. The ranking is the point. A crash costs minutes. A silently mis-scoped comparison produces numbers that look exactly like the intended result, and is caught only by re-reading what the arms actually are before reading the table -- which is the same discipline the fifth gate criterion enforces at the level of whole pipelines. Three habits follow: derive a leftover-scan from the names being removed rather than hand-listing them; assemble model parameters in one place per script rather than merging dicts at the call site; and state explicitly which arms differ before quoting any contrast.

toolingcodegenself-correctioncontrolsscripts/train/bagged_gate.py · 2026-08-26
high

Two ticks running, my auto-verdicts overstated -- both were conjunctions over proxies instead of the mechanism

A rule that ANDs together several proxy metrics fires on whichever one is weakest, and then narrates a conclusion its own numbers contradict.

Deep dive

In #76 the diagnostic returned DEGENERATE because predicted positive rate exceeded 1.5x the base rate, while precision held at 1.92x base -- an operating-point shift, not a degenerate classifier. In #77 the diagnostic returned DEAD-ZONE EXPLOITATION because a conjunction over MAE and Spearman failed on Spearman (-0.0045), while MAE had actually IMPROVED (-0.0061) and the in-band rate had FALLEN rather than risen; the printed sentence cited its own improvement as evidence against improvement. Both verdicts were pre-registered, which is what made them worth trusting enough to notice when they were wrong, and both are preserved as superseded rather than quietly rewritten. The common defect is structural rather than careless: each rule was assembled as a conjunction over PROXY metrics that correlate with the failure mode, rather than measuring the failure mode itself. Exploitation of a forgiveness region is directly measurable -- count how often predictions land in it. A degenerate classifier is directly measurable -- watch precision collapse toward the base rate. When the mechanism can be measured, an AND across proxies is strictly worse: it fires on noise in the weakest term and misattributes the reason. The rules are now written against the mechanism in both scripts.

diagnosticspre-registrationself-correctionepistemicsdata/processed/deadzone_objective_cyp1a2.json · 2026-08-26
high

Validate on scaffold / leave-cluster-out folds and a never-tuned holdout — the single most important discipline, learned the hard way on PXR.

If a gain doesn't survive leave-cluster-out, it won't survive the blind leaderboard; trust the pessimistic estimate.

Deep dive

PXR's most expensive lesson: a blend tuned on the unblinded subset looked great locally and fell apart on the blind leaderboard, because the test was a series shift, not a random draw. Random CV was ~0.1 RAE optimistic.

  • Use scaffold_kfold_indices and leave_cluster_out_indices (both in cyp.eval).
  • Keep a genuinely never-touched holdout; report its RAE, not the tuned-set RAE.
  • A feature/blend deploys only if it wins on the honest gate. Treat leave-cluster-out as the truth and the optimistic number as marketing.

This applies doubly to any external-data or physics gain — validate on held-out *series*, not held-out rows.

validationleave-cluster-outoverfittingseed · 2026-08-12
high

Verified the official submission spec from the HF Space source - our draft was missing the required SMILES column and would have been rejected

The submission format was never documented in the dataset repo. Reading the validator's source is cheaper than burning a rate-limited submission slot.

Deep dive

The dataset repo ships no submission spec; it lives in the Space at openadmet/cyp-challenge (config.py + submission.py), which is public and readable. Authoritative REGRESSION spec: IDENTIFIER_COLUMNS = ['SMILES','Molecule_Name'] plus REGRESSION_ENDPOINTS = CYP1A2/CYP2C9/CYP2D6/CYP3A4_pIC50_direct_inhibition, exactly ACTIVITY_DATASET_SIZE = 750 rows, no NaN or inf, .csv or .parquet. CLASSIFICATION spec: same two identifiers plus CYP2D6_is_TDI and CYP3A4_is_TDI as bools, and CLASSIFICATION_ACTIVE is True so the TDI track is open now. Our first draft wrote Molecule_Name plus the four endpoints and OMITTED SMILES - the validator would have failed it on 'Missing required columns'. Also learned: HOURS_BETWEEN_SUBMISSIONS = 12, so submissions are rate-limited and each wasted upload costs half a day; and STRUCTURE_TRACK_LIVE is False with STRUCTURE_DATASET_SIZE = 184, so the structure track opens later and is much smaller than the 3,000-cofold plan assumed. Assertions for row count and finiteness now run before any file is written.

submissionvalidationhf-spacerate-limitcombined-gate · 2026-08-23
high

When you tighten a check that over-fired, verify it did not start UNDER-firing

Over-firing is loud and annoying; under-firing is silent and dangerous. Fixing the first without testing for the second trades a nuisance for the exact bug the check existed to catch.

Deep dive

check_layers.py flagged 8 artifacts on its first run. Six were the check being broader than the property it cared about -- the sixth such over-fire in this campaign -- so each was classified artifact-by-artifact (expressed via recipe()'s cfg string; belongs to the separate TDI track; an exploratory arm whose DEPLOY means 'bagging wins at leaves=31' rather than 'ship leaves=31'). The tightening then introduced the opposite defect: treating any 'superseded' key as 'not deployed' silently skipped bagged_cyp2c9, which rederive_gate.py had superseded by APPENDING the previous verdict to a list while the artifact stayed live and still ships bag15. A check that hides a live layer is precisely the failure it was written to catch, and it produced a clean exit-0 report while doing so. Narrowed to an explicit {'by': ...} retirement marker. Both runs are kept in the file so the correction is visible rather than tidied away.

epistemicschecksover-firingunder-firingself-correctionscripts/check_layers.py; ledger #101 · 2026-08-27
medium

Estimate each isoform's assay noise floor early — it is the real ceiling, and the weak/inactive tail is largely irreducible by post-hoc tricks.

Don't chase RAE below the assay SE; spend effort where signal exists, not on calibrating the noisy inactive tail.

Deep dive

PXR spent cycles confirming that the noise floor set the ceiling and that the weak-activity tail (regression-to-mean shrinkage) could not be fixed by post-hoc calibration/asymmetric loss — it was information-bound.
For CYP:

  • Early, estimate per-isoform assay SE / replicate agreement from the DRC data; that is the floor.
  • Expect an inactive/weak tail (many compounds are simply not inhibitors) — this is a censored-data problem; a classifier-style gate on 'is it active at all' may beat forcing a regressor onto flat-inactive values.
  • Report how much of the error is concentrated in that tail before trying to model it.

Set expectations from the floor, not from a hoped-for R².

noise-floorirreducibletailseed · 2026-08-12
medium

Expect the leaderboard to score worse than local CV: the test set is more active, so its bands are narrower

Two unrelated assays agree the blinded compounds are more potent, and ST-RAE forgives less where the assay was precise.

Deep dive

The shipped CYP3A4 model predicts 0.435 higher than its own out-of-fold predictions, which raised the question of whether the submission builder reproduces the gated model. It does: replicating both imputation paths gives essentially identical donor labels (gap -0.008), so the divergence is in the inputs and not the code. An independent probe settles what the inputs are doing. A model trained on the single-concentration screen -- a different assay, no pIC50 involved -- predicts test compounds at -2.248 log2fc against train at -1.710, and log2fc is negative for inhibition, so the blinded set is enriched for MORE ACTIVE compounds by 0.538. The consequence follows from a property already measured: band width anti-correlates with potency at -0.891 on CYP3A4 and -0.846, -0.733, -0.868 on the others, because the assay measured actives tightly and inactives loosely. A test set shifted 0.54 units more active therefore has systematically narrower credible bands -- a linear fit puts CYP3A4's expected width at 0.477 against 0.878 at the training mean, roughly half. Since ST-RAE charges nothing inside the band, narrower bands mean less forgiveness, so the leaderboard should read worse than local CV on every isoform for reasons unrelated to model quality. No deploy decision changes, because every comparison was made under identical conditions, but macro 0.6753 should be read as a ranking device rather than a forecast.

test-setst-raeband-widthexpectationssubmissionscripts/test_chemistry_probe.py · 2026-08-27
medium

On a test-like activity distribution CYP2D6 falls below the mean predictor: 0.879 becomes 1.045

The test set is more active, active compounds have tighter bands, and ST-RAE only forgives inside the band.

Deep dive

Re-scoring the deployed models' existing out-of-fold predictions under a reweighting that matches the test set's predicted-activity distribution moves macro from 0.6810 to 0.7784. Per isoform: CYP3A4 0.4479 to 0.5878, CYP2C9 0.6118 to 0.6229, CYP1A2 0.7847 to 0.8728, CYP2D6 0.8796 to 1.0300. CYP2D6 crossing 1.0 has a precise meaning, because the weighted mean predictor scores exactly 1.0 under any weighting by construction -- its soft error is the denominator. Re-run at three seeds it gives 1.0300, 1.0484 and 1.0560, mean 1.0448 with sd 0.0134, above 1.0 on all three. So on a test-like distribution the deployed CYP2D6 model is roughly 4.5% worse than predicting the mean. Three bounds apply and they do not point the same way: clipping the weights at 5x biases the estimate toward the unweighted number so the true shift may be larger; the reweighting depends on an independent activity model whose own error is unmeasured; and CYP2D6 has the lowest effective sample size of the four at 505 of 1493. No deploy decision is affected, since every gate compared arms under identical conditions and only the forecast moves. The practical consequence is that a model can be honestly gated, ablation-verified and format-validated and still be worth less than a constant on the distribution it will actually meet -- which is an argument for measuring the deployment distribution early rather than at the end.

covariate-shiftst-raecyp2d6expectationstest-setdata/processed/shift_corrected_estimate.json · 2026-08-27
medium

Pre-registering two outcomes is not the same as pre-registering all of them

I named 'null equals real' and 'null worse than real'. Reality returned 'null better than real', which was the informative case.

Deep dive

Before the CYP2D6 permutation null ran, two interpretations were committed in advance: if the signal-free block did as much harm as the real one, the rejection would be about dimensionality and the result inconclusive; if the null did LESS harm than the real block, the embedding carried some signal but not enough to pay for its width. The measured outcome was neither -- the null did essentially NO harm (+0.0001) while the real block did substantial harm (+0.0178), which is the case where the block is actively misleading rather than merely insufficient. Pre-registration still did its job: it fixed the diagnostic and stopped the reading from being chosen after the fact. But an enumeration that omits the region of outcome space where the effect is strongest is a weaker instrument than it looks, and the omission is easy to miss because the named cases feel exhaustive. Practice going forward: when pre-registering outcomes for a null comparison, state the sign AND the magnitude ordering for all three regions -- null worse, null equal, null better -- rather than the two that match the hypothesis being entertained.

pre-registrationepistemicsnullsself-correctiondata/processed/reverify_emb_cyp2d6_perm11.json · 2026-08-25
No findings match that filter.