@idlekit/cli 0.5.0 → 0.5.1
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/main.js +1146 -1065
- package/dist/main.js.map +9 -6
- package/dist/runtime/workers/entry.js +361 -3
- package/dist/runtime/workers/entry.js.map +7 -4
- package/package.json +2 -2
package/dist/main.js
CHANGED
|
@@ -19440,448 +19440,806 @@ function compareScenarios(args) {
|
|
|
19440
19440
|
};
|
|
19441
19441
|
}
|
|
19442
19442
|
|
|
19443
|
-
// ../core/dist/
|
|
19444
|
-
|
|
19445
|
-
|
|
19446
|
-
|
|
19447
|
-
|
|
19448
|
-
|
|
19449
|
-
|
|
19450
|
-
init_emitter();
|
|
19451
|
-
init_tickMoney2();
|
|
19452
|
-
init_suffixer();
|
|
19453
|
-
init_formatMoney2();
|
|
19454
|
-
init_parseMoney2();
|
|
19455
|
-
init_visibilityTracker2();
|
|
19456
|
-
init_moneyState();
|
|
19457
|
-
init_simState();
|
|
19458
|
-
init_types7();
|
|
19459
|
-
init_validate();
|
|
19460
|
-
init_concreteValidator();
|
|
19461
|
-
init_scenarioContract();
|
|
19462
|
-
init_compile2();
|
|
19463
|
-
init_simulator();
|
|
19464
|
-
init_advance();
|
|
19465
|
-
init_step();
|
|
19466
|
-
init_stepTypes();
|
|
19467
|
-
init_offline();
|
|
19468
|
-
init_session();
|
|
19469
|
-
init_monteCarlo();
|
|
19470
|
-
init_executionBackend();
|
|
19471
|
-
init_integrationMatrix();
|
|
19472
|
-
init_runFactory();
|
|
19473
|
-
init_constraints();
|
|
19474
|
-
init_observation();
|
|
19475
|
-
init_types8();
|
|
19476
|
-
init_contracts();
|
|
19477
|
-
init_stability();
|
|
19478
|
-
init_scripted();
|
|
19479
|
-
init_greedy();
|
|
19480
|
-
init_planner();
|
|
19481
|
-
init_params();
|
|
19482
|
-
init_builtins();
|
|
19483
|
-
init_tuneSpec();
|
|
19484
|
-
init_validate2();
|
|
19485
|
-
init_objective();
|
|
19486
|
-
init_params2();
|
|
19487
|
-
init_builtins2();
|
|
19488
|
-
init_tuner();
|
|
19489
|
-
init_randomSearch();
|
|
19490
|
-
init_runner();
|
|
19491
|
-
init_eta();
|
|
19492
|
-
init_prestigeCycle();
|
|
19493
|
-
init_resetCycle();
|
|
19494
|
-
init_resetOptimization();
|
|
19495
|
-
init_distribution();
|
|
19496
|
-
init_experiment();
|
|
19497
|
-
init_sensitivity();
|
|
19498
|
-
init_growthFit();
|
|
19499
|
-
init_contracts2();
|
|
19500
|
-
init_metrics();
|
|
19501
|
-
init_registry();
|
|
19502
|
-
init_analyzers();
|
|
19503
|
-
init_inspect();
|
|
19504
|
-
init_checks3();
|
|
19505
|
-
init_judge();
|
|
19506
|
-
init_flow();
|
|
19507
|
-
init_jevAdvisory();
|
|
19508
|
-
});
|
|
19509
|
-
|
|
19510
|
-
// src/runtime/bun.ts
|
|
19511
|
-
var {$ } = globalThis.Bun;
|
|
19512
|
-
import { dirname } from "path";
|
|
19513
|
-
async function readTextFile(path) {
|
|
19514
|
-
return Bun.file(path).text();
|
|
19515
|
-
}
|
|
19516
|
-
async function readJsonFile(path) {
|
|
19517
|
-
return Bun.file(path).json();
|
|
19518
|
-
}
|
|
19519
|
-
async function writeTextFile(path, body) {
|
|
19520
|
-
try {
|
|
19521
|
-
await ensureDir(dirname(path));
|
|
19522
|
-
await Bun.write(path, body);
|
|
19523
|
-
} catch (error) {
|
|
19524
|
-
throw cliError("OUTPUT_WRITE_FAILED", `Unable to write output file: ${path}`, {
|
|
19525
|
-
detail: errorDetail(error),
|
|
19526
|
-
hint: "Choose a writable output path and check that its parent is a directory.",
|
|
19527
|
-
cause: error
|
|
19528
|
-
});
|
|
19529
|
-
}
|
|
19530
|
-
}
|
|
19531
|
-
function writeStdout(body) {
|
|
19532
|
-
return new Promise((resolve, reject) => {
|
|
19533
|
-
process.stdout.write(body, (error) => error ? reject(error) : resolve());
|
|
19534
|
-
});
|
|
19535
|
-
}
|
|
19536
|
-
async function ensureDir(path) {
|
|
19537
|
-
await $`mkdir -p ${path}`.quiet();
|
|
19538
|
-
}
|
|
19539
|
-
async function fileExists(path) {
|
|
19540
|
-
return Bun.file(path).exists();
|
|
19541
|
-
}
|
|
19542
|
-
function sha256Hex(value) {
|
|
19543
|
-
const hasher = new Bun.CryptoHasher("sha256");
|
|
19544
|
-
hasher.update(value);
|
|
19545
|
-
return hasher.digest("hex");
|
|
19546
|
-
}
|
|
19547
|
-
function runText(args, opts) {
|
|
19548
|
-
const proc = Bun.spawnSync(args, {
|
|
19549
|
-
cwd: opts?.cwd,
|
|
19550
|
-
env: opts?.env,
|
|
19551
|
-
stdout: "pipe",
|
|
19552
|
-
stderr: "pipe",
|
|
19553
|
-
maxBuffer: MAX_CAPTURE_BYTES
|
|
19554
|
-
});
|
|
19555
|
-
if (proc.exitCode !== 0) {
|
|
19556
|
-
const stderr = proc.stderr.toString().trim();
|
|
19557
|
-
const stdout = proc.stdout.toString().trim();
|
|
19558
|
-
const detail = stderr || stdout || `command failed with exit code ${proc.exitCode}`;
|
|
19559
|
-
throw new Error(detail);
|
|
19560
|
-
}
|
|
19561
|
-
return proc.stdout.toString();
|
|
19443
|
+
// ../core/dist/ltv/projection.js
|
|
19444
|
+
function clamp012(v) {
|
|
19445
|
+
if (v < 0)
|
|
19446
|
+
return 0;
|
|
19447
|
+
if (v > 1)
|
|
19448
|
+
return 1;
|
|
19449
|
+
return v;
|
|
19562
19450
|
}
|
|
19563
|
-
|
|
19564
|
-
|
|
19565
|
-
|
|
19566
|
-
|
|
19567
|
-
|
|
19568
|
-
|
|
19569
|
-
|
|
19570
|
-
var require_package = __commonJS(function(exports, module) {
|
|
19571
|
-
module.exports = {
|
|
19572
|
-
name: "@idlekit/cli",
|
|
19573
|
-
version: "0.5.0",
|
|
19574
|
-
description: "Bun-first idk CLI for idle game scenario validation, simulation, tuning, and reporting.",
|
|
19575
|
-
license: "MIT",
|
|
19576
|
-
type: "module",
|
|
19577
|
-
files: [
|
|
19578
|
-
"dist",
|
|
19579
|
-
"README.md",
|
|
19580
|
-
"LICENSE"
|
|
19581
|
-
],
|
|
19582
|
-
bin: {
|
|
19583
|
-
idk: "dist/main.js"
|
|
19584
|
-
},
|
|
19585
|
-
repository: {
|
|
19586
|
-
type: "git",
|
|
19587
|
-
url: "git+https://github.com/imjlk/idlekit.git",
|
|
19588
|
-
directory: "packages/cli"
|
|
19589
|
-
},
|
|
19590
|
-
homepage: "https://github.com/imjlk/idlekit/tree/main/packages/cli#readme",
|
|
19591
|
-
bugs: {
|
|
19592
|
-
url: "https://github.com/imjlk/idlekit/issues"
|
|
19593
|
-
},
|
|
19594
|
-
keywords: [
|
|
19595
|
-
"bun",
|
|
19596
|
-
"idle-game",
|
|
19597
|
-
"incremental-game",
|
|
19598
|
-
"cli",
|
|
19599
|
-
"economy-simulator",
|
|
19600
|
-
"game-balancing",
|
|
19601
|
-
"simulation"
|
|
19602
|
-
],
|
|
19603
|
-
publishConfig: {
|
|
19604
|
-
access: "public"
|
|
19451
|
+
function deriveMonetizationConfig(scenario) {
|
|
19452
|
+
const m = scenario.monetization;
|
|
19453
|
+
const q = (m?.uncertainty?.quantiles ?? [0.5, 0.9]).filter((x) => typeof x === "number");
|
|
19454
|
+
const quantiles = q.length > 0 ? [...new Set(q)].sort((a, b) => a - b) : [0.5, 0.9];
|
|
19455
|
+
return {
|
|
19456
|
+
cohorts: {
|
|
19457
|
+
baseUsers: m?.cohorts?.baseUsers ?? 1
|
|
19605
19458
|
},
|
|
19606
|
-
|
|
19607
|
-
|
|
19459
|
+
retention: {
|
|
19460
|
+
d1: m?.retention?.d1 ?? 0.42,
|
|
19461
|
+
d7: m?.retention?.d7 ?? 0.2,
|
|
19462
|
+
d30: m?.retention?.d30 ?? 0.09,
|
|
19463
|
+
d90: m?.retention?.d90 ?? 0.04,
|
|
19464
|
+
longTailDailyDecay: m?.retention?.longTailDailyDecay ?? 0.02
|
|
19608
19465
|
},
|
|
19609
|
-
|
|
19610
|
-
|
|
19611
|
-
|
|
19612
|
-
|
|
19613
|
-
|
|
19614
|
-
|
|
19615
|
-
|
|
19616
|
-
|
|
19617
|
-
typecheck: "ttsc -p tsconfig.json --noEmit",
|
|
19618
|
-
test: "bun scripts/cli-bundle.ts --outdir .test-bundle && IDLEKIT_CLI_ENTRY=.test-bundle/main.js bun test --timeout 90000 src",
|
|
19619
|
-
prepack: "bun run build && cd ../.. && bun tools/package-manifest.ts prepare packages/cli",
|
|
19620
|
-
postpack: "cd ../.. && bun tools/package-manifest.ts restore packages/cli"
|
|
19466
|
+
revenue: {
|
|
19467
|
+
payerConversion: m?.revenue?.payerConversion ?? 0.03,
|
|
19468
|
+
arppuDaily: m?.revenue?.arppuDaily ?? 0.7,
|
|
19469
|
+
adArpDau: m?.revenue?.adArpDau ?? 0.02,
|
|
19470
|
+
platformFeeRate: m?.revenue?.platformFeeRate ?? 0.3,
|
|
19471
|
+
grossMarginRate: m?.revenue?.grossMarginRate ?? 0.92,
|
|
19472
|
+
progressionRevenueLift: m?.revenue?.progressionRevenueLift ?? 0.4,
|
|
19473
|
+
progressionLogSpan: m?.revenue?.progressionLogSpan ?? 6
|
|
19621
19474
|
},
|
|
19622
|
-
|
|
19623
|
-
|
|
19624
|
-
"@gunshi/plugin-completion": "0.37.3",
|
|
19625
|
-
"@gunshi/plugin-suggestion": "0.37.3",
|
|
19626
|
-
"@idlekit/core": "0.4.1",
|
|
19627
|
-
ajv: "^8.20.0",
|
|
19628
|
-
gunshi: "0.37.3",
|
|
19629
|
-
"proper-lockfile": "4.1.2",
|
|
19630
|
-
yaml: "^2.9.1",
|
|
19631
|
-
zod: "^4.6.5"
|
|
19475
|
+
acquisition: {
|
|
19476
|
+
cpi: m?.acquisition?.cpi ?? 0
|
|
19632
19477
|
},
|
|
19633
|
-
|
|
19634
|
-
|
|
19478
|
+
uncertainty: {
|
|
19479
|
+
enabled: m?.uncertainty?.enabled ?? false,
|
|
19480
|
+
draws: m?.uncertainty?.draws ?? 300,
|
|
19481
|
+
quantiles,
|
|
19482
|
+
seed: m?.uncertainty?.seed,
|
|
19483
|
+
sigma: {
|
|
19484
|
+
retention: m?.uncertainty?.sigma?.retention ?? 0.08,
|
|
19485
|
+
conversion: m?.uncertainty?.sigma?.conversion ?? 0.12,
|
|
19486
|
+
arppu: m?.uncertainty?.sigma?.arppu ?? 0.2,
|
|
19487
|
+
ad: m?.uncertainty?.sigma?.ad ?? 0.15
|
|
19488
|
+
},
|
|
19489
|
+
correlation: {
|
|
19490
|
+
retentionConversion: m?.uncertainty?.correlation?.retentionConversion ?? 0,
|
|
19491
|
+
retentionArppu: m?.uncertainty?.correlation?.retentionArppu ?? 0,
|
|
19492
|
+
retentionAd: m?.uncertainty?.correlation?.retentionAd ?? 0,
|
|
19493
|
+
conversionArppu: m?.uncertainty?.correlation?.conversionArppu ?? 0,
|
|
19494
|
+
conversionAd: m?.uncertainty?.correlation?.conversionAd ?? 0,
|
|
19495
|
+
arppuAd: m?.uncertainty?.correlation?.arppuAd ?? 0
|
|
19496
|
+
}
|
|
19635
19497
|
}
|
|
19636
19498
|
};
|
|
19637
|
-
});
|
|
19638
|
-
|
|
19639
|
-
// src/runtime/selfCli.ts
|
|
19640
|
-
import { existsSync } from "fs";
|
|
19641
|
-
import { dirname as dirname3, resolve as resolve2 } from "path";
|
|
19642
|
-
function currentEntry() {
|
|
19643
|
-
return Bun.main;
|
|
19644
19499
|
}
|
|
19645
|
-
function
|
|
19646
|
-
|
|
19500
|
+
function retentionAtDay(ret, day) {
|
|
19501
|
+
if (day <= 0)
|
|
19502
|
+
return 1;
|
|
19503
|
+
if (day <= 1)
|
|
19504
|
+
return ret.d1;
|
|
19505
|
+
if (day <= 7)
|
|
19506
|
+
return ret.d1 + (ret.d7 - ret.d1) * (day - 1) / 6;
|
|
19507
|
+
if (day <= 30)
|
|
19508
|
+
return ret.d7 + (ret.d30 - ret.d7) * (day - 7) / 23;
|
|
19509
|
+
if (day <= 90)
|
|
19510
|
+
return ret.d30 + (ret.d90 - ret.d30) * (day - 30) / 60;
|
|
19511
|
+
const extra = day - 90;
|
|
19512
|
+
return clamp012(ret.d90 * Math.exp(-ret.longTailDailyDecay * extra));
|
|
19647
19513
|
}
|
|
19648
|
-
function
|
|
19649
|
-
|
|
19514
|
+
function progressionFactor(startWorthLog10, endWorthLog10, span) {
|
|
19515
|
+
if (!Number.isFinite(startWorthLog10) || !Number.isFinite(endWorthLog10) || !Number.isFinite(span) || span <= 0) {
|
|
19516
|
+
return 0;
|
|
19517
|
+
}
|
|
19518
|
+
return clamp012((endWorthLog10 - startWorthLog10) / span);
|
|
19650
19519
|
}
|
|
19651
|
-
function
|
|
19652
|
-
|
|
19520
|
+
function estimateLtvPerUser(args) {
|
|
19521
|
+
const { config, horizonDays } = args;
|
|
19522
|
+
const progression = clamp012(args.progression);
|
|
19523
|
+
const arppuDaily = config.revenue.arppuDaily * (1 + progression * config.revenue.progressionRevenueLift);
|
|
19524
|
+
const adArpDau = config.revenue.adArpDau * (1 + progression * config.revenue.progressionRevenueLift * 0.5);
|
|
19525
|
+
const payerConversion = config.revenue.payerConversion;
|
|
19526
|
+
const fullDays = Math.floor(Math.max(0, horizonDays));
|
|
19527
|
+
const remainder = Math.max(0, horizonDays - fullDays);
|
|
19528
|
+
let gross = 0;
|
|
19529
|
+
for (let day = 1;day <= fullDays; day++) {
|
|
19530
|
+
const active = retentionAtDay(config.retention, day);
|
|
19531
|
+
gross += active * (payerConversion * arppuDaily + adArpDau);
|
|
19532
|
+
}
|
|
19533
|
+
if (remainder > 0) {
|
|
19534
|
+
const active = retentionAtDay(config.retention, fullDays + 1);
|
|
19535
|
+
gross += remainder * active * (payerConversion * arppuDaily + adArpDau);
|
|
19536
|
+
}
|
|
19537
|
+
const net = gross * (1 - config.revenue.platformFeeRate) * config.revenue.grossMarginRate;
|
|
19538
|
+
const ltv = net - config.acquisition.cpi;
|
|
19539
|
+
return {
|
|
19540
|
+
cumulativeGrossRevenuePerUser: gross,
|
|
19541
|
+
cumulativeNetRevenuePerUser: net,
|
|
19542
|
+
cumulativeLtvPerUser: ltv
|
|
19543
|
+
};
|
|
19653
19544
|
}
|
|
19654
|
-
|
|
19655
|
-
|
|
19656
|
-
|
|
19657
|
-
if (
|
|
19658
|
-
return
|
|
19659
|
-
|
|
19545
|
+
|
|
19546
|
+
// ../core/dist/ltv/uncertainty.js
|
|
19547
|
+
function clamp013(v) {
|
|
19548
|
+
if (v < 0)
|
|
19549
|
+
return 0;
|
|
19550
|
+
if (v > 1)
|
|
19551
|
+
return 1;
|
|
19552
|
+
return v;
|
|
19660
19553
|
}
|
|
19661
|
-
function
|
|
19662
|
-
|
|
19663
|
-
|
|
19664
|
-
|
|
19665
|
-
|
|
19554
|
+
function clampCorrelation(v) {
|
|
19555
|
+
if (!Number.isFinite(v))
|
|
19556
|
+
return 0;
|
|
19557
|
+
if (v < -1)
|
|
19558
|
+
return -1;
|
|
19559
|
+
if (v > 1)
|
|
19560
|
+
return 1;
|
|
19561
|
+
return v;
|
|
19666
19562
|
}
|
|
19667
|
-
function
|
|
19668
|
-
|
|
19669
|
-
|
|
19670
|
-
|
|
19671
|
-
|
|
19672
|
-
|
|
19673
|
-
|
|
19674
|
-
|
|
19675
|
-
|
|
19563
|
+
function randNormal(rng) {
|
|
19564
|
+
const u1 = Math.max(0.000000000001, rng());
|
|
19565
|
+
const u2 = Math.max(0.000000000001, rng());
|
|
19566
|
+
return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
|
|
19567
|
+
}
|
|
19568
|
+
function buildCorrelationMatrix(base) {
|
|
19569
|
+
const rc = clampCorrelation(base.retentionConversion);
|
|
19570
|
+
const ra = clampCorrelation(base.retentionArppu);
|
|
19571
|
+
const rd = clampCorrelation(base.retentionAd);
|
|
19572
|
+
const ca = clampCorrelation(base.conversionArppu);
|
|
19573
|
+
const cd = clampCorrelation(base.conversionAd);
|
|
19574
|
+
const ad = clampCorrelation(base.arppuAd);
|
|
19575
|
+
return [
|
|
19576
|
+
[1, rc, ra, rd],
|
|
19577
|
+
[rc, 1, ca, cd],
|
|
19578
|
+
[ra, ca, 1, ad],
|
|
19579
|
+
[rd, cd, ad, 1]
|
|
19580
|
+
];
|
|
19581
|
+
}
|
|
19582
|
+
function withOffDiagonalScale(matrix, alpha) {
|
|
19583
|
+
return matrix.map((row, i) => row.map((v, j) => {
|
|
19584
|
+
if (i === j)
|
|
19585
|
+
return 1;
|
|
19586
|
+
return v * alpha;
|
|
19587
|
+
}));
|
|
19588
|
+
}
|
|
19589
|
+
function cholesky4(matrix) {
|
|
19590
|
+
const n = 4;
|
|
19591
|
+
const l = Array.from({ length: n }, () => Array.from({ length: n }, () => 0));
|
|
19592
|
+
for (let i = 0;i < n; i++) {
|
|
19593
|
+
for (let j = 0;j <= i; j++) {
|
|
19594
|
+
let sum = 0;
|
|
19595
|
+
for (let k = 0;k < j; k++) {
|
|
19596
|
+
sum += (l[i]?.[k] ?? 0) * (l[j]?.[k] ?? 0);
|
|
19597
|
+
}
|
|
19598
|
+
if (i === j) {
|
|
19599
|
+
const val = (matrix[i]?.[i] ?? 0) - sum;
|
|
19600
|
+
if (val <= 0.000000001)
|
|
19601
|
+
return null;
|
|
19602
|
+
l[i][j] = Math.sqrt(val);
|
|
19603
|
+
} else {
|
|
19604
|
+
const denom = l[j]?.[j] ?? 0;
|
|
19605
|
+
if (Math.abs(denom) < 0.000000001)
|
|
19606
|
+
return null;
|
|
19607
|
+
l[i][j] = ((matrix[i]?.[j] ?? 0) - sum) / denom;
|
|
19608
|
+
}
|
|
19609
|
+
}
|
|
19676
19610
|
}
|
|
19677
|
-
|
|
19678
|
-
|
|
19679
|
-
|
|
19611
|
+
return l;
|
|
19612
|
+
}
|
|
19613
|
+
function multiplyLowerTriangular(l, z) {
|
|
19614
|
+
const out = [0, 0, 0, 0];
|
|
19615
|
+
for (let i = 0;i < 4; i++) {
|
|
19616
|
+
let sum = 0;
|
|
19617
|
+
for (let j = 0;j <= i; j++) {
|
|
19618
|
+
sum += (l[i]?.[j] ?? 0) * (z[j] ?? 0);
|
|
19619
|
+
}
|
|
19620
|
+
out[i] = sum;
|
|
19680
19621
|
}
|
|
19681
|
-
return
|
|
19622
|
+
return out;
|
|
19682
19623
|
}
|
|
19683
|
-
function
|
|
19684
|
-
const
|
|
19685
|
-
|
|
19686
|
-
|
|
19687
|
-
|
|
19688
|
-
|
|
19689
|
-
|
|
19690
|
-
|
|
19624
|
+
function sampleCorrelatedNormals(rng, corr) {
|
|
19625
|
+
const z = [randNormal(rng), randNormal(rng), randNormal(rng), randNormal(rng)];
|
|
19626
|
+
const matrix = buildCorrelationMatrix(corr);
|
|
19627
|
+
let l = cholesky4(matrix);
|
|
19628
|
+
let alpha = 0.99;
|
|
19629
|
+
while (!l && alpha > 0.000001) {
|
|
19630
|
+
l = cholesky4(withOffDiagonalScale(matrix, alpha));
|
|
19631
|
+
alpha *= 0.8;
|
|
19632
|
+
}
|
|
19633
|
+
if (!l) {
|
|
19634
|
+
return {
|
|
19635
|
+
retention: z[0] ?? 0,
|
|
19636
|
+
conversion: z[1] ?? 0,
|
|
19637
|
+
arppu: z[2] ?? 0,
|
|
19638
|
+
ad: z[3] ?? 0
|
|
19639
|
+
};
|
|
19640
|
+
}
|
|
19641
|
+
const c = multiplyLowerTriangular(l, z);
|
|
19691
19642
|
return {
|
|
19692
|
-
|
|
19693
|
-
|
|
19694
|
-
|
|
19643
|
+
retention: c[0] ?? 0,
|
|
19644
|
+
conversion: c[1] ?? 0,
|
|
19645
|
+
arppu: c[2] ?? 0,
|
|
19646
|
+
ad: c[3] ?? 0
|
|
19695
19647
|
};
|
|
19696
19648
|
}
|
|
19697
|
-
|
|
19698
|
-
|
|
19699
|
-
|
|
19700
|
-
|
|
19701
|
-
});
|
|
19702
|
-
|
|
19703
|
-
// src/io/outputMeta.ts
|
|
19704
|
-
import { dirname as dirname4, resolve as resolve3 } from "path";
|
|
19705
|
-
function repoRoot() {
|
|
19706
|
-
if (isBundledCliProcess())
|
|
19707
|
-
return resolve3(dirname4(Bun.main), "../..");
|
|
19708
|
-
return resolve3(import.meta.dir, "../../..");
|
|
19649
|
+
function applyLogNormalShock(mean, sigma, z) {
|
|
19650
|
+
if (sigma <= 0)
|
|
19651
|
+
return mean;
|
|
19652
|
+
return mean * Math.exp(sigma * z - 0.5 * sigma * sigma);
|
|
19709
19653
|
}
|
|
19710
|
-
function
|
|
19711
|
-
|
|
19712
|
-
|
|
19654
|
+
function makeUncertainConfig(base, rng) {
|
|
19655
|
+
const shock = sampleCorrelatedNormals(rng, base.uncertainty.correlation);
|
|
19656
|
+
const d1 = clamp013(applyLogNormalShock(base.retention.d1, base.uncertainty.sigma.retention, shock.retention));
|
|
19657
|
+
const d7 = Math.min(d1, clamp013(applyLogNormalShock(base.retention.d7, base.uncertainty.sigma.retention, shock.retention)));
|
|
19658
|
+
const d30 = Math.min(d7, clamp013(applyLogNormalShock(base.retention.d30, base.uncertainty.sigma.retention, shock.retention)));
|
|
19659
|
+
const d90 = Math.min(d30, clamp013(applyLogNormalShock(base.retention.d90, base.uncertainty.sigma.retention, shock.retention)));
|
|
19660
|
+
return {
|
|
19661
|
+
...base,
|
|
19662
|
+
retention: {
|
|
19663
|
+
...base.retention,
|
|
19664
|
+
d1,
|
|
19665
|
+
d7,
|
|
19666
|
+
d30,
|
|
19667
|
+
d90
|
|
19668
|
+
},
|
|
19669
|
+
revenue: {
|
|
19670
|
+
...base.revenue,
|
|
19671
|
+
payerConversion: clamp013(applyLogNormalShock(base.revenue.payerConversion, base.uncertainty.sigma.conversion, shock.conversion)),
|
|
19672
|
+
arppuDaily: Math.max(0, applyLogNormalShock(base.revenue.arppuDaily, base.uncertainty.sigma.arppu, shock.arppu)),
|
|
19673
|
+
adArpDau: Math.max(0, applyLogNormalShock(base.revenue.adArpDau, base.uncertainty.sigma.ad, shock.ad))
|
|
19674
|
+
}
|
|
19675
|
+
};
|
|
19676
|
+
}
|
|
19677
|
+
function quantileFromSorted(sorted, q) {
|
|
19678
|
+
if (sorted.length === 0)
|
|
19679
|
+
return 0;
|
|
19680
|
+
const idx = Math.max(0, Math.min(sorted.length - 1, Math.floor((sorted.length - 1) * q)));
|
|
19681
|
+
return sorted[idx] ?? 0;
|
|
19682
|
+
}
|
|
19683
|
+
function estimateLtvDistribution(args) {
|
|
19684
|
+
const rng = mulberry32(args.seed);
|
|
19685
|
+
const grossSamples = [];
|
|
19686
|
+
const netSamples = [];
|
|
19687
|
+
const ltvSamples = [];
|
|
19688
|
+
const draws = Math.max(1, args.draws);
|
|
19689
|
+
for (let i = 0;i < draws; i++) {
|
|
19690
|
+
const sampled = makeUncertainConfig(args.config, rng);
|
|
19691
|
+
const point = estimateLtvPerUser({
|
|
19692
|
+
config: sampled,
|
|
19693
|
+
horizonDays: args.horizonDays,
|
|
19694
|
+
progression: args.progression
|
|
19695
|
+
});
|
|
19696
|
+
grossSamples.push(point.cumulativeGrossRevenuePerUser);
|
|
19697
|
+
netSamples.push(point.cumulativeNetRevenuePerUser);
|
|
19698
|
+
ltvSamples.push(point.cumulativeLtvPerUser);
|
|
19713
19699
|
}
|
|
19714
|
-
|
|
19715
|
-
|
|
19700
|
+
const avg = (arr) => arr.reduce((a, b) => a + b, 0) / Math.max(1, arr.length);
|
|
19701
|
+
const sortedLtv = [...ltvSamples].sort((a, b) => a - b);
|
|
19702
|
+
const byQuantile = {};
|
|
19703
|
+
for (const q of args.quantiles) {
|
|
19704
|
+
byQuantile[`q${Math.round(q * 100)}`] = quantileFromSorted(sortedLtv, q);
|
|
19716
19705
|
}
|
|
19717
|
-
|
|
19718
|
-
|
|
19706
|
+
return {
|
|
19707
|
+
mean: {
|
|
19708
|
+
cumulativeGrossRevenuePerUser: avg(grossSamples),
|
|
19709
|
+
cumulativeNetRevenuePerUser: avg(netSamples),
|
|
19710
|
+
cumulativeLtvPerUser: avg(ltvSamples)
|
|
19711
|
+
},
|
|
19712
|
+
quantiles: byQuantile
|
|
19713
|
+
};
|
|
19719
19714
|
}
|
|
19720
|
-
|
|
19721
|
-
|
|
19715
|
+
var init_uncertainty = () => {};
|
|
19716
|
+
|
|
19717
|
+
// ../core/dist/ltv/public.js
|
|
19718
|
+
function assertMonetization(monetization) {
|
|
19719
|
+
const valid = validateScenarioV1({
|
|
19720
|
+
schemaVersion: 1,
|
|
19721
|
+
unit: { code: "LTV" },
|
|
19722
|
+
policy: { mode: "drop" },
|
|
19723
|
+
model: { id: "ltv-input", version: 1 },
|
|
19724
|
+
initial: { wallet: { unit: "LTV", amount: "0" } },
|
|
19725
|
+
clock: { stepSec: 1, durationSec: 1 },
|
|
19726
|
+
monetization
|
|
19727
|
+
});
|
|
19728
|
+
if (!valid.ok)
|
|
19729
|
+
throw new RangeError(valid.issues.map((issue) => issue.message).join("; "));
|
|
19722
19730
|
}
|
|
19723
|
-
function
|
|
19724
|
-
if (
|
|
19725
|
-
|
|
19726
|
-
|
|
19727
|
-
|
|
19728
|
-
|
|
19729
|
-
cachedGitSha = envSha;
|
|
19730
|
-
return cachedGitSha;
|
|
19731
|
+
function assertQuantileValues(quantiles) {
|
|
19732
|
+
if (quantiles.length === 0)
|
|
19733
|
+
throw new RangeError("quantiles must be nonempty and in 0..1");
|
|
19734
|
+
for (const q of quantiles) {
|
|
19735
|
+
if (!Number.isFinite(q) || q < 0 || q > 1)
|
|
19736
|
+
throw new RangeError("quantiles must be nonempty and in 0..1");
|
|
19731
19737
|
}
|
|
19732
|
-
|
|
19733
|
-
|
|
19734
|
-
|
|
19735
|
-
|
|
19736
|
-
|
|
19737
|
-
|
|
19738
|
-
|
|
19738
|
+
}
|
|
19739
|
+
function assertConfig(config) {
|
|
19740
|
+
const { retention, ...other } = config;
|
|
19741
|
+
for (const day of ["d1", "d7", "d30", "d90"]) {
|
|
19742
|
+
if (!Number.isFinite(retention[day]) || retention[day] < 0 || retention[day] > 1) {
|
|
19743
|
+
throw new RangeError(`retention.${day} must be finite and in 0..1`);
|
|
19744
|
+
}
|
|
19739
19745
|
}
|
|
19740
|
-
|
|
19746
|
+
if (!Number.isFinite(retention.longTailDailyDecay) || retention.longTailDailyDecay < 0) {
|
|
19747
|
+
throw new RangeError("retention.longTailDailyDecay must be finite and >= 0");
|
|
19748
|
+
}
|
|
19749
|
+
const { quantiles, ...uncertainty } = other.uncertainty;
|
|
19750
|
+
assertQuantileValues(quantiles);
|
|
19751
|
+
assertMonetization({ ...other, uncertainty });
|
|
19741
19752
|
}
|
|
19742
|
-
function
|
|
19743
|
-
|
|
19753
|
+
function deriveMonetizationConfig2(scenario) {
|
|
19754
|
+
assertMonetization(scenario.monetization ?? {});
|
|
19755
|
+
const config = deriveMonetizationConfig(scenario);
|
|
19756
|
+
assertConfig(config);
|
|
19757
|
+
return config;
|
|
19744
19758
|
}
|
|
19745
|
-
function
|
|
19746
|
-
|
|
19747
|
-
|
|
19748
|
-
|
|
19749
|
-
|
|
19750
|
-
|
|
19751
|
-
|
|
19752
|
-
seed: args.seed,
|
|
19753
|
-
scope: args.scope ?? null
|
|
19754
|
-
}).slice(0, 8);
|
|
19755
|
-
return `${args.command}-${args.seed.toString(36)}-${suffix}`;
|
|
19759
|
+
function assertPoint(args) {
|
|
19760
|
+
assertConfig(args.config);
|
|
19761
|
+
if (!Number.isFinite(args.horizonDays) || args.horizonDays < 0 || args.horizonDays > 36500) {
|
|
19762
|
+
throw new RangeError("horizonDays must be finite and in 0..36500");
|
|
19763
|
+
}
|
|
19764
|
+
if (!Number.isFinite(args.progression))
|
|
19765
|
+
throw new RangeError("progression must be finite");
|
|
19756
19766
|
}
|
|
19757
|
-
function
|
|
19758
|
-
|
|
19767
|
+
function finiteResult(result) {
|
|
19768
|
+
if (!Object.values(result).every((value) => typeof value === "number" && Number.isFinite(value))) {
|
|
19769
|
+
throw new RangeError("LTV calculation overflowed; reduce the input magnitudes");
|
|
19770
|
+
}
|
|
19771
|
+
return result;
|
|
19759
19772
|
}
|
|
19760
|
-
function
|
|
19761
|
-
|
|
19762
|
-
|
|
19763
|
-
|
|
19764
|
-
|
|
19765
|
-
|
|
19766
|
-
|
|
19767
|
-
|
|
19768
|
-
|
|
19769
|
-
|
|
19770
|
-
|
|
19771
|
-
|
|
19772
|
-
|
|
19773
|
-
|
|
19774
|
-
|
|
19775
|
-
|
|
19776
|
-
|
|
19777
|
-
|
|
19778
|
-
|
|
19779
|
-
|
|
19780
|
-
|
|
19781
|
-
seed: args.seed,
|
|
19782
|
-
scenarioPath: args.scenarioPath,
|
|
19783
|
-
scenarioHash,
|
|
19784
|
-
tuneSpecHash: args.tuneSpec !== undefined ? hashContent(args.tuneSpec) : undefined,
|
|
19785
|
-
telemetryHash: args.telemetry !== undefined ? hashContent(args.telemetry) : undefined,
|
|
19786
|
-
pluginDigest: args.pluginDigest ?? {},
|
|
19787
|
-
...args.effectiveRunHash !== undefined ? { effectiveRunHash: args.effectiveRunHash } : {},
|
|
19788
|
-
...args.effectiveEngine !== undefined ? { effectiveEngine: args.effectiveEngine } : {},
|
|
19789
|
-
...args.stageScope !== undefined ? { stageScope: args.stageScope } : {}
|
|
19790
|
-
};
|
|
19773
|
+
function estimateLtvPerUser2(args) {
|
|
19774
|
+
assertPoint(args);
|
|
19775
|
+
return finiteResult(estimateLtvPerUser(args));
|
|
19776
|
+
}
|
|
19777
|
+
function estimateLtvDistribution2(args) {
|
|
19778
|
+
assertPoint(args);
|
|
19779
|
+
if (!Number.isSafeInteger(args.draws) || args.draws < 1 || args.draws > 1e5 || args.draws * Math.max(1, Math.ceil(args.horizonDays)) > 1e7) {
|
|
19780
|
+
throw new RangeError("draws must be 1..100000 and draws \xD7 ceil(horizonDays) at most 10000000");
|
|
19781
|
+
}
|
|
19782
|
+
if (!Number.isFinite(args.seed))
|
|
19783
|
+
throw new RangeError("seed must be finite");
|
|
19784
|
+
if (args.quantiles.length > 101)
|
|
19785
|
+
throw new RangeError("quantiles exceed the 101 distinct percent keys");
|
|
19786
|
+
assertQuantileValues(args.quantiles);
|
|
19787
|
+
if (new Set(args.quantiles.map((q) => Math.round(q * 100))).size !== args.quantiles.length) {
|
|
19788
|
+
throw new RangeError("quantiles must be nonempty, in 0..1 and have distinct rounded percent keys");
|
|
19789
|
+
}
|
|
19790
|
+
const result = estimateLtvDistribution(args);
|
|
19791
|
+
finiteResult(result.mean);
|
|
19792
|
+
finiteResult(result.quantiles);
|
|
19793
|
+
return result;
|
|
19791
19794
|
}
|
|
19792
|
-
var
|
|
19793
|
-
|
|
19794
|
-
|
|
19795
|
-
init_selfCli();
|
|
19796
|
-
import_package = __toESM(require_package(), 1);
|
|
19797
|
-
REPO_ROOT = repoRoot();
|
|
19798
|
-
OUTPUT_SCHEMA_REF = {
|
|
19799
|
-
simulate: "docs/schemas/simulate.output.schema.json",
|
|
19800
|
-
eta: "docs/schemas/eta.output.schema.json",
|
|
19801
|
-
compare: "docs/schemas/compare.output.schema.json",
|
|
19802
|
-
doctor: "docs/schemas/doctor.output.schema.json",
|
|
19803
|
-
evaluate: "docs/schemas/evaluate.output.schema.json",
|
|
19804
|
-
"judge.jev": "docs/schemas/jev.judge.output.schema.json",
|
|
19805
|
-
"execution.batch": "docs/worker-execution.md",
|
|
19806
|
-
experience: "docs/schemas/experience.output.schema.json",
|
|
19807
|
-
"setup.completions": "docs/schemas/setup.output.schema.json",
|
|
19808
|
-
"setup.plugin-trust": "docs/schemas/setup.output.schema.json",
|
|
19809
|
-
tune: "docs/schemas/tune.output.schema.json",
|
|
19810
|
-
ltv: "docs/schemas/ltv.output.schema.json",
|
|
19811
|
-
calibrate: "docs/schemas/calibrate.output.schema.json",
|
|
19812
|
-
"kpi.regress": "docs/schemas/kpi.regress.output.schema.json",
|
|
19813
|
-
"replay.verify": "docs/schemas/replay.verify.output.schema.json"
|
|
19814
|
-
};
|
|
19815
|
-
cliVersion = import_package.default.version ?? "0.0.0";
|
|
19795
|
+
var init_public = __esm(() => {
|
|
19796
|
+
init_validate();
|
|
19797
|
+
init_uncertainty();
|
|
19816
19798
|
});
|
|
19817
19799
|
|
|
19818
|
-
//
|
|
19819
|
-
var
|
|
19820
|
-
|
|
19821
|
-
|
|
19822
|
-
|
|
19823
|
-
|
|
19824
|
-
|
|
19825
|
-
|
|
19826
|
-
|
|
19827
|
-
|
|
19828
|
-
|
|
19829
|
-
|
|
19830
|
-
|
|
19831
|
-
|
|
19832
|
-
|
|
19833
|
-
|
|
19834
|
-
|
|
19835
|
-
|
|
19836
|
-
|
|
19837
|
-
|
|
19838
|
-
|
|
19839
|
-
|
|
19840
|
-
|
|
19800
|
+
// ../core/dist/index.js
|
|
19801
|
+
var init_dist2 = __esm(() => {
|
|
19802
|
+
init_types();
|
|
19803
|
+
init_breakInfinity2();
|
|
19804
|
+
init_breakEternity2();
|
|
19805
|
+
init_types5();
|
|
19806
|
+
init_types6();
|
|
19807
|
+
init_emitter();
|
|
19808
|
+
init_tickMoney2();
|
|
19809
|
+
init_suffixer();
|
|
19810
|
+
init_formatMoney2();
|
|
19811
|
+
init_parseMoney2();
|
|
19812
|
+
init_visibilityTracker2();
|
|
19813
|
+
init_moneyState();
|
|
19814
|
+
init_simState();
|
|
19815
|
+
init_types7();
|
|
19816
|
+
init_validate();
|
|
19817
|
+
init_concreteValidator();
|
|
19818
|
+
init_scenarioContract();
|
|
19819
|
+
init_compile2();
|
|
19820
|
+
init_simulator();
|
|
19821
|
+
init_advance();
|
|
19822
|
+
init_step();
|
|
19823
|
+
init_stepTypes();
|
|
19824
|
+
init_offline();
|
|
19825
|
+
init_session();
|
|
19826
|
+
init_monteCarlo();
|
|
19827
|
+
init_executionBackend();
|
|
19828
|
+
init_integrationMatrix();
|
|
19829
|
+
init_runFactory();
|
|
19830
|
+
init_constraints();
|
|
19831
|
+
init_observation();
|
|
19832
|
+
init_types8();
|
|
19833
|
+
init_contracts();
|
|
19834
|
+
init_stability();
|
|
19835
|
+
init_scripted();
|
|
19836
|
+
init_greedy();
|
|
19837
|
+
init_planner();
|
|
19838
|
+
init_params();
|
|
19839
|
+
init_builtins();
|
|
19840
|
+
init_tuneSpec();
|
|
19841
|
+
init_validate2();
|
|
19842
|
+
init_objective();
|
|
19843
|
+
init_params2();
|
|
19844
|
+
init_builtins2();
|
|
19845
|
+
init_tuner();
|
|
19846
|
+
init_randomSearch();
|
|
19847
|
+
init_runner();
|
|
19848
|
+
init_eta();
|
|
19849
|
+
init_prestigeCycle();
|
|
19850
|
+
init_resetCycle();
|
|
19851
|
+
init_resetOptimization();
|
|
19852
|
+
init_distribution();
|
|
19853
|
+
init_experiment();
|
|
19854
|
+
init_sensitivity();
|
|
19855
|
+
init_growthFit();
|
|
19856
|
+
init_contracts2();
|
|
19857
|
+
init_metrics();
|
|
19858
|
+
init_registry();
|
|
19859
|
+
init_analyzers();
|
|
19860
|
+
init_inspect();
|
|
19861
|
+
init_checks3();
|
|
19862
|
+
init_judge();
|
|
19863
|
+
init_flow();
|
|
19864
|
+
init_jevAdvisory();
|
|
19865
|
+
init_public();
|
|
19866
|
+
});
|
|
19867
|
+
|
|
19868
|
+
// src/runtime/bun.ts
|
|
19869
|
+
var {$ } = globalThis.Bun;
|
|
19870
|
+
import { dirname } from "path";
|
|
19871
|
+
async function readTextFile(path) {
|
|
19872
|
+
return Bun.file(path).text();
|
|
19873
|
+
}
|
|
19874
|
+
async function readJsonFile(path) {
|
|
19875
|
+
return Bun.file(path).json();
|
|
19876
|
+
}
|
|
19877
|
+
async function writeTextFile(path, body) {
|
|
19878
|
+
try {
|
|
19879
|
+
await ensureDir(dirname(path));
|
|
19880
|
+
await Bun.write(path, body);
|
|
19881
|
+
} catch (error) {
|
|
19882
|
+
throw cliError("OUTPUT_WRITE_FAILED", `Unable to write output file: ${path}`, {
|
|
19883
|
+
detail: errorDetail(error),
|
|
19884
|
+
hint: "Choose a writable output path and check that its parent is a directory.",
|
|
19885
|
+
cause: error
|
|
19886
|
+
});
|
|
19841
19887
|
}
|
|
19842
|
-
|
|
19843
|
-
|
|
19844
|
-
|
|
19845
|
-
|
|
19846
|
-
|
|
19847
|
-
|
|
19848
|
-
|
|
19849
|
-
|
|
19850
|
-
|
|
19851
|
-
|
|
19888
|
+
}
|
|
19889
|
+
function writeStdout(body) {
|
|
19890
|
+
return new Promise((resolve, reject) => {
|
|
19891
|
+
process.stdout.write(body, (error) => error ? reject(error) : resolve());
|
|
19892
|
+
});
|
|
19893
|
+
}
|
|
19894
|
+
async function ensureDir(path) {
|
|
19895
|
+
await $`mkdir -p ${path}`.quiet();
|
|
19896
|
+
}
|
|
19897
|
+
async function fileExists(path) {
|
|
19898
|
+
return Bun.file(path).exists();
|
|
19899
|
+
}
|
|
19900
|
+
function sha256Hex(value) {
|
|
19901
|
+
const hasher = new Bun.CryptoHasher("sha256");
|
|
19902
|
+
hasher.update(value);
|
|
19903
|
+
return hasher.digest("hex");
|
|
19904
|
+
}
|
|
19905
|
+
function runText(args, opts) {
|
|
19906
|
+
const proc = Bun.spawnSync(args, {
|
|
19907
|
+
cwd: opts?.cwd,
|
|
19908
|
+
env: opts?.env,
|
|
19909
|
+
stdout: "pipe",
|
|
19910
|
+
stderr: "pipe",
|
|
19911
|
+
maxBuffer: MAX_CAPTURE_BYTES
|
|
19912
|
+
});
|
|
19913
|
+
if (proc.exitCode !== 0) {
|
|
19914
|
+
const stderr = proc.stderr.toString().trim();
|
|
19915
|
+
const stdout = proc.stdout.toString().trim();
|
|
19916
|
+
const detail = stderr || stdout || `command failed with exit code ${proc.exitCode}`;
|
|
19917
|
+
throw new Error(detail);
|
|
19852
19918
|
}
|
|
19853
|
-
|
|
19854
|
-
|
|
19855
|
-
|
|
19856
|
-
|
|
19857
|
-
|
|
19858
|
-
|
|
19859
|
-
exports.SCALAR = SCALAR;
|
|
19860
|
-
exports.SEQ = SEQ;
|
|
19861
|
-
exports.hasAnchor = hasAnchor;
|
|
19862
|
-
exports.isAlias = isAlias;
|
|
19863
|
-
exports.isCollection = isCollection;
|
|
19864
|
-
exports.isDocument = isDocument;
|
|
19865
|
-
exports.isMap = isMap;
|
|
19866
|
-
exports.isNode = isNode;
|
|
19867
|
-
exports.isPair = isPair;
|
|
19868
|
-
exports.isScalar = isScalar;
|
|
19869
|
-
exports.isSeq = isSeq;
|
|
19919
|
+
return proc.stdout.toString();
|
|
19920
|
+
}
|
|
19921
|
+
var MAX_CAPTURE_BYTES;
|
|
19922
|
+
var init_bun = __esm(() => {
|
|
19923
|
+
init_errors3();
|
|
19924
|
+
MAX_CAPTURE_BYTES = 8 * 1024 * 1024;
|
|
19870
19925
|
});
|
|
19871
19926
|
|
|
19872
|
-
//
|
|
19873
|
-
var
|
|
19874
|
-
|
|
19875
|
-
|
|
19876
|
-
|
|
19877
|
-
|
|
19878
|
-
|
|
19879
|
-
|
|
19880
|
-
|
|
19881
|
-
|
|
19882
|
-
|
|
19883
|
-
|
|
19884
|
-
|
|
19927
|
+
// package.json
|
|
19928
|
+
var require_package = __commonJS(function(exports, module) {
|
|
19929
|
+
module.exports = {
|
|
19930
|
+
name: "@idlekit/cli",
|
|
19931
|
+
version: "0.5.1",
|
|
19932
|
+
description: "Bun-first idk CLI for idle game scenario validation, simulation, tuning, and reporting.",
|
|
19933
|
+
license: "MIT",
|
|
19934
|
+
type: "module",
|
|
19935
|
+
files: [
|
|
19936
|
+
"dist",
|
|
19937
|
+
"README.md",
|
|
19938
|
+
"LICENSE"
|
|
19939
|
+
],
|
|
19940
|
+
bin: {
|
|
19941
|
+
idk: "dist/main.js"
|
|
19942
|
+
},
|
|
19943
|
+
repository: {
|
|
19944
|
+
type: "git",
|
|
19945
|
+
url: "git+https://github.com/imjlk/idlekit.git",
|
|
19946
|
+
directory: "packages/cli"
|
|
19947
|
+
},
|
|
19948
|
+
homepage: "https://github.com/imjlk/idlekit/tree/main/packages/cli#readme",
|
|
19949
|
+
bugs: {
|
|
19950
|
+
url: "https://github.com/imjlk/idlekit/issues"
|
|
19951
|
+
},
|
|
19952
|
+
keywords: [
|
|
19953
|
+
"bun",
|
|
19954
|
+
"idle-game",
|
|
19955
|
+
"incremental-game",
|
|
19956
|
+
"cli",
|
|
19957
|
+
"economy-simulator",
|
|
19958
|
+
"game-balancing",
|
|
19959
|
+
"simulation"
|
|
19960
|
+
],
|
|
19961
|
+
publishConfig: {
|
|
19962
|
+
access: "public"
|
|
19963
|
+
},
|
|
19964
|
+
engines: {
|
|
19965
|
+
bun: ">=1.4.2"
|
|
19966
|
+
},
|
|
19967
|
+
scripts: {
|
|
19968
|
+
dev: "bun src/main.ts",
|
|
19969
|
+
"dev:watch": "bun --watch src/main.ts",
|
|
19970
|
+
"generate:check": "bun scripts/command-check.ts",
|
|
19971
|
+
"doctor:completions": "bun scripts/command-check.ts",
|
|
19972
|
+
"review:smoke": "bun scripts/review-smoke.ts",
|
|
19973
|
+
build: "bun scripts/cli-bundle.ts --outdir dist --sourcemap",
|
|
19974
|
+
"build:bin": "bun scripts/cli-bundle.ts --entry src/bin.ts --outdir dist/bin --targets native",
|
|
19975
|
+
typecheck: "ttsc -p tsconfig.json --noEmit",
|
|
19976
|
+
test: "bun scripts/cli-bundle.ts --outdir .test-bundle && IDLEKIT_CLI_ENTRY=.test-bundle/main.js bun test --timeout 90000 src",
|
|
19977
|
+
prepack: "bun run build && cd ../.. && bun tools/package-manifest.ts prepare packages/cli",
|
|
19978
|
+
postpack: "cd ../.. && bun tools/package-manifest.ts restore packages/cli"
|
|
19979
|
+
},
|
|
19980
|
+
dependencies: {
|
|
19981
|
+
"@clack/prompts": "1.8.1",
|
|
19982
|
+
"@gunshi/plugin-completion": "0.37.3",
|
|
19983
|
+
"@gunshi/plugin-suggestion": "0.37.3",
|
|
19984
|
+
"@idlekit/core": "0.5.0",
|
|
19985
|
+
ajv: "^8.20.0",
|
|
19986
|
+
gunshi: "0.37.3",
|
|
19987
|
+
"proper-lockfile": "4.1.2",
|
|
19988
|
+
yaml: "^2.9.1",
|
|
19989
|
+
zod: "^4.6.5"
|
|
19990
|
+
},
|
|
19991
|
+
devDependencies: {
|
|
19992
|
+
"@types/proper-lockfile": "4.1.4"
|
|
19993
|
+
}
|
|
19994
|
+
};
|
|
19995
|
+
});
|
|
19996
|
+
|
|
19997
|
+
// src/runtime/selfCli.ts
|
|
19998
|
+
import { existsSync } from "fs";
|
|
19999
|
+
import { dirname as dirname3, resolve as resolve2 } from "path";
|
|
20000
|
+
function currentEntry() {
|
|
20001
|
+
return Bun.main;
|
|
20002
|
+
}
|
|
20003
|
+
function isJavaScriptEntry(entry) {
|
|
20004
|
+
return entry.endsWith(".js") || entry.endsWith(".mjs");
|
|
20005
|
+
}
|
|
20006
|
+
function isNativeEntry(entry) {
|
|
20007
|
+
return entry.includes("$bunfs") || entry.includes("~BUN");
|
|
20008
|
+
}
|
|
20009
|
+
function isBundledCliProcess(entry = currentEntry()) {
|
|
20010
|
+
return isJavaScriptEntry(entry) || isNativeEntry(entry);
|
|
20011
|
+
}
|
|
20012
|
+
function cliPackageRoot(entry = currentEntry()) {
|
|
20013
|
+
if (isNativeEntry(entry))
|
|
20014
|
+
return process.cwd();
|
|
20015
|
+
if (isBundledCliProcess(entry))
|
|
20016
|
+
return resolve2(dirname3(entry), "..");
|
|
20017
|
+
return resolve2(import.meta.dir, "../..");
|
|
20018
|
+
}
|
|
20019
|
+
function bundledCliConfig(entry) {
|
|
20020
|
+
const config = resolve2(dirname3(entry), "../../../tools/bundled-cli-bunfig.toml");
|
|
20021
|
+
if (!existsSync(config))
|
|
20022
|
+
return;
|
|
20023
|
+
return config;
|
|
20024
|
+
}
|
|
20025
|
+
function selfCliCommand(args, entry = currentEntry()) {
|
|
20026
|
+
if (isNativeEntry(entry))
|
|
20027
|
+
return [process.execPath, ...args];
|
|
20028
|
+
const bun = process.argv[0] ?? "bun";
|
|
20029
|
+
if (isJavaScriptEntry(entry)) {
|
|
20030
|
+
const config = bundledCliConfig(entry);
|
|
20031
|
+
if (config)
|
|
20032
|
+
return [bun, `--config=${config}`, entry, ...args];
|
|
20033
|
+
return [bun, entry, ...args];
|
|
20034
|
+
}
|
|
20035
|
+
const bunfigApplies = resolve2(process.cwd()) === CLI_ROOT;
|
|
20036
|
+
if (entry.endsWith(".ts") && !bunfigApplies) {
|
|
20037
|
+
return [bun, "--preload", "@ttsc/unplugin/bun-register", entry, ...args];
|
|
20038
|
+
}
|
|
20039
|
+
return [bun, entry, ...args];
|
|
20040
|
+
}
|
|
20041
|
+
function runSelfCli(args) {
|
|
20042
|
+
const proc = Bun.spawnSync(selfCliCommand(args), {
|
|
20043
|
+
cwd: process.cwd(),
|
|
20044
|
+
stdout: "pipe",
|
|
20045
|
+
stderr: "pipe",
|
|
20046
|
+
env: process.env,
|
|
20047
|
+
maxBuffer: MAX_CAPTURE_BYTES2
|
|
20048
|
+
});
|
|
20049
|
+
return {
|
|
20050
|
+
exitCode: proc.exitCode,
|
|
20051
|
+
stdout: proc.stdout.toString(),
|
|
20052
|
+
stderr: proc.stderr.toString()
|
|
20053
|
+
};
|
|
20054
|
+
}
|
|
20055
|
+
var CLI_ROOT, MAX_CAPTURE_BYTES2;
|
|
20056
|
+
var init_selfCli = __esm(() => {
|
|
20057
|
+
CLI_ROOT = resolve2(import.meta.dir, "../..");
|
|
20058
|
+
MAX_CAPTURE_BYTES2 = 8 * 1024 * 1024;
|
|
20059
|
+
});
|
|
20060
|
+
|
|
20061
|
+
// src/io/outputMeta.ts
|
|
20062
|
+
import { dirname as dirname4, resolve as resolve3 } from "path";
|
|
20063
|
+
function repoRoot() {
|
|
20064
|
+
if (isBundledCliProcess())
|
|
20065
|
+
return resolve3(dirname4(Bun.main), "../..");
|
|
20066
|
+
return resolve3(import.meta.dir, "../../..");
|
|
20067
|
+
}
|
|
20068
|
+
function stableStringify(value) {
|
|
20069
|
+
if (value === null || typeof value !== "object") {
|
|
20070
|
+
return JSON.stringify(value);
|
|
20071
|
+
}
|
|
20072
|
+
if (Array.isArray(value)) {
|
|
20073
|
+
return `[${value.map((x) => stableStringify(x)).join(",")}]`;
|
|
20074
|
+
}
|
|
20075
|
+
const entries = Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
20076
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(",")}}`;
|
|
20077
|
+
}
|
|
20078
|
+
function sha256(value) {
|
|
20079
|
+
return sha256Hex(value);
|
|
20080
|
+
}
|
|
20081
|
+
function resolveGitSha() {
|
|
20082
|
+
if (hasResolvedGitSha)
|
|
20083
|
+
return cachedGitSha ?? UNKNOWN_GIT_SHA;
|
|
20084
|
+
hasResolvedGitSha = true;
|
|
20085
|
+
const envSha = process.env.GITHUB_SHA?.trim();
|
|
20086
|
+
if (envSha) {
|
|
20087
|
+
cachedGitSha = envSha;
|
|
20088
|
+
return cachedGitSha;
|
|
20089
|
+
}
|
|
20090
|
+
try {
|
|
20091
|
+
cachedGitSha = runText(["git", "rev-parse", "--short", "HEAD"], {
|
|
20092
|
+
cwd: REPO_ROOT,
|
|
20093
|
+
env: process.env
|
|
20094
|
+
}).trim();
|
|
20095
|
+
} catch {
|
|
20096
|
+
cachedGitSha = UNKNOWN_GIT_SHA;
|
|
20097
|
+
}
|
|
20098
|
+
return cachedGitSha ?? UNKNOWN_GIT_SHA;
|
|
20099
|
+
}
|
|
20100
|
+
function hashContent(value) {
|
|
20101
|
+
return sha256(stableStringify(value));
|
|
20102
|
+
}
|
|
20103
|
+
function deriveDeterministicSeed(value) {
|
|
20104
|
+
const digest = hashContent(value);
|
|
20105
|
+
return Number.parseInt(digest.slice(0, 8), 16) >>> 0;
|
|
20106
|
+
}
|
|
20107
|
+
function deriveDeterministicRunId(args) {
|
|
20108
|
+
const suffix = hashContent({
|
|
20109
|
+
command: args.command,
|
|
20110
|
+
seed: args.seed,
|
|
20111
|
+
scope: args.scope ?? null
|
|
20112
|
+
}).slice(0, 8);
|
|
20113
|
+
return `${args.command}-${args.seed.toString(36)}-${suffix}`;
|
|
20114
|
+
}
|
|
20115
|
+
function outputSchemaRefForCommand(command) {
|
|
20116
|
+
return OUTPUT_SCHEMA_REF[command];
|
|
20117
|
+
}
|
|
20118
|
+
function buildOutputMeta(args) {
|
|
20119
|
+
const scenarioHash = (() => {
|
|
20120
|
+
if (args.scenario !== undefined)
|
|
20121
|
+
return hashContent(args.scenario);
|
|
20122
|
+
if (args.scenarios) {
|
|
20123
|
+
const out = {};
|
|
20124
|
+
for (const [k, v] of Object.entries(args.scenarios)) {
|
|
20125
|
+
out[k] = hashContent(v);
|
|
20126
|
+
}
|
|
20127
|
+
return out;
|
|
20128
|
+
}
|
|
20129
|
+
return;
|
|
20130
|
+
})();
|
|
20131
|
+
return {
|
|
20132
|
+
command: args.command,
|
|
20133
|
+
generatedAt: new Date().toISOString(),
|
|
20134
|
+
contractVersion: OUTPUT_CONTRACT_VERSION,
|
|
20135
|
+
schemaRef: args.schemaRef ?? outputSchemaRefForCommand(args.command),
|
|
20136
|
+
cliVersion,
|
|
20137
|
+
gitSha: resolveGitSha(),
|
|
20138
|
+
runId: args.runId,
|
|
20139
|
+
seed: args.seed,
|
|
20140
|
+
scenarioPath: args.scenarioPath,
|
|
20141
|
+
scenarioHash,
|
|
20142
|
+
tuneSpecHash: args.tuneSpec !== undefined ? hashContent(args.tuneSpec) : undefined,
|
|
20143
|
+
telemetryHash: args.telemetry !== undefined ? hashContent(args.telemetry) : undefined,
|
|
20144
|
+
pluginDigest: args.pluginDigest ?? {},
|
|
20145
|
+
...args.effectiveRunHash !== undefined ? { effectiveRunHash: args.effectiveRunHash } : {},
|
|
20146
|
+
...args.effectiveEngine !== undefined ? { effectiveEngine: args.effectiveEngine } : {},
|
|
20147
|
+
...args.stageScope !== undefined ? { stageScope: args.stageScope } : {}
|
|
20148
|
+
};
|
|
20149
|
+
}
|
|
20150
|
+
var import_package, REPO_ROOT, OUTPUT_CONTRACT_VERSION = "1.4.0", UNKNOWN_GIT_SHA = "unknown", OUTPUT_SCHEMA_REF, cachedGitSha, hasResolvedGitSha = false, cliVersion;
|
|
20151
|
+
var init_outputMeta = __esm(() => {
|
|
20152
|
+
init_bun();
|
|
20153
|
+
init_selfCli();
|
|
20154
|
+
import_package = __toESM(require_package(), 1);
|
|
20155
|
+
REPO_ROOT = repoRoot();
|
|
20156
|
+
OUTPUT_SCHEMA_REF = {
|
|
20157
|
+
simulate: "docs/schemas/simulate.output.schema.json",
|
|
20158
|
+
eta: "docs/schemas/eta.output.schema.json",
|
|
20159
|
+
compare: "docs/schemas/compare.output.schema.json",
|
|
20160
|
+
doctor: "docs/schemas/doctor.output.schema.json",
|
|
20161
|
+
evaluate: "docs/schemas/evaluate.output.schema.json",
|
|
20162
|
+
"judge.jev": "docs/schemas/jev.judge.output.schema.json",
|
|
20163
|
+
"execution.batch": "docs/worker-execution.md",
|
|
20164
|
+
experience: "docs/schemas/experience.output.schema.json",
|
|
20165
|
+
"setup.completions": "docs/schemas/setup.output.schema.json",
|
|
20166
|
+
"setup.plugin-trust": "docs/schemas/setup.output.schema.json",
|
|
20167
|
+
tune: "docs/schemas/tune.output.schema.json",
|
|
20168
|
+
ltv: "docs/schemas/ltv.output.schema.json",
|
|
20169
|
+
calibrate: "docs/schemas/calibrate.output.schema.json",
|
|
20170
|
+
"kpi.regress": "docs/schemas/kpi.regress.output.schema.json",
|
|
20171
|
+
"replay.verify": "docs/schemas/replay.verify.output.schema.json"
|
|
20172
|
+
};
|
|
20173
|
+
cliVersion = import_package.default.version ?? "0.0.0";
|
|
20174
|
+
});
|
|
20175
|
+
|
|
20176
|
+
// ../../node_modules/.bun/yaml@2.9.1/node_modules/yaml/dist/nodes/identity.js
|
|
20177
|
+
var require_identity = __commonJS(function(exports) {
|
|
20178
|
+
var ALIAS = Symbol.for("yaml.alias");
|
|
20179
|
+
var DOC = Symbol.for("yaml.document");
|
|
20180
|
+
var MAP = Symbol.for("yaml.map");
|
|
20181
|
+
var PAIR = Symbol.for("yaml.pair");
|
|
20182
|
+
var SCALAR = Symbol.for("yaml.scalar");
|
|
20183
|
+
var SEQ = Symbol.for("yaml.seq");
|
|
20184
|
+
var NODE_TYPE = Symbol.for("yaml.node.type");
|
|
20185
|
+
var isAlias = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === ALIAS;
|
|
20186
|
+
var isDocument = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === DOC;
|
|
20187
|
+
var isMap = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === MAP;
|
|
20188
|
+
var isPair = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === PAIR;
|
|
20189
|
+
var isScalar = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === SCALAR;
|
|
20190
|
+
var isSeq = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === SEQ;
|
|
20191
|
+
function isCollection(node) {
|
|
20192
|
+
if (node && typeof node === "object")
|
|
20193
|
+
switch (node[NODE_TYPE]) {
|
|
20194
|
+
case MAP:
|
|
20195
|
+
case SEQ:
|
|
20196
|
+
return true;
|
|
20197
|
+
}
|
|
20198
|
+
return false;
|
|
20199
|
+
}
|
|
20200
|
+
function isNode(node) {
|
|
20201
|
+
if (node && typeof node === "object")
|
|
20202
|
+
switch (node[NODE_TYPE]) {
|
|
20203
|
+
case ALIAS:
|
|
20204
|
+
case MAP:
|
|
20205
|
+
case SCALAR:
|
|
20206
|
+
case SEQ:
|
|
20207
|
+
return true;
|
|
20208
|
+
}
|
|
20209
|
+
return false;
|
|
20210
|
+
}
|
|
20211
|
+
var hasAnchor = (node) => (isScalar(node) || isCollection(node)) && !!node.anchor;
|
|
20212
|
+
exports.ALIAS = ALIAS;
|
|
20213
|
+
exports.DOC = DOC;
|
|
20214
|
+
exports.MAP = MAP;
|
|
20215
|
+
exports.NODE_TYPE = NODE_TYPE;
|
|
20216
|
+
exports.PAIR = PAIR;
|
|
20217
|
+
exports.SCALAR = SCALAR;
|
|
20218
|
+
exports.SEQ = SEQ;
|
|
20219
|
+
exports.hasAnchor = hasAnchor;
|
|
20220
|
+
exports.isAlias = isAlias;
|
|
20221
|
+
exports.isCollection = isCollection;
|
|
20222
|
+
exports.isDocument = isDocument;
|
|
20223
|
+
exports.isMap = isMap;
|
|
20224
|
+
exports.isNode = isNode;
|
|
20225
|
+
exports.isPair = isPair;
|
|
20226
|
+
exports.isScalar = isScalar;
|
|
20227
|
+
exports.isSeq = isSeq;
|
|
20228
|
+
});
|
|
20229
|
+
|
|
20230
|
+
// ../../node_modules/.bun/yaml@2.9.1/node_modules/yaml/dist/visit.js
|
|
20231
|
+
var require_visit = __commonJS(function(exports) {
|
|
20232
|
+
var identity = require_identity();
|
|
20233
|
+
var BREAK = Symbol("break visit");
|
|
20234
|
+
var SKIP = Symbol("skip children");
|
|
20235
|
+
var REMOVE = Symbol("remove node");
|
|
20236
|
+
function visit(node, visitor) {
|
|
20237
|
+
const visitor_ = initVisitor(visitor);
|
|
20238
|
+
if (identity.isDocument(node)) {
|
|
20239
|
+
const cd = visit_(null, node.contents, visitor_, Object.freeze([node]));
|
|
20240
|
+
if (cd === REMOVE)
|
|
20241
|
+
node.contents = null;
|
|
20242
|
+
} else
|
|
19885
20243
|
visit_(null, node, visitor_, Object.freeze([]));
|
|
19886
20244
|
}
|
|
19887
20245
|
visit.BREAK = BREAK;
|
|
@@ -36322,532 +36680,165 @@ function judgeCsvRows(report) {
|
|
|
36322
36680
|
unit: result.evidence.unit,
|
|
36323
36681
|
operator: result.evidence.operator,
|
|
36324
36682
|
threshold: result.evidence.threshold,
|
|
36325
|
-
upperThreshold: result.evidence.upperThreshold,
|
|
36326
|
-
diagnosticStatus: diagnostic?.status ?? null,
|
|
36327
|
-
recommendationId: diagnostic?.recommendationId ?? null
|
|
36328
|
-
};
|
|
36329
|
-
});
|
|
36330
|
-
}
|
|
36331
|
-
async function runJudge(flags, analysisPath, hooks) {
|
|
36332
|
-
const requestedModel = (flags.model ?? "jev-latest").trim();
|
|
36333
|
-
if (flags.provider === "jev" && requestedModel.length === 0) {
|
|
36334
|
-
throw usageError("--model requires a nonempty model name.");
|
|
36335
|
-
}
|
|
36336
|
-
if (flags.provider === "jev" && flags.format === "csv") {
|
|
36337
|
-
throw usageError("--provider jev writes json or md. csv cannot hold the rules report and the advisory together.");
|
|
36338
|
-
}
|
|
36339
|
-
if (flags.provider === "jev" && flags.cache) {
|
|
36340
|
-
if (flags.out && sameFile(flags.cache, flags.out)) {
|
|
36341
|
-
throw usageError("--cache and --out name the same file. Give the Jev cache its own path.");
|
|
36342
|
-
}
|
|
36343
|
-
if (flags.out && (ancestorFile(flags.cache, flags.out) || ancestorFile(flags.out, flags.cache))) {
|
|
36344
|
-
throw usageError("--cache and --out cannot be ancestor paths. Give each file a separate target.");
|
|
36345
|
-
}
|
|
36346
|
-
if (sameFile(flags.cache, analysisPath) || sameFile(flags.cache, flags.rubric)) {
|
|
36347
|
-
throw usageError("--cache names an input file. Give the Jev cache its own path.");
|
|
36348
|
-
}
|
|
36349
|
-
}
|
|
36350
|
-
if (flags.provider === "jev") {
|
|
36351
|
-
if (flags.cache)
|
|
36352
|
-
requireFileTarget(flags.cache, "cache");
|
|
36353
|
-
if (flags.out)
|
|
36354
|
-
requireFileTarget(flags.out, "out");
|
|
36355
|
-
}
|
|
36356
|
-
let artifactRaw;
|
|
36357
|
-
let rubricRaw;
|
|
36358
|
-
try {
|
|
36359
|
-
artifactRaw = await readJsonFile(analysisPath);
|
|
36360
|
-
rubricRaw = await readJsonFile(flags.rubric);
|
|
36361
|
-
} catch (error) {
|
|
36362
|
-
throw usageError(`Invalid judge input: ${error instanceof Error ? error.message : String(error)}`);
|
|
36363
|
-
}
|
|
36364
|
-
const parsed = readJudgeInputs(artifactRaw, rubricRaw);
|
|
36365
|
-
if (!parsed.ok) {
|
|
36366
|
-
throw usageError(`Invalid judge input: ${parsed.issues.join("; ")}`);
|
|
36367
|
-
}
|
|
36368
|
-
let report;
|
|
36369
|
-
try {
|
|
36370
|
-
report = evaluateRules(parsed);
|
|
36371
|
-
} catch (error) {
|
|
36372
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
36373
|
-
throw usageError(`Invalid judge input: ${message}`);
|
|
36374
|
-
}
|
|
36375
|
-
if (flags.provider === "rules") {
|
|
36376
|
-
await writeOutput({
|
|
36377
|
-
format: flags.format,
|
|
36378
|
-
outPath: flags.out,
|
|
36379
|
-
data: flags.format === "md" ? renderJudgeMarkdown(report) : flags.format === "csv" ? judgeCsvRows(report) : report
|
|
36380
|
-
});
|
|
36381
|
-
return report;
|
|
36382
|
-
}
|
|
36383
|
-
await Promise.resolve().then(() => init_jev());
|
|
36384
|
-
const key = (hooks?.env ?? process.env).TYPESAFE_API_KEY?.trim() ?? "";
|
|
36385
|
-
if (!isSafeJevReport(report, key)) {
|
|
36386
|
-
throw usageError("Unsafe Jev numerical report metadata. Remove secrets and local paths from the judge inputs.");
|
|
36387
|
-
}
|
|
36388
|
-
const advisory = await adviseWithJev({
|
|
36389
|
-
artifact: parsed.artifact,
|
|
36390
|
-
rubric: parsed.rubric,
|
|
36391
|
-
numerical: report,
|
|
36392
|
-
requestedModel,
|
|
36393
|
-
dryRun: flags["dry-run"] === true,
|
|
36394
|
-
cachePath: flags.cache,
|
|
36395
|
-
transport: hooks?.transport,
|
|
36396
|
-
env: hooks?.env,
|
|
36397
|
-
nowMs: hooks?.nowMs,
|
|
36398
|
-
sleep: hooks?.sleep
|
|
36399
|
-
});
|
|
36400
|
-
const bundle = { numericalReport: report, advisory };
|
|
36401
|
-
if (flags.format === "md") {
|
|
36402
|
-
const markdown = [
|
|
36403
|
-
renderJudgeMarkdown(report),
|
|
36404
|
-
`- Jev: ${advisory.status} ${advisory.reason}`,
|
|
36405
|
-
`- Endpoint: ${advisory.endpoint}`,
|
|
36406
|
-
`- Planned requests: ${advisory.plannedRequests}`,
|
|
36407
|
-
"",
|
|
36408
|
-
"## Jev answers",
|
|
36409
|
-
"",
|
|
36410
|
-
...advisory.answers.map((answer) => {
|
|
36411
|
-
const value = answer.noul ?? answer.choice ?? answer.score ?? "none";
|
|
36412
|
-
const confidence = answer.confidence === null ? "" : `; confidence=${answer.confidence}`;
|
|
36413
|
-
return `- ${answer.id}: ${answer.type}=${value}${confidence}; ${answer.disposition}`;
|
|
36414
|
-
}),
|
|
36415
|
-
...advisory.answers.length === 0 ? ["None."] : []
|
|
36416
|
-
].join(`
|
|
36417
|
-
`);
|
|
36418
|
-
if (flags.out) {
|
|
36419
|
-
const outPath = resolve7(flags.out);
|
|
36420
|
-
await writeTextFile(outPath, `${markdown}
|
|
36421
|
-
`);
|
|
36422
|
-
console.log(`Wrote md output to ${outPath}`);
|
|
36423
|
-
} else {
|
|
36424
|
-
await writeStdout(`${markdown}
|
|
36425
|
-
`);
|
|
36426
|
-
}
|
|
36427
|
-
return bundle;
|
|
36428
|
-
}
|
|
36429
|
-
await writeOutput({
|
|
36430
|
-
format: flags.format,
|
|
36431
|
-
outPath: flags.out,
|
|
36432
|
-
data: bundle,
|
|
36433
|
-
meta: buildOutputMeta({
|
|
36434
|
-
command: "judge",
|
|
36435
|
-
pluginDigest: {},
|
|
36436
|
-
schemaRef: outputSchemaRefForCommand("judge.jev")
|
|
36437
|
-
})
|
|
36438
|
-
});
|
|
36439
|
-
return bundle;
|
|
36440
|
-
}
|
|
36441
|
-
var judge_default = defineCommand({
|
|
36442
|
-
name: "judge",
|
|
36443
|
-
description: "Advise on an analysis artifact with a versioned rules rubric",
|
|
36444
|
-
options: {
|
|
36445
|
-
rubric: option(string2(), { description: "Versioned rubric JSON path" }),
|
|
36446
|
-
provider: option(_enum(["rules", "jev"]).default("rules"), {
|
|
36447
|
-
description: "Advisory provider. rules is local. jev is an opt-in network provider."
|
|
36448
|
-
}),
|
|
36449
|
-
"dry-run": option(exports_coerce.boolean().default(false), {
|
|
36450
|
-
description: "Show the Jev request plan without calling the network"
|
|
36451
|
-
}),
|
|
36452
|
-
model: option(string2().trim().min(1).default("jev-latest"), {
|
|
36453
|
-
description: "Requested Jev model. jev-latest is an alias, not a pinned id."
|
|
36454
|
-
}),
|
|
36455
|
-
cache: option(string2().optional(), { description: "Optional Jev advisory cache path" }),
|
|
36456
|
-
out: option(string2().optional(), { description: "Output path" }),
|
|
36457
|
-
format: option(_enum(["json", "md", "csv"]).default("json"), { description: "Output format" })
|
|
36458
|
-
},
|
|
36459
|
-
async handler({ flags, positional }) {
|
|
36460
|
-
const analysisPath = positional[0];
|
|
36461
|
-
if (!analysisPath) {
|
|
36462
|
-
throw usageError("Usage: idk judge <analysis.json> --rubric <rubric.json> --provider rules");
|
|
36463
|
-
}
|
|
36464
|
-
await runJudge(flags, analysisPath);
|
|
36465
|
-
}
|
|
36466
|
-
});
|
|
36467
|
-
|
|
36468
|
-
// src/commands/calibrate.ts
|
|
36469
|
-
init_zod();
|
|
36470
|
-
init_errors3();
|
|
36471
|
-
init_outputMeta();
|
|
36472
|
-
import { extname as extname3, resolve as resolve8 } from "path";
|
|
36473
|
-
|
|
36474
|
-
// src/lib/ltvCalibration.ts
|
|
36475
|
-
function assertFiniteNumbers(value, path) {
|
|
36476
|
-
if (typeof value === "number" && !Number.isFinite(value)) {
|
|
36477
|
-
throw new RangeError(`Calibration requires finite numbers; numeric overflow at ${path}`);
|
|
36478
|
-
}
|
|
36479
|
-
if (typeof value === "object" && value !== null) {
|
|
36480
|
-
for (const [key, child] of Object.entries(value))
|
|
36481
|
-
assertFiniteNumbers(child, `${path}.${key}`);
|
|
36482
|
-
}
|
|
36483
|
-
}
|
|
36484
|
-
function clamp012(v) {
|
|
36485
|
-
if (v < 0)
|
|
36486
|
-
return 0;
|
|
36487
|
-
if (v > 1)
|
|
36488
|
-
return 1;
|
|
36489
|
-
return v;
|
|
36490
|
-
}
|
|
36491
|
-
function clampCorrelation(v) {
|
|
36492
|
-
if (!Number.isFinite(v))
|
|
36493
|
-
return 0;
|
|
36494
|
-
if (v < -1)
|
|
36495
|
-
return -1;
|
|
36496
|
-
if (v > 1)
|
|
36497
|
-
return 1;
|
|
36498
|
-
return v;
|
|
36499
|
-
}
|
|
36500
|
-
function mean(values) {
|
|
36501
|
-
if (values.length === 0)
|
|
36502
|
-
return 0;
|
|
36503
|
-
return values.reduce((a, b) => a + b, 0) / values.length;
|
|
36504
|
-
}
|
|
36505
|
-
function variance(values) {
|
|
36506
|
-
if (values.length === 0)
|
|
36507
|
-
return 0;
|
|
36508
|
-
const m = mean(values);
|
|
36509
|
-
let acc = 0;
|
|
36510
|
-
for (const v of values) {
|
|
36511
|
-
const d = v - m;
|
|
36512
|
-
acc += d * d;
|
|
36513
|
-
}
|
|
36514
|
-
return acc / values.length;
|
|
36515
|
-
}
|
|
36516
|
-
function estimateCorrelation(x, y) {
|
|
36517
|
-
const n = Math.min(x.length, y.length);
|
|
36518
|
-
if (n < 3) {
|
|
36519
|
-
return {
|
|
36520
|
-
raw: 0,
|
|
36521
|
-
value: 0,
|
|
36522
|
-
confidence: 0,
|
|
36523
|
-
shrinkage: 0,
|
|
36524
|
-
sampleSize: n,
|
|
36525
|
-
varianceX: 0,
|
|
36526
|
-
varianceY: 0
|
|
36527
|
-
};
|
|
36528
|
-
}
|
|
36529
|
-
const sx = x.slice(0, n);
|
|
36530
|
-
const sy = y.slice(0, n);
|
|
36531
|
-
const mx = mean(sx);
|
|
36532
|
-
const my = mean(sy);
|
|
36533
|
-
let cov = 0;
|
|
36534
|
-
let vx = 0;
|
|
36535
|
-
let vy = 0;
|
|
36536
|
-
for (let i = 0;i < n; i++) {
|
|
36537
|
-
const dx = (sx[i] ?? 0) - mx;
|
|
36538
|
-
const dy = (sy[i] ?? 0) - my;
|
|
36539
|
-
cov += dx * dy;
|
|
36540
|
-
vx += dx * dx;
|
|
36541
|
-
vy += dy * dy;
|
|
36542
|
-
}
|
|
36543
|
-
const varianceX = vx / n;
|
|
36544
|
-
const varianceY = vy / n;
|
|
36545
|
-
if (vx <= 0.000000000001 || vy <= 0.000000000001) {
|
|
36546
|
-
return {
|
|
36547
|
-
raw: 0,
|
|
36548
|
-
value: 0,
|
|
36549
|
-
confidence: 0,
|
|
36550
|
-
shrinkage: 0,
|
|
36551
|
-
sampleSize: n,
|
|
36552
|
-
varianceX,
|
|
36553
|
-
varianceY
|
|
36683
|
+
upperThreshold: result.evidence.upperThreshold,
|
|
36684
|
+
diagnosticStatus: diagnostic?.status ?? null,
|
|
36685
|
+
recommendationId: diagnostic?.recommendationId ?? null
|
|
36554
36686
|
};
|
|
36555
|
-
}
|
|
36556
|
-
const raw = clampCorrelation(cov / Math.sqrt(vx) / Math.sqrt(vy));
|
|
36557
|
-
const ci95 = 1.96 / Math.sqrt(Math.max(1, n - 3));
|
|
36558
|
-
const sampleConfidence = clamp012(1 - ci95);
|
|
36559
|
-
const varianceConfidenceX = varianceX / (varianceX + 0.01);
|
|
36560
|
-
const varianceConfidenceY = varianceY / (varianceY + 0.01);
|
|
36561
|
-
const varianceConfidence = Math.sqrt(varianceConfidenceX * varianceConfidenceY);
|
|
36562
|
-
const confidence = clamp012(sampleConfidence * varianceConfidence);
|
|
36563
|
-
const shrinkage = confidence;
|
|
36564
|
-
const value = raw * shrinkage;
|
|
36565
|
-
return {
|
|
36566
|
-
raw,
|
|
36567
|
-
value,
|
|
36568
|
-
confidence,
|
|
36569
|
-
shrinkage,
|
|
36570
|
-
sampleSize: n,
|
|
36571
|
-
varianceX,
|
|
36572
|
-
varianceY
|
|
36573
|
-
};
|
|
36687
|
+
});
|
|
36574
36688
|
}
|
|
36575
|
-
function
|
|
36576
|
-
|
|
36577
|
-
|
|
36689
|
+
async function runJudge(flags, analysisPath, hooks) {
|
|
36690
|
+
const requestedModel = (flags.model ?? "jev-latest").trim();
|
|
36691
|
+
if (flags.provider === "jev" && requestedModel.length === 0) {
|
|
36692
|
+
throw usageError("--model requires a nonempty model name.");
|
|
36578
36693
|
}
|
|
36579
|
-
|
|
36580
|
-
|
|
36581
|
-
const day = Math.max(0, Math.floor(row.day));
|
|
36582
|
-
const u = users.get(row.userId) ?? { days: new Set, iap: 0, ad: 0, acq: undefined };
|
|
36583
|
-
if (row.active ?? true)
|
|
36584
|
-
u.days.add(day);
|
|
36585
|
-
u.iap += Math.max(0, row.iapRevenue);
|
|
36586
|
-
u.ad += Math.max(0, row.adRevenue);
|
|
36587
|
-
if (row.acquisitionCost !== undefined && Number.isFinite(row.acquisitionCost)) {
|
|
36588
|
-
u.acq = (u.acq ?? 0) + Math.max(0, row.acquisitionCost);
|
|
36589
|
-
}
|
|
36590
|
-
users.set(row.userId, u);
|
|
36694
|
+
if (flags.provider === "jev" && flags.format === "csv") {
|
|
36695
|
+
throw usageError("--provider jev writes json or md. csv cannot hold the rules report and the advisory together.");
|
|
36591
36696
|
}
|
|
36592
|
-
|
|
36593
|
-
|
|
36594
|
-
|
|
36595
|
-
for (const user of users.values())
|
|
36596
|
-
if (user.days.has(targetDay))
|
|
36597
|
-
count += 1;
|
|
36598
|
-
return count;
|
|
36599
|
-
};
|
|
36600
|
-
const d1 = dayActiveCount(1) / cohortSize;
|
|
36601
|
-
const d7 = dayActiveCount(7) / cohortSize;
|
|
36602
|
-
const d30 = dayActiveCount(30) / cohortSize;
|
|
36603
|
-
const d90 = dayActiveCount(90) / cohortSize;
|
|
36604
|
-
let totalIap = 0;
|
|
36605
|
-
let totalAd = 0;
|
|
36606
|
-
let payerUsers = 0;
|
|
36607
|
-
let activeUserDays = 0;
|
|
36608
|
-
let payerDays = 0;
|
|
36609
|
-
let totalAcq = 0;
|
|
36610
|
-
let acqUsers = 0;
|
|
36611
|
-
for (const user of users.values()) {
|
|
36612
|
-
totalIap += user.iap;
|
|
36613
|
-
totalAd += user.ad;
|
|
36614
|
-
if (user.iap > 0)
|
|
36615
|
-
payerUsers += 1;
|
|
36616
|
-
activeUserDays += user.days.size;
|
|
36617
|
-
for (const day of user.days) {
|
|
36618
|
-
if (day >= 0 && user.iap > 0)
|
|
36619
|
-
payerDays += 1;
|
|
36697
|
+
if (flags.provider === "jev" && flags.cache) {
|
|
36698
|
+
if (flags.out && sameFile(flags.cache, flags.out)) {
|
|
36699
|
+
throw usageError("--cache and --out name the same file. Give the Jev cache its own path.");
|
|
36620
36700
|
}
|
|
36621
|
-
if (
|
|
36622
|
-
|
|
36623
|
-
|
|
36701
|
+
if (flags.out && (ancestorFile(flags.cache, flags.out) || ancestorFile(flags.out, flags.cache))) {
|
|
36702
|
+
throw usageError("--cache and --out cannot be ancestor paths. Give each file a separate target.");
|
|
36703
|
+
}
|
|
36704
|
+
if (sameFile(flags.cache, analysisPath) || sameFile(flags.cache, flags.rubric)) {
|
|
36705
|
+
throw usageError("--cache names an input file. Give the Jev cache its own path.");
|
|
36624
36706
|
}
|
|
36625
36707
|
}
|
|
36626
|
-
|
|
36627
|
-
|
|
36628
|
-
|
|
36629
|
-
|
|
36630
|
-
|
|
36631
|
-
const userConversion = [];
|
|
36632
|
-
const userArppu = [];
|
|
36633
|
-
const userAdArpDau = [];
|
|
36634
|
-
for (const user of users.values()) {
|
|
36635
|
-
const activeDays = Math.max(1, user.days.size);
|
|
36636
|
-
const retentionScore = clamp012(activeDays / 30);
|
|
36637
|
-
const conversionScore = user.iap > 0 ? 1 : 0;
|
|
36638
|
-
const arppuScore = user.iap > 0 ? user.iap / activeDays : 0;
|
|
36639
|
-
const adScore = user.ad / activeDays;
|
|
36640
|
-
userRetention.push(retentionScore);
|
|
36641
|
-
userConversion.push(conversionScore);
|
|
36642
|
-
userArppu.push(arppuScore);
|
|
36643
|
-
userAdArpDau.push(adScore);
|
|
36708
|
+
if (flags.provider === "jev") {
|
|
36709
|
+
if (flags.cache)
|
|
36710
|
+
requireFileTarget(flags.cache, "cache");
|
|
36711
|
+
if (flags.out)
|
|
36712
|
+
requireFileTarget(flags.out, "out");
|
|
36644
36713
|
}
|
|
36645
|
-
|
|
36646
|
-
|
|
36647
|
-
|
|
36648
|
-
|
|
36649
|
-
|
|
36650
|
-
|
|
36651
|
-
|
|
36652
|
-
}
|
|
36653
|
-
const
|
|
36654
|
-
|
|
36655
|
-
|
|
36656
|
-
|
|
36657
|
-
|
|
36658
|
-
|
|
36659
|
-
|
|
36660
|
-
}
|
|
36661
|
-
|
|
36662
|
-
|
|
36663
|
-
|
|
36664
|
-
|
|
36665
|
-
|
|
36666
|
-
|
|
36667
|
-
|
|
36668
|
-
|
|
36669
|
-
|
|
36670
|
-
|
|
36671
|
-
|
|
36672
|
-
|
|
36673
|
-
|
|
36674
|
-
|
|
36675
|
-
|
|
36676
|
-
}
|
|
36677
|
-
const
|
|
36678
|
-
|
|
36679
|
-
|
|
36680
|
-
|
|
36681
|
-
|
|
36682
|
-
|
|
36683
|
-
|
|
36684
|
-
|
|
36685
|
-
|
|
36686
|
-
|
|
36687
|
-
|
|
36688
|
-
|
|
36689
|
-
|
|
36690
|
-
|
|
36691
|
-
|
|
36692
|
-
|
|
36693
|
-
|
|
36694
|
-
|
|
36695
|
-
}
|
|
36696
|
-
|
|
36697
|
-
|
|
36698
|
-
|
|
36699
|
-
|
|
36700
|
-
|
|
36701
|
-
|
|
36702
|
-
|
|
36703
|
-
|
|
36704
|
-
|
|
36705
|
-
|
|
36706
|
-
|
|
36707
|
-
|
|
36708
|
-
|
|
36709
|
-
|
|
36710
|
-
|
|
36711
|
-
|
|
36712
|
-
|
|
36713
|
-
|
|
36714
|
-
|
|
36715
|
-
arppuAd: estimatedCorrelation.arppuAd
|
|
36716
|
-
}
|
|
36717
|
-
}
|
|
36718
|
-
},
|
|
36719
|
-
diagnostics: {
|
|
36720
|
-
users: cohortSize,
|
|
36721
|
-
totalRows: rows.length,
|
|
36722
|
-
totalIap,
|
|
36723
|
-
totalAd,
|
|
36724
|
-
activeUserDays,
|
|
36725
|
-
payerUsers,
|
|
36726
|
-
payerDays,
|
|
36727
|
-
retention: {
|
|
36728
|
-
d1,
|
|
36729
|
-
d7,
|
|
36730
|
-
d30,
|
|
36731
|
-
d90
|
|
36732
|
-
},
|
|
36733
|
-
userFeatureVariance: {
|
|
36734
|
-
retention: variance(userRetention),
|
|
36735
|
-
conversion: variance(userConversion),
|
|
36736
|
-
arppu: variance(userArppu),
|
|
36737
|
-
adArpDau: variance(userAdArpDau)
|
|
36738
|
-
},
|
|
36739
|
-
estimatedCorrelation,
|
|
36740
|
-
estimatedCorrelationRaw,
|
|
36741
|
-
correlationConfidence,
|
|
36742
|
-
correlationDiagnostics: estimatedCorrelationPair
|
|
36714
|
+
let artifactRaw;
|
|
36715
|
+
let rubricRaw;
|
|
36716
|
+
try {
|
|
36717
|
+
artifactRaw = await readJsonFile(analysisPath);
|
|
36718
|
+
rubricRaw = await readJsonFile(flags.rubric);
|
|
36719
|
+
} catch (error) {
|
|
36720
|
+
throw usageError(`Invalid judge input: ${error instanceof Error ? error.message : String(error)}`);
|
|
36721
|
+
}
|
|
36722
|
+
const parsed = readJudgeInputs(artifactRaw, rubricRaw);
|
|
36723
|
+
if (!parsed.ok) {
|
|
36724
|
+
throw usageError(`Invalid judge input: ${parsed.issues.join("; ")}`);
|
|
36725
|
+
}
|
|
36726
|
+
let report;
|
|
36727
|
+
try {
|
|
36728
|
+
report = evaluateRules(parsed);
|
|
36729
|
+
} catch (error) {
|
|
36730
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
36731
|
+
throw usageError(`Invalid judge input: ${message}`);
|
|
36732
|
+
}
|
|
36733
|
+
if (flags.provider === "rules") {
|
|
36734
|
+
await writeOutput({
|
|
36735
|
+
format: flags.format,
|
|
36736
|
+
outPath: flags.out,
|
|
36737
|
+
data: flags.format === "md" ? renderJudgeMarkdown(report) : flags.format === "csv" ? judgeCsvRows(report) : report
|
|
36738
|
+
});
|
|
36739
|
+
return report;
|
|
36740
|
+
}
|
|
36741
|
+
await Promise.resolve().then(() => init_jev());
|
|
36742
|
+
const key = (hooks?.env ?? process.env).TYPESAFE_API_KEY?.trim() ?? "";
|
|
36743
|
+
if (!isSafeJevReport(report, key)) {
|
|
36744
|
+
throw usageError("Unsafe Jev numerical report metadata. Remove secrets and local paths from the judge inputs.");
|
|
36745
|
+
}
|
|
36746
|
+
const advisory = await adviseWithJev({
|
|
36747
|
+
artifact: parsed.artifact,
|
|
36748
|
+
rubric: parsed.rubric,
|
|
36749
|
+
numerical: report,
|
|
36750
|
+
requestedModel,
|
|
36751
|
+
dryRun: flags["dry-run"] === true,
|
|
36752
|
+
cachePath: flags.cache,
|
|
36753
|
+
transport: hooks?.transport,
|
|
36754
|
+
env: hooks?.env,
|
|
36755
|
+
nowMs: hooks?.nowMs,
|
|
36756
|
+
sleep: hooks?.sleep
|
|
36757
|
+
});
|
|
36758
|
+
const bundle = { numericalReport: report, advisory };
|
|
36759
|
+
if (flags.format === "md") {
|
|
36760
|
+
const markdown = [
|
|
36761
|
+
renderJudgeMarkdown(report),
|
|
36762
|
+
`- Jev: ${advisory.status} ${advisory.reason}`,
|
|
36763
|
+
`- Endpoint: ${advisory.endpoint}`,
|
|
36764
|
+
`- Planned requests: ${advisory.plannedRequests}`,
|
|
36765
|
+
"",
|
|
36766
|
+
"## Jev answers",
|
|
36767
|
+
"",
|
|
36768
|
+
...advisory.answers.map((answer) => {
|
|
36769
|
+
const value = answer.noul ?? answer.choice ?? answer.score ?? "none";
|
|
36770
|
+
const confidence = answer.confidence === null ? "" : `; confidence=${answer.confidence}`;
|
|
36771
|
+
return `- ${answer.id}: ${answer.type}=${value}${confidence}; ${answer.disposition}`;
|
|
36772
|
+
}),
|
|
36773
|
+
...advisory.answers.length === 0 ? ["None."] : []
|
|
36774
|
+
].join(`
|
|
36775
|
+
`);
|
|
36776
|
+
if (flags.out) {
|
|
36777
|
+
const outPath = resolve7(flags.out);
|
|
36778
|
+
await writeTextFile(outPath, `${markdown}
|
|
36779
|
+
`);
|
|
36780
|
+
console.log(`Wrote md output to ${outPath}`);
|
|
36781
|
+
} else {
|
|
36782
|
+
await writeStdout(`${markdown}
|
|
36783
|
+
`);
|
|
36743
36784
|
}
|
|
36744
|
-
|
|
36745
|
-
|
|
36746
|
-
|
|
36747
|
-
|
|
36748
|
-
|
|
36749
|
-
|
|
36750
|
-
|
|
36751
|
-
|
|
36752
|
-
|
|
36753
|
-
|
|
36754
|
-
|
|
36785
|
+
return bundle;
|
|
36786
|
+
}
|
|
36787
|
+
await writeOutput({
|
|
36788
|
+
format: flags.format,
|
|
36789
|
+
outPath: flags.out,
|
|
36790
|
+
data: bundle,
|
|
36791
|
+
meta: buildOutputMeta({
|
|
36792
|
+
command: "judge",
|
|
36793
|
+
pluginDigest: {},
|
|
36794
|
+
schemaRef: outputSchemaRefForCommand("judge.jev")
|
|
36795
|
+
})
|
|
36796
|
+
});
|
|
36797
|
+
return bundle;
|
|
36755
36798
|
}
|
|
36756
|
-
|
|
36757
|
-
|
|
36758
|
-
|
|
36759
|
-
|
|
36760
|
-
|
|
36761
|
-
|
|
36762
|
-
|
|
36763
|
-
},
|
|
36764
|
-
|
|
36765
|
-
|
|
36766
|
-
|
|
36767
|
-
|
|
36768
|
-
|
|
36769
|
-
|
|
36770
|
-
},
|
|
36771
|
-
|
|
36772
|
-
|
|
36773
|
-
|
|
36774
|
-
|
|
36775
|
-
|
|
36776
|
-
|
|
36777
|
-
|
|
36778
|
-
progressionLogSpan: m?.revenue?.progressionLogSpan ?? 6
|
|
36779
|
-
},
|
|
36780
|
-
acquisition: {
|
|
36781
|
-
cpi: m?.acquisition?.cpi ?? 0
|
|
36782
|
-
},
|
|
36783
|
-
uncertainty: {
|
|
36784
|
-
enabled: m?.uncertainty?.enabled ?? false,
|
|
36785
|
-
draws: m?.uncertainty?.draws ?? 300,
|
|
36786
|
-
quantiles,
|
|
36787
|
-
seed: m?.uncertainty?.seed,
|
|
36788
|
-
sigma: {
|
|
36789
|
-
retention: m?.uncertainty?.sigma?.retention ?? 0.08,
|
|
36790
|
-
conversion: m?.uncertainty?.sigma?.conversion ?? 0.12,
|
|
36791
|
-
arppu: m?.uncertainty?.sigma?.arppu ?? 0.2,
|
|
36792
|
-
ad: m?.uncertainty?.sigma?.ad ?? 0.15
|
|
36793
|
-
},
|
|
36794
|
-
correlation: {
|
|
36795
|
-
retentionConversion: m?.uncertainty?.correlation?.retentionConversion ?? 0,
|
|
36796
|
-
retentionArppu: m?.uncertainty?.correlation?.retentionArppu ?? 0,
|
|
36797
|
-
retentionAd: m?.uncertainty?.correlation?.retentionAd ?? 0,
|
|
36798
|
-
conversionArppu: m?.uncertainty?.correlation?.conversionArppu ?? 0,
|
|
36799
|
-
conversionAd: m?.uncertainty?.correlation?.conversionAd ?? 0,
|
|
36800
|
-
arppuAd: m?.uncertainty?.correlation?.arppuAd ?? 0
|
|
36801
|
-
}
|
|
36799
|
+
var judge_default = defineCommand({
|
|
36800
|
+
name: "judge",
|
|
36801
|
+
description: "Advise on an analysis artifact with a versioned rules rubric",
|
|
36802
|
+
options: {
|
|
36803
|
+
rubric: option(string2(), { description: "Versioned rubric JSON path" }),
|
|
36804
|
+
provider: option(_enum(["rules", "jev"]).default("rules"), {
|
|
36805
|
+
description: "Advisory provider. rules is local. jev is an opt-in network provider."
|
|
36806
|
+
}),
|
|
36807
|
+
"dry-run": option(exports_coerce.boolean().default(false), {
|
|
36808
|
+
description: "Show the Jev request plan without calling the network"
|
|
36809
|
+
}),
|
|
36810
|
+
model: option(string2().trim().min(1).default("jev-latest"), {
|
|
36811
|
+
description: "Requested Jev model. jev-latest is an alias, not a pinned id."
|
|
36812
|
+
}),
|
|
36813
|
+
cache: option(string2().optional(), { description: "Optional Jev advisory cache path" }),
|
|
36814
|
+
out: option(string2().optional(), { description: "Output path" }),
|
|
36815
|
+
format: option(_enum(["json", "md", "csv"]).default("json"), { description: "Output format" })
|
|
36816
|
+
},
|
|
36817
|
+
async handler({ flags, positional }) {
|
|
36818
|
+
const analysisPath = positional[0];
|
|
36819
|
+
if (!analysisPath) {
|
|
36820
|
+
throw usageError("Usage: idk judge <analysis.json> --rubric <rubric.json> --provider rules");
|
|
36802
36821
|
}
|
|
36803
|
-
|
|
36804
|
-
}
|
|
36805
|
-
function retentionAtDay(ret, day) {
|
|
36806
|
-
if (day <= 0)
|
|
36807
|
-
return 1;
|
|
36808
|
-
if (day <= 1)
|
|
36809
|
-
return ret.d1;
|
|
36810
|
-
if (day <= 7)
|
|
36811
|
-
return ret.d1 + (ret.d7 - ret.d1) * (day - 1) / 6;
|
|
36812
|
-
if (day <= 30)
|
|
36813
|
-
return ret.d7 + (ret.d30 - ret.d7) * (day - 7) / 23;
|
|
36814
|
-
if (day <= 90)
|
|
36815
|
-
return ret.d30 + (ret.d90 - ret.d30) * (day - 30) / 60;
|
|
36816
|
-
const extra = day - 90;
|
|
36817
|
-
return clamp013(ret.d90 * Math.exp(-ret.longTailDailyDecay * extra));
|
|
36818
|
-
}
|
|
36819
|
-
function progressionFactor(startWorthLog10, endWorthLog10, span) {
|
|
36820
|
-
if (!Number.isFinite(startWorthLog10) || !Number.isFinite(endWorthLog10) || !Number.isFinite(span) || span <= 0) {
|
|
36821
|
-
return 0;
|
|
36822
|
+
await runJudge(flags, analysisPath);
|
|
36822
36823
|
}
|
|
36823
|
-
|
|
36824
|
-
|
|
36825
|
-
|
|
36826
|
-
|
|
36827
|
-
|
|
36828
|
-
|
|
36829
|
-
|
|
36830
|
-
|
|
36831
|
-
|
|
36832
|
-
|
|
36833
|
-
|
|
36834
|
-
|
|
36835
|
-
const active = retentionAtDay(config.retention, day);
|
|
36836
|
-
gross += active * (payerConversion * arppuDaily + adArpDau);
|
|
36824
|
+
});
|
|
36825
|
+
|
|
36826
|
+
// src/commands/calibrate.ts
|
|
36827
|
+
init_zod();
|
|
36828
|
+
init_errors3();
|
|
36829
|
+
init_outputMeta();
|
|
36830
|
+
import { extname as extname3, resolve as resolve8 } from "path";
|
|
36831
|
+
|
|
36832
|
+
// src/lib/ltvCalibration.ts
|
|
36833
|
+
function assertFiniteNumbers(value, path) {
|
|
36834
|
+
if (typeof value === "number" && !Number.isFinite(value)) {
|
|
36835
|
+
throw new RangeError(`Calibration requires finite numbers; numeric overflow at ${path}`);
|
|
36837
36836
|
}
|
|
36838
|
-
if (
|
|
36839
|
-
const
|
|
36840
|
-
|
|
36837
|
+
if (typeof value === "object" && value !== null) {
|
|
36838
|
+
for (const [key, child] of Object.entries(value))
|
|
36839
|
+
assertFiniteNumbers(child, `${path}.${key}`);
|
|
36841
36840
|
}
|
|
36842
|
-
const net = gross * (1 - config.revenue.platformFeeRate) * config.revenue.grossMarginRate;
|
|
36843
|
-
const ltv = net - config.acquisition.cpi;
|
|
36844
|
-
return {
|
|
36845
|
-
cumulativeGrossRevenuePerUser: gross,
|
|
36846
|
-
cumulativeNetRevenuePerUser: net,
|
|
36847
|
-
cumulativeLtvPerUser: ltv
|
|
36848
|
-
};
|
|
36849
36841
|
}
|
|
36850
|
-
// src/lib/ltvUncertainty.ts
|
|
36851
36842
|
function clamp014(v) {
|
|
36852
36843
|
if (v < 0)
|
|
36853
36844
|
return 0;
|
|
@@ -36864,168 +36855,258 @@ function clampCorrelation2(v) {
|
|
|
36864
36855
|
return 1;
|
|
36865
36856
|
return v;
|
|
36866
36857
|
}
|
|
36867
|
-
function
|
|
36868
|
-
|
|
36869
|
-
|
|
36870
|
-
|
|
36871
|
-
a = a + 1831565813 | 0;
|
|
36872
|
-
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
36873
|
-
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
36874
|
-
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
36875
|
-
};
|
|
36876
|
-
}
|
|
36877
|
-
function randNormal(rng) {
|
|
36878
|
-
const u1 = Math.max(0.000000000001, rng());
|
|
36879
|
-
const u2 = Math.max(0.000000000001, rng());
|
|
36880
|
-
return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
|
|
36881
|
-
}
|
|
36882
|
-
function buildCorrelationMatrix(base) {
|
|
36883
|
-
const rc = clampCorrelation2(base.retentionConversion);
|
|
36884
|
-
const ra = clampCorrelation2(base.retentionArppu);
|
|
36885
|
-
const rd = clampCorrelation2(base.retentionAd);
|
|
36886
|
-
const ca = clampCorrelation2(base.conversionArppu);
|
|
36887
|
-
const cd = clampCorrelation2(base.conversionAd);
|
|
36888
|
-
const ad = clampCorrelation2(base.arppuAd);
|
|
36889
|
-
return [
|
|
36890
|
-
[1, rc, ra, rd],
|
|
36891
|
-
[rc, 1, ca, cd],
|
|
36892
|
-
[ra, ca, 1, ad],
|
|
36893
|
-
[rd, cd, ad, 1]
|
|
36894
|
-
];
|
|
36895
|
-
}
|
|
36896
|
-
function withOffDiagonalScale(matrix, alpha) {
|
|
36897
|
-
return matrix.map((row, i) => row.map((v, j) => {
|
|
36898
|
-
if (i === j)
|
|
36899
|
-
return 1;
|
|
36900
|
-
return v * alpha;
|
|
36901
|
-
}));
|
|
36858
|
+
function mean(values) {
|
|
36859
|
+
if (values.length === 0)
|
|
36860
|
+
return 0;
|
|
36861
|
+
return values.reduce((a, b) => a + b, 0) / values.length;
|
|
36902
36862
|
}
|
|
36903
|
-
function
|
|
36904
|
-
|
|
36905
|
-
|
|
36906
|
-
|
|
36907
|
-
|
|
36908
|
-
|
|
36909
|
-
|
|
36910
|
-
|
|
36911
|
-
}
|
|
36912
|
-
if (i === j) {
|
|
36913
|
-
const val = (matrix[i]?.[i] ?? 0) - sum;
|
|
36914
|
-
if (val <= 0.000000001)
|
|
36915
|
-
return null;
|
|
36916
|
-
l[i][j] = Math.sqrt(val);
|
|
36917
|
-
} else {
|
|
36918
|
-
const denom = l[j]?.[j] ?? 0;
|
|
36919
|
-
if (Math.abs(denom) < 0.000000001)
|
|
36920
|
-
return null;
|
|
36921
|
-
l[i][j] = ((matrix[i]?.[j] ?? 0) - sum) / denom;
|
|
36922
|
-
}
|
|
36923
|
-
}
|
|
36863
|
+
function variance(values) {
|
|
36864
|
+
if (values.length === 0)
|
|
36865
|
+
return 0;
|
|
36866
|
+
const m = mean(values);
|
|
36867
|
+
let acc = 0;
|
|
36868
|
+
for (const v of values) {
|
|
36869
|
+
const d = v - m;
|
|
36870
|
+
acc += d * d;
|
|
36924
36871
|
}
|
|
36925
|
-
return
|
|
36872
|
+
return acc / values.length;
|
|
36926
36873
|
}
|
|
36927
|
-
function
|
|
36928
|
-
const
|
|
36929
|
-
|
|
36930
|
-
|
|
36931
|
-
|
|
36932
|
-
|
|
36933
|
-
|
|
36934
|
-
|
|
36874
|
+
function estimateCorrelation(x, y) {
|
|
36875
|
+
const n = Math.min(x.length, y.length);
|
|
36876
|
+
if (n < 3) {
|
|
36877
|
+
return {
|
|
36878
|
+
raw: 0,
|
|
36879
|
+
value: 0,
|
|
36880
|
+
confidence: 0,
|
|
36881
|
+
shrinkage: 0,
|
|
36882
|
+
sampleSize: n,
|
|
36883
|
+
varianceX: 0,
|
|
36884
|
+
varianceY: 0
|
|
36885
|
+
};
|
|
36935
36886
|
}
|
|
36936
|
-
|
|
36937
|
-
|
|
36938
|
-
|
|
36939
|
-
const
|
|
36940
|
-
|
|
36941
|
-
let
|
|
36942
|
-
let
|
|
36943
|
-
|
|
36944
|
-
|
|
36945
|
-
|
|
36887
|
+
const sx = x.slice(0, n);
|
|
36888
|
+
const sy = y.slice(0, n);
|
|
36889
|
+
const mx = mean(sx);
|
|
36890
|
+
const my = mean(sy);
|
|
36891
|
+
let cov = 0;
|
|
36892
|
+
let vx = 0;
|
|
36893
|
+
let vy = 0;
|
|
36894
|
+
for (let i = 0;i < n; i++) {
|
|
36895
|
+
const dx = (sx[i] ?? 0) - mx;
|
|
36896
|
+
const dy = (sy[i] ?? 0) - my;
|
|
36897
|
+
cov += dx * dy;
|
|
36898
|
+
vx += dx * dx;
|
|
36899
|
+
vy += dy * dy;
|
|
36946
36900
|
}
|
|
36947
|
-
|
|
36901
|
+
const varianceX = vx / n;
|
|
36902
|
+
const varianceY = vy / n;
|
|
36903
|
+
if (vx <= 0.000000000001 || vy <= 0.000000000001) {
|
|
36948
36904
|
return {
|
|
36949
|
-
|
|
36950
|
-
|
|
36951
|
-
|
|
36952
|
-
|
|
36905
|
+
raw: 0,
|
|
36906
|
+
value: 0,
|
|
36907
|
+
confidence: 0,
|
|
36908
|
+
shrinkage: 0,
|
|
36909
|
+
sampleSize: n,
|
|
36910
|
+
varianceX,
|
|
36911
|
+
varianceY
|
|
36953
36912
|
};
|
|
36954
36913
|
}
|
|
36955
|
-
const
|
|
36914
|
+
const raw = clampCorrelation2(cov / Math.sqrt(vx) / Math.sqrt(vy));
|
|
36915
|
+
const ci95 = 1.96 / Math.sqrt(Math.max(1, n - 3));
|
|
36916
|
+
const sampleConfidence = clamp014(1 - ci95);
|
|
36917
|
+
const varianceConfidenceX = varianceX / (varianceX + 0.01);
|
|
36918
|
+
const varianceConfidenceY = varianceY / (varianceY + 0.01);
|
|
36919
|
+
const varianceConfidence = Math.sqrt(varianceConfidenceX * varianceConfidenceY);
|
|
36920
|
+
const confidence = clamp014(sampleConfidence * varianceConfidence);
|
|
36921
|
+
const shrinkage = confidence;
|
|
36922
|
+
const value = raw * shrinkage;
|
|
36956
36923
|
return {
|
|
36957
|
-
|
|
36958
|
-
|
|
36959
|
-
|
|
36960
|
-
|
|
36924
|
+
raw,
|
|
36925
|
+
value,
|
|
36926
|
+
confidence,
|
|
36927
|
+
shrinkage,
|
|
36928
|
+
sampleSize: n,
|
|
36929
|
+
varianceX,
|
|
36930
|
+
varianceY
|
|
36961
36931
|
};
|
|
36962
36932
|
}
|
|
36963
|
-
function
|
|
36964
|
-
if (
|
|
36965
|
-
|
|
36966
|
-
|
|
36967
|
-
|
|
36968
|
-
|
|
36969
|
-
|
|
36970
|
-
|
|
36971
|
-
|
|
36972
|
-
|
|
36973
|
-
|
|
36974
|
-
|
|
36975
|
-
|
|
36976
|
-
|
|
36977
|
-
...base.retention,
|
|
36978
|
-
d1,
|
|
36979
|
-
d7,
|
|
36980
|
-
d30,
|
|
36981
|
-
d90
|
|
36982
|
-
},
|
|
36983
|
-
revenue: {
|
|
36984
|
-
...base.revenue,
|
|
36985
|
-
payerConversion: clamp014(applyLogNormalShock(base.revenue.payerConversion, base.uncertainty.sigma.conversion, shock.conversion)),
|
|
36986
|
-
arppuDaily: Math.max(0, applyLogNormalShock(base.revenue.arppuDaily, base.uncertainty.sigma.arppu, shock.arppu)),
|
|
36987
|
-
adArpDau: Math.max(0, applyLogNormalShock(base.revenue.adArpDau, base.uncertainty.sigma.ad, shock.ad))
|
|
36933
|
+
function calibrateMonetization(rows) {
|
|
36934
|
+
if (rows.length === 0) {
|
|
36935
|
+
throw new Error("calibration rows cannot be empty");
|
|
36936
|
+
}
|
|
36937
|
+
const users = new Map;
|
|
36938
|
+
for (const row of rows) {
|
|
36939
|
+
const day = Math.max(0, Math.floor(row.day));
|
|
36940
|
+
const u = users.get(row.userId) ?? { days: new Set, iap: 0, ad: 0, acq: undefined };
|
|
36941
|
+
if (row.active ?? true)
|
|
36942
|
+
u.days.add(day);
|
|
36943
|
+
u.iap += Math.max(0, row.iapRevenue);
|
|
36944
|
+
u.ad += Math.max(0, row.adRevenue);
|
|
36945
|
+
if (row.acquisitionCost !== undefined && Number.isFinite(row.acquisitionCost)) {
|
|
36946
|
+
u.acq = (u.acq ?? 0) + Math.max(0, row.acquisitionCost);
|
|
36988
36947
|
}
|
|
36948
|
+
users.set(row.userId, u);
|
|
36949
|
+
}
|
|
36950
|
+
const cohortSize = Math.max(1, users.size);
|
|
36951
|
+
const dayActiveCount = (targetDay) => {
|
|
36952
|
+
let count = 0;
|
|
36953
|
+
for (const user of users.values())
|
|
36954
|
+
if (user.days.has(targetDay))
|
|
36955
|
+
count += 1;
|
|
36956
|
+
return count;
|
|
36989
36957
|
};
|
|
36990
|
-
|
|
36991
|
-
|
|
36992
|
-
|
|
36993
|
-
|
|
36994
|
-
|
|
36995
|
-
|
|
36996
|
-
|
|
36997
|
-
|
|
36998
|
-
|
|
36999
|
-
|
|
37000
|
-
|
|
37001
|
-
const
|
|
37002
|
-
|
|
37003
|
-
|
|
37004
|
-
|
|
37005
|
-
|
|
37006
|
-
|
|
37007
|
-
|
|
37008
|
-
|
|
37009
|
-
|
|
37010
|
-
}
|
|
37011
|
-
|
|
37012
|
-
|
|
37013
|
-
|
|
36958
|
+
const d1 = dayActiveCount(1) / cohortSize;
|
|
36959
|
+
const d7 = dayActiveCount(7) / cohortSize;
|
|
36960
|
+
const d30 = dayActiveCount(30) / cohortSize;
|
|
36961
|
+
const d90 = dayActiveCount(90) / cohortSize;
|
|
36962
|
+
let totalIap = 0;
|
|
36963
|
+
let totalAd = 0;
|
|
36964
|
+
let payerUsers = 0;
|
|
36965
|
+
let activeUserDays = 0;
|
|
36966
|
+
let payerDays = 0;
|
|
36967
|
+
let totalAcq = 0;
|
|
36968
|
+
let acqUsers = 0;
|
|
36969
|
+
for (const user of users.values()) {
|
|
36970
|
+
totalIap += user.iap;
|
|
36971
|
+
totalAd += user.ad;
|
|
36972
|
+
if (user.iap > 0)
|
|
36973
|
+
payerUsers += 1;
|
|
36974
|
+
activeUserDays += user.days.size;
|
|
36975
|
+
for (const day of user.days) {
|
|
36976
|
+
if (day >= 0 && user.iap > 0)
|
|
36977
|
+
payerDays += 1;
|
|
36978
|
+
}
|
|
36979
|
+
if (user.acq !== undefined) {
|
|
36980
|
+
totalAcq += user.acq;
|
|
36981
|
+
acqUsers += 1;
|
|
36982
|
+
}
|
|
37014
36983
|
}
|
|
37015
|
-
const
|
|
37016
|
-
const
|
|
37017
|
-
|
|
37018
|
-
|
|
36984
|
+
const payerConversion = payerUsers / cohortSize;
|
|
36985
|
+
const arppuDaily = totalIap / Math.max(1, payerDays);
|
|
36986
|
+
const adArpDau = totalAd / Math.max(1, activeUserDays);
|
|
36987
|
+
const cpi = totalAcq / Math.max(1, acqUsers);
|
|
36988
|
+
const userRetention = [];
|
|
36989
|
+
const userConversion = [];
|
|
36990
|
+
const userArppu = [];
|
|
36991
|
+
const userAdArpDau = [];
|
|
36992
|
+
for (const user of users.values()) {
|
|
36993
|
+
const activeDays = Math.max(1, user.days.size);
|
|
36994
|
+
const retentionScore = clamp014(activeDays / 30);
|
|
36995
|
+
const conversionScore = user.iap > 0 ? 1 : 0;
|
|
36996
|
+
const arppuScore = user.iap > 0 ? user.iap / activeDays : 0;
|
|
36997
|
+
const adScore = user.ad / activeDays;
|
|
36998
|
+
userRetention.push(retentionScore);
|
|
36999
|
+
userConversion.push(conversionScore);
|
|
37000
|
+
userArppu.push(arppuScore);
|
|
37001
|
+
userAdArpDau.push(adScore);
|
|
37019
37002
|
}
|
|
37020
|
-
|
|
37021
|
-
|
|
37022
|
-
|
|
37023
|
-
|
|
37024
|
-
|
|
37003
|
+
const estimatedCorrelationPair = {
|
|
37004
|
+
retentionConversion: estimateCorrelation(userRetention, userConversion),
|
|
37005
|
+
retentionArppu: estimateCorrelation(userRetention, userArppu),
|
|
37006
|
+
retentionAd: estimateCorrelation(userRetention, userAdArpDau),
|
|
37007
|
+
conversionArppu: estimateCorrelation(userConversion, userArppu),
|
|
37008
|
+
conversionAd: estimateCorrelation(userConversion, userAdArpDau),
|
|
37009
|
+
arppuAd: estimateCorrelation(userArppu, userAdArpDau)
|
|
37010
|
+
};
|
|
37011
|
+
const estimatedCorrelation = {
|
|
37012
|
+
retentionConversion: estimatedCorrelationPair.retentionConversion.value,
|
|
37013
|
+
retentionArppu: estimatedCorrelationPair.retentionArppu.value,
|
|
37014
|
+
retentionAd: estimatedCorrelationPair.retentionAd.value,
|
|
37015
|
+
conversionArppu: estimatedCorrelationPair.conversionArppu.value,
|
|
37016
|
+
conversionAd: estimatedCorrelationPair.conversionAd.value,
|
|
37017
|
+
arppuAd: estimatedCorrelationPair.arppuAd.value
|
|
37018
|
+
};
|
|
37019
|
+
const estimatedCorrelationRaw = {
|
|
37020
|
+
retentionConversion: estimatedCorrelationPair.retentionConversion.raw,
|
|
37021
|
+
retentionArppu: estimatedCorrelationPair.retentionArppu.raw,
|
|
37022
|
+
retentionAd: estimatedCorrelationPair.retentionAd.raw,
|
|
37023
|
+
conversionArppu: estimatedCorrelationPair.conversionArppu.raw,
|
|
37024
|
+
conversionAd: estimatedCorrelationPair.conversionAd.raw,
|
|
37025
|
+
arppuAd: estimatedCorrelationPair.arppuAd.raw
|
|
37026
|
+
};
|
|
37027
|
+
const correlationConfidence = {
|
|
37028
|
+
retentionConversion: estimatedCorrelationPair.retentionConversion.confidence,
|
|
37029
|
+
retentionArppu: estimatedCorrelationPair.retentionArppu.confidence,
|
|
37030
|
+
retentionAd: estimatedCorrelationPair.retentionAd.confidence,
|
|
37031
|
+
conversionArppu: estimatedCorrelationPair.conversionArppu.confidence,
|
|
37032
|
+
conversionAd: estimatedCorrelationPair.conversionAd.confidence,
|
|
37033
|
+
arppuAd: estimatedCorrelationPair.arppuAd.confidence
|
|
37034
|
+
};
|
|
37035
|
+
const result = {
|
|
37036
|
+
monetization: {
|
|
37037
|
+
cohorts: {
|
|
37038
|
+
baseUsers: cohortSize
|
|
37039
|
+
},
|
|
37040
|
+
retention: {
|
|
37041
|
+
d1: clamp014(d1),
|
|
37042
|
+
d7: clamp014(Math.min(d1, d7)),
|
|
37043
|
+
d30: clamp014(Math.min(d1, d7, d30)),
|
|
37044
|
+
d90: clamp014(Math.min(d1, d7, d30, d90)),
|
|
37045
|
+
longTailDailyDecay: 0.02
|
|
37046
|
+
},
|
|
37047
|
+
revenue: {
|
|
37048
|
+
payerConversion: clamp014(payerConversion),
|
|
37049
|
+
arppuDaily: Math.max(0, arppuDaily),
|
|
37050
|
+
adArpDau: Math.max(0, adArpDau),
|
|
37051
|
+
platformFeeRate: 0.3,
|
|
37052
|
+
grossMarginRate: 0.92
|
|
37053
|
+
},
|
|
37054
|
+
acquisition: {
|
|
37055
|
+
cpi: Math.max(0, cpi)
|
|
37056
|
+
},
|
|
37057
|
+
uncertainty: {
|
|
37058
|
+
enabled: true,
|
|
37059
|
+
draws: 300,
|
|
37060
|
+
quantiles: [0.5, 0.9],
|
|
37061
|
+
sigma: {
|
|
37062
|
+
retention: 0.08,
|
|
37063
|
+
conversion: 0.12,
|
|
37064
|
+
arppu: 0.2,
|
|
37065
|
+
ad: 0.15
|
|
37066
|
+
},
|
|
37067
|
+
correlation: {
|
|
37068
|
+
retentionConversion: estimatedCorrelation.retentionConversion,
|
|
37069
|
+
retentionArppu: estimatedCorrelation.retentionArppu,
|
|
37070
|
+
retentionAd: estimatedCorrelation.retentionAd,
|
|
37071
|
+
conversionArppu: estimatedCorrelation.conversionArppu,
|
|
37072
|
+
conversionAd: estimatedCorrelation.conversionAd,
|
|
37073
|
+
arppuAd: estimatedCorrelation.arppuAd
|
|
37074
|
+
}
|
|
37075
|
+
}
|
|
37025
37076
|
},
|
|
37026
|
-
|
|
37077
|
+
diagnostics: {
|
|
37078
|
+
users: cohortSize,
|
|
37079
|
+
totalRows: rows.length,
|
|
37080
|
+
totalIap,
|
|
37081
|
+
totalAd,
|
|
37082
|
+
activeUserDays,
|
|
37083
|
+
payerUsers,
|
|
37084
|
+
payerDays,
|
|
37085
|
+
retention: {
|
|
37086
|
+
d1,
|
|
37087
|
+
d7,
|
|
37088
|
+
d30,
|
|
37089
|
+
d90
|
|
37090
|
+
},
|
|
37091
|
+
userFeatureVariance: {
|
|
37092
|
+
retention: variance(userRetention),
|
|
37093
|
+
conversion: variance(userConversion),
|
|
37094
|
+
arppu: variance(userArppu),
|
|
37095
|
+
adArpDau: variance(userAdArpDau)
|
|
37096
|
+
},
|
|
37097
|
+
estimatedCorrelation,
|
|
37098
|
+
estimatedCorrelationRaw,
|
|
37099
|
+
correlationConfidence,
|
|
37100
|
+
correlationDiagnostics: estimatedCorrelationPair
|
|
37101
|
+
}
|
|
37027
37102
|
};
|
|
37103
|
+
assertFiniteNumbers(result, "calibration");
|
|
37104
|
+
return result;
|
|
37028
37105
|
}
|
|
37106
|
+
// src/lib/ltvProjection.ts
|
|
37107
|
+
init_dist2();
|
|
37108
|
+
// src/lib/ltvUncertainty.ts
|
|
37109
|
+
init_dist2();
|
|
37029
37110
|
// src/lib/cohortObservation.ts
|
|
37030
37111
|
var RETENTION_DAYS = [1, 7, 30, 90];
|
|
37031
37112
|
function observeCohort(rows, observedThroughDay) {
|
|
@@ -40329,7 +40410,7 @@ function parseHorizons(raw) {
|
|
|
40329
40410
|
}
|
|
40330
40411
|
var DEFAULT_LTV_HORIZONS = "30m,2h,24h,7d,30d,90d";
|
|
40331
40412
|
function ltvUncertaintyDraws(scenario, draws) {
|
|
40332
|
-
const uncertainty =
|
|
40413
|
+
const uncertainty = deriveMonetizationConfig2(scenario).uncertainty;
|
|
40333
40414
|
return uncertainty.enabled || draws !== undefined ? draws ?? uncertainty.draws : null;
|
|
40334
40415
|
}
|
|
40335
40416
|
function ltvStageInputs(scenario, horizons, draws) {
|
|
@@ -40391,7 +40472,7 @@ function runLtvAnalysis(args) {
|
|
|
40391
40472
|
const horizons = parseHorizons(args.horizonsRaw);
|
|
40392
40473
|
const stepSec = args.step ?? args.compiled.run.stepSec;
|
|
40393
40474
|
const runFast = resolveFastMode(args.fast, args.compiled.run.fast);
|
|
40394
|
-
const monetizationConfig =
|
|
40475
|
+
const monetizationConfig = deriveMonetizationConfig2(args.scenario);
|
|
40395
40476
|
const uncertainEnabled = monetizationConfig.uncertainty.enabled || args.draws !== undefined;
|
|
40396
40477
|
const draws = args.draws ?? monetizationConfig.uncertainty.draws;
|
|
40397
40478
|
let state = args.compiled.initial;
|
|
@@ -40451,12 +40532,12 @@ function runLtvAnalysis(args) {
|
|
|
40451
40532
|
const endWorthLog10 = args.compiled.ctx.E.absLog10(worth.amount);
|
|
40452
40533
|
const progression = progressionFactor(startWorthLog10, endWorthLog10, monetizationConfig.revenue.progressionLogSpan);
|
|
40453
40534
|
const horizonDays = h.seconds / 86400;
|
|
40454
|
-
const point =
|
|
40535
|
+
const point = estimateLtvPerUser2({
|
|
40455
40536
|
config: monetizationConfig,
|
|
40456
40537
|
horizonDays,
|
|
40457
40538
|
progression
|
|
40458
40539
|
});
|
|
40459
|
-
const distribution = uncertainEnabled && draws > 1 ?
|
|
40540
|
+
const distribution = uncertainEnabled && draws > 1 ? estimateLtvDistribution2({
|
|
40460
40541
|
config: monetizationConfig,
|
|
40461
40542
|
horizonDays,
|
|
40462
40543
|
progression,
|
|
@@ -44105,4 +44186,4 @@ export {
|
|
|
44105
44186
|
cli2 as cli
|
|
44106
44187
|
};
|
|
44107
44188
|
|
|
44108
|
-
//# debugId=
|
|
44189
|
+
//# debugId=A3CAAFDF1955C7F064756E2164756E21
|