@arclabs561/ai-visual-test 0.7.4 → 0.7.5
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/CHANGELOG.md +7 -0
- package/README.md +3 -0
- package/index.d.ts +140 -0
- package/package.json +2 -6
- package/src/batch-optimizer.mjs +3 -3
- package/src/cache.mjs +3 -4
- package/src/calibration-suite.mjs +197 -0
- package/src/constants.mjs +11 -0
- package/src/cost-optimization.mjs +1 -1
- package/src/explanation-manager.mjs +10 -6
- package/src/human-validation-manager.mjs +21 -8
- package/src/index.mjs +20 -10
- package/src/integrations/playwright.mjs +9 -9
- package/src/judge.mjs +7 -18
- package/src/limitations.mjs +106 -0
- package/src/load-env.mjs +3 -2
- package/src/model-tier-selector.mjs +1 -1
- package/src/rubrics.mjs +22 -2
- package/src/score-calibration.mjs +177 -0
- package/src/temporal-decision-manager.mjs +1 -1
- package/src/temporal-preprocessor.mjs +1 -1
- package/src/type-guards.mjs +5 -5
- package/src/utils/cached-llm.mjs +1 -1
- package/src/validation-result-normalizer.mjs +8 -0
- package/src/validation.mjs +13 -13
- package/src/validators/index.mjs +23 -2
- package/src/pricing.mjs +0 -28
- package/src/utils/path-validator.mjs +0 -88
- package/src/validation-framework.mjs +0 -325
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to ai-visual-test will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.7.5] - 2026-03-16
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **Test image generator**: replaced 1x1 pixel PNG stub with programmatic 100x100 gradient PNG using raw PNG chunk construction (zlib deflate). Fixes Groq API rejections ("Image must have at least 2 pixels") in integration tests.
|
|
9
|
+
- **BatchOptimizer queue-full test**: matched error type to actual `ValidationError` thrown by `_queueRequest` (was expecting `TimeoutError`).
|
|
10
|
+
- **Deleted stale test**: removed `validation-framework.test.mjs` referencing deleted `src/validation-framework.mjs` module.
|
|
11
|
+
|
|
5
12
|
## [0.7.4] - 2026-03-03
|
|
6
13
|
|
|
7
14
|
### Added
|
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# ai-visual-test
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@arclabs561/ai-visual-test)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
3
6
|
Visual testing framework using Vision Language Models. Validates screenshots, checks accessibility, and can play games.
|
|
4
7
|
|
|
5
8
|
## Why This Package
|
package/index.d.ts
CHANGED
|
@@ -2116,3 +2116,143 @@ export function validateWithProgrammaticContext(
|
|
|
2116
2116
|
options?: ValidationContext
|
|
2117
2117
|
): Promise<HybridValidationResult>;
|
|
2118
2118
|
|
|
2119
|
+
// --- Score Calibration (arXiv:2601.05114) ---
|
|
2120
|
+
|
|
2121
|
+
export interface CalibrationProfile {
|
|
2122
|
+
offset: number;
|
|
2123
|
+
scale: number;
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
export interface DerivedCalibrationProfile extends CalibrationProfile {
|
|
2127
|
+
r2: number;
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
export interface ScoreDistribution {
|
|
2131
|
+
mean: number;
|
|
2132
|
+
stddev: number;
|
|
2133
|
+
skew: number;
|
|
2134
|
+
histogram: Record<number, number>;
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
export function calibrateScore(score: number | null, provider: string): number | null;
|
|
2138
|
+
export function setCalibrationProfile(provider: string, profile: CalibrationProfile): void;
|
|
2139
|
+
export function getCalibrationProfile(provider: string): CalibrationProfile;
|
|
2140
|
+
export function resetCalibrationProfiles(): void;
|
|
2141
|
+
export function deriveCalibrationProfile(pairs: Array<{ raw: number; expected: number }>): DerivedCalibrationProfile;
|
|
2142
|
+
export function analyzeScoreDistribution(scores: number[]): ScoreDistribution;
|
|
2143
|
+
|
|
2144
|
+
// --- Calibration Suite / Meta-evaluation (arXiv:2507.10062) ---
|
|
2145
|
+
|
|
2146
|
+
export interface CalibrationSample {
|
|
2147
|
+
screenshot: string;
|
|
2148
|
+
expectedScore: number;
|
|
2149
|
+
label?: string;
|
|
2150
|
+
prompt?: string;
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
export interface CalibrationReport {
|
|
2154
|
+
sampleCount: number;
|
|
2155
|
+
scoredCount: number;
|
|
2156
|
+
pearsonR: number | null;
|
|
2157
|
+
spearmanR: number | null;
|
|
2158
|
+
meanAbsoluteError: number | null;
|
|
2159
|
+
maxError: number | null;
|
|
2160
|
+
falsePositiveRate: number | null;
|
|
2161
|
+
falseNegativeRate: number | null;
|
|
2162
|
+
suggestedCalibration: DerivedCalibrationProfile | null;
|
|
2163
|
+
scoreDistribution: ScoreDistribution;
|
|
2164
|
+
details: Array<{
|
|
2165
|
+
screenshot: string;
|
|
2166
|
+
label: string;
|
|
2167
|
+
expected: number;
|
|
2168
|
+
actual: number | null;
|
|
2169
|
+
error: number | null;
|
|
2170
|
+
skipped?: string;
|
|
2171
|
+
}>;
|
|
2172
|
+
error?: string;
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
export interface CalibrationSuiteInstance {
|
|
2176
|
+
samples: CalibrationSample[];
|
|
2177
|
+
run(judgeOptions?: ConfigOptions): Promise<CalibrationReport>;
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
export function createCalibrationSuite(
|
|
2181
|
+
samples: CalibrationSample[],
|
|
2182
|
+
options?: { passThreshold?: number }
|
|
2183
|
+
): CalibrationSuiteInstance;
|
|
2184
|
+
|
|
2185
|
+
// --- Known VLM Limitations (arXiv:2501.09236, arXiv:2511.03471) ---
|
|
2186
|
+
|
|
2187
|
+
export interface VLMLimitation {
|
|
2188
|
+
description: string;
|
|
2189
|
+
severity: 'high' | 'medium' | 'low';
|
|
2190
|
+
recommendation: string;
|
|
2191
|
+
vlmAccuracy: 'none' | 'low' | 'medium' | 'high';
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
export type VLMLimitationKey =
|
|
2195
|
+
| 'subtleSpatialShifts'
|
|
2196
|
+
| 'elementOverlap'
|
|
2197
|
+
| 'keyboardNavigation'
|
|
2198
|
+
| 'screenReaderOrder'
|
|
2199
|
+
| 'colorContrastPrecision'
|
|
2200
|
+
| 'dynamicContent'
|
|
2201
|
+
| 'textContent'
|
|
2202
|
+
| 'interactiveState';
|
|
2203
|
+
|
|
2204
|
+
export type TestType = 'accessibility' | 'layout' | 'visual' | 'interaction' | 'general';
|
|
2205
|
+
|
|
2206
|
+
export const VLM_LIMITATIONS: Record<VLMLimitationKey, VLMLimitation>;
|
|
2207
|
+
|
|
2208
|
+
export function getLimitationsForTestType(
|
|
2209
|
+
testType: TestType
|
|
2210
|
+
): Array<{ key: string } & VLMLimitation>;
|
|
2211
|
+
|
|
2212
|
+
export function shouldUseHybridValidation(testType: TestType): boolean;
|
|
2213
|
+
|
|
2214
|
+
// --- Human Validation Manager ---
|
|
2215
|
+
|
|
2216
|
+
export interface HumanValidationManagerOptions {
|
|
2217
|
+
enabled?: boolean;
|
|
2218
|
+
autoCollect?: boolean;
|
|
2219
|
+
smartSampling?: boolean;
|
|
2220
|
+
calibrationThreshold?: number;
|
|
2221
|
+
humanValidatorFn?: ((vllmResult: ValidationResult) => Promise<unknown>) | null;
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
export class HumanValidationManager {
|
|
2225
|
+
constructor(options?: HumanValidationManagerOptions);
|
|
2226
|
+
enabled: boolean;
|
|
2227
|
+
collectJudgment(result: ValidationResult): void;
|
|
2228
|
+
requestValidation(result: ValidationResult): Promise<unknown>;
|
|
2229
|
+
getCalibrationData(): Promise<unknown>;
|
|
2230
|
+
isCalibrated(): boolean;
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
export function getHumanValidationManager(): HumanValidationManager;
|
|
2234
|
+
export function initHumanValidation(options?: HumanValidationManagerOptions): HumanValidationManager;
|
|
2235
|
+
|
|
2236
|
+
// --- Explanation Manager ---
|
|
2237
|
+
|
|
2238
|
+
export class ExplanationManager {
|
|
2239
|
+
constructor(options?: ConfigOptions);
|
|
2240
|
+
explainJudgment(vllmJudgment: object, question?: string | null, options?: object): Promise<object>;
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
export function getExplanationManager(options?: ConfigOptions): ExplanationManager;
|
|
2244
|
+
|
|
2245
|
+
// --- Temporal Batch Optimizer ---
|
|
2246
|
+
|
|
2247
|
+
export class TemporalBatchOptimizer extends BatchOptimizer {
|
|
2248
|
+
constructor(options?: object);
|
|
2249
|
+
addWithDependencies(request: object, dependencies?: string[]): Promise<ValidationResult>;
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
// --- Latency-Aware Batch Optimizer ---
|
|
2253
|
+
|
|
2254
|
+
export class LatencyAwareBatchOptimizer extends BatchOptimizer {
|
|
2255
|
+
constructor(options?: { maxConcurrency?: number; batchSize?: number; cacheEnabled?: boolean; defaultMaxLatency?: number; adaptiveBatchSize?: boolean });
|
|
2256
|
+
addWithLatencyTarget(request: object, maxLatencyMs?: number): Promise<ValidationResult>;
|
|
2257
|
+
}
|
|
2258
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arclabs561/ai-visual-test",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5",
|
|
4
4
|
"description": "Visual testing framework for web applications using Vision Language Models",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.mjs",
|
|
@@ -39,11 +39,7 @@
|
|
|
39
39
|
"author": "arclabs561 <henry@henrywallace.io>",
|
|
40
40
|
"license": "MIT",
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"
|
|
43
|
-
"@google/generative-ai": "0.24.1",
|
|
44
|
-
"async-mutex": "0.5.0",
|
|
45
|
-
"dotenv": "^16.4.5",
|
|
46
|
-
"openai": "6.9.1"
|
|
42
|
+
"async-mutex": "0.5.0"
|
|
47
43
|
},
|
|
48
44
|
"peerDependencies": {
|
|
49
45
|
"@arclabs561/llm-utils": "*",
|
package/src/batch-optimizer.mjs
CHANGED
|
@@ -29,7 +29,7 @@ import { createHash } from 'crypto';
|
|
|
29
29
|
* @class BatchOptimizer
|
|
30
30
|
*/
|
|
31
31
|
import { API_CONSTANTS, BATCH_OPTIMIZER_CONSTANTS } from './constants.mjs';
|
|
32
|
-
import { TimeoutError } from './errors.mjs';
|
|
32
|
+
import { TimeoutError, ValidationError } from './errors.mjs';
|
|
33
33
|
import { warn } from './logger.mjs';
|
|
34
34
|
|
|
35
35
|
export class BatchOptimizer {
|
|
@@ -103,7 +103,7 @@ export class BatchOptimizer {
|
|
|
103
103
|
const keyData = {
|
|
104
104
|
imagePath,
|
|
105
105
|
prompt: prompt || '',
|
|
106
|
-
context: context ? JSON.stringify(context) : ''
|
|
106
|
+
context: context ? JSON.stringify(context, Object.keys(context).sort()) : ''
|
|
107
107
|
};
|
|
108
108
|
const keyString = JSON.stringify(keyData);
|
|
109
109
|
return createHash('sha256').update(keyString).digest('hex');
|
|
@@ -238,7 +238,7 @@ export class BatchOptimizer {
|
|
|
238
238
|
});
|
|
239
239
|
|
|
240
240
|
warn(`[BatchOptimizer] Queue is full (${this.queue.length}/${this.maxQueueSize}). Rejecting request to prevent memory leak. Total rejections: ${this.metrics.queueRejections}`);
|
|
241
|
-
throw new
|
|
241
|
+
throw new ValidationError(
|
|
242
242
|
`Queue is full (${this.queue.length}/${this.maxQueueSize}). Too many concurrent requests.`,
|
|
243
243
|
{ queueSize: this.queue.length, maxQueueSize: this.maxQueueSize }
|
|
244
244
|
);
|
package/src/cache.mjs
CHANGED
|
@@ -55,12 +55,11 @@ let cacheMetrics = { atomicWrites: 0, atomicWriteFailures: 0, tempFileCleanups:
|
|
|
55
55
|
export function initCache(cacheDir) {
|
|
56
56
|
// SECURITY: Validate and normalize cache directory to prevent path traversal
|
|
57
57
|
if (cacheDir) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (normalized.includes('..')) {
|
|
58
|
+
// Prevent path traversal: reject raw input containing '..' segments
|
|
59
|
+
if (cacheDir.includes('..')) {
|
|
61
60
|
throw new CacheError('Invalid cache directory: path traversal detected', { cacheDir });
|
|
62
61
|
}
|
|
63
|
-
CACHE_DIR =
|
|
62
|
+
CACHE_DIR = normalize(resolve(cacheDir));
|
|
64
63
|
} else {
|
|
65
64
|
CACHE_DIR = join(__dirname, '..', '..', '..', 'test-results', 'vllm-cache');
|
|
66
65
|
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calibration Suite (Meta-evaluation)
|
|
3
|
+
*
|
|
4
|
+
* Measures how well a VLM judge performs against human-labeled ground truth.
|
|
5
|
+
* When using ML as a test oracle, you need tests of the tests
|
|
6
|
+
* (LLMShot, arXiv:2507.10062; Stanford CS 329T).
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* const suite = createCalibrationSuite([
|
|
10
|
+
* { screenshot: 'good.png', expectedScore: 9, label: 'clean homepage' },
|
|
11
|
+
* { screenshot: 'broken.png', expectedScore: 2, label: 'broken layout' },
|
|
12
|
+
* ]);
|
|
13
|
+
* const report = await suite.run({ provider: 'gemini' });
|
|
14
|
+
* // report.correlation, report.meanAbsoluteError, report.falsePositiveRate, ...
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { VLLMJudge } from './judge.mjs';
|
|
18
|
+
import { ValidationError, ConfigError } from './errors.mjs';
|
|
19
|
+
import { warn } from './logger.mjs';
|
|
20
|
+
import { pearsonCorrelation, spearmanCorrelation } from './metrics.mjs';
|
|
21
|
+
import { deriveCalibrationProfile, analyzeScoreDistribution } from './score-calibration.mjs';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {Object} CalibrationSample
|
|
25
|
+
* @property {string} screenshot - Path to screenshot file
|
|
26
|
+
* @property {number} expectedScore - Human-labeled expected score (0-10)
|
|
27
|
+
* @property {string} [label] - Description of the sample
|
|
28
|
+
* @property {string} [prompt] - Custom prompt (defaults to generic quality evaluation)
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {Object} CalibrationReport
|
|
33
|
+
* @property {number} sampleCount - Number of samples evaluated
|
|
34
|
+
* @property {number} pearsonR - Pearson correlation between VLM and expected scores
|
|
35
|
+
* @property {number} spearmanR - Spearman rank correlation
|
|
36
|
+
* @property {number} meanAbsoluteError - Average |VLM score - expected score|
|
|
37
|
+
* @property {number} maxError - Worst single-sample error
|
|
38
|
+
* @property {number} falsePositiveRate - Rate of VLM pass (>=7) when expected fail (<7)
|
|
39
|
+
* @property {number} falseNegativeRate - Rate of VLM fail (<7) when expected pass (>=7)
|
|
40
|
+
* @property {{ offset: number, scale: number, r2: number }} suggestedCalibration - Derived calibration profile
|
|
41
|
+
* @property {{ mean: number, stddev: number, skew: number }} scoreDistribution - VLM score distribution
|
|
42
|
+
* @property {Array<{ screenshot: string, label: string, expected: number, actual: number, error: number }>} details
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
const DEFAULT_PROMPT = 'Evaluate the overall quality of this screenshot. Consider layout, visual design, readability, accessibility, and functional correctness.';
|
|
46
|
+
const DEFAULT_PASS_THRESHOLD = 7;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Create a calibration suite
|
|
50
|
+
*
|
|
51
|
+
* @param {CalibrationSample[]} samples - Labeled screenshot samples
|
|
52
|
+
* @param {{ passThreshold?: number }} [options={}] - Suite options
|
|
53
|
+
* @returns {{ run: (judgeOptions: object) => Promise<CalibrationReport>, samples: CalibrationSample[] }}
|
|
54
|
+
*/
|
|
55
|
+
export function createCalibrationSuite(samples, options = {}) {
|
|
56
|
+
if (!Array.isArray(samples) || samples.length < 2) {
|
|
57
|
+
throw new ValidationError('Calibration suite requires at least 2 labeled samples', { count: samples?.length ?? 0 });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for (const s of samples) {
|
|
61
|
+
if (typeof s.screenshot !== 'string' || typeof s.expectedScore !== 'number') {
|
|
62
|
+
throw new ValidationError('Each sample must have a screenshot path (string) and expectedScore (number)', { screenshot: typeof s.screenshot, expectedScore: typeof s.expectedScore });
|
|
63
|
+
}
|
|
64
|
+
if (s.expectedScore < 0 || s.expectedScore > 10) {
|
|
65
|
+
throw new ValidationError(`expectedScore must be 0-10, got ${s.expectedScore}`, { expectedScore: s.expectedScore });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const passThreshold = options.passThreshold ?? DEFAULT_PASS_THRESHOLD;
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
samples: [...samples],
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Run the calibration suite against a judge
|
|
76
|
+
*
|
|
77
|
+
* @param {object} judgeOptions - Options passed to VLLMJudge constructor
|
|
78
|
+
* @returns {Promise<CalibrationReport>}
|
|
79
|
+
*/
|
|
80
|
+
async run(judgeOptions = {}) {
|
|
81
|
+
const judge = new VLLMJudge(judgeOptions);
|
|
82
|
+
|
|
83
|
+
if (!judge.enabled) {
|
|
84
|
+
throw new ConfigError('VLLMJudge is disabled -- cannot run calibration suite');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const details = [];
|
|
88
|
+
const rawScores = [];
|
|
89
|
+
const expectedScores = [];
|
|
90
|
+
|
|
91
|
+
for (const sample of samples) {
|
|
92
|
+
const prompt = sample.prompt || DEFAULT_PROMPT;
|
|
93
|
+
try {
|
|
94
|
+
const result = await judge.judgeScreenshot(sample.screenshot, prompt);
|
|
95
|
+
const parsed = judge.extractSemanticInfo(result.judgment || '');
|
|
96
|
+
const score = parsed.score ?? result.score ?? null;
|
|
97
|
+
|
|
98
|
+
if (score !== null) {
|
|
99
|
+
const error = Math.abs(score - sample.expectedScore);
|
|
100
|
+
rawScores.push(score);
|
|
101
|
+
expectedScores.push(sample.expectedScore);
|
|
102
|
+
details.push({
|
|
103
|
+
screenshot: sample.screenshot,
|
|
104
|
+
label: sample.label || sample.screenshot,
|
|
105
|
+
expected: sample.expectedScore,
|
|
106
|
+
actual: score,
|
|
107
|
+
error
|
|
108
|
+
});
|
|
109
|
+
} else {
|
|
110
|
+
details.push({
|
|
111
|
+
screenshot: sample.screenshot,
|
|
112
|
+
label: sample.label || sample.screenshot,
|
|
113
|
+
expected: sample.expectedScore,
|
|
114
|
+
actual: null,
|
|
115
|
+
error: null,
|
|
116
|
+
skipped: 'No score returned'
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
} catch (err) {
|
|
120
|
+
warn(`[CalibrationSuite] Sample "${sample.label || sample.screenshot}" failed: ${err.message}`);
|
|
121
|
+
details.push({
|
|
122
|
+
screenshot: sample.screenshot,
|
|
123
|
+
label: sample.label || sample.screenshot,
|
|
124
|
+
expected: sample.expectedScore,
|
|
125
|
+
actual: null,
|
|
126
|
+
error: null,
|
|
127
|
+
errorMessage: err.message,
|
|
128
|
+
skipped: true
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Compute metrics from scored samples only
|
|
134
|
+
const scored = details.filter(d => d.actual !== null);
|
|
135
|
+
const n = scored.length;
|
|
136
|
+
|
|
137
|
+
if (n < 2) {
|
|
138
|
+
return {
|
|
139
|
+
sampleCount: samples.length,
|
|
140
|
+
scoredCount: n,
|
|
141
|
+
pearsonR: null,
|
|
142
|
+
spearmanR: null,
|
|
143
|
+
meanAbsoluteError: null,
|
|
144
|
+
maxError: null,
|
|
145
|
+
falsePositiveRate: null,
|
|
146
|
+
falseNegativeRate: null,
|
|
147
|
+
suggestedCalibration: null,
|
|
148
|
+
scoreDistribution: analyzeScoreDistribution(rawScores),
|
|
149
|
+
details,
|
|
150
|
+
error: 'Fewer than 2 samples scored -- cannot compute metrics'
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const errors = scored.map(d => d.error);
|
|
155
|
+
const meanAbsoluteError = errors.reduce((a, b) => a + b, 0) / n;
|
|
156
|
+
const maxError = Math.max(...errors);
|
|
157
|
+
|
|
158
|
+
// Correlation
|
|
159
|
+
const actualScores = scored.map(d => d.actual);
|
|
160
|
+
const expScores = scored.map(d => d.expected);
|
|
161
|
+
const pearsonR = pearsonCorrelation(actualScores, expScores);
|
|
162
|
+
const spearmanR = spearmanCorrelation(actualScores, expScores);
|
|
163
|
+
|
|
164
|
+
// False positive/negative rates (relative to passThreshold)
|
|
165
|
+
let fp = 0, fn = 0, expectedFail = 0, expectedPass = 0;
|
|
166
|
+
for (const d of scored) {
|
|
167
|
+
if (d.expected >= passThreshold) {
|
|
168
|
+
expectedPass++;
|
|
169
|
+
if (d.actual < passThreshold) fn++;
|
|
170
|
+
} else {
|
|
171
|
+
expectedFail++;
|
|
172
|
+
if (d.actual >= passThreshold) fp++;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const falsePositiveRate = expectedFail > 0 ? fp / expectedFail : 0;
|
|
176
|
+
const falseNegativeRate = expectedPass > 0 ? fn / expectedPass : 0;
|
|
177
|
+
|
|
178
|
+
// Derive calibration profile
|
|
179
|
+
const pairs = scored.map(d => ({ raw: d.actual, expected: d.expected }));
|
|
180
|
+
const suggestedCalibration = deriveCalibrationProfile(pairs);
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
sampleCount: samples.length,
|
|
184
|
+
scoredCount: n,
|
|
185
|
+
pearsonR,
|
|
186
|
+
spearmanR,
|
|
187
|
+
meanAbsoluteError,
|
|
188
|
+
maxError,
|
|
189
|
+
falsePositiveRate,
|
|
190
|
+
falseNegativeRate,
|
|
191
|
+
suggestedCalibration,
|
|
192
|
+
scoreDistribution: analyzeScoreDistribution(rawScores),
|
|
193
|
+
details
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
}
|
package/src/constants.mjs
CHANGED
|
@@ -98,6 +98,17 @@ export const API_ENDPOINT_CONSTANTS = {
|
|
|
98
98
|
RATE_LIMIT_MAX_REQUESTS: 10
|
|
99
99
|
};
|
|
100
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Rate Limit Validation Bounds
|
|
103
|
+
*/
|
|
104
|
+
export const RATE_LIMIT_BOUNDS = {
|
|
105
|
+
/** Minimum allowed value for RATE_LIMIT_MAX_REQUESTS */
|
|
106
|
+
MIN: 1,
|
|
107
|
+
|
|
108
|
+
/** Maximum allowed value for RATE_LIMIT_MAX_REQUESTS */
|
|
109
|
+
MAX: 1000
|
|
110
|
+
};
|
|
111
|
+
|
|
101
112
|
/**
|
|
102
113
|
* Retry Configuration
|
|
103
114
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(_0x18385b,_0x2f2f0a){const _0xa53b50=_0x2d2e,_0x49ca27=_0x18385b();while(!![]){try{const _0xdd5ddf=parseInt(_0xa53b50(0x171))/0x1*(parseInt(_0xa53b50(0x16e))/0x2)+parseInt(_0xa53b50(0x199))/0x3*(-parseInt(_0xa53b50(0x174))/0x4)+parseInt(_0xa53b50(0x19c))/0x5*(parseInt(_0xa53b50(0x180))/0x6)+parseInt(_0xa53b50(0x16b))/0x7*(-parseInt(_0xa53b50(0x195))/0x8)+-parseInt(_0xa53b50(0x17c))/0x9*(-parseInt(_0xa53b50(0x197))/0xa)+-parseInt(_0xa53b50(0x18b))/0xb*(-parseInt(_0xa53b50(0x177))/0xc)+parseInt(_0xa53b50(0x17a))/0xd;if(_0xdd5ddf===_0x2f2f0a)break;else _0x49ca27['push'](_0x49ca27['shift']());}catch(_0x16dc43){_0x49ca27['push'](_0x49ca27['shift']());}}}(_0x1ecb,0x4479d));const _0x40f56c=(function(){let _0x180237=!![];return function(_0x539394,_0xb40faa){const _0x42db3d=_0x180237?function(){if(_0xb40faa){const _0x4b441b=_0xb40faa['apply'](_0x539394,arguments);return _0xb40faa=null,_0x4b441b;}}:function(){};return _0x180237=![],_0x42db3d;};}()),_0xd2e06c=_0x40f56c(this,function(){const _0x212118=_0x2d2e;return _0xd2e06c[_0x212118(0x18c)+'ing']()['searc'+'h'](_0x212118(0x193)+_0x212118(0x188)+'+$')['toStr'+_0x212118(0x198)]()['const'+'ructo'+'r'](_0xd2e06c)[_0x212118(0x167)+'h'](_0x212118(0x193)+_0x212118(0x188)+'+$');});_0xd2e06c();import{selectModelTier,selectProvider,selectModelTierAndProvider}from'./model-tier-selector.mjs';import{createConfig,getProvider}from'./config.mjs';export function calculateCostComparison(_0x57230a={},_0x52fd2f={}){const _0xaa590d=_0x2d2e,_0x33bd6a=parseFloat(_0x52fd2f[_0xaa590d(0x184)+'atedC'+_0xaa590d(0x168)]?.['total'+'Cost']||'0'),_0x397464=_0x57230a['model'+_0xaa590d(0x1a7)]||'balan'+_0xaa590d(0x173),_0x238e4b=_0x52fd2f['provi'+'der']||'gemin'+'i',_0x5ef67d=getProvider(_0x238e4b),_0x48530e={};_0x48530e['input']=0x0,_0x48530e['outpu'+'t']=0x0;const _0x26c913=_0x5ef67d?.[_0xaa590d(0x181)+'ng']||_0x48530e,_0x4f9c20=0x3e8,_0x321d8d=0x1f4,_0x578480={};for(const _0x1ff61d of[_0xaa590d(0x182),_0xaa590d(0x19b)+'ced','best']){const _0x161f86=_0x4f9c20/0xf4240*_0x26c913['input'],_0x519e8b=_0x321d8d/0xf4240*_0x26c913[_0xaa590d(0x1a1)+'t'];_0x578480[_0x1ff61d]=_0x161f86+_0x519e8b;}const _0x420aba={};for(const _0xf4fc80 of[_0xaa590d(0x182),'balan'+_0xaa590d(0x173),_0xaa590d(0x1a9)]){if(_0x578480[_0xf4fc80]&&_0x33bd6a>0x0){const _0x1573e6=_0x33bd6a-_0x578480[_0xf4fc80],_0x36dd72=_0x1573e6/_0x33bd6a*0x64;_0x420aba[_0xf4fc80]={'absolute':_0x1573e6,'percent':_0x36dd72,'cost':_0x578480[_0xf4fc80]};}}const _0x31be62={};return _0x31be62['tier']=_0x397464,_0x31be62[_0xaa590d(0x169)+'der']=_0x238e4b,_0x31be62[_0xaa590d(0x1a4)]=_0x33bd6a,{'current':_0x31be62,'tiers':_0x578480,'savings':_0x420aba,'recommendation':getCostOptimizationRecommendation(_0x57230a,_0x33bd6a,_0x578480)};}function getCostOptimizationRecommendation(_0x48b039,_0x19d13e,_0x2fc7c2){const _0x509a09=_0x2d2e,{frequency:_0x3a7aef,criticality:_0x1ccde8,costSensitive:_0x4f54d4}=_0x48b039;let _0x2bcee6=_0x509a09(0x19b)+_0x509a09(0x173);if(_0x3a7aef===_0x509a09(0x18e)||_0x3a7aef>=0xa||_0x4f54d4)_0x2bcee6=_0x509a09(0x182);else _0x1ccde8===_0x509a09(0x18f)+_0x509a09(0x194)&&(_0x2bcee6='best');const _0x505d15=_0x2fc7c2[_0x2bcee6]||_0x19d13e,_0x44afb3=_0x19d13e-_0x505d15,_0x10c8ba=_0x19d13e>0x0?_0x44afb3/_0x19d13e*0x64:0x0;return{'tier':_0x2bcee6,'cost':_0x505d15,'savings':_0x44afb3,'savingsPercent':_0x10c8ba,'reason':getRecommendationReason(_0x48b039,_0x2bcee6)};}function getRecommendationReason(_0x598c0e,_0xf227a9){const _0x30edec=_0x2d2e;if(_0xf227a9==='fast'){if(_0x598c0e[_0x30edec(0x185)+'ency']===_0x30edec(0x18e)||_0x598c0e['frequ'+_0x30edec(0x1a2)]>=0xa)return _0x30edec(0x1a5)+_0x30edec(0x185)+'ency\x20'+_0x30edec(0x17d)+_0x30edec(0x187)+'\x20requ'+_0x30edec(0x196)+_0x30edec(0x179)+_0x30edec(0x192);if(_0x598c0e[_0x30edec(0x189)+'ensit'+'ive'])return _0x30edec(0x1ac)+'sensi'+'tive\x20'+_0x30edec(0x183)+_0x30edec(0x1ab)+'\x20use\x20'+_0x30edec(0x179)+_0x30edec(0x192);}if(_0xf227a9==='best')return'Criti'+_0x30edec(0x178)+'valua'+_0x30edec(0x190)+_0x30edec(0x16f)+'res\x20b'+'est\x20t'+_0x30edec(0x1a3)+'or\x20qu'+_0x30edec(0x1aa);return'Balan'+_0x30edec(0x18a)+_0x30edec(0x170)+'rovid'+'es\x20sp'+'eed/q'+_0x30edec(0x1a6)+'y\x20tra'+'deoff';}function _0x2d2e(_0x2bf370,_0x100b7e){const _0x3462a3=_0x1ecb();return _0x2d2e=function(_0xd2e06c,_0x40f56c){_0xd2e06c=_0xd2e06c-0x167;let _0x1ecb23=_0x3462a3[_0xd2e06c];if(_0x2d2e['clmymu']===undefined){var _0x2d2e4c=function(_0x4d9406){const _0x4347ab='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x91f6aa='',_0x4491a1='',_0x428b38=_0x91f6aa+_0x2d2e4c;for(let _0x2de447=0x0,_0x464545,_0x5ec257,_0x372eca=0x0;_0x5ec257=_0x4d9406['charAt'](_0x372eca++);~_0x5ec257&&(_0x464545=_0x2de447%0x4?_0x464545*0x40+_0x5ec257:_0x5ec257,_0x2de447++%0x4)?_0x91f6aa+=_0x428b38['charCodeAt'](_0x372eca+0xa)-0xa!==0x0?String['fromCharCode'](0xff&_0x464545>>(-0x2*_0x2de447&0x6)):_0x2de447:0x0){_0x5ec257=_0x4347ab['indexOf'](_0x5ec257);}for(let _0x180237=0x0,_0x539394=_0x91f6aa['length'];_0x180237<_0x539394;_0x180237++){_0x4491a1+='%'+('00'+_0x91f6aa['charCodeAt'](_0x180237)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x4491a1);};_0x2d2e['YBBLwq']=_0x2d2e4c,_0x2bf370=arguments,_0x2d2e['clmymu']=!![];}const _0x5bd10d=_0x3462a3[0x0],_0x25f117=_0xd2e06c+_0x5bd10d,_0x4bfa19=_0x2bf370[_0x25f117];if(!_0x4bfa19){const _0xb40faa=function(_0x42db3d){this['tUiOdk']=_0x42db3d,this['yllaQc']=[0x1,0x0,0x0],this['eWSZJt']=function(){return'newState';},this['NeSNJo']='\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*',this['jVFOWK']='[\x27|\x22].+[\x27|\x22];?\x20*}';};_0xb40faa['prototype']['JTaKsI']=function(){const _0x4b441b=new RegExp(this['NeSNJo']+this['jVFOWK']),_0x57230a=_0x4b441b['test'](this['eWSZJt']['toString']())?--this['yllaQc'][0x1]:--this['yllaQc'][0x0];return this['eHArMZ'](_0x57230a);},_0xb40faa['prototype']['eHArMZ']=function(_0x52fd2f){if(!Boolean(~_0x52fd2f))return _0x52fd2f;return this['AIYzCx'](this['tUiOdk']);},_0xb40faa['prototype']['AIYzCx']=function(_0x33bd6a){for(let _0x397464=0x0,_0x238e4b=this['yllaQc']['length'];_0x397464<_0x238e4b;_0x397464++){this['yllaQc']['push'](Math['round'](Math['random']())),_0x238e4b=this['yllaQc']['length'];}return _0x33bd6a(this['yllaQc'][0x0]);},new _0xb40faa(_0x2d2e)['JTaKsI'](),_0x1ecb23=_0x2d2e['YBBLwq'](_0x1ecb23),_0x2bf370[_0x25f117]=_0x1ecb23;}else _0x1ecb23=_0x4bfa19;return _0x1ecb23;},_0x2d2e(_0x2bf370,_0x100b7e);}export function optimizeCost(_0x157b14={}){const _0x46d2fd=_0x2d2e,{frequency:_0x11c64d,criticality:_0x1162ee,costSensitive:_0x2e4e15,budget:_0x2fbb39,requirements:requirements={}}=_0x157b14,_0x5d2c5d={};_0x5d2c5d[_0x46d2fd(0x185)+'ency']=_0x11c64d,_0x5d2c5d[_0x46d2fd(0x18f)+_0x46d2fd(0x1a0)+'y']=_0x1162ee,_0x5d2c5d['costS'+'ensit'+'ive']=_0x2e4e15,_0x5d2c5d[_0x46d2fd(0x16f)+_0x46d2fd(0x186)+'ts']={...requirements},_0x5d2c5d[_0x46d2fd(0x16f)+_0x46d2fd(0x186)+'ts'][_0x46d2fd(0x189)+'ensit'+'ive']=_0x2e4e15,_0x5d2c5d[_0x46d2fd(0x16f)+_0x46d2fd(0x186)+'ts']['env']=process[_0x46d2fd(0x17f)];const {tier:_0x214f9b,provider:_0x20c49a,reason:_0x5c1bce}=selectModelTierAndProvider(_0x5d2c5d),_0xdaa5b8={};_0xdaa5b8['model'+'Tier']=_0x214f9b,_0xdaa5b8[_0x46d2fd(0x169)+_0x46d2fd(0x19a)]=_0x20c49a;const _0xfb6ace=createConfig(_0xdaa5b8),_0x56ddaf=getProvider(_0x20c49a),_0x409eff={};_0x409eff['input']=0x0,_0x409eff['outpu'+'t']=0x0;const _0x422e41=_0x56ddaf?.[_0x46d2fd(0x181)+'ng']||_0x409eff,_0x117a81=0x3e8,_0x510ec9=0x1f4,_0x59906d=_0x117a81/0xf4240*_0x422e41[_0x46d2fd(0x16c)]+_0x510ec9/0xf4240*_0x422e41[_0x46d2fd(0x1a1)+'t'],_0x39264a={};for(const _0x149af7 of['fast',_0x46d2fd(0x19b)+'ced','best']){if(_0x149af7!==_0x214f9b){const _0x87f361=_0x59906d,_0x3fad65={};_0x3fad65['cost']=_0x87f361,_0x3fad65[_0x46d2fd(0x19e)+'gs']=_0x59906d-_0x87f361,_0x3fad65[_0x46d2fd(0x19e)+'gsPer'+_0x46d2fd(0x19f)]=_0x59906d>0x0?(_0x59906d-_0x87f361)/_0x59906d*0x64:0x0,_0x39264a[_0x149af7]=_0x3fad65;}}const _0x449d7a=_0x2fbb39?_0x59906d<=_0x2fbb39:null;return{'recommendedTier':_0x214f9b,'recommendedProvider':_0x20c49a,'estimatedCost':_0x59906d,'savings':getSavingsEstimate(_0x214f9b,_0x20c49a,_0x39264a),'config':_0xfb6ace,'reason':_0x5c1bce,'withinBudget':_0x449d7a,'comparisons':_0x39264a,'recommendation':_0x449d7a===![]?_0x46d2fd(0x191)+'ated\x20'+'cost\x20'+'($'+_0x59906d[_0x46d2fd(0x1ad)+'ed'](0x6)+(')\x20exc'+_0x46d2fd(0x175)+'budge'+'t\x20($')+_0x2fbb39[_0x46d2fd(0x1ad)+'ed'](0x6)+(_0x46d2fd(0x17b)+_0x46d2fd(0x172)+'r\x20usi'+'ng\x20\x27f'+_0x46d2fd(0x16d)+'tier.'):'Optim'+'al\x20co'+_0x46d2fd(0x18d)+'ratio'+'n:\x20'+_0x20c49a+'\x20'+_0x214f9b+(_0x46d2fd(0x19d)+'\x20(est'+'imate'+_0x46d2fd(0x1ae))+_0x59906d[_0x46d2fd(0x1ad)+'ed'](0x6)+('\x20per\x20'+'valid'+_0x46d2fd(0x187)+')')};}function getSavingsEstimate(_0x1521bc,_0x3e64cf,_0x151d7f){const _0x373a2c=_0x2d2e;if(_0x1521bc===_0x373a2c(0x182)){const _0x36077e=_0x151d7f['balan'+_0x373a2c(0x173)]?.['savin'+'gs']||0x0,_0xbf31ee=_0x151d7f['best']?.[_0x373a2c(0x19e)+'gs']||0x0;return{'vsBalanced':_0x36077e>0x0?(_0x151d7f[_0x373a2c(0x19b)+_0x373a2c(0x173)]['savin'+_0x373a2c(0x16a)+_0x373a2c(0x19f)]||0x0)['toFix'+'ed'](0x0)+'%':'0%','vsBest':_0xbf31ee>0x0?(_0x151d7f[_0x373a2c(0x1a9)][_0x373a2c(0x19e)+_0x373a2c(0x16a)+_0x373a2c(0x19f)]||0x0)[_0x373a2c(0x1ad)+'ed'](0x0)+'%':'0%'};}if(_0x1521bc===_0x373a2c(0x19b)+'ced'){const _0xa4c8f8=_0x151d7f['fast']?.['savin'+'gs']||0x0,_0x23d59f=_0x151d7f['best']?.[_0x373a2c(0x19e)+'gs']||0x0;return{'vsFast':_0xa4c8f8<0x0?Math[_0x373a2c(0x1a8)](_0x151d7f[_0x373a2c(0x182)]?.['savin'+_0x373a2c(0x16a)+_0x373a2c(0x19f)]||0x0)['toFix'+'ed'](0x0)+(_0x373a2c(0x17e)+'e\x20exp'+_0x373a2c(0x176)+'e'):'0%','vsBest':_0x23d59f>0x0?(_0x151d7f['best'][_0x373a2c(0x19e)+'gsPer'+'cent']||0x0)[_0x373a2c(0x1ad)+'ed'](0x0)+'%':'0%'};}return{'vsFast':_0x151d7f[_0x373a2c(0x182)]?Math['abs'](_0x151d7f[_0x373a2c(0x182)]['savin'+_0x373a2c(0x16a)+'cent']||0x0)['toFix'+'ed'](0x0)+(_0x373a2c(0x17e)+'e\x20exp'+'ensiv'+'e'):'0%','vsBalanced':_0x151d7f[_0x373a2c(0x19b)+'ced']?Math['abs'](_0x151d7f[_0x373a2c(0x19b)+'ced'][_0x373a2c(0x19e)+_0x373a2c(0x16a)+'cent']||0x0)['toFix'+'ed'](0x0)+('%\x20mor'+'e\x20exp'+'ensiv'+'e'):'0%'};}function _0x1ecb(){const _0xa9d53f=['B3v0Chu','zw5JEq','AwvYigy','y29ZDa','sgLNAc0','DwfSAxq','vgLLCG','ywjZ','yMvZDa','ywXPDhK','DgLVBIW','q29ZDc0','Dg9gAxG','zdOGja','C2vHCMm','B3n0','ChjVDMK','z3nqzxi','mti2zgnotfDj','Aw5WDxq','yxn0jYa','nJyYogXUA1nRBq','CMvXDwK','AwvYiha','mtjgCNb5Cxe','BNnPzgu','y2vK','mtaYmtzJB3bkChu','zwvKCYa','zw5ZAxy','mtq2nZzxvgnduMO','y2fSigu','zMfZDca','ntKWmZnwsxPhruO','ks4Gq28','ndqWnZnkBhHUsK0','DMfSAwq','jsbTB3i','zw52','mZC5ogrsuuvgza','ChjPy2K','zMfZDa','B3bLCMe','zxn0Aw0','zNjLCxu','CMvTzw4','yxrPB24','ksSPkYK','y29ZDfm','y2vKihq','ndK5nezSrvjpDW','Dg9tDhi','BMzPz3u','AgLNAa','y3jPDgK','DgLVBIa','rxn0Aw0','DgLLCG','kcGOlIS','y2fS','mJiYntq0r0vtywDL','AxjLCYa','odKWCfPWyNfX','Aw5N','mZKZBhLSD3jo','zgvY','yMfSyw4','nJm1qwHeEeTT','ihrPzxi','C2f2Aw4','y2vUDa','y2fSAxq'];_0x1ecb=function(){return _0xa9d53f;};return _0x1ecb();}
|
|
1
|
+
function _0x44fa(_0x1b7e6c,_0x4f2e91){const _0x39b127=_0x41cc();return _0x44fa=function(_0x42fad3,_0x7d21ab){_0x42fad3=_0x42fad3-0xa0;let _0x41cca5=_0x39b127[_0x42fad3];if(_0x44fa['mZqzuW']===undefined){var _0x44fa51=function(_0x137d0a){const _0x9cebc5='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x10951a='',_0xb545cc='',_0x3eaa48=_0x10951a+_0x44fa51;for(let _0x5ce45a=0x0,_0x4393b2,_0x28fb3f,_0x197a35=0x0;_0x28fb3f=_0x137d0a['charAt'](_0x197a35++);~_0x28fb3f&&(_0x4393b2=_0x5ce45a%0x4?_0x4393b2*0x40+_0x28fb3f:_0x28fb3f,_0x5ce45a++%0x4)?_0x10951a+=_0x3eaa48['charCodeAt'](_0x197a35+0xa)-0xa!==0x0?String['fromCharCode'](0xff&_0x4393b2>>(-0x2*_0x5ce45a&0x6)):_0x5ce45a:0x0){_0x28fb3f=_0x9cebc5['indexOf'](_0x28fb3f);}for(let _0x3eae9f=0x0,_0x50f621=_0x10951a['length'];_0x3eae9f<_0x50f621;_0x3eae9f++){_0xb545cc+='%'+('00'+_0x10951a['charCodeAt'](_0x3eae9f)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0xb545cc);};_0x44fa['ctDYOL']=_0x44fa51,_0x1b7e6c=arguments,_0x44fa['mZqzuW']=!![];}const _0x91b6e8=_0x39b127[0x0],_0x5a6c85=_0x42fad3+_0x91b6e8,_0x1549a6=_0x1b7e6c[_0x5a6c85];if(!_0x1549a6){const _0x4de8b5=function(_0x193daf){this['bmzfVw']=_0x193daf,this['pBTXLc']=[0x1,0x0,0x0],this['loDRVx']=function(){return'newState';},this['dKehkO']='\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*',this['vrQtQR']='[\x27|\x22].+[\x27|\x22];?\x20*}';};_0x4de8b5['prototype']['ZpGEKi']=function(){const _0x504a68=new RegExp(this['dKehkO']+this['vrQtQR']),_0xa11c38=_0x504a68['test'](this['loDRVx']['toString']())?--this['pBTXLc'][0x1]:--this['pBTXLc'][0x0];return this['WIOdvz'](_0xa11c38);},_0x4de8b5['prototype']['WIOdvz']=function(_0x37beac){if(!Boolean(~_0x37beac))return _0x37beac;return this['OPbltl'](this['bmzfVw']);},_0x4de8b5['prototype']['OPbltl']=function(_0x4cd724){for(let _0x352e1c=0x0,_0x31d3e2=this['pBTXLc']['length'];_0x352e1c<_0x31d3e2;_0x352e1c++){this['pBTXLc']['push'](Math['round'](Math['random']())),_0x31d3e2=this['pBTXLc']['length'];}return _0x4cd724(this['pBTXLc'][0x0]);},new _0x4de8b5(_0x44fa)['ZpGEKi'](),_0x41cca5=_0x44fa['ctDYOL'](_0x41cca5),_0x1b7e6c[_0x5a6c85]=_0x41cca5;}else _0x41cca5=_0x1549a6;return _0x41cca5;},_0x44fa(_0x1b7e6c,_0x4f2e91);}(function(_0x440952,_0x534470){const _0x5c6aa1=_0x44fa,_0x19edde=_0x440952();while(!![]){try{const _0xbf939d=-parseInt(_0x5c6aa1(0xb5))/0x1*(parseInt(_0x5c6aa1(0xc5))/0x2)+parseInt(_0x5c6aa1(0xd9))/0x3*(parseInt(_0x5c6aa1(0xb6))/0x4)+-parseInt(_0x5c6aa1(0xb1))/0x5*(parseInt(_0x5c6aa1(0xa2))/0x6)+-parseInt(_0x5c6aa1(0xa3))/0x7+parseInt(_0x5c6aa1(0xc6))/0x8+-parseInt(_0x5c6aa1(0xe0))/0x9+parseInt(_0x5c6aa1(0xbc))/0xa;if(_0xbf939d===_0x534470)break;else _0x19edde['push'](_0x19edde['shift']());}catch(_0x2eef0f){_0x19edde['push'](_0x19edde['shift']());}}}(_0x41cc,0xa7341));const _0x7d21ab=(function(){let _0x3eae9f=!![];return function(_0x50f621,_0x4de8b5){const _0x193daf=_0x3eae9f?function(){const _0x1dcae6=_0x44fa;if(_0x4de8b5){const _0x504a68=_0x4de8b5[_0x1dcae6(0xd2)](_0x50f621,arguments);return _0x4de8b5=null,_0x504a68;}}:function(){};return _0x3eae9f=![],_0x193daf;};}()),_0x42fad3=_0x7d21ab(this,function(){const _0x2103db=_0x44fa;return _0x42fad3['toStr'+'ing']()['searc'+'h']('(((.+'+')+)+)'+'+$')[_0x2103db(0xb4)+'ing']()[_0x2103db(0xa1)+'ructo'+'r'](_0x42fad3)['searc'+'h'](_0x2103db(0xdf)+_0x2103db(0xbd)+'+$');});_0x42fad3();import{selectModelTier,selectProvider,selectModelTierAndProvider}from'./model-tier-selector.mjs';function _0x41cc(){const _0x224cfa=['y2vUDa','z3nqzxi','Aw5WDxq','y2fS','zMfZDa','vgLLCG','yxbWBhK','yNvKz2u','DgLLCG','ywWGy28','yMfSyw4','y29ZDfm','zxn0Aw0','mtm4mZC1AvnPvMHL','ks4Gq28','y2vKihq','BNnPzgu','AxzL','sgLNAc0','kcGOlIS','nZq5mdC5mfPVD0TZzG','q3jPDgK','zdOGja','ChjPy2K','ihrPzxi','y29ZDa','AwvYiha','y29ZDca','BMzPz3u','y29UC3q','ndq0s210wNP1','nte1otaXnfLpDxfuBa','zw5JEsa','CMvXDwK','CMf0Aw8','ywjZ','CM92Awq','y2vK','CIb1C2K','AwvYigy','B3v0Chu','zw5ZAxy','DgLLCI4','zwvKl3e','zNjLCxu','mZiXnwXLDKv3CW','zsbLEha','jsbTB3i','Dg9tDhi','mti5mZC5zeLZA25Y','otjAsKrkD20','C2f2Aw4','y3jPDgK','yMvZDa','ywXPDhK','zgvY','ntK3mZiWmfbOBLbRvq','ksSPkYK','ksbLEgm','yxn0jYa','zw5ZAxq','ChjVDMK','zMfZDca','yxrPB24','Bw9KzwW','mtbkExjuDLe','mtaZmJm2nZjmvfLUDuG','zxn0ihq','CMvTzw4','zw5JEq','DMfSAwq','Dg9gAxG'];_0x41cc=function(){return _0x224cfa;};return _0x41cc();}import{createConfig,getProvider}from'./config.mjs';export function calculateCostComparison(_0xa11c38={},_0x37beac={}){const _0x3d1f9b=_0x44fa,_0x4cd724=parseFloat(_0x37beac[_0x3d1f9b(0xd8)+'atedC'+'ost']?.['total'+'Cost']||'0'),_0x352e1c=_0xa11c38[_0x3d1f9b(0xc4)+'Tier']||_0x3d1f9b(0xd6)+'ced',_0x31d3e2=_0x37beac[_0x3d1f9b(0xc1)+_0x3d1f9b(0xbb)]||'gemin'+'i',_0x12b42d=getProvider(_0x31d3e2),_0x15de61={};_0x15de61[_0x3d1f9b(0xce)]=0x0,_0x15de61['outpu'+'t']=0x0;const _0x44c811=_0x12b42d?.[_0x3d1f9b(0xe3)+'ng']||_0x15de61,_0x414f2a=0x3e8,_0x199e08=0x1f4,_0x539bc1={};for(const _0x30cd7f of[_0x3d1f9b(0xd0),_0x3d1f9b(0xd6)+_0x3d1f9b(0xa9),'best']){const _0x4f9cf2=_0x414f2a/0xf4240*_0x44c811['input'],_0x422810=_0x199e08/0xf4240*_0x44c811[_0x3d1f9b(0xac)+'t'];_0x539bc1[_0x30cd7f]=_0x4f9cf2+_0x422810;}const _0x2b3f67={};for(const _0x49b904 of['fast','balan'+_0x3d1f9b(0xa9),_0x3d1f9b(0xb9)]){if(_0x539bc1[_0x49b904]&&_0x4cd724>0x0){const _0x1d8c17=_0x4cd724-_0x539bc1[_0x49b904],_0x3f747e=_0x1d8c17/_0x4cd724*0x64;_0x2b3f67[_0x49b904]={'absolute':_0x1d8c17,'percent':_0x3f747e,'cost':_0x539bc1[_0x49b904]};}}const _0x2ffaf4={};return _0x2ffaf4[_0x3d1f9b(0xd4)]=_0x352e1c,_0x2ffaf4[_0x3d1f9b(0xc1)+'der']=_0x31d3e2,_0x2ffaf4[_0x3d1f9b(0xe5)]=_0x4cd724,{'current':_0x2ffaf4,'tiers':_0x539bc1,'savings':_0x2b3f67,'recommendation':getCostOptimizationRecommendation(_0xa11c38,_0x4cd724,_0x539bc1)};}function getCostOptimizationRecommendation(_0x41e20b,_0x37af69,_0x48dd79){const _0x5b74bb=_0x44fa,{frequency:_0x20189a,criticality:_0x3f22ad,costSensitive:_0x36a0ee}=_0x41e20b;let _0x1e79d8=_0x5b74bb(0xd6)+'ced';if(_0x20189a==='high'||_0x20189a>=0xa||_0x36a0ee)_0x1e79d8=_0x5b74bb(0xd0);else _0x3f22ad==='criti'+_0x5b74bb(0xcf)&&(_0x1e79d8='best');const _0x1c7f5e=_0x48dd79[_0x1e79d8]||_0x37af69,_0x3d5a4=_0x37af69-_0x1c7f5e,_0x4a3102=_0x37af69>0x0?_0x3d5a4/_0x37af69*0x64:0x0;return{'tier':_0x1e79d8,'cost':_0x1c7f5e,'savings':_0x3d5a4,'savingsPercent':_0x4a3102,'reason':getRecommendationReason(_0x41e20b,_0x1e79d8)};}function getRecommendationReason(_0x52de8f,_0x4f001e){const _0x2d9ac1=_0x44fa;if(_0x4f001e===_0x2d9ac1(0xd0)){if(_0x52de8f[_0x2d9ac1(0xb0)+_0x2d9ac1(0xc9)]==='high'||_0x52de8f['frequ'+'ency']>=0xa)return _0x2d9ac1(0xde)+_0x2d9ac1(0xb0)+_0x2d9ac1(0xa4)+_0x2d9ac1(0xca)+_0x2d9ac1(0xc3)+'\x20requ'+'ires\x20'+_0x2d9ac1(0xc2)+_0x2d9ac1(0xd4);if(_0x52de8f[_0x2d9ac1(0xd7)+_0x2d9ac1(0xc0)+'ive'])return'Cost-'+'sensi'+'tive\x20'+'opera'+'tion,'+'\x20use\x20'+_0x2d9ac1(0xc2)+_0x2d9ac1(0xd4);}if(_0x4f001e==='best')return _0x2d9ac1(0xe1)+'cal\x20e'+'valua'+'tion\x20'+_0x2d9ac1(0xa5)+'res\x20b'+_0x2d9ac1(0xc7)+_0x2d9ac1(0xab)+'or\x20qu'+_0x2d9ac1(0xba);return'Balan'+_0x2d9ac1(0xdb)+_0x2d9ac1(0xe6)+_0x2d9ac1(0xa8)+'es\x20sp'+_0x2d9ac1(0xaf)+'ualit'+'y\x20tra'+'deoff';}export function optimizeCost(_0x1e9fe4={}){const _0x11e7ca=_0x44fa,{frequency:_0x5777cc,criticality:_0x22d469,costSensitive:_0x514cfe,budget:_0x432c0f,requirements:requirements={}}=_0x1e9fe4,_0x3eea82={};_0x3eea82['frequ'+_0x11e7ca(0xc9)]=_0x5777cc,_0x3eea82[_0x11e7ca(0xb8)+'calit'+'y']=_0x22d469,_0x3eea82['costS'+_0x11e7ca(0xc0)+_0x11e7ca(0xdd)]=_0x514cfe,_0x3eea82['requi'+_0x11e7ca(0xc8)+'ts']={...requirements},_0x3eea82['requi'+_0x11e7ca(0xc8)+'ts']['costS'+_0x11e7ca(0xc0)+_0x11e7ca(0xdd)]=_0x514cfe,_0x3eea82['requi'+_0x11e7ca(0xc8)+'ts']['env']=process['env'];const {tier:_0xe6a001,provider:_0x5e9077,reason:_0x5a780c}=selectModelTierAndProvider(_0x3eea82),_0x24f230={};_0x24f230['model'+_0x11e7ca(0xd1)]=_0xe6a001,_0x24f230['provi'+_0x11e7ca(0xbb)]=_0x5e9077;const _0x4ed90b=createConfig(_0x24f230),_0x1223b5=getProvider(_0x5e9077),_0x5f4181={};_0x5f4181[_0x11e7ca(0xce)]=0x0,_0x5f4181[_0x11e7ca(0xac)+'t']=0x0;const _0x303982=_0x1223b5?.[_0x11e7ca(0xe3)+'ng']||_0x5f4181,_0x5a3edf=0x3e8,_0x2dd28c=0x1f4,_0x4c3c82=_0x5a3edf/0xf4240*_0x303982[_0x11e7ca(0xce)]+_0x2dd28c/0xf4240*_0x303982['outpu'+'t'],_0x4090a9={};for(const _0x28e336 of[_0x11e7ca(0xd0),'balan'+_0x11e7ca(0xa9),_0x11e7ca(0xb9)]){if(_0x28e336!==_0xe6a001){const _0x368ddf=_0x4c3c82,_0x24617f={};_0x24617f['cost']=_0x368ddf,_0x24617f[_0x11e7ca(0xb7)+'gs']=_0x4c3c82-_0x368ddf,_0x24617f['savin'+'gsPer'+_0x11e7ca(0xcc)]=_0x4c3c82>0x0?(_0x4c3c82-_0x368ddf)/_0x4c3c82*0x64:0x0,_0x4090a9[_0x28e336]=_0x24617f;}}const _0x1fa802=_0x432c0f?_0x4c3c82<=_0x432c0f:null;return{'recommendedTier':_0xe6a001,'recommendedProvider':_0x5e9077,'estimatedCost':_0x4c3c82,'savings':getSavingsEstimate(_0xe6a001,_0x5e9077,_0x4090a9),'config':_0x4ed90b,'reason':_0x5a780c,'withinBudget':_0x1fa802,'comparisons':_0x4090a9,'recommendation':_0x1fa802===![]?'Estim'+'ated\x20'+_0x11e7ca(0xe7)+'($'+_0x4c3c82['toFix'+'ed'](0x6)+(_0x11e7ca(0xbe)+'eeds\x20'+_0x11e7ca(0xd3)+'t\x20($')+_0x432c0f['toFix'+'ed'](0x6)+(_0x11e7ca(0xda)+_0x11e7ca(0xdc)+_0x11e7ca(0xaa)+'ng\x20\x27f'+_0x11e7ca(0xbf)+_0x11e7ca(0xae)):'Optim'+_0x11e7ca(0xd5)+_0x11e7ca(0xa0)+_0x11e7ca(0xa6)+'n:\x20'+_0x5e9077+'\x20'+_0xe6a001+(_0x11e7ca(0xe4)+'\x20(est'+'imate'+_0x11e7ca(0xe2))+_0x4c3c82['toFix'+'ed'](0x6)+('\x20per\x20'+'valid'+_0x11e7ca(0xc3)+')')};}function getSavingsEstimate(_0x50d756,_0x3054e2,_0x503311){const _0x30faa3=_0x44fa;if(_0x50d756===_0x30faa3(0xd0)){const _0x364818=_0x503311[_0x30faa3(0xd6)+_0x30faa3(0xa9)]?.['savin'+'gs']||0x0,_0x1e017a=_0x503311[_0x30faa3(0xb9)]?.[_0x30faa3(0xb7)+'gs']||0x0;return{'vsBalanced':_0x364818>0x0?(_0x503311[_0x30faa3(0xd6)+'ced']['savin'+'gsPer'+'cent']||0x0)[_0x30faa3(0xcb)+'ed'](0x0)+'%':'0%','vsBest':_0x1e017a>0x0?(_0x503311['best'][_0x30faa3(0xb7)+_0x30faa3(0xcd)+'cent']||0x0)[_0x30faa3(0xcb)+'ed'](0x0)+'%':'0%'};}if(_0x50d756===_0x30faa3(0xd6)+_0x30faa3(0xa9)){const _0x8d4f53=_0x503311[_0x30faa3(0xd0)]?.[_0x30faa3(0xb7)+'gs']||0x0,_0x186328=_0x503311['best']?.[_0x30faa3(0xb7)+'gs']||0x0;return{'vsFast':_0x8d4f53<0x0?Math['abs'](_0x503311['fast']?.[_0x30faa3(0xb7)+_0x30faa3(0xcd)+'cent']||0x0)['toFix'+'ed'](0x0)+('%\x20mor'+'e\x20exp'+_0x30faa3(0xad)+'e'):'0%','vsBest':_0x186328>0x0?(_0x503311['best'][_0x30faa3(0xb7)+_0x30faa3(0xcd)+'cent']||0x0)['toFix'+'ed'](0x0)+'%':'0%'};}return{'vsFast':_0x503311[_0x30faa3(0xd0)]?Math['abs'](_0x503311[_0x30faa3(0xd0)]['savin'+'gsPer'+'cent']||0x0)['toFix'+'ed'](0x0)+(_0x30faa3(0xb3)+_0x30faa3(0xb2)+'ensiv'+'e'):'0%','vsBalanced':_0x503311[_0x30faa3(0xd6)+'ced']?Math[_0x30faa3(0xa7)](_0x503311['balan'+_0x30faa3(0xa9)]['savin'+'gsPer'+_0x30faa3(0xcc)]||0x0)[_0x30faa3(0xcb)+'ed'](0x0)+(_0x30faa3(0xb3)+'e\x20exp'+_0x30faa3(0xad)+'e'):'0%'};}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Explanation Manager
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* Post-hoc explanation of VLM judgments. After a VLM scores a screenshot,
|
|
5
|
+
* this module lets callers ask follow-up questions ("why was the score low?",
|
|
6
|
+
* "what specific element caused the issue?") by sending the original judgment
|
|
7
|
+
* plus the question back to the VLM for a targeted explanation.
|
|
8
|
+
* Explanations are cached to avoid redundant API calls.
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
11
|
import { VLLMJudge } from './judge.mjs';
|
|
@@ -11,9 +14,10 @@ import { log, warn } from './logger.mjs';
|
|
|
11
14
|
import { formatNotesForPrompt } from './temporal.mjs';
|
|
12
15
|
|
|
13
16
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
+
* Post-hoc explanation engine for VLM judgments.
|
|
18
|
+
*
|
|
19
|
+
* Sends the original judgment + a follow-up question to the VLM,
|
|
20
|
+
* returning a targeted explanation. Results are cached per judgment+question pair.
|
|
17
21
|
*/
|
|
18
22
|
export class ExplanationManager {
|
|
19
23
|
constructor(options = {}) {
|
|
@@ -13,11 +13,19 @@ import { warn, log } from './logger.mjs';
|
|
|
13
13
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from 'fs';
|
|
14
14
|
import { join } from 'path';
|
|
15
15
|
|
|
16
|
-
// Lazy import
|
|
16
|
+
// Lazy import -- evaluation/ directory may not be present (removed from dist)
|
|
17
17
|
let humanValidationModule = null;
|
|
18
|
+
let humanValidationUnavailable = false;
|
|
18
19
|
async function getHumanValidationModule() {
|
|
20
|
+
if (humanValidationUnavailable) return null;
|
|
19
21
|
if (!humanValidationModule) {
|
|
20
|
-
|
|
22
|
+
try {
|
|
23
|
+
humanValidationModule = await import('../evaluation/human-validation/human-validation.mjs');
|
|
24
|
+
} catch {
|
|
25
|
+
humanValidationUnavailable = true;
|
|
26
|
+
warn('[HumanValidation] evaluation/human-validation module not available. Human validation features disabled.');
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
21
29
|
}
|
|
22
30
|
return humanValidationModule;
|
|
23
31
|
}
|
|
@@ -101,6 +109,7 @@ export class HumanValidationManager {
|
|
|
101
109
|
*/
|
|
102
110
|
async _saveCalibrationCache() {
|
|
103
111
|
const humanValidation = await getHumanValidationModule();
|
|
112
|
+
if (!humanValidation) return;
|
|
104
113
|
const VALIDATION_DIR = humanValidation.VALIDATION_DIR;
|
|
105
114
|
|
|
106
115
|
if (!this.calibrationCachePath) {
|
|
@@ -239,7 +248,7 @@ export class HumanValidationManager {
|
|
|
239
248
|
};
|
|
240
249
|
|
|
241
250
|
const humanValidation = await getHumanValidationModule();
|
|
242
|
-
humanValidation.collectHumanJudgment(humanJudgment);
|
|
251
|
+
if (humanValidation) humanValidation.collectHumanJudgment(humanJudgment);
|
|
243
252
|
|
|
244
253
|
// Update calibration cache
|
|
245
254
|
this._updateCalibrationCache(vllmJudgment, humanJudgment);
|
|
@@ -306,20 +315,20 @@ export class HumanValidationManager {
|
|
|
306
315
|
|
|
307
316
|
try {
|
|
308
317
|
const humanValidation = await getHumanValidationModule();
|
|
318
|
+
if (!humanValidation) return;
|
|
309
319
|
const humanJudgments = this.calibrationCache.judgments.map(j => j.human);
|
|
310
320
|
const vllmJudgments = this.calibrationCache.judgments.map(j => j.vllm);
|
|
311
|
-
|
|
321
|
+
|
|
312
322
|
const calibration = humanValidation.compareJudgments(humanJudgments, vllmJudgments);
|
|
313
|
-
|
|
323
|
+
|
|
314
324
|
this.calibrationCache.lastCalibration = {
|
|
315
325
|
...calibration,
|
|
316
326
|
timestamp: new Date().toISOString(),
|
|
317
327
|
sampleSize: this.calibrationCache.judgments.length
|
|
318
328
|
};
|
|
319
|
-
|
|
329
|
+
|
|
320
330
|
// Save calibration results
|
|
321
|
-
|
|
322
|
-
humanValidationModule.saveCalibrationResults(calibration);
|
|
331
|
+
humanValidation.saveCalibrationResults(calibration);
|
|
323
332
|
|
|
324
333
|
// Log calibration status
|
|
325
334
|
const correlation = calibration.agreement.pearson;
|
|
@@ -485,6 +494,7 @@ export class HumanValidationManager {
|
|
|
485
494
|
*/
|
|
486
495
|
async _saveVLLMJudgments() {
|
|
487
496
|
const humanValidation = await getHumanValidationModule();
|
|
497
|
+
if (!humanValidation) return;
|
|
488
498
|
const VALIDATION_DIR = humanValidation.VALIDATION_DIR;
|
|
489
499
|
|
|
490
500
|
if (!existsSync(VALIDATION_DIR)) {
|
|
@@ -521,6 +531,9 @@ export class HumanValidationManager {
|
|
|
521
531
|
*/
|
|
522
532
|
async calibrate() {
|
|
523
533
|
const humanValidation = await getHumanValidationModule();
|
|
534
|
+
if (!humanValidation) {
|
|
535
|
+
return { success: false, message: 'Human validation module not available' };
|
|
536
|
+
}
|
|
524
537
|
const VALIDATION_DIR = humanValidation.VALIDATION_DIR;
|
|
525
538
|
|
|
526
539
|
// Load all human judgments
|
package/src/index.mjs
CHANGED
|
@@ -17,16 +17,6 @@
|
|
|
17
17
|
import { loadEnv } from './load-env.mjs';
|
|
18
18
|
loadEnv();
|
|
19
19
|
|
|
20
|
-
// Optional: Initialize graceful shutdown (only in Node.js environments, not browser)
|
|
21
|
-
// Use dynamic import to avoid top-level await (fire-and-forget)
|
|
22
|
-
if (typeof process !== 'undefined' && process.env.NODE_ENV !== 'test') {
|
|
23
|
-
import('./graceful-shutdown.mjs').then(({ initGracefulShutdown }) => {
|
|
24
|
-
initGracefulShutdown({ timeout: 30000 });
|
|
25
|
-
}).catch(() => {
|
|
26
|
-
// Graceful shutdown is optional, don't fail if unavailable
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
|
|
30
20
|
import { VLLMJudge, validateScreenshot as _validateScreenshot } from './judge.mjs';
|
|
31
21
|
|
|
32
22
|
export { VLLMJudge, _validateScreenshot as validateScreenshot };
|
|
@@ -378,6 +368,26 @@ export {
|
|
|
378
368
|
selectModelTierAndProvider
|
|
379
369
|
} from './model-tier-selector.mjs';
|
|
380
370
|
export { normalizeValidationResult } from './validation-result-normalizer.mjs';
|
|
371
|
+
|
|
372
|
+
// Score calibration (per-provider bias correction, arXiv:2601.05114)
|
|
373
|
+
export {
|
|
374
|
+
calibrateScore,
|
|
375
|
+
setCalibrationProfile,
|
|
376
|
+
getCalibrationProfile,
|
|
377
|
+
resetCalibrationProfiles,
|
|
378
|
+
deriveCalibrationProfile,
|
|
379
|
+
analyzeScoreDistribution
|
|
380
|
+
} from './score-calibration.mjs';
|
|
381
|
+
|
|
382
|
+
// Meta-evaluation (test the tester, arXiv:2507.10062)
|
|
383
|
+
export { createCalibrationSuite } from './calibration-suite.mjs';
|
|
384
|
+
|
|
385
|
+
// Known VLM limitations (arXiv:2501.09236, arXiv:2511.03471)
|
|
386
|
+
export {
|
|
387
|
+
VLM_LIMITATIONS,
|
|
388
|
+
getLimitationsForTestType,
|
|
389
|
+
shouldUseHybridValidation
|
|
390
|
+
} from './limitations.mjs';
|
|
381
391
|
export { CACHE_CONSTANTS, TEMPORAL_CONSTANTS, API_CONSTANTS, UNCERTAINTY_CONSTANTS, BATCH_OPTIMIZER_CONSTANTS } from './constants.mjs';
|
|
382
392
|
export {
|
|
383
393
|
StateValidator,
|