@elaraai/e3-ui-components 1.0.12 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/experiment/charts.d.ts +31 -2
- package/dist/experiment/charts.d.ts.map +1 -1
- package/dist/experiment/derive.d.ts +176 -74
- package/dist/experiment/derive.d.ts.map +1 -1
- package/dist/experiment/help-ui.d.ts +29 -0
- package/dist/experiment/help-ui.d.ts.map +1 -0
- package/dist/experiment/help.d.ts +319 -0
- package/dist/experiment/help.d.ts.map +1 -0
- package/dist/experiment/index.d.ts.map +1 -1
- package/dist/experiment/run-runtime.d.ts +9 -0
- package/dist/experiment/run-runtime.d.ts.map +1 -1
- package/dist/index.cjs +1334 -393
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1335 -394
- package/dist/index.js.map +1 -1
- package/package.json +14 -14
package/dist/index.cjs
CHANGED
|
@@ -7464,6 +7464,11 @@ var faArrowDown = {
|
|
|
7464
7464
|
iconName: "arrow-down",
|
|
7465
7465
|
icon: [384, 512, [8595], "f063", "M169.4 502.6c12.5 12.5 32.8 12.5 45.3 0l160-160c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L224 402.7 224 32c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 370.7-105.4-105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l160 160z"]
|
|
7466
7466
|
};
|
|
7467
|
+
var faCircleInfo = {
|
|
7468
|
+
prefix: "fas",
|
|
7469
|
+
iconName: "circle-info",
|
|
7470
|
+
icon: [512, 512, ["info-circle"], "f05a", "M256 512a256 256 0 1 0 0-512 256 256 0 1 0 0 512zM224 160a32 32 0 1 1 64 0 32 32 0 1 1 -64 0zm-8 64l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24z"]
|
|
7471
|
+
};
|
|
7467
7472
|
const E3ConfigContext = React.createContext(null);
|
|
7468
7473
|
function E3Provider({ children: children2, config: config2, queryClient: externalClient }) {
|
|
7469
7474
|
const client = React.useMemo(
|
|
@@ -21879,13 +21884,13 @@ var hashIterableInts = function hashIterableInts2(iterator) {
|
|
|
21879
21884
|
}
|
|
21880
21885
|
return hash2;
|
|
21881
21886
|
};
|
|
21882
|
-
var hashInt = function hashInt2(
|
|
21887
|
+
var hashInt = function hashInt2(num2) {
|
|
21883
21888
|
var seed = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : DEFAULT_HASH_SEED;
|
|
21884
|
-
return seed * K +
|
|
21889
|
+
return seed * K + num2 | 0;
|
|
21885
21890
|
};
|
|
21886
|
-
var hashIntAlt = function hashIntAlt2(
|
|
21891
|
+
var hashIntAlt = function hashIntAlt2(num2) {
|
|
21887
21892
|
var seed = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : DEFAULT_HASH_SEED_ALT;
|
|
21888
|
-
return (seed << 5) + seed +
|
|
21893
|
+
return (seed << 5) + seed + num2 | 0;
|
|
21889
21894
|
};
|
|
21890
21895
|
var combineHashes = function combineHashes2(hash1, hash2) {
|
|
21891
21896
|
return hash1 * 2097152 + hash2;
|
|
@@ -23472,7 +23477,7 @@ var max = function max2(arr2) {
|
|
|
23472
23477
|
}
|
|
23473
23478
|
return max5;
|
|
23474
23479
|
};
|
|
23475
|
-
var mean
|
|
23480
|
+
var mean = function mean2(arr2) {
|
|
23476
23481
|
var begin3 = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : 0;
|
|
23477
23482
|
var end3 = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : arr2.length;
|
|
23478
23483
|
var total = 0;
|
|
@@ -25785,7 +25790,7 @@ var getPreference = function getPreference2(S2, preference) {
|
|
|
25785
25790
|
if (preference === "median") {
|
|
25786
25791
|
p2 = median(S2);
|
|
25787
25792
|
} else if (preference === "mean") {
|
|
25788
|
-
p2 = mean
|
|
25793
|
+
p2 = mean(S2);
|
|
25789
25794
|
} else if (preference === "min") {
|
|
25790
25795
|
p2 = min(S2);
|
|
25791
25796
|
} else if (preference === "max") {
|
|
@@ -56758,7 +56763,7 @@ function useBindingValue(binding) {
|
|
|
56758
56763
|
return { value, mode, pending, mutate, commit, discard };
|
|
56759
56764
|
}
|
|
56760
56765
|
const IDLE = { call: () => {
|
|
56761
|
-
}, result: null, status: "idle", pending: false };
|
|
56766
|
+
}, result: null, status: "idle", pending: false, error: null };
|
|
56762
56767
|
function useFuncCall(name, inputs, output2) {
|
|
56763
56768
|
const workspace = getReactiveDatasetCache().getConfig().workspace ?? "";
|
|
56764
56769
|
const ready4 = !!name && !!inputs && workspace !== "";
|
|
@@ -56781,15 +56786,22 @@ function useFuncCall(name, inputs, output2) {
|
|
|
56781
56786
|
React.useCallback(() => channelKey ? defaultFuncRuntime.getKeyVersion(channelKey) : 0, [channelKey])
|
|
56782
56787
|
);
|
|
56783
56788
|
return React.useMemo(() => {
|
|
56789
|
+
var _a2;
|
|
56784
56790
|
if (!handle) return IDLE;
|
|
56785
56791
|
let result = null;
|
|
56786
56792
|
let status = "idle";
|
|
56787
56793
|
let pending = false;
|
|
56794
|
+
let error3 = null;
|
|
56788
56795
|
try {
|
|
56789
56796
|
const read = handle.read();
|
|
56790
56797
|
result = read.type === "some" ? read.value : null;
|
|
56791
56798
|
status = handle.status().type;
|
|
56792
56799
|
pending = handle.pending();
|
|
56800
|
+
const err = handle.error();
|
|
56801
|
+
if (err.type === "some") {
|
|
56802
|
+
const v3 = err.value;
|
|
56803
|
+
error3 = { kind: ((_a2 = v3.kind) == null ? void 0 : _a2.type) ?? "failed", message: v3.message ?? "", stdout: v3.stdout ?? "", stderr: v3.stderr ?? "" };
|
|
56804
|
+
}
|
|
56793
56805
|
} catch {
|
|
56794
56806
|
}
|
|
56795
56807
|
return { call: (...args) => {
|
|
@@ -56797,7 +56809,7 @@ function useFuncCall(name, inputs, output2) {
|
|
|
56797
56809
|
handle.call(...args);
|
|
56798
56810
|
} catch {
|
|
56799
56811
|
}
|
|
56800
|
-
}, result, status, pending };
|
|
56812
|
+
}, result, status, pending, error: error3 };
|
|
56801
56813
|
}, [handle, version2]);
|
|
56802
56814
|
}
|
|
56803
56815
|
var propTypes = { exports: {} };
|
|
@@ -59041,6 +59053,370 @@ function Circle(_ref2) {
|
|
|
59041
59053
|
className: cx("visx-circle", className)
|
|
59042
59054
|
}, restProps));
|
|
59043
59055
|
}
|
|
59056
|
+
function fillHelp(entry, vars) {
|
|
59057
|
+
const sub = (s2) => s2.replace(/\{(\w+)\}/g, (m2, k2) => k2 in vars ? vars[k2] : m2);
|
|
59058
|
+
return {
|
|
59059
|
+
term: sub(entry.term),
|
|
59060
|
+
gist: sub(entry.gist),
|
|
59061
|
+
detail: sub(entry.detail),
|
|
59062
|
+
...entry.jargon !== void 0 ? { jargon: sub(entry.jargon) } : {}
|
|
59063
|
+
};
|
|
59064
|
+
}
|
|
59065
|
+
const HELP = {
|
|
59066
|
+
// ── Guidance mode (the toggle itself) ───────────────────────────────
|
|
59067
|
+
guidance: {
|
|
59068
|
+
term: "Guidance",
|
|
59069
|
+
gist: "Plain-language explainers for everything on this surface.",
|
|
59070
|
+
detail: "While this is on, hover any heading, value or chart label to see what it means — in business terms, with the statistical name underneath. Click to turn it off."
|
|
59071
|
+
},
|
|
59072
|
+
// ── Header ──────────────────────────────────────────────────────────
|
|
59073
|
+
header: {
|
|
59074
|
+
term: "The question",
|
|
59075
|
+
gist: "You’re asking whether {treatment} actually moved {outcome}.",
|
|
59076
|
+
detail: "Pick the lever you changed and the result you cared about, then hit Run — the surface answers whether the change is real and trustworthy.",
|
|
59077
|
+
jargon: "a binary-treatment causal effect"
|
|
59078
|
+
},
|
|
59079
|
+
// ── Set-up rail ─────────────────────────────────────────────────────
|
|
59080
|
+
step_treatment: {
|
|
59081
|
+
term: "What did you change?",
|
|
59082
|
+
gist: "The lever you’re testing — a yes/no flag on each {subject}.",
|
|
59083
|
+
detail: "It splits {subjects} into a “did” group and a “didn’t” group; the whole experiment compares those two.",
|
|
59084
|
+
jargon: "the treatment (binary)"
|
|
59085
|
+
},
|
|
59086
|
+
step_outcome: {
|
|
59087
|
+
term: "What did you want it to improve?",
|
|
59088
|
+
gist: "The result you’re hoping {treatment} moved.",
|
|
59089
|
+
detail: "A number measured on each {subject} (revenue, a duration, a score, …). The effect is the change in this we can credit to the lever.",
|
|
59090
|
+
jargon: "the outcome"
|
|
59091
|
+
},
|
|
59092
|
+
step_confounders: {
|
|
59093
|
+
term: "What else was different?",
|
|
59094
|
+
gist: "Things about the “did” group that could also explain the change.",
|
|
59095
|
+
detail: "If the {subjects} you treated already differed on something that matters — bigger, older, higher-value — that, not the lever, could be moving the result. We level the playing field on each of these so the comparison is fair. Add the ones you suspect.",
|
|
59096
|
+
jargon: "confounders / the backdoor adjustment set"
|
|
59097
|
+
},
|
|
59098
|
+
step_population: {
|
|
59099
|
+
term: "Which {subjects}?",
|
|
59100
|
+
gist: "Narrow the experiment to a slice of the {subjects}.",
|
|
59101
|
+
detail: "Filter to a subset you care about — a region, a period, a category — before measuring, so the answer is about the {subjects} that matter.",
|
|
59102
|
+
jargon: "population / cohort filter"
|
|
59103
|
+
},
|
|
59104
|
+
adv_method: {
|
|
59105
|
+
term: "How to compare",
|
|
59106
|
+
gist: "The maths used to make the like-for-like comparison.",
|
|
59107
|
+
detail: "“Regression” fits a line through the confounders; “re-weighting” up-weights rare look-alikes. They usually agree — if they don’t, treat the result with care.",
|
|
59108
|
+
jargon: "estimator: linear regression vs propensity-score weighting"
|
|
59109
|
+
},
|
|
59110
|
+
adv_estimand: {
|
|
59111
|
+
term: "Answer for",
|
|
59112
|
+
gist: "Who the effect is reported for.",
|
|
59113
|
+
detail: "“All” averages over everyone; “only treated” asks what the lever did for the {subjects} you actually treated — useful when you’d only ever apply it to them.",
|
|
59114
|
+
jargon: "estimand: ATE vs ATT"
|
|
59115
|
+
},
|
|
59116
|
+
confounder_imbalance: {
|
|
59117
|
+
term: "How lopsided this one was",
|
|
59118
|
+
gist: "How different the two groups were on this factor, before adjusting.",
|
|
59119
|
+
detail: "A long bar means the treated and untreated {subjects} started far apart here — exactly the kind of gap the adjustment has to close.",
|
|
59120
|
+
jargon: "standardised mean difference (SMD)"
|
|
59121
|
+
},
|
|
59122
|
+
// ── Tabs ────────────────────────────────────────────────────────────
|
|
59123
|
+
tab_answer: {
|
|
59124
|
+
term: "Answer",
|
|
59125
|
+
gist: "The headline: did {treatment} move {outcome}, and by how much?",
|
|
59126
|
+
detail: "Shows the raw gap, the fair like-for-like estimate, and an honest verdict."
|
|
59127
|
+
},
|
|
59128
|
+
tab_trust: {
|
|
59129
|
+
term: "Can we trust it?",
|
|
59130
|
+
gist: "The stress tests we ran to try to break the answer.",
|
|
59131
|
+
detail: "Each check probes a way the result could be a fluke; green means it held up."
|
|
59132
|
+
},
|
|
59133
|
+
tab_dose: {
|
|
59134
|
+
term: "How much?",
|
|
59135
|
+
gist: "How the result changes as a factor goes up or down.",
|
|
59136
|
+
detail: "Not just yes/no — the shape of the response, so you can find the sweet spot."
|
|
59137
|
+
},
|
|
59138
|
+
// ── Answer tab ──────────────────────────────────────────────────────
|
|
59139
|
+
answer_effect: {
|
|
59140
|
+
term: "Like-for-like effect",
|
|
59141
|
+
gist: "The change in {outcome} we can credit to {treatment}.",
|
|
59142
|
+
detail: "It compares treated vs untreated {subjects} who were otherwise similar — so it strips out “who you targeted” and leaves the lever’s real impact.",
|
|
59143
|
+
jargon: "the backdoor-adjusted average treatment effect"
|
|
59144
|
+
},
|
|
59145
|
+
answer_ci: {
|
|
59146
|
+
term: "95% range",
|
|
59147
|
+
gist: "Where the true effect most likely sits.",
|
|
59148
|
+
detail: "If this range doesn’t cross 0, the effect is real, not noise. The wider it is, the less certain — usually because of fewer {subjects}.",
|
|
59149
|
+
jargon: "95% confidence interval"
|
|
59150
|
+
},
|
|
59151
|
+
answer_flip: {
|
|
59152
|
+
term: "Raw and like-for-like disagree",
|
|
59153
|
+
gist: "The plain average points one way; the fair comparison points the other.",
|
|
59154
|
+
detail: "This happens when the treated group differed sharply on a confounder. Trust the like-for-like number — the raw gap is misleading here.",
|
|
59155
|
+
jargon: "confounding / Simpson’s-paradox sign flip"
|
|
59156
|
+
},
|
|
59157
|
+
answer_cautious: {
|
|
59158
|
+
term: "Treat this as provisional",
|
|
59159
|
+
gist: "We got a number, but a trust check failed.",
|
|
59160
|
+
detail: "The estimate may still be driven by something we didn’t adjust for. See “Can we trust it?” before acting on it.",
|
|
59161
|
+
jargon: "adjustment_insufficient verdict"
|
|
59162
|
+
},
|
|
59163
|
+
forest_plot: {
|
|
59164
|
+
term: "Raw average vs. like-for-like",
|
|
59165
|
+
gist: "The misleading number next to the fair one.",
|
|
59166
|
+
detail: "Each bar is an estimate with its uncertainty range; the dashed line is “no effect”. A bar clear of that line is a real change.",
|
|
59167
|
+
jargon: "a forest plot of naive vs adjusted estimates"
|
|
59168
|
+
},
|
|
59169
|
+
forest_naive: {
|
|
59170
|
+
term: "Raw average",
|
|
59171
|
+
gist: "The plain difference, with no adjusting.",
|
|
59172
|
+
detail: "Just treated-minus-untreated. It mixes in who you targeted, so it can mislead — it’s here only as the “before” picture.",
|
|
59173
|
+
jargon: "the unadjusted mean difference"
|
|
59174
|
+
},
|
|
59175
|
+
forest_adjusted: {
|
|
59176
|
+
term: "Like-for-like",
|
|
59177
|
+
gist: "The fair comparison after levelling the confounders.",
|
|
59178
|
+
detail: "This is the number to act on — what {treatment} did among otherwise-similar {subjects}.",
|
|
59179
|
+
jargon: "the adjusted effect"
|
|
59180
|
+
},
|
|
59181
|
+
balance: {
|
|
59182
|
+
term: "How unbalanced each one was",
|
|
59183
|
+
gist: "How far apart the two groups started on each factor.",
|
|
59184
|
+
detail: "Big bars are the factors the adjustment had to work hardest to correct — and the ones most worth double-checking.",
|
|
59185
|
+
jargon: "pre-adjustment covariate balance (SMD)"
|
|
59186
|
+
},
|
|
59187
|
+
counts: {
|
|
59188
|
+
term: "The {subject} counts",
|
|
59189
|
+
gist: "How many {subjects} went into the comparison.",
|
|
59190
|
+
detail: "“Compared like-for-like” are the ones with a fair match on the other side; “no fair match” were set aside because nobody comparable existed.",
|
|
59191
|
+
jargon: "n total / on-support / off-support"
|
|
59192
|
+
},
|
|
59193
|
+
// ── Refusal zones + overlap ─────────────────────────────────────────
|
|
59194
|
+
overlap_histogram: {
|
|
59195
|
+
term: "Do the two groups overlap?",
|
|
59196
|
+
gist: "Whether treated and untreated {subjects} actually look alike anywhere.",
|
|
59197
|
+
detail: "Each bar is how many {subjects} sit at a given “likelihood of being treated”. Where the two arms both have bars, a fair comparison exists; if they barely meet, there’s nothing to compare.",
|
|
59198
|
+
jargon: "propensity-score overlap / common support"
|
|
59199
|
+
},
|
|
59200
|
+
refusal_positivity: {
|
|
59201
|
+
term: "No like-for-like comparison exists",
|
|
59202
|
+
gist: "The treated and untreated {subjects} are too different to compare.",
|
|
59203
|
+
detail: "For almost every treated {subject} there’s no similar untreated one (or vice-versa), so there’s no fair “other side”. We refuse rather than extrapolate into thin air. Try fewer or different confounders.",
|
|
59204
|
+
jargon: "positivity / overlap violation"
|
|
59205
|
+
},
|
|
59206
|
+
refusal_not_estimable: {
|
|
59207
|
+
term: "Can’t be estimated",
|
|
59208
|
+
gist: "Almost every {subject} is on one side of {treatment}, so there’s no comparison group.",
|
|
59209
|
+
detail: "Measuring an effect needs a decent number of {subjects} who got the lever AND who didn’t. When one side is just a handful, we refuse to guess rather than read too much into a few people.",
|
|
59210
|
+
jargon: "no treatment variation"
|
|
59211
|
+
},
|
|
59212
|
+
// ── Trust tab ───────────────────────────────────────────────────────
|
|
59213
|
+
trust_intro: {
|
|
59214
|
+
term: "Can we trust it?",
|
|
59215
|
+
gist: "Before believing the answer, we tried to break it.",
|
|
59216
|
+
detail: "Each row is a different way the result could be a fluke; colour shows whether it held up (pass) or wobbled (caution)."
|
|
59217
|
+
},
|
|
59218
|
+
check_shuffle: {
|
|
59219
|
+
term: "Shuffle test",
|
|
59220
|
+
gist: "We randomly re-label who got {treatment} and re-measure.",
|
|
59221
|
+
detail: "On fake labels a genuine effect should collapse to nothing. If a “fake” effect still appears, the real one isn’t trustworthy.",
|
|
59222
|
+
jargon: "placebo / permutation refuter"
|
|
59223
|
+
},
|
|
59224
|
+
check_dropsome: {
|
|
59225
|
+
term: "Drop-some test",
|
|
59226
|
+
gist: "We re-run on random slices of the {subjects}.",
|
|
59227
|
+
detail: "A trustworthy effect barely moves when you drop some data; a jumpy one is resting on a few {subjects}.",
|
|
59228
|
+
jargon: "data-subset refuter"
|
|
59229
|
+
},
|
|
59230
|
+
check_decoy: {
|
|
59231
|
+
term: "Decoy factor",
|
|
59232
|
+
gist: "We add a totally random extra factor and re-measure.",
|
|
59233
|
+
detail: "A made-up factor shouldn’t change the answer. If it does, the model is over-reacting to noise.",
|
|
59234
|
+
jargon: "random-common-cause refuter"
|
|
59235
|
+
},
|
|
59236
|
+
check_hidden: {
|
|
59237
|
+
term: "Hidden cause",
|
|
59238
|
+
gist: "How strong an unrecorded cause would have to be to overturn the result.",
|
|
59239
|
+
detail: "Something you didn’t measure could drive both who got {treatment} and {outcome}. “Holds throughout” means only an implausibly strong hidden cause could flip it.",
|
|
59240
|
+
jargon: "unobserved-confounding sensitivity / E-value"
|
|
59241
|
+
},
|
|
59242
|
+
sensitivity: {
|
|
59243
|
+
term: "Effect as a hidden cause gets stronger",
|
|
59244
|
+
gist: "What happens to the effect if an unrecorded cause is dialled up.",
|
|
59245
|
+
detail: "The line is the estimate as a simulated hidden cause grows. The point where it crosses 0 is how much hidden bias it would take to erase the result.",
|
|
59246
|
+
jargon: "sensitivity (tipping-point) curve"
|
|
59247
|
+
},
|
|
59248
|
+
// ── Dose tab ────────────────────────────────────────────────────────
|
|
59249
|
+
dose_curve: {
|
|
59250
|
+
term: "{outcome} gained vs. {feature}",
|
|
59251
|
+
gist: "How {outcome} responds as {feature} goes up.",
|
|
59252
|
+
detail: "The line is the average change in {outcome} at each level of {feature}, with its uncertainty band — the shape tells you where more pays off and where it flattens.",
|
|
59253
|
+
jargon: "accumulated local effects (ALE) dose-response"
|
|
59254
|
+
},
|
|
59255
|
+
dose_here: {
|
|
59256
|
+
term: "You are here",
|
|
59257
|
+
gist: "Where most of your {subjects} currently sit on {feature}.",
|
|
59258
|
+
detail: "The busiest level today — your starting point for deciding whether to push higher or lower.",
|
|
59259
|
+
jargon: "the modal bin"
|
|
59260
|
+
},
|
|
59261
|
+
dose_sweet: {
|
|
59262
|
+
term: "Sweet spot",
|
|
59263
|
+
gist: "Where the gains are solid and start to flatten.",
|
|
59264
|
+
detail: "Past here, pushing {feature} higher buys little extra {outcome} — a sensible target.",
|
|
59265
|
+
jargon: "the diminishing-returns knee"
|
|
59266
|
+
},
|
|
59267
|
+
dose_reco: {
|
|
59268
|
+
term: "Recommended",
|
|
59269
|
+
gist: "The level of {feature} we’d aim for.",
|
|
59270
|
+
detail: "The sweet-spot level and the {outcome} you’d expect there, with its range — and the trade-off of going one step further.",
|
|
59271
|
+
jargon: "the recommended operating point"
|
|
59272
|
+
},
|
|
59273
|
+
dose_marginal: {
|
|
59274
|
+
term: "Extra {outcome} per step",
|
|
59275
|
+
gist: "What each additional step of {feature} buys you.",
|
|
59276
|
+
detail: "Tall early bars then short ones is the classic diminishing-returns shape — spend effort where the bars are tall.",
|
|
59277
|
+
jargon: "marginal / incremental effect per bin"
|
|
59278
|
+
},
|
|
59279
|
+
// ── Verdicts ────────────────────────────────────────────────────────
|
|
59280
|
+
verdict_causal: {
|
|
59281
|
+
term: "Causal",
|
|
59282
|
+
gist: "A real change you can credit to {treatment} — and it held up.",
|
|
59283
|
+
detail: "The like-for-like effect is clear, sizeable, and survived the trust checks. Safe to act on.",
|
|
59284
|
+
jargon: "a robust, identified effect"
|
|
59285
|
+
},
|
|
59286
|
+
verdict_modest: {
|
|
59287
|
+
term: "Modest / unclear",
|
|
59288
|
+
gist: "There may be an effect, but it’s small or fuzzy.",
|
|
59289
|
+
detail: "The fair comparison can’t cleanly separate it from no-effect (the range straddles 0, or it’s tiny). Don’t bet much on it.",
|
|
59290
|
+
jargon: "effect CI spans zero / below materiality"
|
|
59291
|
+
},
|
|
59292
|
+
verdict_adjustment_insufficient: {
|
|
59293
|
+
term: "Not trustworthy yet",
|
|
59294
|
+
gist: "We got a number, but a trust check failed.",
|
|
59295
|
+
detail: "Likely something we didn’t adjust for is still in play. Treat as provisional and look at “Can we trust it?”.",
|
|
59296
|
+
jargon: "placebo / robustness failure"
|
|
59297
|
+
},
|
|
59298
|
+
verdict_non_identifiable_positivity: {
|
|
59299
|
+
term: "No fair comparison",
|
|
59300
|
+
gist: "Treated and untreated {subjects} don’t overlap enough to compare.",
|
|
59301
|
+
detail: "There’s no like-for-like “other side”, so any number would be extrapolation. See the overlap chart.",
|
|
59302
|
+
jargon: "positivity / overlap violation"
|
|
59303
|
+
},
|
|
59304
|
+
verdict_not_estimable: {
|
|
59305
|
+
term: "Can’t be estimated",
|
|
59306
|
+
gist: "Almost everyone is on one side of {treatment} — no comparison group.",
|
|
59307
|
+
detail: "One arm is just a handful of {subjects}, so we won’t guess an effect from it.",
|
|
59308
|
+
jargon: "no treatment variation"
|
|
59309
|
+
},
|
|
59310
|
+
// ── Validate tab ────────────────────────────────────────────────────
|
|
59311
|
+
tab_validate: {
|
|
59312
|
+
term: "Validate",
|
|
59313
|
+
gist: "The real experiment you’d run to prove this — for sure.",
|
|
59314
|
+
detail: "This analysis adjusted for what it could measure. A controlled trial — assigning {treatment} at random — removes any leftover doubt. This tab sizes that trial."
|
|
59315
|
+
},
|
|
59316
|
+
validate_size: {
|
|
59317
|
+
term: "How many to run",
|
|
59318
|
+
gist: "The number of {subjects} the trial needs.",
|
|
59319
|
+
detail: "Enough to spot a change this size if it’s real. A bigger or cleaner effect needs fewer; a small or noisy one needs many more.",
|
|
59320
|
+
jargon: "required sample size at the target power"
|
|
59321
|
+
},
|
|
59322
|
+
validate_split: {
|
|
59323
|
+
term: "How to split them",
|
|
59324
|
+
gist: "How many get {treatment} versus are left alone.",
|
|
59325
|
+
detail: "Assign at random — that’s what makes the two groups comparable with no adjusting. An even split is the most efficient; treating fewer needs a larger total.",
|
|
59326
|
+
jargon: "randomised allocation between arms"
|
|
59327
|
+
},
|
|
59328
|
+
validate_match: {
|
|
59329
|
+
term: "Match the groups on",
|
|
59330
|
+
gist: "Keep both groups balanced on these — they were lopsided last time.",
|
|
59331
|
+
detail: "Give each group the same mix of these factors when you split, so neither one starts ahead. These are the factors that muddied the original comparison most.",
|
|
59332
|
+
jargon: "stratified / blocked randomisation on the high-imbalance covariates"
|
|
59333
|
+
},
|
|
59334
|
+
validate_power: {
|
|
59335
|
+
term: "Chance of detecting it",
|
|
59336
|
+
gist: "How likely the trial is to catch the effect, by size.",
|
|
59337
|
+
detail: "More {subjects} means a better chance of a clear result. The marker shows where today’s data sits — usually well short of a confident answer.",
|
|
59338
|
+
jargon: "statistical power vs sample size"
|
|
59339
|
+
},
|
|
59340
|
+
validate_holdback: {
|
|
59341
|
+
term: "Hold back a control",
|
|
59342
|
+
gist: "Keep a random group untreated to compare against.",
|
|
59343
|
+
detail: "Right now almost everything got {treatment}, so there’s nothing to compare to. Next time, leave a random sample untreated — that group becomes the fair baseline.",
|
|
59344
|
+
jargon: "a randomised control arm"
|
|
59345
|
+
},
|
|
59346
|
+
// ── Journal ─────────────────────────────────────────────────────────
|
|
59347
|
+
journal: {
|
|
59348
|
+
term: "Committed experiments",
|
|
59349
|
+
gist: "A log of the questions you’ve saved.",
|
|
59350
|
+
detail: "Each row is a framing you committed — its verdict and headline effect — so the team can see what’s been tried and what held up.",
|
|
59351
|
+
jargon: "the experiment journal"
|
|
59352
|
+
}
|
|
59353
|
+
};
|
|
59354
|
+
const GuidanceContext = React.createContext({ on: true, vars: {} });
|
|
59355
|
+
function GuidanceProvider({ on: on3, vars, children: children2 }) {
|
|
59356
|
+
return /* @__PURE__ */ jsxRuntime.jsx(GuidanceContext.Provider, { value: { on: on3, vars }, children: children2 });
|
|
59357
|
+
}
|
|
59358
|
+
function HelpCard({ id: id3, extraVars }) {
|
|
59359
|
+
const { vars } = React.useContext(GuidanceContext);
|
|
59360
|
+
const e3 = fillHelp(HELP[id3], { ...vars, ...extraVars });
|
|
59361
|
+
const slot = react.useSlotRecipe({ key: "hoverCard" })();
|
|
59362
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", flexDirection: "column", gap: "1.5", children: [
|
|
59363
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: slot.title, mb: "0", children: e3.term }),
|
|
59364
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", color: "fg.default", lineHeight: "1.5", children: e3.gist }),
|
|
59365
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: slot.description, lineHeight: "1.5", children: e3.detail }),
|
|
59366
|
+
e3.jargon && /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "caption", color: "fg.subtle", mt: "0.5", pt: "1.5", borderTopWidth: "1px", borderColor: "border.subtle", children: [
|
|
59367
|
+
"Technically: ",
|
|
59368
|
+
e3.jargon
|
|
59369
|
+
] })
|
|
59370
|
+
] });
|
|
59371
|
+
}
|
|
59372
|
+
function Hover({ id: id3, extraVars, trigger }) {
|
|
59373
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(react.HoverCard.Root, { openDelay: 150, closeDelay: 80, size: "sm", positioning: { placement: "top" }, children: [
|
|
59374
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.HoverCard.Trigger, { asChild: true, children: trigger }),
|
|
59375
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(react.HoverCard.Positioner, { children: /* @__PURE__ */ jsxRuntime.jsxs(react.HoverCard.Content, { children: [
|
|
59376
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.HoverCard.Arrow, { children: /* @__PURE__ */ jsxRuntime.jsx(react.HoverCard.ArrowTip, {}) }),
|
|
59377
|
+
/* @__PURE__ */ jsxRuntime.jsx(HelpCard, { id: id3, extraVars })
|
|
59378
|
+
] }) }) })
|
|
59379
|
+
] });
|
|
59380
|
+
}
|
|
59381
|
+
function Help({ id: id3, extraVars, children: children2, display = "inline", gap }) {
|
|
59382
|
+
const { on: on3 } = React.useContext(GuidanceContext);
|
|
59383
|
+
if (!on3) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children2 });
|
|
59384
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Hover, { id: id3, extraVars, trigger: (
|
|
59385
|
+
// `tabIndex={0}` makes the term keyboard-focusable — the HoverCard's zag
|
|
59386
|
+
// machine opens on focus, so the glossary is reachable without a pointer.
|
|
59387
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
59388
|
+
react.Box,
|
|
59389
|
+
{
|
|
59390
|
+
as: "span",
|
|
59391
|
+
cursor: "help",
|
|
59392
|
+
tabIndex: 0,
|
|
59393
|
+
display,
|
|
59394
|
+
...gap ? { alignItems: "center", gap } : {},
|
|
59395
|
+
_focusVisible: { outline: "1px dotted", outlineColor: "brand.fg", outlineOffset: "2px", borderRadius: "2px" },
|
|
59396
|
+
children: children2
|
|
59397
|
+
}
|
|
59398
|
+
)
|
|
59399
|
+
) });
|
|
59400
|
+
}
|
|
59401
|
+
function GuidanceToggle({ on: on3, onToggle }) {
|
|
59402
|
+
const iconButton = react.useRecipe({ key: "iconButton" });
|
|
59403
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Hover, { id: "guidance", trigger: /* @__PURE__ */ jsxRuntime.jsx(
|
|
59404
|
+
react.Box,
|
|
59405
|
+
{
|
|
59406
|
+
as: "button",
|
|
59407
|
+
css: iconButton({ variant: on3 ? "subtle" : "ghost", size: "sm" }),
|
|
59408
|
+
onClick: onToggle,
|
|
59409
|
+
"aria-pressed": on3,
|
|
59410
|
+
"aria-label": "Toggle guidance",
|
|
59411
|
+
color: on3 ? "brand.fg" : "fg.muted",
|
|
59412
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faCircleInfo })
|
|
59413
|
+
}
|
|
59414
|
+
) });
|
|
59415
|
+
}
|
|
59416
|
+
function ChartLabel({ x: x2, y: y2, w: w2, align, size: size3, weight: weight8, color: color2, family, help, children: children2 }) {
|
|
59417
|
+
const span = /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: `${size3}px`, fontFamily: family, fontWeight: weight8, color: color2, lineHeight: 1, whiteSpace: "nowrap" }, children: children2 });
|
|
59418
|
+
return /* @__PURE__ */ jsxRuntime.jsx("foreignObject", { x: x2, y: y2, width: w2, height: size3 + 6, style: { overflow: "visible" }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { textAlign: align, fontSize: `${size3}px`, lineHeight: 1 }, children: help ? /* @__PURE__ */ jsxRuntime.jsx(Help, { id: help, children: span }) : span }) });
|
|
59419
|
+
}
|
|
59044
59420
|
function svgText(size3, fill, family, weight8) {
|
|
59045
59421
|
return { fontSize: `${size3}px`, fill, fontFamily: family, ...weight8 !== void 0 ? { fontWeight: weight8 } : {} };
|
|
59046
59422
|
}
|
|
@@ -59096,7 +59472,7 @@ const niceCeil = (v3) => {
|
|
|
59096
59472
|
const step3 = n2 <= 1 ? 1 : n2 <= 1.5 ? 1.5 : n2 <= 2 ? 2 : n2 <= 3 ? 3 : n2 <= 4 ? 4 : n2 <= 5 ? 5 : n2 <= 6 ? 6 : n2 <= 8 ? 8 : 10;
|
|
59097
59473
|
return step3 * mag;
|
|
59098
59474
|
};
|
|
59099
|
-
function ForestPlot({ rows, min: min4, max: max5, unit: unit2, height: height2 }) {
|
|
59475
|
+
function ForestPlot({ rows, min: min4, max: max5, unit: unit2, height: height2, rowHelp }) {
|
|
59100
59476
|
const t4 = useChartTheme();
|
|
59101
59477
|
const [ref, w2] = useMeasuredWidth();
|
|
59102
59478
|
const n2 = rows.length;
|
|
@@ -59106,11 +59482,14 @@ function ForestPlot({ rows, min: min4, max: max5, unit: unit2, height: height2 }
|
|
|
59106
59482
|
const innerH = hPx - padT - padB;
|
|
59107
59483
|
const x2 = createLinearScale({ domain: [min4, max5], range: [0, innerW] });
|
|
59108
59484
|
const rowH = innerH / Math.max(1, n2);
|
|
59109
|
-
const zeroX = x2(0);
|
|
59110
|
-
const
|
|
59485
|
+
const zeroX = Math.max(0, Math.min(innerW, x2(0)));
|
|
59486
|
+
const frac = (v3) => max5 > min4 ? (v3 - min4) / (max5 - min4) : 0.5;
|
|
59487
|
+
const anchorAt = (v3) => frac(v3) < 0.06 ? "start" : frac(v3) > 0.94 ? "end" : "middle";
|
|
59488
|
+
const zf = frac(0);
|
|
59489
|
+
const ticks2 = zf > 0.12 && zf < 0.88 ? [min4, 0, max5] : [min4, max5];
|
|
59111
59490
|
return /* @__PURE__ */ jsxRuntime.jsx(react.Box, { ref, width: "100%", height: `${hPx}px`, children: w2 > 0 && /* @__PURE__ */ jsxRuntime.jsx("svg", { width: w2, height: hPx, style: { display: "block" }, children: /* @__PURE__ */ jsxRuntime.jsxs(Group, { left: padL, top: padT, children: [
|
|
59112
59491
|
/* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: zeroX, y: -4 }, to: { x: zeroX, y: innerH }, stroke: t4.muted, strokeWidth: 1, strokeDasharray: "3 2" }),
|
|
59113
|
-
/* @__PURE__ */ jsxRuntime.jsx("text", { x: zeroX, y: -6, textAnchor:
|
|
59492
|
+
/* @__PURE__ */ jsxRuntime.jsx("text", { x: zeroX, y: -6, textAnchor: anchorAt(0), style: svgText(t4.labelSize, t4.muted, t4.mono), children: "no effect" }),
|
|
59114
59493
|
rows.map((r2, i) => {
|
|
59115
59494
|
const cy = rowH * i + rowH / 2;
|
|
59116
59495
|
const col = t4.tone(r2.tone);
|
|
@@ -59119,20 +59498,20 @@ function ForestPlot({ rows, min: min4, max: max5, unit: unit2, height: height2 }
|
|
|
59119
59498
|
/* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: x2(r2.lo), y: -4 }, to: { x: x2(r2.lo), y: 4 }, stroke: col, strokeWidth: 1.5 }),
|
|
59120
59499
|
/* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: x2(r2.hi), y: -4 }, to: { x: x2(r2.hi), y: 4 }, stroke: col, strokeWidth: 1.5 }),
|
|
59121
59500
|
/* @__PURE__ */ jsxRuntime.jsx(Circle, { cx: x2(r2.est), cy: 0, r: t4.scatterRadius, fill: col, stroke: t4.surface, strokeWidth: 1.8 }),
|
|
59122
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
59501
|
+
/* @__PURE__ */ jsxRuntime.jsx(ChartLabel, { x: -padL + 2, y: -13, w: padL - 6, align: "left", size: t4.labelSize, weight: 600, color: t4.ink, family: t4.body, ...(rowHelp == null ? void 0 : rowHelp[i]) ? { help: rowHelp[i] } : {}, children: r2.label }),
|
|
59123
59502
|
r2.note && /* @__PURE__ */ jsxRuntime.jsx("text", { x: -padL + 2, y: 9, style: svgText(9, t4.faint, t4.body), children: r2.note }),
|
|
59124
59503
|
/* @__PURE__ */ jsxRuntime.jsx("text", { x: innerW + padR - 2, y: 4, textAnchor: "end", style: svgText(t4.titleSize, col, t4.mono, 700), children: signed$1(r2.est) })
|
|
59125
59504
|
] }, i);
|
|
59126
59505
|
}),
|
|
59127
59506
|
/* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: 0, y: innerH }, to: { x: innerW, y: innerH }, stroke: t4.rule, strokeWidth: 1 }),
|
|
59128
|
-
ticks2.map((tk, i) => /* @__PURE__ */ jsxRuntime.jsxs(Group, { left: x2(tk), children: [
|
|
59507
|
+
ticks2.map((tk, i) => /* @__PURE__ */ jsxRuntime.jsxs(Group, { left: Math.max(0, Math.min(innerW, x2(tk))), children: [
|
|
59129
59508
|
/* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: 0, y: innerH }, to: { x: 0, y: innerH + 4 }, stroke: t4.ruleStrong, strokeWidth: 1 }),
|
|
59130
|
-
/* @__PURE__ */ jsxRuntime.jsx("text", { x: 0, y: innerH + 14, textAnchor:
|
|
59509
|
+
/* @__PURE__ */ jsxRuntime.jsx("text", { x: 0, y: innerH + 14, textAnchor: anchorAt(tk), style: svgText(t4.labelSize, t4.muted, t4.mono), children: signed$1(tk) })
|
|
59131
59510
|
] }, `t${i}`)),
|
|
59132
59511
|
unit2 && /* @__PURE__ */ jsxRuntime.jsx("text", { x: innerW / 2, y: innerH + 26, textAnchor: "middle", style: svgText(t4.labelSize, t4.muted, t4.mono), children: unit2 })
|
|
59133
59512
|
] }) }) });
|
|
59134
59513
|
}
|
|
59135
|
-
function AreaRange({ lo: lo2, mid, hi, xTicks = [], yTicks = [], zero: zero2, tone: toneName, marks = [], height: height2 }) {
|
|
59514
|
+
function AreaRange({ lo: lo2, mid, hi, xTicks = [], yTicks = [], zero: zero2, tone: toneName, marks = [], height: height2, yFormat = "signed" }) {
|
|
59136
59515
|
const t4 = useChartTheme();
|
|
59137
59516
|
const [ref, w2] = useMeasuredWidth();
|
|
59138
59517
|
const hPx = height2 ?? 100;
|
|
@@ -59144,13 +59523,18 @@ function AreaRange({ lo: lo2, mid, hi, xTicks = [], yTicks = [], zero: zero2, to
|
|
|
59144
59523
|
const innerW = Math.max(0, w2 - padL - padR);
|
|
59145
59524
|
const innerH = hPx - padT - padB;
|
|
59146
59525
|
const xScale = createLinearScale({ domain: [0, Math.max(1, mid.length - 1)], range: [0, innerW] });
|
|
59147
|
-
const
|
|
59148
|
-
const
|
|
59526
|
+
const fin = (x2, fallback) => typeof x2 === "number" && Number.isFinite(x2) ? x2 : fallback;
|
|
59527
|
+
const data4 = mid.map((m2, i) => {
|
|
59528
|
+
const mm = fin(m2, 0);
|
|
59529
|
+
return { i, lo: fin(lo2[i], mm), hi: fin(hi[i], mm), mid: mm };
|
|
59530
|
+
});
|
|
59531
|
+
const dataMin = Math.min(...data4.map((d2) => d2.lo), zero2 ?? Infinity);
|
|
59532
|
+
const dataMax = Math.max(...data4.map((d2) => d2.hi), zero2 ?? -Infinity);
|
|
59149
59533
|
const yHiN = niceCeil(Math.max(dataMax, 0));
|
|
59150
59534
|
const yLoN = dataMin < -0.08 * yHiN ? -niceCeil(-dataMin) : 0;
|
|
59151
59535
|
const yScale = createLinearScale({ domain: [yLoN, yHiN], range: [innerH, 0] });
|
|
59152
|
-
const
|
|
59153
|
-
const
|
|
59536
|
+
const yfmt = (v3) => yFormat === "percent" ? `${Math.round(v3)}%` : signed$1(v3);
|
|
59537
|
+
const yLabels = [yfmt(yHiN), yfmt((yHiN + yLoN) / 2), yfmt(yLoN)];
|
|
59154
59538
|
return /* @__PURE__ */ jsxRuntime.jsx(react.Box, { ref, width: "100%", height: `${hPx}px`, children: w2 > 0 && /* @__PURE__ */ jsxRuntime.jsx("svg", { width: w2, height: hPx, style: { display: "block" }, children: /* @__PURE__ */ jsxRuntime.jsxs(Group, { left: padL, top: padT, children: [
|
|
59155
59539
|
/* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: 0, y: 0 }, to: { x: 0, y: innerH }, stroke: t4.rule, strokeWidth: 1 }),
|
|
59156
59540
|
yTicks.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
@@ -59163,11 +59547,17 @@ function AreaRange({ lo: lo2, mid, hi, xTicks = [], yTicks = [], zero: zero2, to
|
|
|
59163
59547
|
/* @__PURE__ */ jsxRuntime.jsx(LinePath, { data: data4, x: (d2) => xScale(d2.i), y: (d2) => yScale(d2.mid), curve: monotoneX, stroke: col, strokeWidth: t4.lineWidth }),
|
|
59164
59548
|
marks.map((m2, i) => {
|
|
59165
59549
|
const mc = t4.tone(m2.tone);
|
|
59166
|
-
const dotY = yScale(mid[m2.at]
|
|
59550
|
+
const dotY = yScale(fin(mid[m2.at], 0));
|
|
59551
|
+
const labY = Math.max(9, dotY - 9);
|
|
59552
|
+
const atEnd = m2.at >= mid.length - 2;
|
|
59553
|
+
const atStart = m2.at <= 1;
|
|
59554
|
+
const align = atEnd ? "right" : atStart ? "left" : "center";
|
|
59555
|
+
const labW = m2.label.length * 6 + 6;
|
|
59556
|
+
const labX = atEnd ? -labW : atStart ? 0 : -labW / 2;
|
|
59167
59557
|
return /* @__PURE__ */ jsxRuntime.jsxs(Group, { left: xScale(m2.at), children: [
|
|
59168
59558
|
/* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: 0, y: -2 }, to: { x: 0, y: innerH }, stroke: mc, strokeWidth: 1, strokeDasharray: "2 3" }),
|
|
59169
59559
|
/* @__PURE__ */ jsxRuntime.jsx(Circle, { cx: 0, cy: dotY, r: t4.dotRadius, fill: mc, stroke: t4.surface, strokeWidth: 1.5 }),
|
|
59170
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
59560
|
+
/* @__PURE__ */ jsxRuntime.jsx(ChartLabel, { x: labX, y: labY - 11, w: labW, align, size: t4.labelSize, weight: 500, color: mc, family: t4.body, ...m2.help ? { help: m2.help } : {}, children: m2.label })
|
|
59171
59561
|
] }, `mk${i}`);
|
|
59172
59562
|
}),
|
|
59173
59563
|
/* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: 0, y: innerH }, to: { x: innerW, y: innerH }, stroke: t4.rule, strokeWidth: 1 }),
|
|
@@ -59177,17 +59567,54 @@ function AreaRange({ lo: lo2, mid, hi, xTicks = [], yTicks = [], zero: zero2, to
|
|
|
59177
59567
|
})
|
|
59178
59568
|
] }) }) });
|
|
59179
59569
|
}
|
|
59570
|
+
function OverlapHistogram({ treated, control, domain = [0, 1], supportLabel, positivityOk, height: height2 }) {
|
|
59571
|
+
const t4 = useChartTheme();
|
|
59572
|
+
const [ref, w2] = useMeasuredWidth();
|
|
59573
|
+
const hPx = height2 ?? 150;
|
|
59574
|
+
const padL = 8, padR = 8, padT = 16, padB = 26;
|
|
59575
|
+
const innerW = Math.max(0, w2 - padL - padR);
|
|
59576
|
+
const innerH = hPx - padT - padB;
|
|
59577
|
+
const centerY = innerH / 2;
|
|
59578
|
+
const halfH = centerY - 4;
|
|
59579
|
+
const bins = Math.max(treated.length, control.length);
|
|
59580
|
+
const x2 = createLinearScale({ domain, range: [0, innerW] });
|
|
59581
|
+
const span = domain[1] - domain[0];
|
|
59582
|
+
const binW = bins > 0 ? innerW / bins : innerW;
|
|
59583
|
+
const barW = Math.max(1, binW * 0.78);
|
|
59584
|
+
const maxVal = Math.max(1e-9, ...treated, ...control);
|
|
59585
|
+
const tCol = t4.tone("pos");
|
|
59586
|
+
const cCol = t4.tone("muted");
|
|
59587
|
+
const ticks2 = [domain[0], (domain[0] + domain[1]) / 2, domain[1]];
|
|
59588
|
+
const cx2 = (i) => x2(domain[0] + (i + 0.5) / bins * span);
|
|
59589
|
+
return /* @__PURE__ */ jsxRuntime.jsx(react.Box, { ref, width: "100%", height: `${hPx}px`, children: w2 > 0 && /* @__PURE__ */ jsxRuntime.jsx("svg", { width: w2, height: hPx, style: { display: "block" }, children: /* @__PURE__ */ jsxRuntime.jsxs(Group, { left: padL, top: padT, children: [
|
|
59590
|
+
/* @__PURE__ */ jsxRuntime.jsx("text", { x: 0, y: -4, style: svgText(9, t4.faint, t4.body), children: "treated ↑" }),
|
|
59591
|
+
supportLabel && /* @__PURE__ */ jsxRuntime.jsx("text", { x: innerW, y: -4, textAnchor: "end", style: svgText(t4.labelSize, positivityOk ? t4.tone("pos") : t4.tone("warn"), t4.mono, 600), children: supportLabel }),
|
|
59592
|
+
Array.from({ length: bins }, (_2, i) => {
|
|
59593
|
+
const ht = Number(treated[i] ?? 0) / maxVal * halfH;
|
|
59594
|
+
const hc = Number(control[i] ?? 0) / maxVal * halfH;
|
|
59595
|
+
const left = cx2(i) - barW / 2;
|
|
59596
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(Group, { children: [
|
|
59597
|
+
ht > 0 && /* @__PURE__ */ jsxRuntime.jsx("rect", { x: left, y: centerY - ht, width: barW, height: ht, fill: tCol, fillOpacity: 0.85, rx: 0.5 }),
|
|
59598
|
+
hc > 0 && /* @__PURE__ */ jsxRuntime.jsx("rect", { x: left, y: centerY, width: barW, height: hc, fill: cCol, fillOpacity: 0.6, rx: 0.5 })
|
|
59599
|
+
] }, i);
|
|
59600
|
+
}),
|
|
59601
|
+
/* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: 0, y: centerY }, to: { x: innerW, y: centerY }, stroke: t4.ruleStrong, strokeWidth: 1 }),
|
|
59602
|
+
/* @__PURE__ */ jsxRuntime.jsx("text", { x: 0, y: innerH + 4, style: svgText(9, t4.faint, t4.body), children: "untreated ↓" }),
|
|
59603
|
+
ticks2.map((tk, i) => /* @__PURE__ */ jsxRuntime.jsx("text", { x: x2(tk), y: innerH + 16, textAnchor: i === 0 ? "start" : i === ticks2.length - 1 ? "end" : "middle", style: svgText(t4.labelSize, t4.muted, t4.mono), children: fmt$2(tk) }, `x${i}`))
|
|
59604
|
+
] }) }) });
|
|
59605
|
+
}
|
|
59180
59606
|
const arr = (v3) => v3 ? Array.from(v3, Number) : [];
|
|
59607
|
+
const arrI = (v3) => v3 ? Array.from(v3, Number) : [];
|
|
59181
59608
|
const getOpt = (o2) => o2 && o2.type === "some" ? o2.value : void 0;
|
|
59182
|
-
const fmt$1 = (x2) =>
|
|
59183
|
-
const
|
|
59184
|
-
|
|
59185
|
-
const mean2 = (xs) => xs.length ? xs.reduce((a2, b2) => a2 + b2, 0) / xs.length : 0;
|
|
59186
|
-
const std = (xs) => {
|
|
59187
|
-
if (xs.length < 2) return 0;
|
|
59188
|
-
const m2 = mean2(xs);
|
|
59189
|
-
return Math.sqrt(xs.reduce((a2, b2) => a2 + (b2 - m2) * (b2 - m2), 0) / xs.length);
|
|
59609
|
+
const fmt$1 = (x2) => {
|
|
59610
|
+
const r2 = Number.isInteger(x2) ? String(x2) : x2.toFixed(1);
|
|
59611
|
+
return r2 === "-0.0" ? "0.0" : r2;
|
|
59190
59612
|
};
|
|
59613
|
+
const signed = (x2) => {
|
|
59614
|
+
const f2 = fmt$1(x2);
|
|
59615
|
+
return f2 === "0" || f2 === "0.0" || f2.startsWith("-") ? f2 : `+${f2}`;
|
|
59616
|
+
};
|
|
59617
|
+
const clamp01 = (x2) => x2 < 0 ? 0 : x2 > 1 ? 1 : x2;
|
|
59191
59618
|
function band(absStd) {
|
|
59192
59619
|
if (absStd >= 0.66) return { level: "large gap", tone: "neg" };
|
|
59193
59620
|
if (absStd >= 0.33) return { level: "some", tone: "warn" };
|
|
@@ -59202,11 +59629,22 @@ const kindOf = (cols, name) => {
|
|
|
59202
59629
|
var _a2;
|
|
59203
59630
|
return (_a2 = cols.find((c2) => c2.name === name)) == null ? void 0 : _a2.kind;
|
|
59204
59631
|
};
|
|
59205
|
-
|
|
59206
|
-
|
|
59207
|
-
|
|
59208
|
-
|
|
59209
|
-
|
|
59632
|
+
const VERDICT_TONE = {
|
|
59633
|
+
causal: "pos",
|
|
59634
|
+
modest: "warn",
|
|
59635
|
+
adjustment_insufficient: "warn",
|
|
59636
|
+
non_identifiable_positivity: "neg",
|
|
59637
|
+
not_estimable: "muted"
|
|
59638
|
+
};
|
|
59639
|
+
const VERDICT_LABEL = {
|
|
59640
|
+
causal: "Causal effect",
|
|
59641
|
+
modest: "Modest / unclear",
|
|
59642
|
+
adjustment_insufficient: "Not trustworthy yet",
|
|
59643
|
+
non_identifiable_positivity: "No fair comparison",
|
|
59644
|
+
not_estimable: "Can’t be estimated"
|
|
59645
|
+
};
|
|
59646
|
+
function deriveVerdict(v3) {
|
|
59647
|
+
return { tag: v3.type, tone: VERDICT_TONE[v3.type], label: VERDICT_LABEL[v3.type] };
|
|
59210
59648
|
}
|
|
59211
59649
|
function treatmentKind(cols, treatment) {
|
|
59212
59650
|
switch (kindOf(cols, treatment)) {
|
|
@@ -59222,124 +59660,133 @@ function treatmentKind(cols, treatment) {
|
|
|
59222
59660
|
return "value";
|
|
59223
59661
|
}
|
|
59224
59662
|
}
|
|
59225
|
-
function deriveSpec(
|
|
59226
|
-
const tKind = kindOf(cols,
|
|
59227
|
-
const outUnit = unitOf(meta3,
|
|
59228
|
-
const balByCol =
|
|
59229
|
-
const
|
|
59663
|
+
function deriveSpec(config2, cols, result, meta3, dataLen) {
|
|
59664
|
+
const tKind = kindOf(cols, config2.treatment);
|
|
59665
|
+
const outUnit = unitOf(meta3, config2.outcome);
|
|
59666
|
+
const balByCol = /* @__PURE__ */ new Map();
|
|
59667
|
+
for (const b2 of (result == null ? void 0 : result.balance) ?? []) {
|
|
59668
|
+
const prev2 = balByCol.get(b2.base_column);
|
|
59669
|
+
if (!prev2 || Math.abs(b2.std_diff) > Math.abs(prev2.std_diff)) balByCol.set(b2.base_column, b2);
|
|
59670
|
+
}
|
|
59671
|
+
const confounders = config2.common_causes.map((col) => {
|
|
59230
59672
|
const b2 = balByCol.get(col);
|
|
59231
|
-
const absStd = b2 ? Math.abs(b2.
|
|
59673
|
+
const absStd = b2 ? Math.abs(b2.std_diff) : 0;
|
|
59232
59674
|
const { level, tone } = band(absStd);
|
|
59233
|
-
const dir = b2 ? b2.
|
|
59234
|
-
const reason = b2 ? `treated
|
|
59675
|
+
const dir = b2 ? b2.treated_mean < b2.control_mean ? "lower" : "higher" : "";
|
|
59676
|
+
const reason = b2 ? `the treated group ran ${dir} on ${col}` : "measured once you Run";
|
|
59235
59677
|
return { col, reason, imbalance: clamp01(absStd), level, tone };
|
|
59236
59678
|
});
|
|
59237
|
-
const inPlay = /* @__PURE__ */ new Set([
|
|
59679
|
+
const inPlay = /* @__PURE__ */ new Set([config2.treatment, config2.outcome, ...config2.common_causes]);
|
|
59238
59680
|
const suggest = cols.find((c2) => !inPlay.has(c2.name));
|
|
59239
59681
|
const suggestion = suggest ? `add ${suggest.name}` : "";
|
|
59240
|
-
const methodV = getOpt(
|
|
59682
|
+
const methodV = getOpt(config2.method);
|
|
59241
59683
|
const method = (methodV == null ? void 0 : methodV.type) === "propensity_score_weighting" ? "reweighting" : "regression";
|
|
59242
|
-
const targetV = getOpt(
|
|
59684
|
+
const targetV = getOpt(config2.estimand);
|
|
59243
59685
|
const target = targetV && (targetV.type === "att" || targetV.type === "atc") ? "treated" : "all";
|
|
59244
|
-
const trim2 = getOpt(spec.trim) !== void 0;
|
|
59245
59686
|
return {
|
|
59246
|
-
treatment:
|
|
59247
|
-
treatmentKind: treatmentKind(cols,
|
|
59248
|
-
outcome:
|
|
59687
|
+
treatment: config2.treatment,
|
|
59688
|
+
treatmentKind: treatmentKind(cols, config2.treatment),
|
|
59689
|
+
outcome: config2.outcome,
|
|
59249
59690
|
outcomeKind: outUnit ? `number · ${outUnit}` : "number",
|
|
59250
59691
|
comparison: tKind === "boolean" ? "yes · vs no" : "high · vs low",
|
|
59251
59692
|
confounders,
|
|
59252
59693
|
suggestion,
|
|
59253
59694
|
method,
|
|
59254
59695
|
target,
|
|
59255
|
-
|
|
59256
|
-
dataLabel: `${result ? Number(result.nTotal) : dataLen} rows`
|
|
59696
|
+
dataLabel: `${result ? Number(result.n_total) : dataLen} rows`
|
|
59257
59697
|
};
|
|
59258
59698
|
}
|
|
59259
59699
|
function balanceDisplay(b2, categorical) {
|
|
59260
|
-
const isProp = categorical.has(b2.
|
|
59261
|
-
return isProp ? `${Math.round(b2.
|
|
59262
|
-
}
|
|
59263
|
-
function deriveAnswer(
|
|
59264
|
-
const ci = getOpt(
|
|
59265
|
-
const nci = getOpt(result.
|
|
59266
|
-
const categorical = new Set(
|
|
59267
|
-
const balance = [...result.balance].sort((a2, b2) => Math.abs(b2.
|
|
59700
|
+
const isProp = categorical.has(b2.base_column) && b2.treated_mean >= 0 && b2.treated_mean <= 1 && b2.control_mean >= 0 && b2.control_mean <= 1;
|
|
59701
|
+
return isProp ? `${Math.round(b2.treated_mean * 100)}% vs ${Math.round(b2.control_mean * 100)}%` : `${b2.treated_mean.toFixed(1)} vs ${b2.control_mean.toFixed(1)}`;
|
|
59702
|
+
}
|
|
59703
|
+
function deriveAnswer(config2, result, adj, meta3) {
|
|
59704
|
+
const ci = getOpt(adj.ci);
|
|
59705
|
+
const nci = getOpt(result.naive_ci);
|
|
59706
|
+
const categorical = new Set(getOpt(config2.categorical) ?? []);
|
|
59707
|
+
const balance = [...result.balance].sort((a2, b2) => Math.abs(b2.std_diff) - Math.abs(a2.std_diff)).map((b2) => ({
|
|
59268
59708
|
col: b2.column,
|
|
59269
|
-
treated: b2.
|
|
59270
|
-
control: b2.
|
|
59709
|
+
treated: b2.treated_mean,
|
|
59710
|
+
control: b2.control_mean,
|
|
59271
59711
|
display: balanceDisplay(b2, categorical),
|
|
59272
|
-
frac: clamp01(Math.abs(b2.
|
|
59273
|
-
tone: band(Math.abs(b2.
|
|
59712
|
+
frac: clamp01(Math.abs(b2.std_diff)),
|
|
59713
|
+
tone: band(Math.abs(b2.std_diff)).tone
|
|
59274
59714
|
}));
|
|
59275
|
-
const
|
|
59276
|
-
const
|
|
59715
|
+
const lo2 = (ci == null ? void 0 : ci.lower) ?? adj.effect;
|
|
59716
|
+
const hi = (ci == null ? void 0 : ci.upper) ?? adj.effect;
|
|
59717
|
+
const clear = result.verdict.type === "causal" || (ci ? lo2 > 0 || hi < 0 : false);
|
|
59718
|
+
const flip = Math.sign(result.naive) !== Math.sign(adj.effect) && result.naive !== 0;
|
|
59719
|
+
const nDropped = Number(result.n_dropped);
|
|
59720
|
+
const nTotal = Number(result.n_total);
|
|
59277
59721
|
return {
|
|
59278
|
-
treatment:
|
|
59279
|
-
outcome:
|
|
59280
|
-
unit: unitOf(meta3,
|
|
59722
|
+
treatment: config2.treatment,
|
|
59723
|
+
outcome: config2.outcome,
|
|
59724
|
+
unit: unitOf(meta3, config2.outcome),
|
|
59281
59725
|
naive: result.naive,
|
|
59282
59726
|
naiveLo: (nci == null ? void 0 : nci.lower) ?? result.naive,
|
|
59283
59727
|
naiveHi: (nci == null ? void 0 : nci.upper) ?? result.naive,
|
|
59284
|
-
effect:
|
|
59285
|
-
lo:
|
|
59286
|
-
hi
|
|
59287
|
-
|
|
59288
|
-
|
|
59289
|
-
|
|
59728
|
+
effect: adj.effect,
|
|
59729
|
+
lo: lo2,
|
|
59730
|
+
hi,
|
|
59731
|
+
clear,
|
|
59732
|
+
flip,
|
|
59733
|
+
cautious: result.verdict.type === "adjustment_insufficient",
|
|
59734
|
+
nTotal: result.n_total,
|
|
59735
|
+
nTreated: result.n_treated,
|
|
59736
|
+
nControl: result.n_control,
|
|
59290
59737
|
nCompared: BigInt(Math.max(0, nTotal - nDropped)),
|
|
59291
|
-
nDropped: result.
|
|
59738
|
+
nDropped: result.n_dropped,
|
|
59292
59739
|
balance
|
|
59293
59740
|
};
|
|
59294
59741
|
}
|
|
59295
|
-
function
|
|
59296
|
-
const
|
|
59297
|
-
|
|
59298
|
-
|
|
59299
|
-
|
|
59300
|
-
|
|
59301
|
-
|
|
59302
|
-
|
|
59303
|
-
|
|
59304
|
-
|
|
59305
|
-
value:
|
|
59306
|
-
|
|
59307
|
-
|
|
59308
|
-
|
|
59309
|
-
}
|
|
59310
|
-
case "data_subset": {
|
|
59311
|
-
const passed = Math.abs(m2 - est) < Math.max(0.5, Math.abs(est) * 0.2);
|
|
59312
|
-
return {
|
|
59313
|
-
name: "Drop-some test",
|
|
59314
|
-
desc: "Re-estimate on random subsamples; a trustworthy effect stays put.",
|
|
59315
|
-
value: `${fmt$1(m2)} ± ${fmt$1(std(effects))}`,
|
|
59316
|
-
passed,
|
|
59317
|
-
tip: { type: "none", value: null }
|
|
59318
|
-
};
|
|
59319
|
-
}
|
|
59320
|
-
case "random_common_cause": {
|
|
59321
|
-
const passed = Math.abs(m2 - est) < Math.max(0.5, Math.abs(est) * 0.2);
|
|
59322
|
-
return {
|
|
59323
|
-
name: "Decoy cause",
|
|
59324
|
-
desc: "Add an irrelevant random factor; the answer should not move.",
|
|
59325
|
-
value: `${fmt$1(est)} → ${fmt$1(m2)}`,
|
|
59326
|
-
passed,
|
|
59327
|
-
tip: { type: "none", value: null }
|
|
59328
|
-
};
|
|
59329
|
-
}
|
|
59330
|
-
default: {
|
|
59331
|
-
const strengths = arr(getOpt(c2.strengths));
|
|
59332
|
-
const tip = zeroCrossing(strengths, effects);
|
|
59333
|
-
const passed = tip === null;
|
|
59334
|
-
return {
|
|
59335
|
-
name: "Hidden cause",
|
|
59336
|
-
desc: "How strong an unrecorded common cause would need to be to overturn the result.",
|
|
59337
|
-
value: tip === null ? "holds throughout" : `tips at ${fmt$1(tip)}`,
|
|
59338
|
-
passed,
|
|
59339
|
-
tip: { type: "some", value: "Something unrecorded could drive both the treatment choice and the outcome." }
|
|
59340
|
-
};
|
|
59341
|
-
}
|
|
59742
|
+
function deriveRefusal(config2, result) {
|
|
59743
|
+
const nT = Number(result.n_treated), nC = Number(result.n_control), nTot = Number(result.n_total);
|
|
59744
|
+
if (result.verdict.type === "not_estimable") {
|
|
59745
|
+
const reason = result.verdict.value || "The treatment barely varies, so no comparison can be formed.";
|
|
59746
|
+
return {
|
|
59747
|
+
kind: "not_estimable",
|
|
59748
|
+
title: `Can’t estimate the effect of ${config2.treatment}`,
|
|
59749
|
+
body: reason,
|
|
59750
|
+
evidence: [
|
|
59751
|
+
{ label: "treated", value: String(nT) },
|
|
59752
|
+
{ label: "untreated", value: String(nC) },
|
|
59753
|
+
{ label: "rows", value: String(nTot) }
|
|
59754
|
+
]
|
|
59755
|
+
};
|
|
59342
59756
|
}
|
|
59757
|
+
if (result.verdict.type === "non_identifiable_positivity") {
|
|
59758
|
+
const pct = Math.round(result.overlap.common_support_frac * 100);
|
|
59759
|
+
return {
|
|
59760
|
+
kind: "positivity",
|
|
59761
|
+
title: "No like-for-like comparison exists",
|
|
59762
|
+
body: `The treated and untreated groups barely overlap on the confounders — only ${pct}% sit in a range where both occur — so there is no fair comparison to adjust toward.`,
|
|
59763
|
+
evidence: [
|
|
59764
|
+
{ label: "common support", value: `${pct}%` },
|
|
59765
|
+
{ label: "treated", value: String(nT) },
|
|
59766
|
+
{ label: "untreated", value: String(nC) }
|
|
59767
|
+
]
|
|
59768
|
+
};
|
|
59769
|
+
}
|
|
59770
|
+
return {
|
|
59771
|
+
kind: "not_estimable",
|
|
59772
|
+
title: `Can’t estimate the effect of ${config2.treatment}`,
|
|
59773
|
+
body: "The engine could not produce a like-for-like estimate for this configuration.",
|
|
59774
|
+
evidence: [
|
|
59775
|
+
{ label: "treated", value: String(nT) },
|
|
59776
|
+
{ label: "untreated", value: String(nC) },
|
|
59777
|
+
{ label: "rows", value: String(nTot) }
|
|
59778
|
+
]
|
|
59779
|
+
};
|
|
59780
|
+
}
|
|
59781
|
+
function deriveOverlap(o2) {
|
|
59782
|
+
const pct = Math.round(o2.common_support_frac * 100);
|
|
59783
|
+
return {
|
|
59784
|
+
treated: arr(o2.treated_propensity),
|
|
59785
|
+
control: arr(o2.control_propensity),
|
|
59786
|
+
commonSupportFrac: o2.common_support_frac,
|
|
59787
|
+
positivityOk: o2.positivity_ok,
|
|
59788
|
+
supportLabel: `${pct}% common support`
|
|
59789
|
+
};
|
|
59343
59790
|
}
|
|
59344
59791
|
function zeroCrossing(xs, ys) {
|
|
59345
59792
|
for (let i = 1; i < ys.length && i < xs.length; i++) {
|
|
@@ -59351,27 +59798,80 @@ function zeroCrossing(xs, ys) {
|
|
|
59351
59798
|
}
|
|
59352
59799
|
return null;
|
|
59353
59800
|
}
|
|
59354
|
-
function deriveRefute(
|
|
59355
|
-
const checks =
|
|
59356
|
-
const
|
|
59357
|
-
const
|
|
59358
|
-
const
|
|
59359
|
-
|
|
59360
|
-
|
|
59361
|
-
|
|
59362
|
-
|
|
59363
|
-
|
|
59364
|
-
|
|
59365
|
-
|
|
59366
|
-
|
|
59367
|
-
}
|
|
59368
|
-
|
|
59369
|
-
|
|
59801
|
+
function deriveRefute(r2, adj) {
|
|
59802
|
+
const checks = [];
|
|
59803
|
+
const est = (adj == null ? void 0 : adj.effect) ?? 0;
|
|
59804
|
+
const placeboEffect = getOpt(r2.placebo_effect);
|
|
59805
|
+
const placeboPasses = getOpt(r2.placebo_passes);
|
|
59806
|
+
if (placeboEffect !== void 0 || placeboPasses !== void 0) {
|
|
59807
|
+
checks.push({
|
|
59808
|
+
name: "Shuffle test",
|
|
59809
|
+
desc: "Shuffle which rows were treated; a real effect should collapse to zero.",
|
|
59810
|
+
value: placeboEffect !== void 0 ? `→ ${signed(placeboEffect)}` : placeboPasses ? "passed" : "failed",
|
|
59811
|
+
passed: placeboPasses ?? (placeboEffect !== void 0 && Math.abs(placeboEffect) < Math.max(0.5, Math.abs(est) * 0.15)),
|
|
59812
|
+
tip: { type: "some", value: "Randomly re-label which rows were treated. A genuine effect should vanish." },
|
|
59813
|
+
help: "check_shuffle"
|
|
59814
|
+
});
|
|
59815
|
+
}
|
|
59816
|
+
const ds = getOpt(r2.data_subset_effect);
|
|
59817
|
+
if (ds !== void 0) {
|
|
59818
|
+
const dstd = getOpt(r2.data_subset_std) ?? 0;
|
|
59819
|
+
checks.push({
|
|
59820
|
+
name: "Drop-some test",
|
|
59821
|
+
desc: "Re-estimate on random subsamples; a trustworthy effect stays put.",
|
|
59822
|
+
value: `${fmt$1(ds)} ± ${fmt$1(dstd)}`,
|
|
59823
|
+
passed: Math.abs(ds - est) < Math.max(0.5, Math.abs(est) * 0.2),
|
|
59824
|
+
tip: { type: "none", value: null },
|
|
59825
|
+
help: "check_dropsome"
|
|
59826
|
+
});
|
|
59827
|
+
}
|
|
59828
|
+
const rcc = getOpt(r2.random_cc_within_ci);
|
|
59829
|
+
if (rcc !== void 0) {
|
|
59830
|
+
checks.push({
|
|
59831
|
+
name: "Decoy cause",
|
|
59832
|
+
desc: "Add an irrelevant random factor; the answer should not move.",
|
|
59833
|
+
value: rcc ? "within range" : "moved",
|
|
59834
|
+
passed: rcc,
|
|
59835
|
+
tip: { type: "none", value: null },
|
|
59836
|
+
help: "check_decoy"
|
|
59837
|
+
});
|
|
59838
|
+
}
|
|
59839
|
+
const sens = getOpt(r2.sensitivity);
|
|
59840
|
+
const evalue = getOpt(r2.robustness_value);
|
|
59841
|
+
if (sens !== void 0 || evalue !== void 0) {
|
|
59842
|
+
const strengths = sens ? arr(sens.strengths) : [];
|
|
59843
|
+
const effects = sens ? arr(sens.effects) : [];
|
|
59844
|
+
const tip = sens ? zeroCrossing(strengths, effects) : null;
|
|
59845
|
+
const value = tip !== null ? `tips at ${fmt$1(tip)}` : evalue !== void 0 ? `E-value ${fmt$1(evalue)}` : "holds throughout";
|
|
59846
|
+
checks.push({
|
|
59847
|
+
name: "Hidden cause",
|
|
59848
|
+
desc: "How strong an unrecorded common cause would need to be to overturn the result.",
|
|
59849
|
+
value,
|
|
59850
|
+
passed: tip === null,
|
|
59851
|
+
tip: { type: "some", value: "Something unrecorded could drive both the treatment choice and the outcome." },
|
|
59852
|
+
help: "check_hidden"
|
|
59853
|
+
});
|
|
59854
|
+
}
|
|
59855
|
+
let sensVM = null;
|
|
59856
|
+
if (sens !== void 0) {
|
|
59857
|
+
const mid = arr(sens.effects);
|
|
59858
|
+
if (mid.length) {
|
|
59859
|
+
const ci = getOpt(adj == null ? void 0 : adj.ci);
|
|
59860
|
+
const half = ci ? Math.abs(ci.upper - ci.lower) / 2 : 0;
|
|
59861
|
+
const lo2 = mid.map((m2) => m2 - half);
|
|
59862
|
+
const hi = mid.map((m2) => m2 + half);
|
|
59863
|
+
const yLo = Math.min(0, ...lo2), yHi = Math.max(...hi, 0);
|
|
59864
|
+
sensVM = { lo: lo2, mid, hi, xTicks: ["none", "weaker", "stronger"], yTicks: [fmt$1(yLo), fmt$1((yLo + yHi) / 2), fmt$1(yHi)] };
|
|
59865
|
+
}
|
|
59866
|
+
}
|
|
59867
|
+
return { checks, sens: sensVM };
|
|
59868
|
+
}
|
|
59869
|
+
function deriveDose(dose, config2, meta3) {
|
|
59370
59870
|
const grid = arr(dose.grid);
|
|
59371
59871
|
const mid = arr(dose.effect);
|
|
59372
59872
|
const lo2 = getOpt(dose.lower) ? arr(getOpt(dose.lower)) : mid.slice();
|
|
59373
59873
|
const hi = getOpt(dose.upper) ? arr(getOpt(dose.upper)) : mid.slice();
|
|
59374
|
-
const sizes =
|
|
59874
|
+
const sizes = arrI(dose.size);
|
|
59375
59875
|
const featureUnit = unitOf(meta3, dose.feature);
|
|
59376
59876
|
const here = sizes.length ? sizes.indexOf(Math.max(...sizes)) : 0;
|
|
59377
59877
|
const marg = mid.map((m2, i) => i === 0 ? 0 : m2 - mid[i - 1]);
|
|
@@ -59391,31 +59891,28 @@ function deriveDose(dose, spec, meta3) {
|
|
|
59391
59891
|
let lastStep = margSteps.length - 1;
|
|
59392
59892
|
while (lastStep > 0 && Math.abs(margSteps[lastStep]) < 0.12 * maxMarg) lastStep--;
|
|
59393
59893
|
const marginal = margSteps.slice(0, lastStep + 1).map((v3, i) => ({ label: `${fmt$1(grid[i + 1])}`, value: v3, frac: clamp01(Math.abs(v3) / maxMarg) }));
|
|
59394
|
-
const segLabels = ((_a2 = getOpt(dose.segments)) == null ? void 0 : _a2.map((s2) => s2.label)) ?? [];
|
|
59395
|
-
const segments = segLabels.length ? ["all", ...segLabels] : ["all"];
|
|
59396
59894
|
const stepFrom = here < sweet ? here : Math.max(0, sweet - 1);
|
|
59397
59895
|
const gainToSweet = (mid[sweet] ?? 0) - (mid[stepFrom] ?? 0);
|
|
59398
59896
|
const nextGain = sweet + 1 < mid.length ? mid[sweet + 1] - mid[sweet] : 0;
|
|
59399
59897
|
const tradeoff = `Going from ${fmt$1(grid[stepFrom] ?? 0)} to ${fmt$1(grid[sweet] ?? 0)} adds ${signed(gainToSweet)}; the next step adds only ${signed(nextGain)}.`;
|
|
59898
|
+
const marks = [];
|
|
59899
|
+
if (sizes.length) marks.push({ at: here, label: "you are here", tone: "muted", help: "dose_here" });
|
|
59900
|
+
marks.push({ at: sweet, label: "sweet spot", tone: "pos", help: "dose_sweet" });
|
|
59400
59901
|
return {
|
|
59401
59902
|
feature: dose.feature,
|
|
59402
|
-
outcome:
|
|
59903
|
+
outcome: config2.outcome,
|
|
59403
59904
|
lo: lo2,
|
|
59404
59905
|
mid,
|
|
59405
59906
|
hi,
|
|
59406
59907
|
xTicks,
|
|
59407
59908
|
yTicks,
|
|
59408
|
-
marks
|
|
59409
|
-
{ at: BigInt(here), label: "you are here", tone: "muted" },
|
|
59410
|
-
{ at: BigInt(sweet), label: "sweet spot", tone: "pos" }
|
|
59411
|
-
],
|
|
59909
|
+
marks,
|
|
59412
59910
|
recoLabel: `≈ ${fmt$1(grid[sweet] ?? 0)}${featureUnit ? " " + featureUnit : ""}`,
|
|
59413
59911
|
recoEffect: mid[sweet] ?? 0,
|
|
59414
59912
|
recoLo: lo2[sweet] ?? 0,
|
|
59415
59913
|
recoHi: hi[sweet] ?? 0,
|
|
59416
59914
|
tradeoff,
|
|
59417
|
-
marginal
|
|
59418
|
-
segments
|
|
59915
|
+
marginal
|
|
59419
59916
|
};
|
|
59420
59917
|
}
|
|
59421
59918
|
function relTime(d2, now2) {
|
|
@@ -59425,30 +59922,126 @@ function relTime(d2, now2) {
|
|
|
59425
59922
|
if (days < 7) return d2.toLocaleDateString(void 0, { weekday: "short" });
|
|
59426
59923
|
return "last wk";
|
|
59427
59924
|
}
|
|
59925
|
+
const VERDICT_WORD = {
|
|
59926
|
+
causal: "causal",
|
|
59927
|
+
modest: "modest",
|
|
59928
|
+
adjustment_insufficient: "not robust",
|
|
59929
|
+
non_identifiable_positivity: "no overlap",
|
|
59930
|
+
not_estimable: "n/a"
|
|
59931
|
+
};
|
|
59428
59932
|
function deriveJournalRow(row, now2) {
|
|
59429
|
-
const
|
|
59430
|
-
const clear = ci ? ci.lower > 0 || ci.upper < 0 : true;
|
|
59431
|
-
const dirUp = row.effect > 0;
|
|
59933
|
+
const adj = getOpt(row.adjusted);
|
|
59432
59934
|
return {
|
|
59433
|
-
treatment: row.
|
|
59434
|
-
outcome: row.
|
|
59435
|
-
confounders: row.
|
|
59436
|
-
effect: signed(row.
|
|
59437
|
-
verdict:
|
|
59438
|
-
verdictTone:
|
|
59439
|
-
who: row.
|
|
59440
|
-
when: relTime(row.
|
|
59441
|
-
};
|
|
59442
|
-
}
|
|
59443
|
-
function deriveView(
|
|
59935
|
+
treatment: row.config.treatment,
|
|
59936
|
+
outcome: row.config.outcome,
|
|
59937
|
+
confounders: row.config.common_causes.join(", "),
|
|
59938
|
+
effect: adj !== void 0 ? signed(adj) : signed(row.naive),
|
|
59939
|
+
verdict: VERDICT_WORD[row.verdict.type],
|
|
59940
|
+
verdictTone: VERDICT_TONE[row.verdict.type],
|
|
59941
|
+
who: row.committed_by,
|
|
59942
|
+
when: relTime(row.committed_at, now2)
|
|
59943
|
+
};
|
|
59944
|
+
}
|
|
59945
|
+
function deriveView(config2, ranConfig, cols, result, journal, meta3, dataLen, now2) {
|
|
59946
|
+
const adj = result ? getOpt(result.adjusted) : void 0;
|
|
59947
|
+
const refutation = result ? getOpt(result.refutation) : void 0;
|
|
59948
|
+
const doseR = result ? getOpt(result.dose_response) : void 0;
|
|
59444
59949
|
return {
|
|
59445
|
-
|
|
59446
|
-
|
|
59447
|
-
|
|
59448
|
-
|
|
59950
|
+
// The set-up rail reflects the LIVE config (the editor); the result deck
|
|
59951
|
+
// reflects RANCONFIG — the config that produced `result` — so the result
|
|
59952
|
+
// strings never drift ahead of the numbers on a live picker edit.
|
|
59953
|
+
spec: deriveSpec(config2, cols, result, meta3, dataLen),
|
|
59954
|
+
verdict: result ? deriveVerdict(result.verdict) : null,
|
|
59955
|
+
answer: result && adj ? deriveAnswer(ranConfig, result, adj, meta3) : null,
|
|
59956
|
+
refusal: result && !adj ? deriveRefusal(ranConfig, result) : null,
|
|
59957
|
+
overlap: result ? deriveOverlap(result.overlap) : null,
|
|
59958
|
+
refute: refutation ? deriveRefute(refutation, adj) : null,
|
|
59959
|
+
dose: doseR && doseR.effect.length ? deriveDose(doseR, ranConfig, meta3) : null,
|
|
59449
59960
|
journal: journal ? journal.map((r2) => deriveJournalRow(r2, now2)) : null
|
|
59450
59961
|
};
|
|
59451
59962
|
}
|
|
59963
|
+
const DESIGN_HEADLINE = {
|
|
59964
|
+
detect_observed: "Confirm it with a controlled trial",
|
|
59965
|
+
de_bias: "Make it trustworthy with a controlled trial",
|
|
59966
|
+
resolve_vs_null: "Settle it with a bigger trial",
|
|
59967
|
+
restrict_to_overlap: "You can’t compare yet",
|
|
59968
|
+
create_control: "Hold some back next time"
|
|
59969
|
+
};
|
|
59970
|
+
function deriveDesignOption(o2) {
|
|
59971
|
+
return {
|
|
59972
|
+
label: o2.label,
|
|
59973
|
+
nTotal: Number(o2.n_total),
|
|
59974
|
+
nTreated: Number(o2.n_treated),
|
|
59975
|
+
nControl: Number(o2.n_control),
|
|
59976
|
+
treatedShare: o2.treated_share
|
|
59977
|
+
};
|
|
59978
|
+
}
|
|
59979
|
+
function deriveDesign(d2, result, ranConfig, meta3) {
|
|
59980
|
+
const basis = d2.basis.type;
|
|
59981
|
+
const outcome = ranConfig.outcome;
|
|
59982
|
+
const unit2 = unitOf(meta3, outcome);
|
|
59983
|
+
const options2 = d2.options.map(deriveDesignOption);
|
|
59984
|
+
const primary = options2[0] ?? { label: "Even split", nTotal: 0, nTreated: 0, nControl: 0, treatedShare: 0.5 };
|
|
59985
|
+
const balByCol = /* @__PURE__ */ new Map();
|
|
59986
|
+
for (const b2 of (result == null ? void 0 : result.balance) ?? []) {
|
|
59987
|
+
const prev2 = balByCol.get(b2.base_column);
|
|
59988
|
+
if (!prev2 || Math.abs(b2.std_diff) > Math.abs(prev2.std_diff)) balByCol.set(b2.base_column, b2);
|
|
59989
|
+
}
|
|
59990
|
+
const matchOn = d2.match_on.map((col) => {
|
|
59991
|
+
var _a2;
|
|
59992
|
+
const b2 = balByCol.get(col);
|
|
59993
|
+
const absStd = b2 ? Math.abs(b2.std_diff) : 0;
|
|
59994
|
+
const { tone } = band(absStd);
|
|
59995
|
+
return { col, frac: clamp01(absStd), tone, display: getOpt((_a2 = colMeta(meta3, col)) == null ? void 0 : _a2.label) ?? col };
|
|
59996
|
+
});
|
|
59997
|
+
const ns = arrI(d2.power_curve.n);
|
|
59998
|
+
const powers = arr(d2.power_curve.power);
|
|
59999
|
+
const len = Math.min(ns.length, powers.length);
|
|
60000
|
+
const nearest = (target) => {
|
|
60001
|
+
let bi = 0, bd = Infinity;
|
|
60002
|
+
for (let i = 0; i < len; i++) {
|
|
60003
|
+
const dd = Math.abs(ns[i] - target);
|
|
60004
|
+
if (dd < bd) {
|
|
60005
|
+
bd = dd;
|
|
60006
|
+
bi = i;
|
|
60007
|
+
}
|
|
60008
|
+
}
|
|
60009
|
+
return bi;
|
|
60010
|
+
};
|
|
60011
|
+
const xTicks = len ? [String(ns[0]), String(ns[Math.floor((len - 1) / 2)]), String(ns[len - 1])] : [];
|
|
60012
|
+
const marks = [];
|
|
60013
|
+
if (len) marks.push({ at: nearest(primary.nTotal), label: `target ${(d2.target_power * 100).toFixed(0)}%`, tone: "pos", help: "validate_power" });
|
|
60014
|
+
const cp = getOpt(d2.current_power);
|
|
60015
|
+
if (cp !== void 0 && len) {
|
|
60016
|
+
const curN = result ? Number(result.n_total) : 0;
|
|
60017
|
+
if (curN > 0) {
|
|
60018
|
+
const offScale = curN >= (ns[len - 1] ?? 0);
|
|
60019
|
+
marks.push({
|
|
60020
|
+
at: offScale ? len - 1 : nearest(curN),
|
|
60021
|
+
label: offScale ? `you’re here · ${(cp * 100).toFixed(0)}% (off-scale)` : `you’re here ${(cp * 100).toFixed(0)}%`,
|
|
60022
|
+
tone: "muted",
|
|
60023
|
+
help: "validate_power"
|
|
60024
|
+
});
|
|
60025
|
+
}
|
|
60026
|
+
}
|
|
60027
|
+
const faint = d2.outcome_sd > 0 && Math.abs(d2.target_effect) / d2.outcome_sd <= 0.011;
|
|
60028
|
+
return {
|
|
60029
|
+
headline: DESIGN_HEADLINE[basis] ?? "Validate this",
|
|
60030
|
+
rationale: d2.rationale,
|
|
60031
|
+
primary,
|
|
60032
|
+
alternates: options2.slice(1),
|
|
60033
|
+
matchOn,
|
|
60034
|
+
curve: { mid: powers.slice(0, len).map((p2) => p2 * 100), xTicks, marks },
|
|
60035
|
+
targetLabel: `${signed(d2.target_effect)}${unit2 ? ` ${unit2}` : ""}`,
|
|
60036
|
+
targetPctLabel: `${(d2.target_power * 100).toFixed(0)}%`,
|
|
60037
|
+
holdback: basis === "create_control",
|
|
60038
|
+
showOverlap: basis === "restrict_to_overlap",
|
|
60039
|
+
faint
|
|
60040
|
+
};
|
|
60041
|
+
}
|
|
60042
|
+
const SUBJECT_ONE = "record";
|
|
60043
|
+
const SUBJECT_MANY = "records";
|
|
60044
|
+
const AUTORUN_MAX_ROWS = 5e4;
|
|
59452
60045
|
const TONE_TOKEN = {
|
|
59453
60046
|
neg: "fg.danger",
|
|
59454
60047
|
pos: "fg.success",
|
|
@@ -59486,13 +60079,73 @@ function useColumns(workspace, source) {
|
|
|
59486
60079
|
};
|
|
59487
60080
|
}, [types == null ? void 0 : types.sourceType]);
|
|
59488
60081
|
}
|
|
59489
|
-
|
|
59490
|
-
|
|
59491
|
-
|
|
60082
|
+
function rowMatchesAll(row, preds) {
|
|
60083
|
+
return preds.every((p2) => {
|
|
60084
|
+
const { fieldId, op } = p2.value;
|
|
60085
|
+
const v3 = row[fieldId];
|
|
60086
|
+
const ov = op.value;
|
|
60087
|
+
switch (op.type) {
|
|
60088
|
+
case "eq":
|
|
60089
|
+
case "is":
|
|
60090
|
+
return v3 === ov;
|
|
60091
|
+
case "neq":
|
|
60092
|
+
return v3 !== ov;
|
|
60093
|
+
case "lt":
|
|
60094
|
+
return num(v3) != null && num(ov) != null ? num(v3) < num(ov) : true;
|
|
60095
|
+
case "lte":
|
|
60096
|
+
return num(v3) != null && num(ov) != null ? num(v3) <= num(ov) : true;
|
|
60097
|
+
case "gt":
|
|
60098
|
+
return num(v3) != null && num(ov) != null ? num(v3) > num(ov) : true;
|
|
60099
|
+
case "gte":
|
|
60100
|
+
return num(v3) != null && num(ov) != null ? num(v3) >= num(ov) : true;
|
|
60101
|
+
case "in":
|
|
60102
|
+
return ov instanceof Set ? ov.has(v3) : true;
|
|
60103
|
+
case "notIn":
|
|
60104
|
+
return ov instanceof Set ? !ov.has(v3) : true;
|
|
60105
|
+
default:
|
|
60106
|
+
return true;
|
|
60107
|
+
}
|
|
60108
|
+
});
|
|
60109
|
+
}
|
|
60110
|
+
const num = (x2) => typeof x2 === "number" ? x2 : typeof x2 === "bigint" ? Number(x2) : null;
|
|
60111
|
+
function Cap({ children: children2, help }) {
|
|
60112
|
+
return /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption.eyebrow", fontSize: "9px", mb: "2.5", children: help ? /* @__PURE__ */ jsxRuntime.jsx(Help, { id: help, children: children2 }) : children2 });
|
|
59492
60113
|
}
|
|
59493
60114
|
function Card({ children: children2, mt = "3" }) {
|
|
59494
60115
|
return /* @__PURE__ */ jsxRuntime.jsx(react.Box, { layerStyle: "card", p: "3.5", borderRadius: "lg", mt, children: children2 });
|
|
59495
60116
|
}
|
|
60117
|
+
function RunError({ error: error3 }) {
|
|
60118
|
+
const tail = (error3.stderr || error3.stdout || "").trim().split("\n").slice(-12).join("\n");
|
|
60119
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { layerStyle: "banner.stale", display: "flex", flexDirection: "column", gap: "2", mt: "3", children: [
|
|
60120
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "inline-flex", alignItems: "flex-start", gap: "2", color: "fg.danger", children: [
|
|
60121
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", mt: "0.5", fontSize: "12px", flexShrink: "0", children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faTriangleExclamation }) }),
|
|
60122
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "body.sm", color: "fg.default", children: [
|
|
60123
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontWeight: "bold", children: "Could not run the experiment." }),
|
|
60124
|
+
" ",
|
|
60125
|
+
error3.message
|
|
60126
|
+
] })
|
|
60127
|
+
] }),
|
|
60128
|
+
tail && /* @__PURE__ */ jsxRuntime.jsx(
|
|
60129
|
+
react.Box,
|
|
60130
|
+
{
|
|
60131
|
+
as: "pre",
|
|
60132
|
+
m: "0",
|
|
60133
|
+
p: "2",
|
|
60134
|
+
maxH: "160px",
|
|
60135
|
+
overflow: "auto",
|
|
60136
|
+
bg: "bg.canvas",
|
|
60137
|
+
borderRadius: "md",
|
|
60138
|
+
borderWidth: "1px",
|
|
60139
|
+
borderColor: "border.subtle",
|
|
60140
|
+
fontFamily: "mono",
|
|
60141
|
+
fontSize: "11px",
|
|
60142
|
+
color: "fg.muted",
|
|
60143
|
+
whiteSpace: "pre-wrap",
|
|
60144
|
+
children: tail
|
|
60145
|
+
}
|
|
60146
|
+
)
|
|
60147
|
+
] });
|
|
60148
|
+
}
|
|
59496
60149
|
function ActionButton({ button, variant: variant2, label, onClick, disabled: disabled2, pulse = false }) {
|
|
59497
60150
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
59498
60151
|
react.Box,
|
|
@@ -59508,8 +60161,46 @@ function ActionButton({ button, variant: variant2, label, onClick, disabled: dis
|
|
|
59508
60161
|
}
|
|
59509
60162
|
);
|
|
59510
60163
|
}
|
|
60164
|
+
function DeckSkeleton({ tab }) {
|
|
60165
|
+
const sk = react.useRecipe({ key: "skeleton" });
|
|
60166
|
+
const bar = (w2, h2) => /* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: sk({ variant: "line" }), width: w2, height: h2 });
|
|
60167
|
+
const block = (h2) => /* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: sk({ variant: "block" }), width: "100%", minHeight: h2 });
|
|
60168
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", display: "flex", flexDirection: "column", gap: "4", children: [
|
|
60169
|
+
tab === "answer" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
60170
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", gap: "4.5", alignItems: "flex-end", children: [
|
|
60171
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", flexDirection: "column", gap: "2", children: [
|
|
60172
|
+
bar("64px", "10px"),
|
|
60173
|
+
bar("120px", "32px")
|
|
60174
|
+
] }),
|
|
60175
|
+
bar("170px", "24px")
|
|
60176
|
+
] }),
|
|
60177
|
+
block("120px"),
|
|
60178
|
+
block("120px")
|
|
60179
|
+
] }),
|
|
60180
|
+
tab === "trust" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
60181
|
+
bar("70%", "14px"),
|
|
60182
|
+
block("160px")
|
|
60183
|
+
] }),
|
|
60184
|
+
tab === "dose" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
60185
|
+
block("240px"),
|
|
60186
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "3", children: [
|
|
60187
|
+
block("90px"),
|
|
60188
|
+
block("90px")
|
|
60189
|
+
] })
|
|
60190
|
+
] }),
|
|
60191
|
+
tab === "validate" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
60192
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", gap: "5", alignItems: "flex-end", children: [
|
|
60193
|
+
bar("120px", "32px"),
|
|
60194
|
+
bar("200px", "10px")
|
|
60195
|
+
] }),
|
|
60196
|
+
block("110px"),
|
|
60197
|
+
block("150px")
|
|
60198
|
+
] })
|
|
60199
|
+
] });
|
|
60200
|
+
}
|
|
60201
|
+
const experimentValueEqual = east.equalFor(internal.Experiment.Component.schema);
|
|
59511
60202
|
const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }) {
|
|
59512
|
-
var _a2, _b2, _c2;
|
|
60203
|
+
var _a2, _b2, _c2, _d2;
|
|
59513
60204
|
const v3 = value;
|
|
59514
60205
|
const button = react.useRecipe({ key: "button" });
|
|
59515
60206
|
const chip = react.useRecipe({ key: "chip" });
|
|
@@ -59519,12 +60210,19 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
|
|
|
59519
60210
|
const es = react.useSlotRecipe({ key: "eyebrowRow" })({});
|
|
59520
60211
|
const workspace = getReactiveDatasetCache().getConfig().workspace ?? "";
|
|
59521
60212
|
const data4 = useBindingValue(v3.data);
|
|
59522
|
-
const
|
|
60213
|
+
const configBind = useBindingValue(v3.config);
|
|
59523
60214
|
const journalBind = useBindingValue(v3.journal.type === "some" ? v3.journal.value : null);
|
|
60215
|
+
const populationBind = useBindingValue(v3.population.type === "some" ? v3.population.value : null);
|
|
59524
60216
|
const meta3 = getOpt(v3.columnMeta);
|
|
59525
60217
|
const readonly = getOpt(v3.readonly) ?? false;
|
|
59526
60218
|
const { columns, rowArrayType } = useColumns(workspace, v3.data.source);
|
|
59527
|
-
const
|
|
60219
|
+
const config2 = configBind.value;
|
|
60220
|
+
const [localPop, setLocalPop] = React.useState([]);
|
|
60221
|
+
const population = populationBind.value ?? localPop;
|
|
60222
|
+
const filteredRows = React.useMemo(() => {
|
|
60223
|
+
if (!data4.value) return null;
|
|
60224
|
+
return population.length ? data4.value.filter((r2) => rowMatchesAll(r2, population)) : data4.value;
|
|
60225
|
+
}, [data4.value, population]);
|
|
59528
60226
|
const fields = React.useMemo(
|
|
59529
60227
|
() => columns.filter((c2) => c2.kind !== "other").map((c2) => {
|
|
59530
60228
|
var _a3;
|
|
@@ -59536,132 +60234,176 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
|
|
|
59536
60234
|
}),
|
|
59537
60235
|
[columns, meta3]
|
|
59538
60236
|
);
|
|
59539
|
-
const estInputs = React.useMemo(() => rowArrayType ? [rowArrayType, internal.Experiment.Types.
|
|
59540
|
-
const
|
|
59541
|
-
const
|
|
59542
|
-
const
|
|
59543
|
-
|
|
59544
|
-
|
|
59545
|
-
|
|
59546
|
-
|
|
59547
|
-
|
|
59548
|
-
|
|
60237
|
+
const estInputs = React.useMemo(() => rowArrayType ? [rowArrayType, internal.Experiment.Types.Config] : null, [rowArrayType]);
|
|
60238
|
+
const experiment = useFuncCall(v3.experiment.name, estInputs, internal.Experiment.Types.Result);
|
|
60239
|
+
const hasDesign = v3.design.type === "some";
|
|
60240
|
+
const designInputs = React.useMemo(
|
|
60241
|
+
() => rowArrayType ? [rowArrayType, internal.Experiment.Types.Config, internal.Experiment.Types.Result, internal.Experiment.Types.DesignConfig] : null,
|
|
60242
|
+
[rowArrayType]
|
|
60243
|
+
);
|
|
60244
|
+
const design = useFuncCall(v3.design.type === "some" ? v3.design.value.name : null, designInputs, internal.Experiment.Types.Design);
|
|
60245
|
+
const pendingConfigRef = React.useRef(null);
|
|
60246
|
+
const [ranConfig, setRanConfig] = React.useState(null);
|
|
59549
60247
|
const runAll = React.useCallback(() => {
|
|
59550
|
-
if (!
|
|
59551
|
-
|
|
59552
|
-
|
|
59553
|
-
|
|
59554
|
-
}, [data4.value, spec, estimate, refute, dose, doseFeature, v3.refute.type, v3.dose.type]);
|
|
60248
|
+
if (!filteredRows || !config2) return;
|
|
60249
|
+
pendingConfigRef.current = config2;
|
|
60250
|
+
experiment.call(filteredRows, config2);
|
|
60251
|
+
}, [filteredRows, config2, experiment]);
|
|
59555
60252
|
const autoRan = React.useRef(false);
|
|
59556
60253
|
React.useEffect(() => {
|
|
59557
60254
|
if (autoRan.current) return;
|
|
59558
|
-
if (!
|
|
59559
|
-
if (
|
|
60255
|
+
if (!filteredRows || !config2 || !rowArrayType) return;
|
|
60256
|
+
if (experiment.result !== null || experiment.status === "running") {
|
|
59560
60257
|
autoRan.current = true;
|
|
59561
60258
|
return;
|
|
59562
60259
|
}
|
|
59563
|
-
if (
|
|
60260
|
+
if (experiment.status !== "idle") return;
|
|
59564
60261
|
autoRan.current = true;
|
|
60262
|
+
if (filteredRows.length > AUTORUN_MAX_ROWS) return;
|
|
59565
60263
|
runAll();
|
|
59566
|
-
}, [
|
|
60264
|
+
}, [filteredRows, config2, rowArrayType, experiment.result, experiment.status, runAll]);
|
|
60265
|
+
React.useEffect(() => {
|
|
60266
|
+
if (experiment.result !== null) setRanConfig(pendingConfigRef.current);
|
|
60267
|
+
}, [experiment.result]);
|
|
59567
60268
|
const [stale, setStale] = React.useState(false);
|
|
59568
|
-
const
|
|
60269
|
+
const editConfig = React.useCallback((next2) => {
|
|
60270
|
+
if (readonly) return;
|
|
60271
|
+
setStale(true);
|
|
60272
|
+
queueMicrotask(() => configBind.mutate(next2));
|
|
60273
|
+
}, [readonly, configBind]);
|
|
60274
|
+
const editPopulation = React.useCallback((next2) => {
|
|
60275
|
+
if (readonly) return;
|
|
59569
60276
|
setStale(true);
|
|
59570
|
-
queueMicrotask(() =>
|
|
59571
|
-
|
|
60277
|
+
if (v3.population.type === "some") queueMicrotask(() => populationBind.mutate(next2));
|
|
60278
|
+
else setLocalPop(next2);
|
|
60279
|
+
}, [readonly, v3.population.type, populationBind]);
|
|
59572
60280
|
const onRun = React.useCallback(() => {
|
|
59573
60281
|
runAll();
|
|
59574
60282
|
setStale(false);
|
|
59575
60283
|
}, [runAll]);
|
|
59576
60284
|
const onCommit = React.useCallback(async () => {
|
|
59577
|
-
if (!
|
|
59578
|
-
const
|
|
60285
|
+
if (!config2 || !experiment.result) return;
|
|
60286
|
+
const r2 = experiment.result;
|
|
60287
|
+
const row = {
|
|
60288
|
+
config: config2,
|
|
60289
|
+
verdict: r2.verdict,
|
|
60290
|
+
naive: r2.naive,
|
|
60291
|
+
adjusted: r2.adjusted.type === "some" ? east.some(r2.adjusted.value.effect) : east.none,
|
|
60292
|
+
committed_at: /* @__PURE__ */ new Date(),
|
|
60293
|
+
committed_by: "you"
|
|
60294
|
+
};
|
|
59579
60295
|
journalBind.mutate([row, ...journalBind.value ?? []]);
|
|
59580
60296
|
try {
|
|
59581
60297
|
await journalBind.commit();
|
|
59582
|
-
await
|
|
60298
|
+
await configBind.commit();
|
|
60299
|
+
if (v3.population.type === "some") await populationBind.commit();
|
|
59583
60300
|
setStale(false);
|
|
59584
60301
|
} catch {
|
|
59585
60302
|
}
|
|
59586
|
-
}, [
|
|
60303
|
+
}, [config2, experiment.result, journalBind, configBind, populationBind, v3.population.type]);
|
|
59587
60304
|
const [tab, setTab] = React.useState(((_a2 = getOpt(v3.defaultTab)) == null ? void 0 : _a2.type) ?? "answer");
|
|
60305
|
+
const [guidance, setGuidance] = React.useState(true);
|
|
59588
60306
|
const now2 = React.useMemo(() => /* @__PURE__ */ new Date(), []);
|
|
59589
|
-
const nRows = (
|
|
60307
|
+
const nRows = (filteredRows == null ? void 0 : filteredRows.length) ?? 0;
|
|
60308
|
+
const helpVars = React.useMemo(() => {
|
|
60309
|
+
const rc = ranConfig ?? config2;
|
|
60310
|
+
const labelOf = (col) => {
|
|
60311
|
+
var _a3;
|
|
60312
|
+
return col ? getOpt((_a3 = meta3 == null ? void 0 : meta3.get(col)) == null ? void 0 : _a3.label) ?? col : "";
|
|
60313
|
+
};
|
|
60314
|
+
return { treatment: labelOf(rc == null ? void 0 : rc.treatment), outcome: labelOf(rc == null ? void 0 : rc.outcome), subject: SUBJECT_ONE, subjects: SUBJECT_MANY };
|
|
60315
|
+
}, [ranConfig, config2, meta3]);
|
|
59590
60316
|
const view = React.useMemo(() => {
|
|
59591
|
-
if (!
|
|
59592
|
-
return deriveView(
|
|
59593
|
-
}, [
|
|
59594
|
-
|
|
59595
|
-
|
|
59596
|
-
|
|
59597
|
-
|
|
59598
|
-
|
|
59599
|
-
|
|
59600
|
-
const
|
|
60317
|
+
if (!config2) return null;
|
|
60318
|
+
return deriveView(config2, ranConfig ?? config2, columns, experiment.result, journalBind.value, meta3, nRows, now2);
|
|
60319
|
+
}, [config2, ranConfig, columns, experiment.result, journalBind.value, meta3, nRows, now2]);
|
|
60320
|
+
const designConfigValue = React.useMemo(() => ({
|
|
60321
|
+
alpha: east.none,
|
|
60322
|
+
target_power: east.none,
|
|
60323
|
+
materiality: east.none,
|
|
60324
|
+
treated_shares: east.some([0.5, 0.3])
|
|
60325
|
+
}), []);
|
|
60326
|
+
const designSnapRef = React.useRef(null);
|
|
60327
|
+
React.useEffect(() => {
|
|
60328
|
+
var _a3;
|
|
60329
|
+
if (tab !== "validate" || !hasDesign || design.pending) return;
|
|
60330
|
+
if (!filteredRows || !config2 || !experiment.result) return;
|
|
60331
|
+
if (((_a3 = designSnapRef.current) == null ? void 0 : _a3.result) === experiment.result) return;
|
|
60332
|
+
designSnapRef.current = { result: experiment.result, config: ranConfig ?? config2 };
|
|
60333
|
+
design.call(filteredRows, ranConfig ?? config2, experiment.result, designConfigValue);
|
|
60334
|
+
}, [tab, hasDesign, filteredRows, config2, ranConfig, experiment.result, design, designConfigValue]);
|
|
60335
|
+
const vmDesign = React.useMemo(
|
|
60336
|
+
() => design.result && designSnapRef.current ? deriveDesign(design.result, designSnapRef.current.result, designSnapRef.current.config, meta3) : null,
|
|
60337
|
+
[design.result, meta3]
|
|
60338
|
+
);
|
|
60339
|
+
const designFresh = design.result !== null && ((_b2 = designSnapRef.current) == null ? void 0 : _b2.result) === experiment.result && !design.pending;
|
|
60340
|
+
if (!config2 || !view) {
|
|
60341
|
+
const failed2 = experiment.status === "failed";
|
|
60342
|
+
return /* @__PURE__ */ jsxRuntime.jsx(react.Box, { layerStyle: "frame", p: "6", children: failed2 && experiment.error ? /* @__PURE__ */ jsxRuntime.jsx(RunError, { error: experiment.error }) : /* @__PURE__ */ jsxRuntime.jsx(react.Text, { className: failed2 ? void 0 : "elara-skeleton", textStyle: "body.sm", color: "fg.muted", children: failed2 ? "Could not run the experiment." : "Loading experiment…" }) });
|
|
60343
|
+
}
|
|
60344
|
+
const { spec: vs, answer: a2, refusal: ref, overlap: ov, refute: vr, dose: vd, journal, verdict } = view;
|
|
59601
60345
|
const higherBetter = getOpt((_c2 = meta3 == null ? void 0 : meta3.get(vs.outcome)) == null ? void 0 : _c2.higherIsBetter);
|
|
59602
|
-
const dirUp = ans.effect > 0;
|
|
59603
|
-
const statusWord = higherBetter === void 0 ? dirUp ? "Higher" : "Lower" : dirUp === higherBetter ? "Better" : "Worse";
|
|
59604
|
-
const flip = Math.sign(ans.naive) !== Math.sign(ans.effect) && ans.naive !== 0;
|
|
59605
|
-
const top = ans.balance[0] ?? { col: "", display: "" };
|
|
59606
|
-
const lowerWord = ans.naive < 0 ? "lower" : "higher";
|
|
59607
|
-
const population = getOpt(spec.population) ?? [];
|
|
59608
60346
|
const dataStatus = statusR({ status: "success", size: "sm" });
|
|
59609
60347
|
const barList = (rows) => /* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: bs.root, children: rows.map((r2, i) => /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { css: bs.row, children: [
|
|
59610
60348
|
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { css: bs.label, textStyle: "mono.sm", color: "fg.default", truncate: true, children: r2.label }),
|
|
59611
60349
|
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: bs.track, children: /* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: bs.fill, width: `${Math.round(r2.frac * 100)}%`, bg: toneToken(r2.tone) }) }),
|
|
59612
60350
|
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { css: bs.value, children: r2.value })
|
|
59613
60351
|
] }, i)) });
|
|
59614
|
-
const specStaged =
|
|
60352
|
+
const specStaged = configBind.mode === "staged";
|
|
59615
60353
|
const hasJournal = v3.journal.type === "some";
|
|
59616
60354
|
const canRun = !readonly;
|
|
59617
60355
|
const canCommit = !readonly && specStaged && hasJournal;
|
|
59618
|
-
const runDisabled = !data4.value ||
|
|
59619
|
-
const commitDisabled =
|
|
59620
|
-
|
|
60356
|
+
const runDisabled = !data4.value || experiment.pending;
|
|
60357
|
+
const commitDisabled = experiment.status !== "succeeded" || experiment.pending || stale;
|
|
60358
|
+
const failed = experiment.status === "failed" && experiment.error;
|
|
60359
|
+
const showResult = experiment.result !== null && !experiment.pending && !failed;
|
|
60360
|
+
const tabKeys = [...["answer", "trust", "dose"], ...hasDesign ? ["validate"] : []];
|
|
60361
|
+
return /* @__PURE__ */ jsxRuntime.jsx(GuidanceProvider, { on: guidance, vars: helpVars, children: /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { layerStyle: "frame", overflow: "visible", children: [
|
|
59621
60362
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { layerStyle: "header.bar", display: "flex", alignItems: "center", gap: "3.5", children: [
|
|
59622
|
-
/* @__PURE__ */ jsxRuntime.
|
|
59623
|
-
"Does
|
|
59624
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "brand.
|
|
59625
|
-
"
|
|
59626
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "brand.
|
|
60363
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "title.card", color: "fg.muted", children: /* @__PURE__ */ jsxRuntime.jsxs(Help, { id: "header", children: [
|
|
60364
|
+
"Does ",
|
|
60365
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "brand.solid", fontWeight: "bold", children: vs.treatment }),
|
|
60366
|
+
" change ",
|
|
60367
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "brand.solid", fontWeight: "bold", children: vs.outcome }),
|
|
59627
60368
|
"?"
|
|
59628
|
-
] }),
|
|
60369
|
+
] }) }),
|
|
59629
60370
|
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { flex: "1" }),
|
|
59630
60371
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { as: "span", css: dataStatus.root, children: [
|
|
59631
60372
|
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", css: dataStatus.indicator }),
|
|
59632
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", css: dataStatus.label, children: vs.dataLabel })
|
|
60373
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", css: dataStatus.label, children: stale ? `${nRows} rows` : vs.dataLabel })
|
|
59633
60374
|
] }),
|
|
60375
|
+
/* @__PURE__ */ jsxRuntime.jsx(GuidanceToggle, { on: guidance, onToggle: () => setGuidance((g2) => !g2) }),
|
|
59634
60376
|
canRun && /* @__PURE__ */ jsxRuntime.jsx(ActionButton, { button, variant: "solid", label: "Run", onClick: onRun, disabled: runDisabled, pulse: stale }),
|
|
59635
60377
|
canCommit && /* @__PURE__ */ jsxRuntime.jsx(ActionButton, { button, variant: "ghost", label: "Commit", onClick: onCommit, disabled: commitDisabled })
|
|
59636
60378
|
] }),
|
|
59637
60379
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "grid", gridTemplateColumns: "304px minmax(0,1fr)", alignItems: "start", children: [
|
|
59638
60380
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { borderRightWidth: "1px", borderColor: "border.subtle", children: [
|
|
59639
|
-
/* @__PURE__ */ jsxRuntime.jsxs(Step, { n: 1, title: "What did you change?", children: [
|
|
59640
|
-
/* @__PURE__ */ jsxRuntime.jsx(ColumnPick, { column: vs.treatment, kind: vs.treatmentKind, badge, button, choices: columns.filter((c2) => c2.kind === "boolean" || c2.kind === "integer").map((c2) => c2.name), onPick: (c2) =>
|
|
60381
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Step, { n: 1, title: "What did you change?", help: "step_treatment", children: [
|
|
60382
|
+
/* @__PURE__ */ jsxRuntime.jsx(ColumnPick, { column: vs.treatment, kind: vs.treatmentKind, badge, button, choices: columns.filter((c2) => c2.kind === "boolean" || c2.kind === "integer").map((c2) => c2.name), onPick: (c2) => editConfig({ ...config2, treatment: c2 }) }),
|
|
59641
60383
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "caption", mt: "1.5", children: [
|
|
59642
60384
|
"Treated = ",
|
|
59643
60385
|
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "fg.default", fontWeight: "semibold", children: vs.comparison })
|
|
59644
60386
|
] })
|
|
59645
60387
|
] }),
|
|
59646
|
-
/* @__PURE__ */ jsxRuntime.jsx(Step, { n: 2, title: "What did you want it to improve?", children: /* @__PURE__ */ jsxRuntime.jsx(ColumnPick, { column: vs.outcome, kind: vs.outcomeKind, badge, button, choices: columns.filter((c2) => c2.kind === "float" || c2.kind === "integer").map((c2) => c2.name), onPick: (c2) =>
|
|
59647
|
-
/* @__PURE__ */ jsxRuntime.jsx(Step, { n: 3, title: "What else was different
|
|
59648
|
-
vs.confounders.map((c2, i) => /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "grid", gridTemplateColumns: "1fr 78px 16px", gap: "2.5", alignItems: "center", px: "2.5", py: "2.5", borderTopWidth: i ? "1px" : "0", borderColor: "border.subtle", children: [
|
|
60388
|
+
/* @__PURE__ */ jsxRuntime.jsx(Step, { n: 2, title: "What did you want it to improve?", help: "step_outcome", children: /* @__PURE__ */ jsxRuntime.jsx(ColumnPick, { column: vs.outcome, kind: vs.outcomeKind, badge, button, choices: columns.filter((c2) => c2.kind === "float" || c2.kind === "integer").map((c2) => c2.name), onPick: (c2) => editConfig({ ...config2, outcome: c2 }) }) }),
|
|
60389
|
+
/* @__PURE__ */ jsxRuntime.jsx(Step, { n: 3, title: "What else was different?", help: "step_confounders", children: /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { layerStyle: "frame.flat", children: [
|
|
60390
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { maxH: "216px", overflowY: "auto", children: vs.confounders.map((c2, i) => /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "grid", gridTemplateColumns: "1fr 78px 16px", gap: "2.5", alignItems: "center", px: "2.5", py: "2.5", borderTopWidth: i ? "1px" : "0", borderColor: "border.subtle", children: [
|
|
59649
60391
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { children: [
|
|
59650
60392
|
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono.sm", fontWeight: "semibold", color: "fg.default", children: c2.col }),
|
|
59651
60393
|
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption", lineHeight: "1.35", mt: "px", children: c2.reason })
|
|
59652
60394
|
] }),
|
|
59653
60395
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { children: [
|
|
59654
60396
|
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: bs.track, children: /* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: bs.fill, width: `${Math.round(c2.imbalance * 100)}%`, bg: toneToken(c2.tone) }) }),
|
|
59655
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption.eyebrow", fontSize: "9px", textAlign: "center", mt: "1.5", children: c2.level })
|
|
60397
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption.eyebrow", fontSize: "9px", textAlign: "center", mt: "1.5", children: /* @__PURE__ */ jsxRuntime.jsx(Help, { id: "confounder_imbalance", children: c2.level }) })
|
|
59656
60398
|
] }),
|
|
59657
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "button", css: button({ variant: "ghost", size: "xs" }), px: "0", minW: "16px", display: "inline-flex", alignItems: "center", justifyContent: "center", "aria-label": `Remove ${c2.col}`, onClick: () =>
|
|
59658
|
-
] }, i)),
|
|
59659
|
-
vs.suggestion && /* @__PURE__ */ jsxRuntime.jsx(ColumnMenu, { choices: columns.filter((c2) => !(/* @__PURE__ */ new Set([
|
|
60399
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "button", css: button({ variant: "ghost", size: "xs" }), px: "0", minW: "16px", display: "inline-flex", alignItems: "center", justifyContent: "center", "aria-label": `Remove ${c2.col}`, onClick: () => editConfig({ ...config2, common_causes: config2.common_causes.filter((x2) => x2 !== c2.col), categorical: config2.categorical.type === "some" ? east.some(config2.categorical.value.filter((x2) => x2 !== c2.col)) : east.none }), children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faXmark, style: { fontSize: "11px" } }) })
|
|
60400
|
+
] }, i)) }),
|
|
60401
|
+
vs.suggestion && /* @__PURE__ */ jsxRuntime.jsx(ColumnMenu, { choices: columns.filter((c2) => !(/* @__PURE__ */ new Set([config2.treatment, config2.outcome, ...config2.common_causes])).has(c2.name)).map((c2) => c2.name), onPick: (c2) => editConfig({ ...config2, common_causes: [...config2.common_causes, c2] }), children: /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { as: "button", css: button({ variant: "ghost", size: "sm" }), justifyContent: "flex-start", width: "100%", color: "brand.fg", fontFamily: "mono", display: "inline-flex", alignItems: "center", gap: "2", children: [
|
|
59660
60402
|
/* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faPlus, style: { fontSize: "9px" } }),
|
|
59661
60403
|
"add another"
|
|
59662
60404
|
] }) })
|
|
59663
60405
|
] }) }),
|
|
59664
|
-
/* @__PURE__ */ jsxRuntime.jsx(Step, { n: 4, title:
|
|
60406
|
+
/* @__PURE__ */ jsxRuntime.jsx(Step, { n: 4, title: `Which ${SUBJECT_MANY}?`, help: "step_population", children: /* @__PURE__ */ jsxRuntime.jsx(FilterRail, { fields, population, onChange: editPopulation, chip, button }) }),
|
|
59665
60407
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { as: "details", borderTopWidth: "1px", borderColor: "border.subtle", children: [
|
|
59666
60408
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
59667
60409
|
react.Box,
|
|
@@ -59694,173 +60436,168 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
|
|
|
59694
60436
|
}
|
|
59695
60437
|
),
|
|
59696
60438
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { px: "4.5", pb: "3.5", pt: "0.5", children: [
|
|
59697
|
-
/* @__PURE__ */ jsxRuntime.jsx(Segmented, { label: "How to compare", left: "regression", right: "reweighting", active: vs.method === "reweighting" ? "right" : "left", onPick: (s2) =>
|
|
59698
|
-
/* @__PURE__ */ jsxRuntime.jsx(Segmented, { label: "Answer for", left: "all
|
|
59699
|
-
/* @__PURE__ */ jsxRuntime.jsx(Segmented, { label: "Drop un-matchable", left: "on", right: "off", active: vs.trim ? "left" : "right", onPick: (s2) => editSpec({ ...spec, trim: s2 === "left" ? east.some(east.variant("overlap", null)) : east.none }), last: true })
|
|
60439
|
+
/* @__PURE__ */ jsxRuntime.jsx(Segmented, { label: "How to compare", help: "adv_method", left: "regression", right: "reweighting", active: vs.method === "reweighting" ? "right" : "left", onPick: (s2) => editConfig({ ...config2, method: east.some(s2 === "right" ? east.variant("propensity_score_weighting", { weighting_scheme: east.none }) : east.variant("linear_regression", null)) }) }),
|
|
60440
|
+
/* @__PURE__ */ jsxRuntime.jsx(Segmented, { label: "Answer for", help: "adv_estimand", left: "all", right: "only treated", active: vs.target === "treated" ? "right" : "left", onPick: (s2) => editConfig({ ...config2, estimand: east.some(east.variant(s2 === "right" ? "att" : "ate", null)) }), last: true })
|
|
59700
60441
|
] })
|
|
59701
60442
|
] })
|
|
59702
60443
|
] }),
|
|
59703
60444
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { minW: "0", children: [
|
|
59704
|
-
/* @__PURE__ */ jsxRuntime.
|
|
59705
|
-
|
|
59706
|
-
|
|
59707
|
-
|
|
59708
|
-
|
|
59709
|
-
|
|
59710
|
-
|
|
59711
|
-
|
|
59712
|
-
|
|
59713
|
-
|
|
59714
|
-
|
|
59715
|
-
|
|
59716
|
-
|
|
59717
|
-
|
|
59718
|
-
|
|
59719
|
-
|
|
59720
|
-
|
|
59721
|
-
|
|
60445
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
60446
|
+
react.Box,
|
|
60447
|
+
{
|
|
60448
|
+
display: "flex",
|
|
60449
|
+
alignItems: "center",
|
|
60450
|
+
px: "4",
|
|
60451
|
+
borderBottomWidth: "1px",
|
|
60452
|
+
borderColor: "border.subtle",
|
|
60453
|
+
role: "tablist",
|
|
60454
|
+
"aria-label": "Result views",
|
|
60455
|
+
onKeyDown: (e3) => {
|
|
60456
|
+
var _a3;
|
|
60457
|
+
const i = tabKeys.indexOf(tab);
|
|
60458
|
+
let j2 = i;
|
|
60459
|
+
if (e3.key === "ArrowRight") j2 = (i + 1) % tabKeys.length;
|
|
60460
|
+
else if (e3.key === "ArrowLeft") j2 = (i - 1 + tabKeys.length) % tabKeys.length;
|
|
60461
|
+
else if (e3.key === "Home") j2 = 0;
|
|
60462
|
+
else if (e3.key === "End") j2 = tabKeys.length - 1;
|
|
60463
|
+
else return;
|
|
60464
|
+
e3.preventDefault();
|
|
60465
|
+
setTab(tabKeys[j2]);
|
|
60466
|
+
const btns = e3.currentTarget.querySelectorAll('[role="tab"]');
|
|
60467
|
+
(_a3 = btns[j2]) == null ? void 0 : _a3.focus();
|
|
59722
60468
|
},
|
|
59723
|
-
|
|
59724
|
-
|
|
59725
|
-
|
|
59726
|
-
|
|
59727
|
-
|
|
59728
|
-
|
|
59729
|
-
|
|
59730
|
-
|
|
59731
|
-
|
|
59732
|
-
|
|
59733
|
-
|
|
59734
|
-
|
|
59735
|
-
|
|
59736
|
-
|
|
59737
|
-
|
|
60469
|
+
children: [
|
|
60470
|
+
tabKeys.map((tk) => {
|
|
60471
|
+
const on3 = tab === tk;
|
|
60472
|
+
const tabHelp = tk === "answer" ? "tab_answer" : tk === "trust" ? "tab_trust" : tk === "dose" ? "tab_dose" : "tab_validate";
|
|
60473
|
+
const tabLabel = tk === "answer" ? "Answer" : tk === "trust" ? "Trust" : tk === "dose" ? "Dose" : "Validate";
|
|
60474
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
60475
|
+
react.Box,
|
|
60476
|
+
{
|
|
60477
|
+
as: "button",
|
|
60478
|
+
role: "tab",
|
|
60479
|
+
"aria-selected": on3,
|
|
60480
|
+
tabIndex: on3 ? 0 : -1,
|
|
60481
|
+
onClick: () => setTab(tk),
|
|
60482
|
+
cursor: "pointer",
|
|
60483
|
+
fontSize: "xs",
|
|
60484
|
+
fontWeight: "semibold",
|
|
60485
|
+
px: "3.5",
|
|
60486
|
+
py: "3",
|
|
60487
|
+
mb: "-1px",
|
|
60488
|
+
color: on3 ? "brand.fg" : "fg.muted",
|
|
60489
|
+
borderBottomWidth: "2px",
|
|
60490
|
+
borderColor: on3 ? "brand.solid" : "transparent",
|
|
60491
|
+
_hover: { color: on3 ? "brand.fg" : "fg.default" },
|
|
60492
|
+
_focusVisible: { outline: "2px solid", outlineColor: "brand.solid", outlineOffset: "-2px" },
|
|
60493
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Help, { id: tabHelp, children: tabLabel })
|
|
60494
|
+
},
|
|
60495
|
+
tk
|
|
60496
|
+
);
|
|
60497
|
+
}),
|
|
60498
|
+
experiment.pending && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { ml: "auto", display: "inline-flex", alignItems: "center", gap: "2", color: "fg.muted", pr: "1", children: [
|
|
60499
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Spinner, { size: "xs", borderWidth: "1.5px", color: "brand.solid" }),
|
|
60500
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption.eyebrow", children: "Running…" })
|
|
59738
60501
|
] })
|
|
59739
|
-
]
|
|
59740
|
-
|
|
59741
|
-
|
|
59742
|
-
|
|
59743
|
-
|
|
59744
|
-
|
|
59745
|
-
flip && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { layerStyle: "banner.stale", display: "flex", alignItems: "flex-start", gap: "2", mt: "3", children: [
|
|
59746
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", color: "fg.warning", flexShrink: "0", mt: "0.5", fontSize: "12px", children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faTriangleExclamation }) }),
|
|
60502
|
+
]
|
|
60503
|
+
}
|
|
60504
|
+
),
|
|
60505
|
+
failed ? /* @__PURE__ */ jsxRuntime.jsx(react.Box, { px: "4.5", pt: "4.5", children: /* @__PURE__ */ jsxRuntime.jsx(RunError, { error: experiment.error }) }) : !showResult ? /* @__PURE__ */ jsxRuntime.jsx(DeckSkeleton, { tab }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
60506
|
+
stale && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { layerStyle: "banner.stale", display: "flex", alignItems: "center", gap: "2", mx: "4.5", mt: "4.5", children: [
|
|
60507
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", color: "fg.warning", flexShrink: "0", fontSize: "12px", children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faTriangleExclamation }) }),
|
|
59747
60508
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "body.sm", color: "fg.default", children: [
|
|
59748
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontWeight: "bold", children: "
|
|
59749
|
-
"
|
|
59750
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontFamily: "mono", children: ans.treatment }),
|
|
59751
|
-
" batches sit ",
|
|
59752
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontStyle: "italic", children: lowerWord }),
|
|
59753
|
-
" on ",
|
|
59754
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontFamily: "mono", children: ans.outcome }),
|
|
59755
|
-
" (",
|
|
59756
|
-
signed(ans.naive),
|
|
59757
|
-
") — but they also differ most on ",
|
|
59758
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontFamily: "mono", children: top.col }),
|
|
59759
|
-
" (",
|
|
59760
|
-
top.display,
|
|
59761
|
-
"). Adjusting for it reverses the result."
|
|
60509
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontWeight: "bold", children: "Showing the previous setup." }),
|
|
60510
|
+
" Hit Run to update these results for your edits."
|
|
59762
60511
|
] })
|
|
59763
60512
|
] }),
|
|
59764
|
-
/* @__PURE__ */ jsxRuntime.
|
|
59765
|
-
|
|
59766
|
-
|
|
59767
|
-
|
|
59768
|
-
|
|
59769
|
-
|
|
59770
|
-
|
|
59771
|
-
|
|
59772
|
-
|
|
59773
|
-
|
|
59774
|
-
|
|
59775
|
-
|
|
59776
|
-
|
|
59777
|
-
|
|
59778
|
-
|
|
59779
|
-
|
|
59780
|
-
|
|
59781
|
-
|
|
59782
|
-
/* @__PURE__ */ jsxRuntime.
|
|
59783
|
-
|
|
59784
|
-
|
|
59785
|
-
|
|
59786
|
-
" ",
|
|
59787
|
-
label
|
|
59788
|
-
] }, i)) })
|
|
59789
|
-
] }),
|
|
59790
|
-
tab === "trust" && vr && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
|
|
59791
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", color: "fg.muted", mb: "3.5", children: "Before trusting the answer we tried to break it — colour shows pass / caution." }),
|
|
59792
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { layerStyle: "frame.flat", children: vr.checks.map((c2, i) => {
|
|
59793
|
-
const cs = statusR({ status: c2.passed ? "success" : "warning", size: "sm" });
|
|
59794
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "grid", gridTemplateColumns: "auto 1fr minmax(96px,auto)", gap: "2.5", alignItems: "start", px: "3.5", py: "2.5", borderTopWidth: i ? "1px" : "0", borderColor: "border.subtle", children: [
|
|
59795
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", css: cs.root, mt: "1", children: /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", css: cs.indicator }) }),
|
|
59796
|
-
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { children: [
|
|
59797
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", fontWeight: "semibold", color: "fg.default", children: c2.name }),
|
|
59798
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption", lineHeight: "1.45", mt: "0.5", children: c2.desc })
|
|
59799
|
-
] }),
|
|
59800
|
-
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "inline-flex", alignItems: "center", justifyContent: "flex-end", gap: "1.5", whiteSpace: "nowrap", color: c2.passed ? "fg.success" : "fg.warning", children: [
|
|
59801
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono.sm", fontWeight: "bold", fontVariantNumeric: "tabular-nums", children: c2.value }),
|
|
59802
|
-
/* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: c2.passed ? faCheck : faTriangleExclamation, style: { fontSize: "11px" } })
|
|
59803
|
-
] })
|
|
59804
|
-
] }, i);
|
|
59805
|
-
}) }),
|
|
59806
|
-
/* @__PURE__ */ jsxRuntime.jsxs(Card, { children: [
|
|
59807
|
-
/* @__PURE__ */ jsxRuntime.jsx(Cap, { children: "Effect as a hidden cause is made stronger" }),
|
|
59808
|
-
/* @__PURE__ */ jsxRuntime.jsx(AreaRange, { lo: vr.sensLo, mid: vr.sensMid, hi: vr.sensHi, zero: 0, tone: "brand", xTicks: vr.sensXTicks, yTicks: vr.sensYTicks, height: 132 })
|
|
59809
|
-
] })
|
|
59810
|
-
] }),
|
|
59811
|
-
tab === "dose" && vd && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
|
|
59812
|
-
vd.segments.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", alignItems: "center", gap: "2.5", mb: "3.5", children: [
|
|
59813
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption.eyebrow", children: "Response for" }),
|
|
59814
|
-
/* @__PURE__ */ jsxRuntime.jsx(SegmentSelect, { options: vd.segments, active: 0, onPick: () => {
|
|
59815
|
-
} })
|
|
60513
|
+
tab === "answer" && a2 && /* @__PURE__ */ jsxRuntime.jsx(AnswerNumeric, { a: a2, verdict, higherBetter, badge, barList }),
|
|
60514
|
+
tab === "answer" && !a2 && ref && /* @__PURE__ */ jsxRuntime.jsx(RefusalZone, { refusal: ref, overlap: ov, naiveValue: ((_d2 = experiment.result) == null ? void 0 : _d2.naive) ?? 0, outcome: vs.outcome }),
|
|
60515
|
+
tab === "trust" && vr && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
|
|
60516
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", color: "fg.muted", mb: "3.5", children: /* @__PURE__ */ jsxRuntime.jsx(Help, { id: "trust_intro", children: "Before trusting the answer we tried to break it — colour shows pass / caution." }) }),
|
|
60517
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { layerStyle: "frame.flat", children: vr.checks.map((c2, i) => {
|
|
60518
|
+
const cs = statusR({ status: c2.passed ? "success" : "warning", size: "sm" });
|
|
60519
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "grid", gridTemplateColumns: "auto 1fr minmax(96px,auto)", gap: "2.5", alignItems: "start", px: "3.5", py: "2.5", borderTopWidth: i ? "1px" : "0", borderColor: "border.subtle", children: [
|
|
60520
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", css: cs.root, mt: "1", children: /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", css: cs.indicator }) }),
|
|
60521
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { children: [
|
|
60522
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", fontWeight: "semibold", color: "fg.default", children: /* @__PURE__ */ jsxRuntime.jsx(Help, { id: c2.help, children: c2.name }) }),
|
|
60523
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption", lineHeight: "1.45", mt: "0.5", children: c2.desc })
|
|
60524
|
+
] }),
|
|
60525
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "inline-flex", alignItems: "center", justifyContent: "flex-end", gap: "1.5", whiteSpace: "nowrap", color: c2.passed ? "fg.success" : "fg.warning", children: [
|
|
60526
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono.sm", fontWeight: "bold", fontVariantNumeric: "tabular-nums", children: c2.value }),
|
|
60527
|
+
/* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: c2.passed ? faCheck : faTriangleExclamation, style: { fontSize: "11px" } })
|
|
60528
|
+
] })
|
|
60529
|
+
] }, i);
|
|
60530
|
+
}) }),
|
|
60531
|
+
vr.sens && /* @__PURE__ */ jsxRuntime.jsxs(Card, { children: [
|
|
60532
|
+
/* @__PURE__ */ jsxRuntime.jsx(Cap, { help: "sensitivity", children: "Effect as a hidden cause is made stronger" }),
|
|
60533
|
+
/* @__PURE__ */ jsxRuntime.jsx(AreaRange, { lo: vr.sens.lo, mid: vr.sens.mid, hi: vr.sens.hi, zero: 0, tone: "brand", xTicks: vr.sens.xTicks, yTicks: vr.sens.yTicks, height: 132 })
|
|
60534
|
+
] })
|
|
59816
60535
|
] }),
|
|
59817
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
59818
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
59819
|
-
|
|
59820
|
-
"
|
|
59821
|
-
vd.feature
|
|
60536
|
+
tab === "trust" && !vr && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
|
|
60537
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "inline-flex", alignItems: "center", gap: "2", mb: "2", color: "fg.muted", children: [
|
|
60538
|
+
/* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faTriangleExclamation, style: { fontSize: "13px" } }),
|
|
60539
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", fontWeight: "semibold", color: "fg.default", children: "Nothing to stress-test" })
|
|
59822
60540
|
] }),
|
|
59823
|
-
/* @__PURE__ */ jsxRuntime.
|
|
60541
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "body.sm", color: "fg.muted", lineHeight: "1.5", children: [
|
|
60542
|
+
"There’s no adjusted estimate to try to break — the experiment couldn’t produce one (see the ",
|
|
60543
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontWeight: "semibold", color: "fg.default", children: "Answer" }),
|
|
60544
|
+
" tab for why)."
|
|
60545
|
+
] })
|
|
59824
60546
|
] }),
|
|
59825
|
-
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, {
|
|
59826
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
59827
|
-
/* @__PURE__ */ jsxRuntime.
|
|
59828
|
-
|
|
59829
|
-
|
|
59830
|
-
|
|
59831
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono-kpi", fontFamily: "heading", color: "brand.solid", children: signed(vd.recoEffect) }),
|
|
59832
|
-
/* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "mono.sm", color: "fg.muted", children: [
|
|
59833
|
-
vd.outcome,
|
|
59834
|
-
" · ",
|
|
59835
|
-
signed(vd.recoLo),
|
|
59836
|
-
" … ",
|
|
59837
|
-
signed(vd.recoHi)
|
|
59838
|
-
] })
|
|
59839
|
-
] })
|
|
60547
|
+
tab === "dose" && vd && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
|
|
60548
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Card, { mt: "0", children: [
|
|
60549
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Cap, { help: "dose_curve", children: [
|
|
60550
|
+
vd.outcome,
|
|
60551
|
+
" gained vs. ",
|
|
60552
|
+
vd.feature
|
|
59840
60553
|
] }),
|
|
59841
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
60554
|
+
/* @__PURE__ */ jsxRuntime.jsx(AreaRange, { lo: vd.lo, mid: vd.mid, hi: vd.hi, tone: "pos", xTicks: vd.xTicks, yTicks: vd.yTicks, marks: vd.marks.map((m2) => ({ at: m2.at, label: m2.label, tone: m2.tone, help: m2.help })), height: 256 })
|
|
59842
60555
|
] }),
|
|
59843
|
-
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, {
|
|
59844
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
59845
|
-
"
|
|
59846
|
-
|
|
59847
|
-
|
|
60556
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "3", mt: "3", children: [
|
|
60557
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { layerStyle: "card", p: "3.5", borderRadius: "lg", children: [
|
|
60558
|
+
/* @__PURE__ */ jsxRuntime.jsx(Cap, { help: "dose_reco", children: "Recommended" }),
|
|
60559
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", flexDirection: "column", gap: "0.5", children: [
|
|
60560
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption.eyebrow", children: vd.recoLabel }),
|
|
60561
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", alignItems: "baseline", gap: "2", children: [
|
|
60562
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono-kpi", fontFamily: "heading", color: "brand.solid", children: signed(vd.recoEffect) }),
|
|
60563
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "mono.sm", color: "fg.muted", children: [
|
|
60564
|
+
vd.outcome,
|
|
60565
|
+
" · ",
|
|
60566
|
+
signed(vd.recoLo),
|
|
60567
|
+
" … ",
|
|
60568
|
+
signed(vd.recoHi)
|
|
60569
|
+
] })
|
|
60570
|
+
] })
|
|
60571
|
+
] }),
|
|
60572
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption", mt: "2.5", pt: "2.5", borderTopWidth: "1px", borderColor: "border.subtle", children: vd.tradeoff })
|
|
59848
60573
|
] }),
|
|
59849
|
-
/* @__PURE__ */ jsxRuntime.
|
|
60574
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { layerStyle: "card", p: "3.5", borderRadius: "lg", children: [
|
|
60575
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Cap, { help: "dose_marginal", children: [
|
|
60576
|
+
"Extra ",
|
|
60577
|
+
vd.outcome,
|
|
60578
|
+
" per step"
|
|
60579
|
+
] }),
|
|
60580
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { py: "0.5", maxH: "200px", overflowY: "auto", children: barList(vd.marginal.map((m2, i) => ({ label: m2.label, frac: m2.frac, tone: i < 2 ? "brand" : "muted", value: signed(m2.value) }))) })
|
|
60581
|
+
] })
|
|
59850
60582
|
] })
|
|
59851
|
-
] })
|
|
60583
|
+
] }),
|
|
60584
|
+
tab === "validate" && (design.status === "failed" && design.error ? /* @__PURE__ */ jsxRuntime.jsx(react.Box, { px: "4.5", pt: "4.5", children: /* @__PURE__ */ jsxRuntime.jsx(RunError, { error: design.error }) }) : designFresh && vmDesign ? /* @__PURE__ */ jsxRuntime.jsx(ValidatePanel, { vm: vmDesign, barList }) : /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", display: "inline-flex", alignItems: "center", gap: "2", color: "fg.muted", children: [
|
|
60585
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Spinner, { size: "xs", borderWidth: "1.5px", color: "brand.solid" }),
|
|
60586
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", children: "Sizing the trial that would confirm this…" })
|
|
60587
|
+
] }))
|
|
59852
60588
|
] })
|
|
59853
60589
|
] })
|
|
59854
60590
|
] }),
|
|
59855
60591
|
journal && journal.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
59856
60592
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { css: es.root, bg: "bg.canvas", borderTopWidth: "1px", borderColor: "border.subtle", children: [
|
|
59857
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: es.lbl, children: "Committed experiments" }),
|
|
60593
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: es.lbl, children: /* @__PURE__ */ jsxRuntime.jsx(Help, { id: "journal", children: "Committed experiments" }) }),
|
|
59858
60594
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { css: es.meta, children: [
|
|
59859
60595
|
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "fg.default", fontWeight: "semibold", children: journal.length }),
|
|
59860
|
-
" on record"
|
|
60596
|
+
" on record",
|
|
60597
|
+
journal.length > 50 ? " · showing newest 50" : ""
|
|
59861
60598
|
] })
|
|
59862
60599
|
] }),
|
|
59863
|
-
journal.map((r2, i) => /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "grid", gridTemplateColumns: "2fr 1fr 1fr 1fr", gap: "3", alignItems: "center", px: "4.5", py: "2.5", borderTopWidth: "1px", borderColor: "border.subtle", children: [
|
|
60600
|
+
journal.slice(0, 50).map((r2, i) => /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "grid", gridTemplateColumns: "2fr 1fr 1fr 1fr", gap: "3", alignItems: "center", px: "4.5", py: "2.5", borderTopWidth: "1px", borderColor: "border.subtle", children: [
|
|
59864
60601
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "body.sm", children: [
|
|
59865
60602
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Text, { as: "span", fontWeight: "bold", children: [
|
|
59866
60603
|
r2.treatment,
|
|
@@ -59874,7 +60611,7 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
|
|
|
59874
60611
|
] })
|
|
59875
60612
|
] }),
|
|
59876
60613
|
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono.sm", fontWeight: "semibold", textAlign: "right", fontVariantNumeric: "tabular-nums", color: r2.verdictTone === "pos" ? "fg.success" : "fg.default", children: r2.effect }),
|
|
59877
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption.eyebrow", textAlign: "right", color: r2.verdictTone
|
|
60614
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption.eyebrow", textAlign: "right", color: toneToken(r2.verdictTone), children: r2.verdict }),
|
|
59878
60615
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "mono.sm", textAlign: "right", color: "fg.muted", children: [
|
|
59879
60616
|
r2.who,
|
|
59880
60617
|
" · ",
|
|
@@ -59882,8 +60619,180 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
|
|
|
59882
60619
|
] })
|
|
59883
60620
|
] }, i))
|
|
59884
60621
|
] })
|
|
60622
|
+
] }) });
|
|
60623
|
+
}, (prev2, next2) => experimentValueEqual(prev2.value, next2.value) && prev2.storageKey === next2.storageKey);
|
|
60624
|
+
function AnswerNumeric({ a: a2, verdict, higherBetter, badge, barList }) {
|
|
60625
|
+
const dirUp = a2.effect > 0;
|
|
60626
|
+
const statusWord = higherBetter === void 0 ? dirUp ? "Higher" : "Lower" : dirUp === higherBetter ? "Better" : "Worse";
|
|
60627
|
+
const lowerWord = a2.naive < 0 ? "lower" : "higher";
|
|
60628
|
+
const top = a2.balance[0] ?? { col: "", display: "" };
|
|
60629
|
+
const kpiColor = a2.clear && !a2.cautious ? "fg.success" : "fg.warning";
|
|
60630
|
+
const badgeOk = a2.clear && !a2.cautious;
|
|
60631
|
+
const badgeText = a2.cautious ? (verdict == null ? void 0 : verdict.label) ?? "Not trustworthy yet" : a2.clear ? `${statusWord} with ${a2.treatment}` : "No clear effect";
|
|
60632
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
|
|
60633
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", alignItems: "center", gap: "4.5", flexWrap: "wrap", children: [
|
|
60634
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", flexDirection: "column", gap: "0.5", children: [
|
|
60635
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono.sm", color: "fg.muted", children: a2.outcome }),
|
|
60636
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", alignItems: "baseline", gap: "2.5", children: [
|
|
60637
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono-kpi", fontFamily: "heading", fontSize: "32px", color: kpiColor, children: /* @__PURE__ */ jsxRuntime.jsx(Help, { id: "answer_effect", children: signed(a2.effect) }) }),
|
|
60638
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono.sm", color: "fg.muted", children: /* @__PURE__ */ jsxRuntime.jsxs(Help, { id: "answer_ci", children: [
|
|
60639
|
+
"95% CI ",
|
|
60640
|
+
signed(a2.lo),
|
|
60641
|
+
" … ",
|
|
60642
|
+
signed(a2.hi)
|
|
60643
|
+
] }) })
|
|
60644
|
+
] })
|
|
60645
|
+
] }),
|
|
60646
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { as: "span", css: badge({ variant: badgeOk ? "ok" : "warn", size: "md" }), alignSelf: "flex-end", mb: "1", display: "inline-flex", alignItems: "center", gap: "1", children: [
|
|
60647
|
+
a2.clear && !a2.cautious && /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: dirUp ? faArrowUp : faArrowDown, style: { fontSize: "10px" } }),
|
|
60648
|
+
/* @__PURE__ */ jsxRuntime.jsx(Help, { id: `verdict_${(verdict == null ? void 0 : verdict.tag) ?? "modest"}`, children: badgeText })
|
|
60649
|
+
] })
|
|
60650
|
+
] }),
|
|
60651
|
+
a2.cautious && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { layerStyle: "banner.stale", display: "flex", alignItems: "flex-start", gap: "2", mt: "3", children: [
|
|
60652
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", color: "fg.warning", flexShrink: "0", mt: "0.5", fontSize: "12px", children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faTriangleExclamation }) }),
|
|
60653
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "body.sm", color: "fg.default", children: [
|
|
60654
|
+
/* @__PURE__ */ jsxRuntime.jsx(Help, { id: "answer_cautious", children: /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontWeight: "bold", children: "Treat this as provisional." }) }),
|
|
60655
|
+
" We adjusted and got a number, but a robustness check failed — the estimate may still be driven by something we didn’t adjust for. See ",
|
|
60656
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontWeight: "semibold", children: "Can we trust it?" })
|
|
60657
|
+
] })
|
|
60658
|
+
] }),
|
|
60659
|
+
a2.flip && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { layerStyle: "banner.stale", display: "flex", alignItems: "flex-start", gap: "2", mt: "3", children: [
|
|
60660
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", color: "fg.warning", flexShrink: "0", mt: "0.5", fontSize: "12px", children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faTriangleExclamation }) }),
|
|
60661
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "body.sm", color: "fg.default", children: [
|
|
60662
|
+
/* @__PURE__ */ jsxRuntime.jsx(Help, { id: "answer_flip", children: /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontWeight: "bold", children: "Raw and like-for-like disagree." }) }),
|
|
60663
|
+
" In the plain average, the ",
|
|
60664
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontFamily: "mono", children: a2.treatment }),
|
|
60665
|
+
" group sits ",
|
|
60666
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontStyle: "italic", children: lowerWord }),
|
|
60667
|
+
" on ",
|
|
60668
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontFamily: "mono", children: a2.outcome }),
|
|
60669
|
+
" (",
|
|
60670
|
+
signed(a2.naive),
|
|
60671
|
+
") — but they also differ most on ",
|
|
60672
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontFamily: "mono", children: top.col }),
|
|
60673
|
+
" (",
|
|
60674
|
+
top.display,
|
|
60675
|
+
"). Adjusting for it reverses the result."
|
|
60676
|
+
] })
|
|
60677
|
+
] }),
|
|
60678
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Card, { children: [
|
|
60679
|
+
/* @__PURE__ */ jsxRuntime.jsx(Cap, { help: "forest_plot", children: "Raw average vs. like-for-like" }),
|
|
60680
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
60681
|
+
ForestPlot,
|
|
60682
|
+
{
|
|
60683
|
+
rows: [
|
|
60684
|
+
{ label: "Raw average", note: "unadjusted", est: a2.naive, lo: a2.naiveLo, hi: a2.naiveHi, tone: a2.naive < 0 ? "neg" : "pos" },
|
|
60685
|
+
{ label: "Like-for-like", note: "adjusted", est: a2.effect, lo: a2.lo, hi: a2.hi, tone: a2.clear && !a2.cautious ? "pos" : "warn" }
|
|
60686
|
+
],
|
|
60687
|
+
min: Math.floor(Math.min(0, a2.naiveLo, a2.lo) - 2),
|
|
60688
|
+
max: Math.ceil(Math.max(0, a2.naiveHi, a2.hi) + 2),
|
|
60689
|
+
unit: `change in ${a2.outcome}${a2.unit ? ` (${a2.unit})` : ""}`,
|
|
60690
|
+
height: 150,
|
|
60691
|
+
rowHelp: ["forest_naive", "forest_adjusted"]
|
|
60692
|
+
}
|
|
60693
|
+
)
|
|
60694
|
+
] }),
|
|
60695
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Card, { children: [
|
|
60696
|
+
/* @__PURE__ */ jsxRuntime.jsx(Cap, { help: "balance", children: "How unbalanced each one was — before adjusting" }),
|
|
60697
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { py: "0.5", maxH: "208px", overflowY: "auto", children: barList(a2.balance.map((b2) => ({ label: b2.col, frac: b2.frac, tone: b2.tone, value: b2.display }))) })
|
|
60698
|
+
] }),
|
|
60699
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { mt: "3.5", children: /* @__PURE__ */ jsxRuntime.jsx(Help, { id: "counts", display: "inline-flex", gap: "4", children: [[a2.nTotal, SUBJECT_MANY], [a2.nCompared, "compared like-for-like"], [a2.nDropped, "had no fair match"]].map(([n2, label], i) => /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "mono.sm", color: "fg.muted", children: [
|
|
60700
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "fg.default", fontWeight: "semibold", children: Number(n2) }),
|
|
60701
|
+
" ",
|
|
60702
|
+
label
|
|
60703
|
+
] }, i)) }) })
|
|
59885
60704
|
] });
|
|
59886
|
-
}
|
|
60705
|
+
}
|
|
60706
|
+
function RefusalZone({ refusal, overlap, naiveValue, outcome }) {
|
|
60707
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
|
|
60708
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "inline-flex", alignItems: "center", gap: "2", mb: "2", color: "fg.warning", children: [
|
|
60709
|
+
/* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faTriangleExclamation, style: { fontSize: "14px" } }),
|
|
60710
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "title.card", color: "fg.default", children: /* @__PURE__ */ jsxRuntime.jsx(Help, { id: refusal.kind === "positivity" ? "refusal_positivity" : "refusal_not_estimable", children: refusal.title }) })
|
|
60711
|
+
] }),
|
|
60712
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", color: "fg.muted", mb: "3", lineHeight: "1.5", children: refusal.body }),
|
|
60713
|
+
refusal.kind === "positivity" && overlap && /* @__PURE__ */ jsxRuntime.jsxs(Card, { mt: "1", children: [
|
|
60714
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Cap, { help: "overlap_histogram", children: [
|
|
60715
|
+
"Propensity overlap — ",
|
|
60716
|
+
overlap.supportLabel
|
|
60717
|
+
] }),
|
|
60718
|
+
/* @__PURE__ */ jsxRuntime.jsx(OverlapHistogram, { treated: overlap.treated, control: overlap.control, supportLabel: overlap.supportLabel, positivityOk: overlap.positivityOk, height: 170 })
|
|
60719
|
+
] }),
|
|
60720
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { display: "flex", gap: "4", mt: "3.5", children: refusal.evidence.map((e3, i) => /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "mono.sm", color: "fg.muted", children: [
|
|
60721
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "fg.default", fontWeight: "semibold", children: e3.value }),
|
|
60722
|
+
" ",
|
|
60723
|
+
e3.label
|
|
60724
|
+
] }, i)) }),
|
|
60725
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "caption", color: "fg.subtle", mt: "3.5", pt: "3", borderTopWidth: "1px", borderColor: "border.subtle", children: [
|
|
60726
|
+
"Raw average difference in ",
|
|
60727
|
+
outcome,
|
|
60728
|
+
" (context only, not an answer): ",
|
|
60729
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontFamily: "mono", color: "fg.muted", children: signed(naiveValue) })
|
|
60730
|
+
] })
|
|
60731
|
+
] });
|
|
60732
|
+
}
|
|
60733
|
+
function ValidatePanel({ vm, barList }) {
|
|
60734
|
+
const treatedPct = Math.round(vm.primary.treatedShare * 100);
|
|
60735
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
|
|
60736
|
+
/* @__PURE__ */ jsxRuntime.jsx(Cap, { help: "tab_validate", children: vm.headline }),
|
|
60737
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", alignItems: "flex-end", gap: "5", flexWrap: "wrap", children: [
|
|
60738
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", flexDirection: "column", gap: "0.5", children: [
|
|
60739
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono.sm", color: "fg.muted", children: /* @__PURE__ */ jsxRuntime.jsx(Help, { id: "validate_size", children: vm.holdback ? "to hold back from" : "to run" }) }),
|
|
60740
|
+
vm.faint ? /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", fontWeight: "semibold", color: "fg.warning", maxW: "220px", children: "Effect too faint to size — set a materiality threshold to size a trial." }) : /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono-kpi", fontFamily: "heading", fontSize: "32px", color: "brand.solid", children: vm.primary.nTotal.toLocaleString() })
|
|
60741
|
+
] }),
|
|
60742
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { flex: "1", minW: "220px", children: [
|
|
60743
|
+
/* @__PURE__ */ jsxRuntime.jsx(Cap, { help: "validate_split", children: vm.holdback ? "Hold-back split" : "Split" }),
|
|
60744
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", h: "10px", borderRadius: "full", overflow: "hidden", borderWidth: "1px", borderColor: "border.subtle", children: [
|
|
60745
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { bg: "brand.solid", width: `${treatedPct}%` }),
|
|
60746
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { bg: "bg.emphasized", flex: "1" })
|
|
60747
|
+
] }),
|
|
60748
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", justifyContent: "space-between", mt: "1.5", children: [
|
|
60749
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "caption", children: [
|
|
60750
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "brand.fg", fontWeight: "semibold", children: vm.primary.nTreated.toLocaleString() }),
|
|
60751
|
+
" ",
|
|
60752
|
+
vm.holdback ? "treated" : "get it"
|
|
60753
|
+
] }),
|
|
60754
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "caption", children: [
|
|
60755
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "fg.default", fontWeight: "semibold", children: vm.primary.nControl.toLocaleString() }),
|
|
60756
|
+
" ",
|
|
60757
|
+
vm.holdback ? "held back" : "left alone"
|
|
60758
|
+
] })
|
|
60759
|
+
] })
|
|
60760
|
+
] })
|
|
60761
|
+
] }),
|
|
60762
|
+
vm.matchOn.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(Card, { children: [
|
|
60763
|
+
/* @__PURE__ */ jsxRuntime.jsx(Cap, { help: "validate_match", children: "Match both groups on" }),
|
|
60764
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { py: "0.5", children: barList(vm.matchOn.map((m2) => ({ label: m2.display, frac: m2.frac, tone: m2.tone, value: "" }))) })
|
|
60765
|
+
] }),
|
|
60766
|
+
vm.curve.mid.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(Card, { children: [
|
|
60767
|
+
/* @__PURE__ */ jsxRuntime.jsx(Cap, { help: "validate_power", children: "Chance of detecting it" }),
|
|
60768
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
60769
|
+
AreaRange,
|
|
60770
|
+
{
|
|
60771
|
+
lo: vm.curve.mid,
|
|
60772
|
+
mid: vm.curve.mid,
|
|
60773
|
+
hi: vm.curve.mid,
|
|
60774
|
+
tone: "brand",
|
|
60775
|
+
xTicks: vm.curve.xTicks,
|
|
60776
|
+
yTicks: ["100", "50", "0"],
|
|
60777
|
+
yFormat: "percent",
|
|
60778
|
+
marks: vm.curve.marks.map((m2) => ({ at: m2.at, label: m2.label, tone: m2.tone, help: m2.help })),
|
|
60779
|
+
height: 170
|
|
60780
|
+
}
|
|
60781
|
+
)
|
|
60782
|
+
] }),
|
|
60783
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", color: "fg.default", lineHeight: "1.5", mt: "3.5", children: vm.rationale }),
|
|
60784
|
+
vm.alternates.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(react.Box, { display: "flex", flexDirection: "column", gap: "1.5", mt: "3", pt: "3", borderTopWidth: "1px", borderColor: "border.subtle", children: vm.alternates.map((o2, i) => /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "caption", color: "fg.muted", children: [
|
|
60785
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontWeight: "semibold", color: "fg.default", children: o2.label }),
|
|
60786
|
+
" · ",
|
|
60787
|
+
o2.nTotal.toLocaleString(),
|
|
60788
|
+
" total (",
|
|
60789
|
+
o2.nTreated.toLocaleString(),
|
|
60790
|
+
" / ",
|
|
60791
|
+
o2.nControl.toLocaleString(),
|
|
60792
|
+
")"
|
|
60793
|
+
] }, i)) })
|
|
60794
|
+
] });
|
|
60795
|
+
}
|
|
59887
60796
|
function FilterRail({ fields, population, onChange: onChange2, chip, button }) {
|
|
59888
60797
|
const [open, setOpen] = React.useState(null);
|
|
59889
60798
|
const replaceAt = (i, p2) => onChange2(population.map((f2, j2) => j2 === i ? p2 : f2));
|
|
@@ -59934,11 +60843,11 @@ function FilterRail({ fields, population, onChange: onChange2, chip, button }) {
|
|
|
59934
60843
|
)
|
|
59935
60844
|
] });
|
|
59936
60845
|
}
|
|
59937
|
-
function Step({ n: n2, title, children: children2 }) {
|
|
60846
|
+
function Step({ n: n2, title, help, children: children2 }) {
|
|
59938
60847
|
return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { px: "4.5", py: "3", borderBottomWidth: "1px", borderColor: "border.subtle", children: [
|
|
59939
|
-
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", alignItems: "baseline", gap: "2", mb: "2", children: [
|
|
59940
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", fontFamily: "mono", fontSize: "
|
|
59941
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "title.row", children: title })
|
|
60848
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", alignItems: "baseline", gap: "2.5", mb: "2", children: [
|
|
60849
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", fontFamily: "mono", fontSize: "11px", fontWeight: "bold", color: "brand.fg", flex: "0 0 auto", lineHeight: "1", children: n2 }),
|
|
60850
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "title.row", children: help ? /* @__PURE__ */ jsxRuntime.jsx(Help, { id: help, children: title }) : title })
|
|
59942
60851
|
] }),
|
|
59943
60852
|
children2
|
|
59944
60853
|
] });
|
|
@@ -59956,13 +60865,45 @@ function ColumnPick({ column: column2, kind, choices, onPick, badge, button }) {
|
|
|
59956
60865
|
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", display: "inline-flex", color: "fg.muted", fontSize: "10px", children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faChevronDown }) })
|
|
59957
60866
|
] }) });
|
|
59958
60867
|
}
|
|
59959
|
-
function SegmentSelect({ options: options2, active, onPick, fill, size: size3 = "
|
|
60868
|
+
function SegmentSelect({ options: options2, active, onPick, fill, size: size3 = "xs" }) {
|
|
59960
60869
|
const s2 = react.useSlotRecipe({ key: "segmentGroup" })({ size: size3 });
|
|
59961
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
60870
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
60871
|
+
react.Box,
|
|
60872
|
+
{
|
|
60873
|
+
css: s2.root,
|
|
60874
|
+
...fill ? { width: "100%" } : {},
|
|
60875
|
+
role: "radiogroup",
|
|
60876
|
+
onKeyDown: (e3) => {
|
|
60877
|
+
var _a2;
|
|
60878
|
+
const d2 = e3.key === "ArrowRight" || e3.key === "ArrowDown" ? 1 : e3.key === "ArrowLeft" || e3.key === "ArrowUp" ? -1 : 0;
|
|
60879
|
+
if (!d2) return;
|
|
60880
|
+
e3.preventDefault();
|
|
60881
|
+
const next2 = (active + d2 + options2.length) % options2.length;
|
|
60882
|
+
onPick(next2);
|
|
60883
|
+
const btns = e3.currentTarget.querySelectorAll('[role="radio"]');
|
|
60884
|
+
(_a2 = btns[next2]) == null ? void 0 : _a2.focus();
|
|
60885
|
+
},
|
|
60886
|
+
children: options2.map((o2, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
60887
|
+
react.Box,
|
|
60888
|
+
{
|
|
60889
|
+
as: "button",
|
|
60890
|
+
role: "radio",
|
|
60891
|
+
"aria-checked": active === i,
|
|
60892
|
+
tabIndex: active === i ? 0 : -1,
|
|
60893
|
+
css: s2.item,
|
|
60894
|
+
...fill ? { flex: "1" } : {},
|
|
60895
|
+
...active === i ? { "data-state": "checked" } : {},
|
|
60896
|
+
onClick: () => onPick(i),
|
|
60897
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", css: s2.itemText, children: o2 })
|
|
60898
|
+
},
|
|
60899
|
+
i
|
|
60900
|
+
))
|
|
60901
|
+
}
|
|
60902
|
+
);
|
|
59962
60903
|
}
|
|
59963
|
-
function Segmented({ label, left, right, active, onPick, last: last2 }) {
|
|
60904
|
+
function Segmented({ label, left, right, active, onPick, last: last2, help }) {
|
|
59964
60905
|
return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", flexDirection: "column", alignItems: "stretch", gap: "1.5", py: "2", borderBottomWidth: last2 ? "0" : "1px", borderColor: "border.subtle", children: [
|
|
59965
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption.eyebrow", textTransform: "none", letterSpacing: "normal", color: "fg.default", children: label }),
|
|
60906
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption.eyebrow", textTransform: "none", letterSpacing: "normal", color: "fg.default", children: help ? /* @__PURE__ */ jsxRuntime.jsx(Help, { id: help, children: label }) : label }),
|
|
59966
60907
|
/* @__PURE__ */ jsxRuntime.jsx(SegmentSelect, { options: [left, right], active: active === "left" ? 0 : 1, onPick: (i) => onPick(i === 0 ? "left" : "right"), fill: true, size: "xs" })
|
|
59967
60908
|
] });
|
|
59968
60909
|
}
|
|
@@ -61664,10 +62605,10 @@ function formatPrimitive(type, value) {
|
|
|
61664
62605
|
case "Integer":
|
|
61665
62606
|
return String(value);
|
|
61666
62607
|
case "Float": {
|
|
61667
|
-
const
|
|
61668
|
-
if (Number.isNaN(
|
|
61669
|
-
if (!Number.isFinite(
|
|
61670
|
-
return String(
|
|
62608
|
+
const num2 = value;
|
|
62609
|
+
if (Number.isNaN(num2)) return "NaN";
|
|
62610
|
+
if (!Number.isFinite(num2)) return num2 > 0 ? "Infinity" : "-Infinity";
|
|
62611
|
+
return String(num2);
|
|
61671
62612
|
}
|
|
61672
62613
|
case "String":
|
|
61673
62614
|
return `"${value}"`;
|