@absmartly/cli 1.3.0 → 1.4.0
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/README.md +251 -0
- package/dist/commands/experiments/analyze.d.ts +3 -0
- package/dist/commands/experiments/analyze.d.ts.map +1 -0
- package/dist/commands/experiments/analyze.js +33 -0
- package/dist/commands/experiments/analyze.js.map +1 -0
- package/dist/commands/experiments/index.d.ts.map +1 -1
- package/dist/commands/experiments/index.js +2 -0
- package/dist/commands/experiments/index.js.map +1 -1
- package/dist/core/experiments/analyze/extract-signals.d.ts +42 -0
- package/dist/core/experiments/analyze/extract-signals.d.ts.map +1 -0
- package/dist/core/experiments/analyze/extract-signals.js +154 -0
- package/dist/core/experiments/analyze/extract-signals.js.map +1 -0
- package/dist/core/experiments/analyze/heuristics.d.ts +43 -0
- package/dist/core/experiments/analyze/heuristics.d.ts.map +1 -0
- package/dist/core/experiments/analyze/heuristics.js +186 -0
- package/dist/core/experiments/analyze/heuristics.js.map +1 -0
- package/dist/core/experiments/analyze/index.d.ts +9 -0
- package/dist/core/experiments/analyze/index.d.ts.map +1 -0
- package/dist/core/experiments/analyze/index.js +114 -0
- package/dist/core/experiments/analyze/index.js.map +1 -0
- package/dist/core/experiments/analyze/related-benchmarks.d.ts +28 -0
- package/dist/core/experiments/analyze/related-benchmarks.d.ts.map +1 -0
- package/dist/core/experiments/analyze/related-benchmarks.js +54 -0
- package/dist/core/experiments/analyze/related-benchmarks.js.map +1 -0
- package/dist/core/experiments/analyze/source-signals.d.ts +8 -0
- package/dist/core/experiments/analyze/source-signals.d.ts.map +1 -0
- package/dist/core/experiments/analyze/source-signals.js +15 -0
- package/dist/core/experiments/analyze/source-signals.js.map +1 -0
- package/dist/core/experiments/analyze/summary.d.ts +3 -0
- package/dist/core/experiments/analyze/summary.d.ts.map +1 -0
- package/dist/core/experiments/analyze/summary.js +35 -0
- package/dist/core/experiments/analyze/summary.js.map +1 -0
- package/dist/core/experiments/analyze/types.d.ts +108 -0
- package/dist/core/experiments/analyze/types.d.ts.map +1 -0
- package/dist/core/experiments/analyze/types.js +2 -0
- package/dist/core/experiments/analyze/types.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -317,6 +317,14 @@ abs experiments alerts dismiss 456
|
|
|
317
317
|
abs experiments recommendations list 123
|
|
318
318
|
abs experiments recommendations dismiss 456
|
|
319
319
|
|
|
320
|
+
# Analyze experiment design + signals + benchmarks (returns JSON by default)
|
|
321
|
+
abs experiments analyze 123 # full structured analysis (JSON)
|
|
322
|
+
abs experiments analyze checkout_redesign # by name
|
|
323
|
+
abs experiments analyze 123 -o table # flat one-row summary
|
|
324
|
+
abs experiments analyze 123 -o yaml # full analysis as YAML
|
|
325
|
+
abs experiments analyze 123 | jq .recommendation # pluck a single section
|
|
326
|
+
abs experiments analyze 123 | jq '[.heuristic_output[] | select(.fired)]'
|
|
327
|
+
|
|
320
328
|
# Access control
|
|
321
329
|
abs experiments access list-users 123
|
|
322
330
|
abs experiments access grant-user 123 --user 1 --role 2
|
|
@@ -384,6 +392,249 @@ abs experiments estimate-participants --unit-type user_id --application absmartl
|
|
|
384
392
|
abs experiments estimate-participants --unit-type user_id -o json
|
|
385
393
|
```
|
|
386
394
|
|
|
395
|
+
#### Analyze experiment
|
|
396
|
+
|
|
397
|
+
`abs experiments analyze <id>` produces a single structured document that combines what you'd otherwise piece together from `get`, `metrics results`, `alerts list`, `recommendations list`, and `list --type`. It's designed to be consumed by an LLM-based analyzer or a human reviewer; the JSON shape is stable.
|
|
398
|
+
|
|
399
|
+
Default output is **JSON** (the `-o table` global default is overridden when `-o` is not explicitly set, since the data is deeply nested). `-o yaml` works the same way. `-o table`/`-o markdown`/`-o plain` switch to a flat one-row summary suitable for tabular display.
|
|
400
|
+
|
|
401
|
+
The output has nine top-level sections:
|
|
402
|
+
|
|
403
|
+
| Section | What it contains |
|
|
404
|
+
|---|---|
|
|
405
|
+
| `experiment` | Headline facts: `name`, `state`, `hypothesis`, `primary_metric_name`, `participant_count`, `leading_variant_*`, `current_recommended_action`, `report_note`. |
|
|
406
|
+
| `alerts` | Active alerts (`{id, type, dismissed}`) — `srm`, `audience_mismatch`, `cleanup_needed`, etc. |
|
|
407
|
+
| `recommendation` | The single best deterministic recommendation (`{theme, title, details}`) picked from the heuristic rule set, or `null`. Warnings outrank successes. |
|
|
408
|
+
| `metric_signals` | Per-metric / per-variant rows from `metrics_snapshot` with `percent_change`, `p_value`, CI bounds, and a derived `status`: `improves` / `contradicts` / `flat` / `inconclusive`. |
|
|
409
|
+
| `related_experiments` | Up to 24 recent same-type experiments. For peers that share the primary metric, `leading_variant_impact_percent` is fetched and compared. |
|
|
410
|
+
| `analysis_confidence` | `high` / `medium` / `low` plus 5 boolean factors and human-readable reasons. Captures *whether there's enough signal to analyze*. |
|
|
411
|
+
| `design_readout` | Whether the experiment design fits the question — summary line, parameter pass-through, and a benchmark from related experiments when available. |
|
|
412
|
+
| `source_signals` | Audit trail mapping each derived field to its API source path (e.g. `experiment.metrics_snapshot.rows[*].percent_change`). Useful for verifying AI analyses. |
|
|
413
|
+
| `heuristic_output` | All 9 heuristic rules with `{rule, fired, theme, title, details, evidence}`. The starting point for an AI analysis when one is available. |
|
|
414
|
+
|
|
415
|
+
**Example output** (illustrative — a running experiment with a metric snapshot, two comparable peers, a primary-metric win, and a contradicting guardrail):
|
|
416
|
+
|
|
417
|
+
```json
|
|
418
|
+
{
|
|
419
|
+
"experiment": {
|
|
420
|
+
"id": 18234,
|
|
421
|
+
"name": "checkout_redesign",
|
|
422
|
+
"type": "test",
|
|
423
|
+
"state": "running",
|
|
424
|
+
"hypothesis": "Single-page checkout reduces drop-off by surfacing all required fields earlier.",
|
|
425
|
+
"primary_metric_name": "Checkout Completion Rate",
|
|
426
|
+
"unit_type_name": "user_id",
|
|
427
|
+
"participant_count": 102450,
|
|
428
|
+
"leading_variant_name": "treatment",
|
|
429
|
+
"leading_variant_impact_percent": 3.8,
|
|
430
|
+
"leading_variant_confidence": 0.987,
|
|
431
|
+
"current_recommended_action": "review",
|
|
432
|
+
"report_note": "Treatment is winning on primary metric; awaiting eng review of latency regression."
|
|
433
|
+
},
|
|
434
|
+
"alerts": [
|
|
435
|
+
{ "id": 1042, "type": "sample_size_reached", "dismissed": false }
|
|
436
|
+
],
|
|
437
|
+
"recommendation": {
|
|
438
|
+
"theme": "warning",
|
|
439
|
+
"title": "Review guardrail regressions before rolling out treatment.",
|
|
440
|
+
"details": "At least one guardrail metric is moving in the wrong direction, so the apparent win on the primary metric should not be treated as rollout-ready yet."
|
|
441
|
+
},
|
|
442
|
+
"metric_signals": [
|
|
443
|
+
{
|
|
444
|
+
"metric_id": 14, "metric_name": "Checkout Completion Rate", "metric_type": "primary",
|
|
445
|
+
"variant_id": 1, "variant_name": "treatment",
|
|
446
|
+
"percent_change": 3.8, "p_value": 0.013, "ci_low": 1.6, "ci_high": 6.0,
|
|
447
|
+
"status": "improves"
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
"metric_id": 18, "metric_name": "P95 Page Latency", "metric_type": "guardrail",
|
|
451
|
+
"variant_id": 1, "variant_name": "treatment",
|
|
452
|
+
"percent_change": 4.2, "p_value": 0.022, "ci_low": 0.9, "ci_high": 7.5,
|
|
453
|
+
"status": "contradicts"
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
"metric_id": 21, "metric_name": "Revenue per Visitor", "metric_type": "secondary",
|
|
457
|
+
"variant_id": 1, "variant_name": "treatment",
|
|
458
|
+
"percent_change": 0.4, "p_value": 0.61, "ci_low": -1.1, "ci_high": 1.9,
|
|
459
|
+
"status": "flat"
|
|
460
|
+
}
|
|
461
|
+
],
|
|
462
|
+
"related_experiments": [
|
|
463
|
+
{
|
|
464
|
+
"id": 17988, "name": "checkout_steps_v2", "state": "stopped",
|
|
465
|
+
"started_at": "2025-09-04T12:00:00Z", "stopped_at": "2025-10-02T16:00:00Z",
|
|
466
|
+
"primary_metric_id": 14, "primary_metric_name": "Checkout Completion Rate",
|
|
467
|
+
"leading_variant_impact_percent": 2.1
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
"id": 18012, "name": "guest_checkout_default", "state": "stopped",
|
|
471
|
+
"started_at": "2025-10-15T09:00:00Z", "stopped_at": "2025-11-12T09:00:00Z",
|
|
472
|
+
"primary_metric_id": 14, "primary_metric_name": "Checkout Completion Rate",
|
|
473
|
+
"leading_variant_impact_percent": 4.6
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
"id": 18103, "name": "trust_badges_above_fold", "state": "running",
|
|
477
|
+
"started_at": "2025-12-01T10:00:00Z", "stopped_at": null,
|
|
478
|
+
"primary_metric_id": 14, "primary_metric_name": "Checkout Completion Rate",
|
|
479
|
+
"leading_variant_impact_percent": null
|
|
480
|
+
}
|
|
481
|
+
],
|
|
482
|
+
"analysis_confidence": {
|
|
483
|
+
"level": "high",
|
|
484
|
+
"reasons": [],
|
|
485
|
+
"factors": {
|
|
486
|
+
"sample_size_reached": true,
|
|
487
|
+
"hypothesis_present": true,
|
|
488
|
+
"primary_metric_present": true,
|
|
489
|
+
"guardrails_present": true,
|
|
490
|
+
"no_blocking_alerts": true
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
"design_readout": {
|
|
494
|
+
"summary": "Designed to detect effects in the expected range; comparable experiments moved this metric by ~3.35%.",
|
|
495
|
+
"notes": [],
|
|
496
|
+
"parameters": {
|
|
497
|
+
"analysis_type": "group_sequential",
|
|
498
|
+
"required_alpha": 0.1,
|
|
499
|
+
"required_power": 0.8,
|
|
500
|
+
"minimum_detectable_effect": 2.5,
|
|
501
|
+
"baseline_primary_metric_mean": 0.382,
|
|
502
|
+
"baseline_participants_per_day": 7400,
|
|
503
|
+
"percentage_of_traffic": 100
|
|
504
|
+
},
|
|
505
|
+
"benchmark": {
|
|
506
|
+
"observed_impacts": [2.1, 4.6],
|
|
507
|
+
"median_abs_impact": 3.35
|
|
508
|
+
}
|
|
509
|
+
},
|
|
510
|
+
"source_signals": [
|
|
511
|
+
{ "covers": "experiment.participant_count",
|
|
512
|
+
"source": "experiment.metrics_snapshot.rows[*].cum_unit_count" },
|
|
513
|
+
{ "covers": "experiment.leading_variant_impact_percent",
|
|
514
|
+
"source": "experiment.metrics_snapshot.rows[*].percent_change" },
|
|
515
|
+
{ "covers": "alerts",
|
|
516
|
+
"source": "experiment.alerts[*].type" },
|
|
517
|
+
{ "covers": "experiment.current_recommended_action",
|
|
518
|
+
"source": "experiment.recommended_action.recommendation" },
|
|
519
|
+
{ "covers": "experiment.report_note",
|
|
520
|
+
"source": "experiment.experiment_report.experiment_note.note" },
|
|
521
|
+
{ "covers": "related_experiments[17988].leading_variant_impact_percent",
|
|
522
|
+
"source": "experiments/17988/metrics/main.percent_change" },
|
|
523
|
+
{ "covers": "related_experiments[18012].leading_variant_impact_percent",
|
|
524
|
+
"source": "experiments/18012/metrics/main.percent_change" }
|
|
525
|
+
],
|
|
526
|
+
"heuristic_output": [
|
|
527
|
+
{ "rule": "blocking_alert", "fired": false, "theme": "warning",
|
|
528
|
+
"title": "Investigate the experiment before making a rollout decision.",
|
|
529
|
+
"details": "Active health checks indicate that the current results may be misleading. Resolve the data-quality or assignment issue before acting on the outcome.",
|
|
530
|
+
"evidence": { "alert_types": [] } },
|
|
531
|
+
{ "rule": "cleanup_needed", "fired": false, "theme": "warning",
|
|
532
|
+
"title": "Clean up stale assignments before proceeding.",
|
|
533
|
+
"details": "The experiment has cleanup_needed signals; old data may skew the analysis.",
|
|
534
|
+
"evidence": { "alert_ids": [] } },
|
|
535
|
+
{ "rule": "guardrail_contradicts", "fired": true, "theme": "warning",
|
|
536
|
+
"title": "Review guardrail regressions before rolling out treatment.",
|
|
537
|
+
"details": "At least one guardrail metric is moving in the wrong direction, so the apparent win on the primary metric should not be treated as rollout-ready yet.",
|
|
538
|
+
"evidence": { "metrics": ["P95 Page Latency"] } },
|
|
539
|
+
{ "rule": "primary_metric_significant_loss", "fired": false, "theme": "warning",
|
|
540
|
+
"title": "Primary metric regressed at significance.",
|
|
541
|
+
"details": "The leading variant moves the primary metric in the wrong direction with p-value below alpha. Do not roll out.",
|
|
542
|
+
"evidence": { "variant": "treatment", "impact_percent": 3.8, "p_value": 0.013 } },
|
|
543
|
+
{ "rule": "primary_metric_significant_win", "fired": true, "theme": "success",
|
|
544
|
+
"title": "Primary metric improved with treatment.",
|
|
545
|
+
"details": "The leading variant beat baseline on the primary metric with p-value below alpha.",
|
|
546
|
+
"evidence": { "variant": "treatment", "impact_percent": 3.8, "p_value": 0.013 } },
|
|
547
|
+
{ "rule": "sample_size_not_reached", "fired": false, "theme": "info",
|
|
548
|
+
"title": "Sample size not reached.",
|
|
549
|
+
"details": "Group-sequential analysis has not yet hit its planned sample size; treat results as interim.",
|
|
550
|
+
"evidence": {} },
|
|
551
|
+
{ "rule": "hypothesis_missing", "fired": false, "theme": "info",
|
|
552
|
+
"title": "No hypothesis recorded for this experiment.",
|
|
553
|
+
"details": "Without a written hypothesis it is hard to judge whether the result answers the original question.",
|
|
554
|
+
"evidence": {} },
|
|
555
|
+
{ "rule": "no_recommendation_overdue", "fired": false, "theme": "info",
|
|
556
|
+
"title": "Experiment has been running long without a recommended action.",
|
|
557
|
+
"details": "Consider whether to stop, full-on, or iterate; running indefinitely is rarely the best option.",
|
|
558
|
+
"evidence": { "days_running": 18 } },
|
|
559
|
+
{ "rule": "snapshot_unavailable", "fired": false, "theme": "info",
|
|
560
|
+
"title": "No metric snapshot is available yet.",
|
|
561
|
+
"details": "The previewer may not have processed this experiment; analysis is limited to design parameters.",
|
|
562
|
+
"evidence": {} }
|
|
563
|
+
]
|
|
564
|
+
}
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
Notice how the sections reinforce each other in this example:
|
|
568
|
+
|
|
569
|
+
- `metric_signals` shows the primary metric improving (`status: improves`) AND a guardrail contradicting (`status: contradicts`).
|
|
570
|
+
- That guardrail status drives `heuristic_output[guardrail_contradicts].fired = true` (theme `warning`).
|
|
571
|
+
- Both `guardrail_contradicts` (warning) and `primary_metric_significant_win` (success) fired. `recommendation` is the first fired warning, so the headline becomes "Review guardrail regressions…" — even though the primary metric is winning.
|
|
572
|
+
- `analysis_confidence` is `high`: all five factors are true (sample size reached, hypothesis present, primary metric present, guardrails present, no blocking alerts).
|
|
573
|
+
- `design_readout.benchmark.median_abs_impact` (3.35%) is computed from the two comparable peers; the summary phrasing "designed to detect effects in the expected range" follows because the median is ≥ 1.5× the MDE (2.5%).
|
|
574
|
+
- `source_signals` records one entry per derived field plus per peer-fetch — the audit trail an LLM analyst can cite.
|
|
575
|
+
|
|
576
|
+
**Example — distilled view via jq:**
|
|
577
|
+
|
|
578
|
+
```bash
|
|
579
|
+
$ abs experiments analyze 18234 \
|
|
580
|
+
| jq '{name: .experiment.name,
|
|
581
|
+
confidence: .analysis_confidence.level,
|
|
582
|
+
recommendation: .recommendation.title,
|
|
583
|
+
fired: [.heuristic_output[] | select(.fired) | .rule]}'
|
|
584
|
+
```
|
|
585
|
+
```json
|
|
586
|
+
{
|
|
587
|
+
"name": "checkout_redesign",
|
|
588
|
+
"confidence": "high",
|
|
589
|
+
"recommendation": "Review guardrail regressions before rolling out treatment.",
|
|
590
|
+
"fired": [
|
|
591
|
+
"guardrail_contradicts",
|
|
592
|
+
"primary_metric_significant_win"
|
|
593
|
+
]
|
|
594
|
+
}
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
Useful jq slices:
|
|
598
|
+
|
|
599
|
+
```bash
|
|
600
|
+
# Headline only
|
|
601
|
+
abs experiments analyze 123 \
|
|
602
|
+
| jq '{state: .experiment.state, confidence: .analysis_confidence.level, recommendation}'
|
|
603
|
+
|
|
604
|
+
# Just the rules that fired
|
|
605
|
+
abs experiments analyze 123 \
|
|
606
|
+
| jq '[.heuristic_output[] | select(.fired) | {rule, theme, title}]'
|
|
607
|
+
|
|
608
|
+
# Audit trail of where each derived field came from
|
|
609
|
+
abs experiments analyze 123 | jq .source_signals
|
|
610
|
+
|
|
611
|
+
# Compare two experiments at a glance
|
|
612
|
+
for id in 123 456; do
|
|
613
|
+
abs experiments analyze "$id" \
|
|
614
|
+
| jq --arg id "$id" '{id: $id, name: .experiment.name,
|
|
615
|
+
confidence: .analysis_confidence.level,
|
|
616
|
+
fired: [.heuristic_output[] | select(.fired) | .rule]}'
|
|
617
|
+
done
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
The 9 heuristic rules, in evaluation order:
|
|
621
|
+
|
|
622
|
+
| Rule | Theme | Fires when |
|
|
623
|
+
|---|---|---|
|
|
624
|
+
| `blocking_alert` | warning | active alert in `{srm, audience_mismatch, assignment_conflict, experiments_interact}` |
|
|
625
|
+
| `cleanup_needed` | warning | active `cleanup_needed` alert |
|
|
626
|
+
| `guardrail_contradicts` | warning | any guardrail metric_signal status is `contradicts` |
|
|
627
|
+
| `primary_metric_significant_loss` | warning | leading-variant primary `p_value < alpha` and `impact_percent < 0` |
|
|
628
|
+
| `primary_metric_significant_win` | success | leading-variant primary `p_value < alpha` and `impact_percent > 0` |
|
|
629
|
+
| `sample_size_not_reached` | info | running, group_sequential, no `sample_size_reached` alert |
|
|
630
|
+
| `hypothesis_missing` | info | `experiment.hypothesis` empty/null |
|
|
631
|
+
| `no_recommendation_overdue` | info | running ≥ 21 days without a recommended action |
|
|
632
|
+
| `snapshot_unavailable` | info | `metrics_snapshot` not yet computed by the previewer |
|
|
633
|
+
|
|
634
|
+
`recommendation` is the first fired `warning` rule, falling back to the first fired `success`, else `null`. When no human/AI analyst is available, `heuristic_output` is the deterministic baseline analysis.
|
|
635
|
+
|
|
636
|
+
`analysis_confidence.level` is computed from five boolean factors (`sample_size_reached`, `hypothesis_present`, `primary_metric_present`, `guardrails_present`, `no_blocking_alerts`): `low` when any blocking alert is active OR ≥2 factors are missing, `medium` when exactly one factor is missing, `high` when all five hold.
|
|
637
|
+
|
|
387
638
|
#### Custom field options
|
|
388
639
|
|
|
389
640
|
After running `abs experiments refresh-fields`, custom fields from your ABsmartly instance appear as native CLI options:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../../src/commands/experiments/analyze.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoBpC,eAAO,MAAM,cAAc,SAmBxB,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { getAPIClientFromOptions, getGlobalOptions, printFormatted, withErrorHandling, } from '../../lib/utils/api-helper.js';
|
|
3
|
+
import { parseExperimentIdOrName } from './resolve-id.js';
|
|
4
|
+
import { analyzeExperiment } from '../../core/experiments/analyze/index.js';
|
|
5
|
+
function isOutputExplicit(cmd) {
|
|
6
|
+
let current = cmd;
|
|
7
|
+
while (current) {
|
|
8
|
+
const src = current.getOptionValueSource('output');
|
|
9
|
+
if (src === 'cli' || src === 'env')
|
|
10
|
+
return true;
|
|
11
|
+
current = current.parent ?? undefined;
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
export const analyzeCommand = new Command('analyze')
|
|
16
|
+
.description('Analyze experiment design, signals, alerts, and related-experiment benchmarks')
|
|
17
|
+
.argument('<id>', 'experiment ID or name', parseExperimentIdOrName)
|
|
18
|
+
.action(withErrorHandling(async (nameOrId) => {
|
|
19
|
+
const globalOptions = getGlobalOptions(analyzeCommand);
|
|
20
|
+
if (!isOutputExplicit(analyzeCommand))
|
|
21
|
+
globalOptions.output = 'json';
|
|
22
|
+
const client = await getAPIClientFromOptions(globalOptions);
|
|
23
|
+
const id = await client.resolveExperimentId(nameOrId);
|
|
24
|
+
const result = await analyzeExperiment(client, { experimentId: id });
|
|
25
|
+
const useFull = globalOptions.output === 'json' || globalOptions.output === 'yaml';
|
|
26
|
+
if (useFull) {
|
|
27
|
+
printFormatted(result.data, globalOptions);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
printFormatted(result.detail, globalOptions);
|
|
31
|
+
}
|
|
32
|
+
}));
|
|
33
|
+
//# sourceMappingURL=analyze.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze.js","sourceRoot":"","sources":["../../../src/commands/experiments/analyze.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EACL,uBAAuB,EACvB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,GAClB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,yCAAyC,CAAC;AAE5E,SAAS,gBAAgB,CAAC,GAAY;IACpC,IAAI,OAAO,GAAwB,GAAG,CAAC;IACvC,OAAO,OAAO,EAAE,CAAC;QACf,MAAM,GAAG,GAAG,OAAO,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;QACnD,IAAI,GAAG,KAAK,KAAK,IAAI,GAAG,KAAK,KAAK;YAAE,OAAO,IAAI,CAAC;QAChD,OAAO,GAAG,OAAO,CAAC,MAAM,IAAI,SAAS,CAAC;IACxC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,OAAO,CAAC,SAAS,CAAC;KACjD,WAAW,CAAC,+EAA+E,CAAC;KAC5F,QAAQ,CAAC,MAAM,EAAE,uBAAuB,EAAE,uBAAuB,CAAC;KAClE,MAAM,CACL,iBAAiB,CAAC,KAAK,EAAE,QAAgB,EAAE,EAAE;IAC3C,MAAM,aAAa,GAAG,gBAAgB,CAAC,cAAc,CAAC,CAAC;IACvD,IAAI,CAAC,gBAAgB,CAAC,cAAc,CAAC;QAAE,aAAa,CAAC,MAAM,GAAG,MAAM,CAAC;IACrE,MAAM,MAAM,GAAG,MAAM,uBAAuB,CAAC,aAAa,CAAC,CAAC;IAC5D,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IAEtD,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,MAAM,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC;IAErE,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,KAAK,MAAM,IAAI,aAAa,CAAC,MAAM,KAAK,MAAM,CAAC;IACnF,IAAI,OAAO,EAAE,CAAC;QACZ,cAAc,CAAC,MAAM,CAAC,IAA0C,EAAE,aAAa,CAAC,CAAC;IACnF,CAAC;SAAM,CAAC;QACN,cAAc,CAAC,MAAM,CAAC,MAAO,EAAE,aAAa,CAAC,CAAC;IAChD,CAAC;AACH,CAAC,CAAC,CACH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/experiments/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/experiments/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoEpC,eAAO,MAAM,kBAAkB,SAKuB,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { listCommand } from './list.js';
|
|
3
3
|
import { getCommand } from './get.js';
|
|
4
|
+
import { analyzeCommand } from './analyze.js';
|
|
4
5
|
import { searchCommand } from './search.js';
|
|
5
6
|
import { startCommand } from './start.js';
|
|
6
7
|
import { stopCommand } from './stop.js';
|
|
@@ -32,6 +33,7 @@ import { estimateParticipantsCommand } from './estimate-participants.js';
|
|
|
32
33
|
const subcommands = [
|
|
33
34
|
listCommand,
|
|
34
35
|
getCommand,
|
|
36
|
+
analyzeCommand,
|
|
35
37
|
searchCommand,
|
|
36
38
|
createCommand,
|
|
37
39
|
cloneCommand,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/experiments/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAC;AAEzE,MAAM,WAAW,GAAG;IAClB,WAAW;IACX,UAAU;IACV,aAAa;IACb,aAAa;IACb,YAAY;IACZ,aAAa;IACb,YAAY;IACZ,WAAW;IACX,cAAc;IACd,eAAe;IACf,uBAAuB;IACvB,kBAAkB;IAClB,cAAc;IACd,aAAa;IACb,eAAe;IACf,cAAc;IACd,aAAa;IACb,aAAa;IACb,aAAa;IACb,eAAe;IACf,kBAAkB;IAClB,aAAa;IACb,sBAAsB;IACtB,aAAa;IACb,oBAAoB;IACpB,oBAAoB;IACpB,WAAW;IACX,YAAY;IACZ,WAAW;IACX,mBAAmB;IACnB,2BAA2B;CAC5B,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,OAAO,CAAC,aAAa,CAAC;KACzD,KAAK,CAAC,KAAK,CAAC;KACZ,KAAK,CAAC,YAAY,CAAC;KACnB,KAAK,CAAC,UAAU,CAAC;KACjB,KAAK,CAAC,SAAS,CAAC;KAChB,WAAW,CAAC,sCAAsC,CAAC,CAAC;AAEvD,KAAK,MAAM,GAAG,IAAI,WAAW;IAAE,kBAAkB,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/experiments/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAC;AAEzE,MAAM,WAAW,GAAG;IAClB,WAAW;IACX,UAAU;IACV,cAAc;IACd,aAAa;IACb,aAAa;IACb,YAAY;IACZ,aAAa;IACb,YAAY;IACZ,WAAW;IACX,cAAc;IACd,eAAe;IACf,uBAAuB;IACvB,kBAAkB;IAClB,cAAc;IACd,aAAa;IACb,eAAe;IACf,cAAc;IACd,aAAa;IACb,aAAa;IACb,aAAa;IACb,eAAe;IACf,kBAAkB;IAClB,aAAa;IACb,sBAAsB;IACtB,aAAa;IACb,oBAAoB;IACpB,oBAAoB;IACpB,WAAW;IACX,YAAY;IACZ,WAAW;IACX,mBAAmB;IACnB,2BAA2B;CAC5B,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,OAAO,CAAC,aAAa,CAAC;KACzD,KAAK,CAAC,KAAK,CAAC;KACZ,KAAK,CAAC,YAAY,CAAC;KACnB,KAAK,CAAC,UAAU,CAAC;KACjB,KAAK,CAAC,SAAS,CAAC;KAChB,WAAW,CAAC,sCAAsC,CAAC,CAAC;AAEvD,KAAK,MAAM,GAAG,IAAI,WAAW;IAAE,kBAAkB,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { LeadingVariant, MetricSignal } from './types.js';
|
|
2
|
+
interface Snapshot {
|
|
3
|
+
columnNames?: string[];
|
|
4
|
+
rows?: unknown[][];
|
|
5
|
+
}
|
|
6
|
+
interface VariantRef {
|
|
7
|
+
variant?: number;
|
|
8
|
+
name?: string;
|
|
9
|
+
}
|
|
10
|
+
interface MetricRef {
|
|
11
|
+
metric_id?: number;
|
|
12
|
+
type?: string;
|
|
13
|
+
metric?: {
|
|
14
|
+
id?: number;
|
|
15
|
+
name?: string;
|
|
16
|
+
lower_is_better?: boolean;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
interface Exp {
|
|
20
|
+
primary_metric_id?: number;
|
|
21
|
+
primary_metric?: {
|
|
22
|
+
id?: number;
|
|
23
|
+
name?: string;
|
|
24
|
+
lower_is_better?: boolean;
|
|
25
|
+
};
|
|
26
|
+
secondary_metrics?: MetricRef[];
|
|
27
|
+
variants?: VariantRef[];
|
|
28
|
+
required_alpha?: string | number | null;
|
|
29
|
+
metrics_snapshot?: Snapshot;
|
|
30
|
+
}
|
|
31
|
+
interface ExtractResult {
|
|
32
|
+
metricSignals: MetricSignal[];
|
|
33
|
+
leadingVariant: LeadingVariant | null;
|
|
34
|
+
participantCount: number | null;
|
|
35
|
+
}
|
|
36
|
+
export declare function extractSignals(experiment: Exp): ExtractResult;
|
|
37
|
+
export declare function leadingPrimaryImpactFromSnapshot(snapshot: {
|
|
38
|
+
columnNames?: string[];
|
|
39
|
+
rows?: unknown[][];
|
|
40
|
+
} | null | undefined, primaryMetricId: number): number | null;
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=extract-signals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract-signals.d.ts","sourceRoot":"","sources":["../../../../src/core/experiments/analyze/extract-signals.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAc,YAAY,EAAgB,MAAM,YAAY,CAAC;AAEzF,UAAU,QAAQ;IAChB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,IAAI,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC;CACpB;AAED,UAAU,UAAU;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,UAAU,SAAS;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CACpE;AAED,UAAU,GAAG;IACX,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC3E,iBAAiB,CAAC,EAAE,SAAS,EAAE,CAAC;IAChC,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACxC,gBAAgB,CAAC,EAAE,QAAQ,CAAC;CAC7B;AAED,UAAU,aAAa;IACrB,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,cAAc,EAAE,cAAc,GAAG,IAAI,CAAC;IACtC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAKD,wBAAgB,cAAc,CAAC,UAAU,EAAE,GAAG,GAAG,aAAa,CAqH7D;AA6BD,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,EAAE,EAAE,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,EAC3E,eAAe,EAAE,MAAM,GACtB,MAAM,GAAG,IAAI,CAgBf"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
const FLAT_PCT = 1; // |percent_change| under 1% counts as flat
|
|
2
|
+
const FLAT_PVALUE = 0.5;
|
|
3
|
+
export function extractSignals(experiment) {
|
|
4
|
+
const snap = experiment.metrics_snapshot;
|
|
5
|
+
if (!snap || !Array.isArray(snap.rows) || snap.rows.length === 0) {
|
|
6
|
+
return { metricSignals: [], leadingVariant: null, participantCount: null };
|
|
7
|
+
}
|
|
8
|
+
const cols = snap.columnNames ?? [];
|
|
9
|
+
const idx = (name) => cols.indexOf(name);
|
|
10
|
+
const cMetric = idx('metric_id');
|
|
11
|
+
const cVariant = idx('variant');
|
|
12
|
+
const cPct = idx('percent_change');
|
|
13
|
+
const cP = idx('p_value');
|
|
14
|
+
const cLow = idx('confidence_interval_low');
|
|
15
|
+
const cHigh = idx('confidence_interval_high');
|
|
16
|
+
const cUnits = idx('cum_unit_count');
|
|
17
|
+
const alpha = parseAlpha(experiment.required_alpha) ?? 0.1;
|
|
18
|
+
const variantNames = new Map();
|
|
19
|
+
for (const v of experiment.variants ?? []) {
|
|
20
|
+
if (typeof v.variant === 'number')
|
|
21
|
+
variantNames.set(v.variant, v.name ?? `Variant ${v.variant}`);
|
|
22
|
+
}
|
|
23
|
+
const primaryMetricId = experiment.primary_metric_id ?? experiment.primary_metric?.id ?? null;
|
|
24
|
+
const metricKindById = new Map();
|
|
25
|
+
const metricNameById = new Map();
|
|
26
|
+
const lowerIsBetter = new Map();
|
|
27
|
+
if (primaryMetricId !== null) {
|
|
28
|
+
metricKindById.set(primaryMetricId, 'primary');
|
|
29
|
+
metricNameById.set(primaryMetricId, experiment.primary_metric?.name ?? `metric_${primaryMetricId}`);
|
|
30
|
+
if (experiment.primary_metric?.lower_is_better)
|
|
31
|
+
lowerIsBetter.set(primaryMetricId, true);
|
|
32
|
+
}
|
|
33
|
+
for (const sm of experiment.secondary_metrics ?? []) {
|
|
34
|
+
const mid = sm.metric_id ?? sm.metric?.id;
|
|
35
|
+
if (typeof mid !== 'number')
|
|
36
|
+
continue;
|
|
37
|
+
const kind = sm.type === 'guardrail'
|
|
38
|
+
? 'guardrail'
|
|
39
|
+
: sm.type === 'exploratory'
|
|
40
|
+
? 'exploratory'
|
|
41
|
+
: 'secondary';
|
|
42
|
+
if (!metricKindById.has(mid))
|
|
43
|
+
metricKindById.set(mid, kind);
|
|
44
|
+
if (!metricNameById.has(mid))
|
|
45
|
+
metricNameById.set(mid, sm.metric?.name ?? `metric_${mid}`);
|
|
46
|
+
if (sm.metric?.lower_is_better)
|
|
47
|
+
lowerIsBetter.set(mid, true);
|
|
48
|
+
}
|
|
49
|
+
const signals = [];
|
|
50
|
+
let bestPrimary = null;
|
|
51
|
+
let participantCount = null;
|
|
52
|
+
for (const row of snap.rows) {
|
|
53
|
+
if (!Array.isArray(row))
|
|
54
|
+
continue;
|
|
55
|
+
const metricId = numAt(row, cMetric);
|
|
56
|
+
const variantId = numAt(row, cVariant);
|
|
57
|
+
if (metricId === null || variantId === null)
|
|
58
|
+
continue;
|
|
59
|
+
if (variantId === 0) {
|
|
60
|
+
// baseline / control row — skip signal emission, but still use for participant count
|
|
61
|
+
const u = numAt(row, cUnits);
|
|
62
|
+
if (primaryMetricId !== null && metricId === primaryMetricId && u !== null) {
|
|
63
|
+
participantCount = participantCount === null ? u : Math.max(participantCount, u);
|
|
64
|
+
}
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const percent = numAt(row, cPct);
|
|
68
|
+
const pValue = numAt(row, cP);
|
|
69
|
+
const ciLow = numAt(row, cLow);
|
|
70
|
+
const ciHigh = numAt(row, cHigh);
|
|
71
|
+
const units = numAt(row, cUnits);
|
|
72
|
+
if (primaryMetricId !== null && metricId === primaryMetricId && units !== null) {
|
|
73
|
+
participantCount = participantCount === null ? units : Math.max(participantCount, units);
|
|
74
|
+
}
|
|
75
|
+
const kind = metricKindById.get(metricId) ?? 'secondary';
|
|
76
|
+
const status = classifyStatus(percent, pValue, alpha, lowerIsBetter.get(metricId) ?? false);
|
|
77
|
+
signals.push({
|
|
78
|
+
metric_id: metricId,
|
|
79
|
+
metric_name: metricNameById.get(metricId) ?? `metric_${metricId}`,
|
|
80
|
+
metric_type: kind,
|
|
81
|
+
variant_id: variantId,
|
|
82
|
+
variant_name: variantNames.get(variantId) ?? `Variant ${variantId}`,
|
|
83
|
+
percent_change: percent,
|
|
84
|
+
p_value: pValue,
|
|
85
|
+
ci_low: ciLow,
|
|
86
|
+
ci_high: ciHigh,
|
|
87
|
+
status,
|
|
88
|
+
});
|
|
89
|
+
if (primaryMetricId !== null &&
|
|
90
|
+
metricId === primaryMetricId &&
|
|
91
|
+
typeof percent === 'number' &&
|
|
92
|
+
Number.isFinite(percent)) {
|
|
93
|
+
if (bestPrimary === null || percent > bestPrimary.impact) {
|
|
94
|
+
bestPrimary = { variantId, impact: percent, pValue };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const leadingVariant = bestPrimary
|
|
99
|
+
? {
|
|
100
|
+
variant_id: bestPrimary.variantId,
|
|
101
|
+
variant_name: variantNames.get(bestPrimary.variantId) ?? `Variant ${bestPrimary.variantId}`,
|
|
102
|
+
impact_percent: bestPrimary.impact,
|
|
103
|
+
p_value: bestPrimary.pValue,
|
|
104
|
+
}
|
|
105
|
+
: null;
|
|
106
|
+
return { metricSignals: signals, leadingVariant, participantCount };
|
|
107
|
+
}
|
|
108
|
+
function numAt(row, i) {
|
|
109
|
+
if (i < 0 || i >= row.length)
|
|
110
|
+
return null;
|
|
111
|
+
const v = row[i];
|
|
112
|
+
return typeof v === 'number' && Number.isFinite(v) ? v : null;
|
|
113
|
+
}
|
|
114
|
+
function parseAlpha(value) {
|
|
115
|
+
if (value === null || value === undefined)
|
|
116
|
+
return null;
|
|
117
|
+
const n = typeof value === 'string' ? parseFloat(value) : value;
|
|
118
|
+
return Number.isFinite(n) ? n : null;
|
|
119
|
+
}
|
|
120
|
+
function classifyStatus(percent, pValue, alpha, lowerIsBetter) {
|
|
121
|
+
if (percent === null || pValue === null)
|
|
122
|
+
return 'inconclusive';
|
|
123
|
+
const significant = pValue < alpha;
|
|
124
|
+
const directionGood = lowerIsBetter ? percent < 0 : percent > 0;
|
|
125
|
+
if (significant && directionGood)
|
|
126
|
+
return 'improves';
|
|
127
|
+
if (significant && !directionGood)
|
|
128
|
+
return 'contradicts';
|
|
129
|
+
if (Math.abs(percent) < FLAT_PCT && pValue > FLAT_PVALUE)
|
|
130
|
+
return 'flat';
|
|
131
|
+
return 'inconclusive';
|
|
132
|
+
}
|
|
133
|
+
export function leadingPrimaryImpactFromSnapshot(snapshot, primaryMetricId) {
|
|
134
|
+
if (!snapshot || !Array.isArray(snapshot.rows))
|
|
135
|
+
return null;
|
|
136
|
+
const cols = snapshot.columnNames ?? [];
|
|
137
|
+
const cMetric = cols.indexOf('metric_id');
|
|
138
|
+
const cVariant = cols.indexOf('variant');
|
|
139
|
+
const cPct = cols.indexOf('percent_change');
|
|
140
|
+
let best = null;
|
|
141
|
+
for (const row of snapshot.rows) {
|
|
142
|
+
if (!Array.isArray(row))
|
|
143
|
+
continue;
|
|
144
|
+
const m = numAt(row, cMetric);
|
|
145
|
+
const v = numAt(row, cVariant);
|
|
146
|
+
const p = numAt(row, cPct);
|
|
147
|
+
if (m !== primaryMetricId || v === null || v === 0 || p === null)
|
|
148
|
+
continue;
|
|
149
|
+
if (best === null || p > best)
|
|
150
|
+
best = p;
|
|
151
|
+
}
|
|
152
|
+
return best;
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=extract-signals.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract-signals.js","sourceRoot":"","sources":["../../../../src/core/experiments/analyze/extract-signals.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { AnalysisConfidence, DesignReadout, HeuristicEntry, LeadingVariant, MetricSignal, Recommendation } from './types.js';
|
|
2
|
+
interface AlertLite {
|
|
3
|
+
id: number;
|
|
4
|
+
type: string;
|
|
5
|
+
dismissed: boolean;
|
|
6
|
+
}
|
|
7
|
+
interface ExpLite {
|
|
8
|
+
state?: string;
|
|
9
|
+
hypothesis?: string | null;
|
|
10
|
+
primary_metric_id?: number | null;
|
|
11
|
+
required_alpha?: string | number | null;
|
|
12
|
+
required_power?: string | number | null;
|
|
13
|
+
analysis_type?: string | null;
|
|
14
|
+
minimum_detectable_effect?: number | null;
|
|
15
|
+
baseline_primary_metric_mean?: number | null;
|
|
16
|
+
baseline_participants_per_day?: number | null;
|
|
17
|
+
percentage_of_traffic?: number | null;
|
|
18
|
+
secondary_metrics?: Array<{
|
|
19
|
+
type?: string;
|
|
20
|
+
}>;
|
|
21
|
+
recommended_action?: unknown;
|
|
22
|
+
started_at?: string | null;
|
|
23
|
+
stopped_at?: string | null;
|
|
24
|
+
}
|
|
25
|
+
export interface HeuristicsInput {
|
|
26
|
+
experiment: ExpLite;
|
|
27
|
+
alerts: AlertLite[];
|
|
28
|
+
metricSignals: MetricSignal[];
|
|
29
|
+
leadingVariant: LeadingVariant | null;
|
|
30
|
+
participantCount: number | null;
|
|
31
|
+
benchmark: {
|
|
32
|
+
observed_impacts: number[];
|
|
33
|
+
median_abs_impact: number | null;
|
|
34
|
+
} | null;
|
|
35
|
+
}
|
|
36
|
+
export declare function runHeuristics(input: HeuristicsInput): {
|
|
37
|
+
heuristicOutput: HeuristicEntry[];
|
|
38
|
+
recommendation: Recommendation | null;
|
|
39
|
+
};
|
|
40
|
+
export declare function computeAnalysisConfidence(input: HeuristicsInput): AnalysisConfidence;
|
|
41
|
+
export declare function computeDesignReadout(input: HeuristicsInput): DesignReadout;
|
|
42
|
+
export {};
|
|
43
|
+
//# sourceMappingURL=heuristics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heuristics.d.ts","sourceRoot":"","sources":["../../../../src/core/experiments/analyze/heuristics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,aAAa,EACb,cAAc,EACd,cAAc,EACd,YAAY,EACZ,cAAc,EAEf,MAAM,YAAY,CAAC;AAYpB,UAAU,SAAS;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,UAAU,OAAO;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACxC,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACxC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,yBAAyB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,4BAA4B,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,6BAA6B,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9C,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,iBAAiB,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7C,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,cAAc,EAAE,cAAc,GAAG,IAAI,CAAC;IACtC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,SAAS,EAAE;QAAE,gBAAgB,EAAE,MAAM,EAAE,CAAC;QAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;CACpF;AAsID,wBAAgB,aAAa,CAAC,KAAK,EAAE,eAAe,GAAG;IACrD,eAAe,EAAE,cAAc,EAAE,CAAC;IAClC,cAAc,EAAE,cAAc,GAAG,IAAI,CAAC;CACvC,CAOA;AAUD,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,eAAe,GAAG,kBAAkB,CAoCpF;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,eAAe,GAAG,aAAa,CA+C1E"}
|