thumbgate 1.27.16 → 1.27.18
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/.claude-plugin/marketplace.json +85 -0
- package/.claude-plugin/plugin.json +3 -4
- package/.well-known/llms.txt +12 -33
- package/.well-known/mcp/server-card.json +8 -8
- package/README.md +30 -215
- package/adapters/chatgpt/openapi.yaml +1695 -0
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/gemini/function-declarations.json +0 -1
- package/adapters/mcp/server-stdio.js +11 -263
- package/adapters/opencode/opencode.json +1 -1
- package/bench/thumbgate-bench.json +2 -2
- package/bin/cli.js +122 -1421
- package/bin/postinstall.js +27 -11
- package/config/gate-templates.json +0 -216
- package/config/gates/claim-verification.json +0 -12
- package/config/gates/default.json +2 -31
- package/config/github-about.json +2 -2
- package/config/mcp-allowlists.json +13 -23
- package/config/merge-quality-checks.json +1 -0
- package/config/model-candidates.json +6 -121
- package/openapi/openapi.yaml +0 -12
- package/package.json +98 -228
- package/public/blog.html +4 -4
- package/public/codex-plugin.html +20 -72
- package/public/compare.html +8 -31
- package/public/dashboard.html +166 -930
- package/public/federal.html +2 -2
- package/public/guide.html +9 -33
- package/public/index.html +116 -495
- package/public/learn.html +18 -191
- package/public/lessons.html +10 -168
- package/public/numbers.html +7 -7
- package/public/pro.html +11 -34
- package/scripts/agent-memory-lifecycle.js +0 -211
- package/scripts/agent-readiness.js +3 -167
- package/scripts/agent-reward-model.js +1 -53
- package/scripts/auto-promote-gates.js +10 -82
- package/scripts/auto-wire-hooks.js +0 -14
- package/scripts/billing.js +1 -93
- package/scripts/build-metadata.js +10 -50
- package/scripts/cli-feedback.js +6 -18
- package/scripts/cli-schema.js +0 -90
- package/scripts/cli-telemetry.js +1 -6
- package/scripts/commercial-offer.js +2 -82
- package/scripts/context-manager.js +6 -74
- package/scripts/dashboard.js +2 -68
- package/scripts/durability/step.js +3 -3
- package/scripts/export-databricks-bundle.js +1 -5
- package/scripts/export-dpo-pairs.js +2 -7
- package/scripts/feedback-loop.js +1 -123
- package/scripts/feedback-quality.js +0 -87
- package/scripts/filesystem-search.js +10 -35
- package/scripts/gate-stats.js +0 -89
- package/scripts/gates-engine.js +85 -1105
- package/scripts/gemini-embedding-policy.js +1 -2
- package/scripts/hook-runtime.js +14 -20
- package/scripts/hook-thumbgate-cache-updater.js +2 -18
- package/scripts/hybrid-feedback-context.js +7 -142
- package/scripts/lesson-inference.js +3 -8
- package/scripts/lesson-search.js +1 -17
- package/scripts/license.js +1 -0
- package/scripts/llm-client.js +9 -390
- package/scripts/local-model-profile.js +8 -15
- package/scripts/memory-scope-readiness.js +0 -159
- package/scripts/meta-agent-loop.js +0 -36
- package/scripts/operational-integrity.js +5 -39
- package/scripts/oss-pr-opportunity-scout.js +5 -35
- package/scripts/plausible-server-events.js +6 -9
- package/scripts/pro-local-dashboard.js +4 -4
- package/scripts/proxy-pointer-rag-guardrails.js +1 -42
- package/scripts/rate-limiter.js +13 -64
- package/scripts/secret-scanner.js +5 -44
- package/scripts/security-scanner.js +10 -260
- package/scripts/self-distill-agent.js +1 -3
- package/scripts/seo-gsd.js +2 -916
- package/scripts/statusline-cache-path.js +2 -17
- package/scripts/statusline-local-stats.js +1 -9
- package/scripts/statusline-meta.js +2 -28
- package/scripts/statusline.sh +4 -20
- package/scripts/telemetry-analytics.js +0 -357
- package/scripts/thompson-sampling.js +10 -31
- package/scripts/thumbgate-bench.js +1 -16
- package/scripts/thumbgate-search.js +19 -85
- package/scripts/tool-registry.js +1 -169
- package/scripts/vector-store.js +0 -45
- package/scripts/workflow-sentinel.js +53 -286
- package/scripts/workspace-evolver.js +2 -62
- package/src/api/server.js +290 -2578
- package/.claude/commands/dashboard.md +0 -15
- package/.claude/commands/thumbgate-blocked.md +0 -27
- package/.claude/commands/thumbgate-dashboard.md +0 -15
- package/.claude/commands/thumbgate-doctor.md +0 -30
- package/.claude/commands/thumbgate-guard.md +0 -36
- package/.claude/commands/thumbgate-protect.md +0 -30
- package/.claude/commands/thumbgate-rules.md +0 -30
- package/.well-known/agentic-verify.txt +0 -1
- package/adapters/gcp/dfcx-webhook-gate.js +0 -295
- package/adapters/letta/README.md +0 -41
- package/adapters/letta/thumbgate-letta-adapter.js +0 -133
- package/adapters/policy-engine/ethicore-guardian-client.js +0 -68
- package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +0 -260
- package/bench/observability-eval-suite.json +0 -26
- package/bin/dashboard-cli.js +0 -7
- package/commands/dashboard.md +0 -15
- package/commands/thumbgate-dashboard.md +0 -15
- package/config/builtin-lessons.json +0 -23
- package/config/gate-classifier-routing.json +0 -98
- package/config/post-deploy-marketing-pages.json +0 -61
- package/public/about.html +0 -162
- package/public/agent-manager.html +0 -179
- package/public/agents-cost-savings.html +0 -153
- package/public/ai-malpractice-prevention.html +0 -818
- package/public/assets/brand/github-social-preview.png +0 -0
- package/public/assets/brand/thumbgate-icon-512.png +0 -0
- package/public/assets/brand/thumbgate-icon-pro-512.png +0 -0
- package/public/assets/brand/thumbgate-icon-team-512.png +0 -0
- package/public/assets/brand/thumbgate-logo-1200x360.png +0 -0
- package/public/assets/brand/thumbgate-logo-transparent.svg +0 -22
- package/public/assets/brand/thumbgate-mark-inline-v3.svg +0 -19
- package/public/assets/brand/thumbgate-mark-pro.svg +0 -23
- package/public/assets/brand/thumbgate-mark-team.svg +0 -26
- package/public/assets/brand/thumbgate-mark.svg +0 -21
- package/public/assets/brand/thumbgate-wordmark.svg +0 -20
- package/public/assets/claude-thumbgate-statusbar.svg +0 -8
- package/public/assets/codex-thumbgate-statusbar-test.svg +0 -9
- package/public/assets/legal-intake-control-flow.svg +0 -66
- package/public/brand/thumbgate-mark.svg +0 -19
- package/public/brand/thumbgate-og.svg +0 -16
- package/public/chatgpt-app.html +0 -330
- package/public/codex-enterprise.html +0 -123
- package/public/js/buyer-intent.js +0 -672
- package/public/pricing.html +0 -399
- package/scripts/action-receipts.js +0 -324
- package/scripts/activation-quickstart.js +0 -187
- package/scripts/agent-operations-planner.js +0 -621
- package/scripts/ai-component-inventory.js +0 -367
- package/scripts/async-eval-observability.js +0 -236
- package/scripts/audit.js +0 -65
- package/scripts/aws-blocks-guardrails.js +0 -228
- package/scripts/classifier-routing.js +0 -130
- package/scripts/dashboard-chat.js +0 -332
- package/scripts/feedback-aggregate.js +0 -281
- package/scripts/feedback-sanitizer.js +0 -105
- package/scripts/hob-pack.js +0 -591
- package/scripts/hook-stop-anti-claim.js +0 -301
- package/scripts/install-shim.js +0 -87
- package/scripts/mcp-oauth.js +0 -293
- package/scripts/noop-detect.js +0 -285
- package/scripts/omlx-smoke.js +0 -192
- package/scripts/parallel-workflow-orchestrator.js +0 -293
- package/scripts/plan-gate.js +0 -243
- package/scripts/plausible-domain-config.js +0 -99
- package/scripts/qa-scenario-planner.js +0 -136
- package/scripts/repeat-metric.js +0 -137
- package/scripts/secret-fixture-tokens.js +0 -61
- package/scripts/secret-redaction.js +0 -166
- package/scripts/self-harness-optimizer.js +0 -141
- package/scripts/self-healing-check.js +0 -193
- package/scripts/silent-failure-cluster.js +0 -531
- package/scripts/statusline-cache-read.js +0 -57
- package/scripts/sync-telemetry-from-prod.js +0 -374
- package/scripts/tool-contract-validator.js +0 -76
- package/scripts/trajectory-scorer.js +0 -63
- package/scripts/verify-marketing-pages-deployed.js +0 -195
- package/scripts/visitor-journey.js +0 -172
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const path = require('
|
|
5
|
-
const os = require('node:os');
|
|
6
|
-
const {
|
|
7
|
-
getAggregateStatuslineCachePath,
|
|
8
|
-
shouldAggregateFeedback,
|
|
9
|
-
} = require('./feedback-aggregate');
|
|
4
|
+
const path = require('path');
|
|
10
5
|
const {
|
|
11
6
|
listFeedbackArtifactPaths,
|
|
12
7
|
resolveFeedbackDir,
|
|
@@ -17,28 +12,18 @@ function unique(values = []) {
|
|
|
17
12
|
return [...new Set(values.filter(Boolean).map((value) => path.resolve(value)))];
|
|
18
13
|
}
|
|
19
14
|
|
|
20
|
-
function hasIsolatedTempFeedbackDir(env = process.env) {
|
|
21
|
-
if (!env.THUMBGATE_FEEDBACK_DIR) return false;
|
|
22
|
-
try {
|
|
23
|
-
return path.resolve(env.THUMBGATE_FEEDBACK_DIR).startsWith(path.resolve(os.tmpdir()) + path.sep);
|
|
24
|
-
} catch {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
15
|
function getStatuslineCacheCandidates(options = {}) {
|
|
30
16
|
const env = options.env || process.env;
|
|
31
17
|
const projectDir = resolveProjectDir({ cwd: options.cwd, env });
|
|
32
18
|
const feedbackDir = resolveFeedbackDir({ projectDir, env });
|
|
33
19
|
|
|
34
20
|
return unique([
|
|
35
|
-
shouldAggregateFeedback({ env }) && !hasIsolatedTempFeedbackDir(env) && getAggregateStatuslineCachePath({ env }),
|
|
36
21
|
...listFeedbackArtifactPaths('statusline_cache.json', { projectDir, env }),
|
|
37
22
|
path.join(feedbackDir, 'statusline_cache.json'),
|
|
38
23
|
]);
|
|
39
24
|
}
|
|
40
25
|
|
|
41
|
-
if (
|
|
26
|
+
if (require.main === module) {
|
|
42
27
|
process.stdout.write(JSON.stringify({ candidates: getStatuslineCacheCandidates() }));
|
|
43
28
|
}
|
|
44
29
|
|
|
@@ -2,10 +2,6 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
const { analyzeFeedback } = require('./feedback-loop');
|
|
5
|
-
const {
|
|
6
|
-
computeAggregateFeedbackStats,
|
|
7
|
-
shouldAggregateFeedback,
|
|
8
|
-
} = require('./feedback-aggregate');
|
|
9
5
|
const { normalizeStatsPayload } = require('./hook-thumbgate-cache-updater');
|
|
10
6
|
const { syncClaudeHistoryFeedback } = require('./claude-feedback-sync');
|
|
11
7
|
const { resolveProjectDir } = require('./feedback-paths');
|
|
@@ -13,13 +9,9 @@ const { resolveProjectDir } = require('./feedback-paths');
|
|
|
13
9
|
try {
|
|
14
10
|
const projectDir = resolveProjectDir({ cwd: process.cwd(), env: process.env });
|
|
15
11
|
syncClaudeHistoryFeedback({ projectDir });
|
|
16
|
-
const
|
|
17
|
-
const stats = scope !== 'project' && shouldAggregateFeedback({ env: process.env })
|
|
18
|
-
? computeAggregateFeedbackStats({ projectDir, env: process.env })
|
|
19
|
-
: analyzeFeedback();
|
|
12
|
+
const stats = analyzeFeedback();
|
|
20
13
|
const payload = {
|
|
21
14
|
...normalizeStatsPayload(stats),
|
|
22
|
-
aggregate: stats.aggregate || { enabled: false },
|
|
23
15
|
updated_at: String(Math.floor(Date.now() / 1000)),
|
|
24
16
|
};
|
|
25
17
|
process.stdout.write(JSON.stringify(payload));
|
|
@@ -2,42 +2,16 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
const path = require('path');
|
|
5
|
-
const { isProLicensed
|
|
5
|
+
const { isProLicensed } = require('./license');
|
|
6
6
|
|
|
7
7
|
function getStatuslineMeta(options = {}) {
|
|
8
8
|
const pkg = require(path.join(__dirname, '..', 'package.json'));
|
|
9
9
|
const env = options.env || process.env;
|
|
10
10
|
const homeDir = options.homeDir || env.HOME || env.USERPROFILE || '.';
|
|
11
|
-
const fs = require('fs');
|
|
12
|
-
|
|
13
|
-
// Enterprise detection based on key prefix
|
|
14
|
-
let apiKey = env.THUMBGATE_OPERATOR_KEY || env.THUMBGATE_API_KEY || '';
|
|
15
|
-
if (apiKey && !isValidKey(apiKey)) {
|
|
16
|
-
apiKey = '';
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Fallback to reading from disk if not in env
|
|
20
|
-
if (!apiKey) {
|
|
21
|
-
try {
|
|
22
|
-
const configPath = path.join(homeDir, '.config', 'thumbgate', 'operator.json');
|
|
23
|
-
if (fs.existsSync(configPath)) {
|
|
24
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
25
|
-
apiKey = config.operatorKey || config.apiKey || '';
|
|
26
|
-
}
|
|
27
|
-
} catch (_) { /* ignore disk read errors */ }
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
let activeTier = 'Free';
|
|
31
|
-
|
|
32
|
-
if (apiKey.startsWith('tg_op_') || apiKey.startsWith('tg_creator_')) {
|
|
33
|
-
activeTier = 'Enterprise';
|
|
34
|
-
} else if (isProLicensed({ homeDir }) || apiKey.startsWith('tg_pro_')) {
|
|
35
|
-
activeTier = 'Pro';
|
|
36
|
-
}
|
|
37
11
|
|
|
38
12
|
return {
|
|
39
13
|
version: String(pkg.version || '').trim() || 'unknown',
|
|
40
|
-
tier:
|
|
14
|
+
tier: isProLicensed({ homeDir }) ? 'Pro' : 'Free',
|
|
41
15
|
};
|
|
42
16
|
}
|
|
43
17
|
|
package/scripts/statusline.sh
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
8
8
|
case "$SCRIPT_DIR" in *[!a-zA-Z0-9/_.-]*) echo "ThumbGate: invalid script path"; exit 1;; esac
|
|
9
9
|
LOCAL_API_ORIGIN="${THUMBGATE_LOCAL_API_ORIGIN:-http://localhost:3456}"
|
|
10
|
-
STATUSLINE_VERBOSE="${THUMBGATE_STATUSLINE_VERBOSE:-0}"
|
|
11
10
|
|
|
12
11
|
# ── Parse Claude Code session JSON from stdin ─────────────────────
|
|
13
12
|
eval "$(cat | jq -r '
|
|
@@ -45,19 +44,7 @@ if [ -z "$THUMBGATE_CACHE" ]; then
|
|
|
45
44
|
fi
|
|
46
45
|
|
|
47
46
|
UP="0"; DOWN="0"; LESSONS="0"; TREND="?"; CACHE_TS="0"
|
|
48
|
-
|
|
49
|
-
# reflects true totals, not whichever folder happens to be cwd. Writes still
|
|
50
|
-
# target $THUMBGATE_CACHE (per-folder), so attribution is preserved.
|
|
51
|
-
_RESOLVED_CACHE_JSON=$(node "${SCRIPT_DIR}/statusline-cache-read.js" 2>/dev/null)
|
|
52
|
-
if [[ -n "$_RESOLVED_CACHE_JSON" ]]; then
|
|
53
|
-
eval "$(echo "$_RESOLVED_CACHE_JSON" | jq -r '
|
|
54
|
-
@sh "UP=\(.thumbs_up // "0")",
|
|
55
|
-
@sh "DOWN=\(.thumbs_down // "0")",
|
|
56
|
-
@sh "LESSONS=\(.lessons // "0")",
|
|
57
|
-
@sh "TREND=\(.trend // "?")",
|
|
58
|
-
@sh "CACHE_TS=\(.updated_at // "0")"
|
|
59
|
-
' 2>/dev/null)"
|
|
60
|
-
elif [[ -f "$THUMBGATE_CACHE" ]]; then
|
|
47
|
+
if [ -f "$THUMBGATE_CACHE" ]; then
|
|
61
48
|
eval "$(jq -r '
|
|
62
49
|
@sh "UP=\(.thumbs_up // "0")",
|
|
63
50
|
@sh "DOWN=\(.thumbs_down // "0")",
|
|
@@ -212,10 +199,8 @@ LINE="${LINE:+${LINE} · }ThumbGate v${TG_VERSION} · ${TG_TIER}"
|
|
|
212
199
|
if [[ "$UP" = "0" && "$DOWN" = "0" ]]; then
|
|
213
200
|
LINE="${D}${LINE}${RST} · no feedback yet"
|
|
214
201
|
[[ -n "$PR_LABEL" ]] && LINE="${LINE} · ${D}${PR_LABEL}${RST}"
|
|
215
|
-
|
|
216
|
-
LINE="${LINE} · ${C}${DASHBOARD_LINK}${RST}"
|
|
202
|
+
LINE="${LINE} · ${C}${DASHBOARD_LINK}${RST} · ${M}${LESSONS_LINK}${RST}"
|
|
217
203
|
[[ -n "$LATEST_LESSON_LINK" ]] && LINE="${LINE} · ${D}${LATEST_LESSON_LINK}${RST}"
|
|
218
|
-
|
|
219
204
|
printf '%b\n' "$LINE"
|
|
220
205
|
else
|
|
221
206
|
LINE="${LINE} · ${G}${BD}${UP}${RST}${UP_LINK} ${R}${BD}${DOWN}${RST}${DOWN_LINK} ${ARROW}"
|
|
@@ -225,9 +210,8 @@ else
|
|
|
225
210
|
[[ "${AT_RISK:-0}" -gt 0 ]] && LINE="${LINE} ${R}${AT_RISK}⚠${RST}"
|
|
226
211
|
[[ "${ANOMALIES:-0}" -gt 0 ]] && LINE="${LINE} ${R}${ANOMALIES}☠${RST}"
|
|
227
212
|
[[ -n "$PR_LABEL" ]] && LINE="${LINE} · ${D}${PR_LABEL}${RST}"
|
|
228
|
-
|
|
229
|
-
LINE="${LINE} · ${C}${DASHBOARD_LINK}${RST}"
|
|
213
|
+
LINE="${LINE} · ${C}${DASHBOARD_LINK}${RST} · ${M}${LESSONS_LINK}${RST}"
|
|
230
214
|
[[ -n "$LATEST_LESSON_LINK" ]] && LINE="${LINE} · ${D}${LATEST_LESSON_LINK}${RST}"
|
|
231
|
-
|
|
215
|
+
|
|
232
216
|
printf '%b\n' "$LINE"
|
|
233
217
|
fi
|
|
@@ -75,13 +75,6 @@ function normalizeInteger(value) {
|
|
|
75
75
|
return Number.isFinite(parsed) ? Math.trunc(parsed) : null;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
function normalizeRate(value) {
|
|
79
|
-
if (value === undefined || value === null || value === '') return null;
|
|
80
|
-
const parsed = Number(value);
|
|
81
|
-
if (!Number.isFinite(parsed)) return null;
|
|
82
|
-
return Math.max(0, Math.min(parsed, 1));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
78
|
function safeRate(num, den) {
|
|
86
79
|
if (!den) return 0;
|
|
87
80
|
return Number((num / den).toFixed(4));
|
|
@@ -262,149 +255,6 @@ function inferTrafficChannel(raw = {}, referrerHost = null) {
|
|
|
262
255
|
return 'referral';
|
|
263
256
|
}
|
|
264
257
|
|
|
265
|
-
function normalizeBoolean(value) {
|
|
266
|
-
if (value === true || value === false) return value;
|
|
267
|
-
const text = normalizeText(value, 32);
|
|
268
|
-
if (!text) return false;
|
|
269
|
-
return ['1', 'true', 'yes', 'y', 'bot'].includes(text.toLowerCase());
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function includesAnyToken(values, tokens) {
|
|
273
|
-
const haystack = values
|
|
274
|
-
.map((value) => normalizeText(value, 512))
|
|
275
|
-
.filter(Boolean)
|
|
276
|
-
.join(' ')
|
|
277
|
-
.toLowerCase();
|
|
278
|
-
if (!haystack) return false;
|
|
279
|
-
return tokens.some((token) => haystack.includes(token));
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function addAudienceReason(reasons, code) {
|
|
283
|
-
if (!reasons.includes(code)) reasons.push(code);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function classifyTelemetryAudience(entry = {}, raw = {}) {
|
|
287
|
-
const reasons = [];
|
|
288
|
-
const identityValues = [
|
|
289
|
-
raw.email,
|
|
290
|
-
raw.customerEmail,
|
|
291
|
-
raw.buyerEmail,
|
|
292
|
-
raw.userEmail,
|
|
293
|
-
entry.visitorId,
|
|
294
|
-
entry.sessionId,
|
|
295
|
-
entry.traceId,
|
|
296
|
-
entry.acquisitionId,
|
|
297
|
-
entry.installId,
|
|
298
|
-
];
|
|
299
|
-
const attributionValues = [
|
|
300
|
-
entry.source,
|
|
301
|
-
entry.utmSource,
|
|
302
|
-
entry.utmMedium,
|
|
303
|
-
entry.utmCampaign,
|
|
304
|
-
entry.utmContent,
|
|
305
|
-
entry.creator,
|
|
306
|
-
entry.offerCode,
|
|
307
|
-
entry.campaignVariant,
|
|
308
|
-
entry.referrerHost,
|
|
309
|
-
];
|
|
310
|
-
const eventValues = [
|
|
311
|
-
entry.eventType,
|
|
312
|
-
entry.event,
|
|
313
|
-
entry.page,
|
|
314
|
-
entry.landingPath,
|
|
315
|
-
entry.ctaId,
|
|
316
|
-
entry.reasonCode,
|
|
317
|
-
entry.reasonDetail,
|
|
318
|
-
];
|
|
319
|
-
const userAgent = normalizeText(entry.userAgent || raw.userAgent, 512) || '';
|
|
320
|
-
const hostValues = [raw.host, raw.hostname, raw.origin, raw.url, raw.pageUrl, entry.referrerHost];
|
|
321
|
-
|
|
322
|
-
if (normalizeBoolean(entry.isBot || raw.isBot) || includesAnyToken([userAgent], [
|
|
323
|
-
'bot',
|
|
324
|
-
'crawler',
|
|
325
|
-
'spider',
|
|
326
|
-
'headless',
|
|
327
|
-
'playwright',
|
|
328
|
-
'puppeteer',
|
|
329
|
-
'curl/',
|
|
330
|
-
'wget/',
|
|
331
|
-
'lighthouse',
|
|
332
|
-
])) {
|
|
333
|
-
addAudienceReason(reasons, 'bot_or_automation_user_agent');
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
if (includesAnyToken(identityValues, [
|
|
337
|
-
'@example.com',
|
|
338
|
-
'buyer@example.com',
|
|
339
|
-
'test@example.com',
|
|
340
|
-
'codex-verification',
|
|
341
|
-
'audit@thumbgate.ai',
|
|
342
|
-
])) {
|
|
343
|
-
addAudienceReason(reasons, 'test_identity');
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if (includesAnyToken([...attributionValues, ...eventValues], [
|
|
347
|
-
'codex',
|
|
348
|
-
'audit',
|
|
349
|
-
'verification',
|
|
350
|
-
'synthetic',
|
|
351
|
-
'smoke',
|
|
352
|
-
'probe',
|
|
353
|
-
'test',
|
|
354
|
-
])) {
|
|
355
|
-
addAudienceReason(reasons, 'test_or_audit_attribution');
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
if (includesAnyToken(hostValues, [
|
|
359
|
-
'localhost',
|
|
360
|
-
'127.0.0.1',
|
|
361
|
-
'::1',
|
|
362
|
-
'thumbgate-production.up.railway.app',
|
|
363
|
-
])) {
|
|
364
|
-
addAudienceReason(reasons, 'internal_host');
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
if (includesAnyToken([userAgent, ...identityValues], [
|
|
368
|
-
'codex',
|
|
369
|
-
'github-actions',
|
|
370
|
-
'node-fetch',
|
|
371
|
-
'thumbgate-analytics',
|
|
372
|
-
'thumbgate-verification',
|
|
373
|
-
])) {
|
|
374
|
-
addAudienceReason(reasons, 'internal_operator_or_ci');
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
if (
|
|
378
|
-
(entry.clientType || entry.client) === 'unknown' &&
|
|
379
|
-
!pickFirstText(entry.visitorId, entry.sessionId, entry.acquisitionId, entry.installId, entry.traceId) &&
|
|
380
|
-
!userAgent &&
|
|
381
|
-
includesAnyToken([entry.source, entry.utmSource], ['direct', 'website'])
|
|
382
|
-
) {
|
|
383
|
-
addAudienceReason(reasons, 'unknown_direct_no_identity');
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
const audience = reasons.includes('test_identity') || reasons.includes('test_or_audit_attribution')
|
|
387
|
-
? 'test'
|
|
388
|
-
: (reasons.includes('bot_or_automation_user_agent')
|
|
389
|
-
? 'bot'
|
|
390
|
-
: (
|
|
391
|
-
reasons.includes('internal_host') ||
|
|
392
|
-
reasons.includes('internal_operator_or_ci') ||
|
|
393
|
-
reasons.includes('unknown_direct_no_identity')
|
|
394
|
-
? 'internal'
|
|
395
|
-
: 'external'
|
|
396
|
-
));
|
|
397
|
-
|
|
398
|
-
return {
|
|
399
|
-
audience,
|
|
400
|
-
reasons,
|
|
401
|
-
isExternal: audience === 'external',
|
|
402
|
-
isInternal: audience === 'internal',
|
|
403
|
-
isTest: audience === 'test',
|
|
404
|
-
isBot: audience === 'bot',
|
|
405
|
-
};
|
|
406
|
-
}
|
|
407
|
-
|
|
408
258
|
function sanitizeTelemetryPayload(payload = {}, headers = {}) {
|
|
409
259
|
const raw = payload && typeof payload === 'object' ? payload : {};
|
|
410
260
|
const clientType = inferClientType(raw);
|
|
@@ -463,12 +313,6 @@ function sanitizeTelemetryPayload(payload = {}, headers = {}) {
|
|
|
463
313
|
pipelineStatus: pickFirstText(raw.pipelineStatus, raw.workflowSprintStatus, raw.status),
|
|
464
314
|
reasonCode,
|
|
465
315
|
reasonDetail: pickFirstText(raw.reasonDetail, raw.reasonText, raw.otherReason, raw.notes),
|
|
466
|
-
integration: pickFirstText(raw.integration, raw.actionIntegration),
|
|
467
|
-
actionOperation: pickFirstText(raw.actionOperation, raw.operationId),
|
|
468
|
-
endpoint: pickFirstText(raw.endpoint, raw.apiPath),
|
|
469
|
-
decisionMode: pickFirstText(raw.decisionMode, raw.executionMode),
|
|
470
|
-
actionStatus: pickFirstText(raw.actionStatus, raw.status),
|
|
471
|
-
accepted: raw.accepted === undefined || raw.accepted === null ? null : Boolean(raw.accepted),
|
|
472
316
|
pricingInterest: pickFirstText(raw.pricingInterest, raw.interestLevel),
|
|
473
317
|
seoQuery: pickFirstText(raw.seoQuery, raw.query),
|
|
474
318
|
seoSurface: pickFirstText(raw.seoSurface, raw.searchSurface, raw.surface),
|
|
@@ -487,22 +331,11 @@ function sanitizeTelemetryPayload(payload = {}, headers = {}) {
|
|
|
487
331
|
httpStatus: normalizeInteger(raw.httpStatus),
|
|
488
332
|
userAgent: pickFirstText(raw.userAgent, headers['user-agent']),
|
|
489
333
|
isBot: pickFirstText(raw.isBot),
|
|
490
|
-
interstitialSampled: pickFirstText(raw.interstitialSampled),
|
|
491
|
-
interstitialSampleRate: normalizeRate(raw.interstitialSampleRate),
|
|
492
334
|
attributionTagged: Boolean(
|
|
493
335
|
pickFirstText(raw.utmSource, raw.utmMedium, raw.utmCampaign, raw.utmContent, raw.utmTerm)
|
|
494
336
|
),
|
|
495
337
|
};
|
|
496
338
|
|
|
497
|
-
const audience = classifyTelemetryAudience(entry, raw);
|
|
498
|
-
entry.audience = audience.audience;
|
|
499
|
-
entry.trafficAudience = audience.audience;
|
|
500
|
-
entry.audienceReasons = audience.reasons;
|
|
501
|
-
entry.isExternal = audience.isExternal;
|
|
502
|
-
entry.isInternal = audience.isInternal;
|
|
503
|
-
entry.isTest = audience.isTest;
|
|
504
|
-
entry.isBotTraffic = audience.isBot;
|
|
505
|
-
|
|
506
339
|
return entry;
|
|
507
340
|
}
|
|
508
341
|
|
|
@@ -593,162 +426,9 @@ function summarizeRecentEvents(events) {
|
|
|
593
426
|
community: entry.community || null,
|
|
594
427
|
offerCode: entry.offerCode || null,
|
|
595
428
|
campaignVariant: entry.campaignVariant || null,
|
|
596
|
-
audience: entry.audience || 'external',
|
|
597
|
-
audienceReasons: entry.audienceReasons || [],
|
|
598
429
|
}));
|
|
599
430
|
}
|
|
600
431
|
|
|
601
|
-
function summarizeExternalTrafficQuality(events) {
|
|
602
|
-
const byAudience = {};
|
|
603
|
-
const byExclusionReason = {};
|
|
604
|
-
const externalVisitors = new Set();
|
|
605
|
-
const externalSessions = new Set();
|
|
606
|
-
const externalCheckoutStarters = new Set();
|
|
607
|
-
const pageViewsBySource = {};
|
|
608
|
-
const pageViewsByPath = {};
|
|
609
|
-
const pageViewsByTrafficChannel = {};
|
|
610
|
-
const checkoutStartsBySource = {};
|
|
611
|
-
const checkoutStartsByTrafficChannel = {};
|
|
612
|
-
const buyerLossReasons = {};
|
|
613
|
-
const visitorPaths = new Map();
|
|
614
|
-
let externalEvents = 0;
|
|
615
|
-
let excludedEvents = 0;
|
|
616
|
-
let externalWebEvents = 0;
|
|
617
|
-
let externalPageViews = 0;
|
|
618
|
-
let externalCtaClicks = 0;
|
|
619
|
-
let externalCheckoutStarts = 0;
|
|
620
|
-
let externalBuyerLossSignals = 0;
|
|
621
|
-
|
|
622
|
-
for (const entry of events) {
|
|
623
|
-
const audience = entry.audience || 'external';
|
|
624
|
-
incrementCounter(byAudience, audience);
|
|
625
|
-
if (audience !== 'external') {
|
|
626
|
-
excludedEvents += 1;
|
|
627
|
-
const reasons = Array.isArray(entry.audienceReasons) && entry.audienceReasons.length > 0
|
|
628
|
-
? entry.audienceReasons
|
|
629
|
-
: [`${audience}_traffic`];
|
|
630
|
-
for (const reason of reasons) incrementCounter(byExclusionReason, reason);
|
|
631
|
-
continue;
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
externalEvents += 1;
|
|
635
|
-
if ((entry.clientType || entry.client) !== 'web') continue;
|
|
636
|
-
|
|
637
|
-
externalWebEvents += 1;
|
|
638
|
-
const visitorKey = pickFirstText(entry.visitorId, entry.installId, entry.sessionId);
|
|
639
|
-
if (visitorKey) externalVisitors.add(visitorKey);
|
|
640
|
-
if (entry.sessionId) externalSessions.add(entry.sessionId);
|
|
641
|
-
|
|
642
|
-
const eventType = entry.eventType || entry.event;
|
|
643
|
-
if (visitorKey) {
|
|
644
|
-
const pathRow = visitorPaths.get(visitorKey) || {
|
|
645
|
-
visitorKey,
|
|
646
|
-
firstSeenAt: entry.receivedAt || null,
|
|
647
|
-
lastSeenAt: entry.receivedAt || null,
|
|
648
|
-
source: entry.source || null,
|
|
649
|
-
trafficChannel: entry.trafficChannel || null,
|
|
650
|
-
events: [],
|
|
651
|
-
pages: [],
|
|
652
|
-
checkoutStarts: 0,
|
|
653
|
-
};
|
|
654
|
-
if (!pathRow.firstSeenAt || String(entry.receivedAt || '') < pathRow.firstSeenAt) {
|
|
655
|
-
pathRow.firstSeenAt = entry.receivedAt || null;
|
|
656
|
-
}
|
|
657
|
-
if (!pathRow.lastSeenAt || String(entry.receivedAt || '') > pathRow.lastSeenAt) {
|
|
658
|
-
pathRow.lastSeenAt = entry.receivedAt || null;
|
|
659
|
-
}
|
|
660
|
-
pathRow.events.push({
|
|
661
|
-
at: entry.receivedAt || null,
|
|
662
|
-
eventType,
|
|
663
|
-
page: entry.page || entry.landingPath || null,
|
|
664
|
-
ctaId: entry.ctaId || null,
|
|
665
|
-
});
|
|
666
|
-
if ((entry.page || entry.landingPath) && !pathRow.pages.includes(entry.page || entry.landingPath)) {
|
|
667
|
-
pathRow.pages.push(entry.page || entry.landingPath);
|
|
668
|
-
}
|
|
669
|
-
if (eventType === 'checkout_start' || eventType === 'checkout_bootstrap') {
|
|
670
|
-
pathRow.checkoutStarts += 1;
|
|
671
|
-
}
|
|
672
|
-
visitorPaths.set(visitorKey, pathRow);
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
if (eventType === 'landing_page_view') {
|
|
676
|
-
externalPageViews += 1;
|
|
677
|
-
incrementCounter(pageViewsBySource, entry.source);
|
|
678
|
-
incrementCounter(pageViewsByPath, entry.page);
|
|
679
|
-
incrementCounter(pageViewsByTrafficChannel, entry.trafficChannel);
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
if (isMarketingClickEvent(eventType)) {
|
|
683
|
-
externalCtaClicks += 1;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
if (eventType === 'checkout_start' || eventType === 'checkout_bootstrap') {
|
|
687
|
-
externalCheckoutStarts += 1;
|
|
688
|
-
incrementCounter(checkoutStartsBySource, entry.source);
|
|
689
|
-
incrementCounter(checkoutStartsByTrafficChannel, entry.trafficChannel);
|
|
690
|
-
const starterKey = pickFirstText(
|
|
691
|
-
entry.acquisitionId,
|
|
692
|
-
entry.visitorId,
|
|
693
|
-
entry.sessionId,
|
|
694
|
-
entry.installId,
|
|
695
|
-
entry.traceId
|
|
696
|
-
);
|
|
697
|
-
if (starterKey) externalCheckoutStarters.add(starterKey);
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
if (eventType === 'checkout_cancelled' || eventType === 'checkout_abandoned' || eventType === 'reason_not_buying') {
|
|
701
|
-
externalBuyerLossSignals += 1;
|
|
702
|
-
incrementCounter(buyerLossReasons, entry.reasonCode);
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
const topPath = getTopCounterEntry(pageViewsByPath);
|
|
707
|
-
const topSource = getTopCounterEntry(pageViewsBySource);
|
|
708
|
-
const topTrafficChannel = getTopCounterEntry(pageViewsByTrafficChannel);
|
|
709
|
-
const topExclusionReason = getTopCounterEntry(byExclusionReason);
|
|
710
|
-
|
|
711
|
-
return {
|
|
712
|
-
rawEvents: events.length,
|
|
713
|
-
externalEvents,
|
|
714
|
-
excludedEvents,
|
|
715
|
-
internalEvents: byAudience.internal || 0,
|
|
716
|
-
testEvents: byAudience.test || 0,
|
|
717
|
-
botEvents: byAudience.bot || 0,
|
|
718
|
-
exclusionRate: safeRate(excludedEvents, events.length),
|
|
719
|
-
byAudience,
|
|
720
|
-
byExclusionReason,
|
|
721
|
-
topExclusionReason: topExclusionReason ? { key: topExclusionReason[0], count: topExclusionReason[1] } : null,
|
|
722
|
-
external: {
|
|
723
|
-
totalEvents: externalWebEvents,
|
|
724
|
-
uniqueVisitors: externalVisitors.size,
|
|
725
|
-
uniqueSessions: externalSessions.size,
|
|
726
|
-
uniqueCheckoutStarters: externalCheckoutStarters.size,
|
|
727
|
-
pageViews: externalPageViews,
|
|
728
|
-
ctaClicks: externalCtaClicks,
|
|
729
|
-
checkoutStarts: externalCheckoutStarts,
|
|
730
|
-
buyerLossSignals: externalBuyerLossSignals,
|
|
731
|
-
pageViewToCheckoutRate: safeRate(externalCheckoutStarts, externalPageViews),
|
|
732
|
-
visitorToCheckoutRate: safeRate(externalCheckoutStarts, externalVisitors.size),
|
|
733
|
-
bySource: pageViewsBySource,
|
|
734
|
-
byPath: pageViewsByPath,
|
|
735
|
-
byTrafficChannel: pageViewsByTrafficChannel,
|
|
736
|
-
checkoutStartsBySource,
|
|
737
|
-
checkoutStartsByTrafficChannel,
|
|
738
|
-
buyerLossReasons,
|
|
739
|
-
topSource: topSource ? { key: topSource[0], count: topSource[1] } : null,
|
|
740
|
-
topPath: topPath ? { key: topPath[0], count: topPath[1] } : null,
|
|
741
|
-
topTrafficChannel: topTrafficChannel ? { key: topTrafficChannel[0], count: topTrafficChannel[1] } : null,
|
|
742
|
-
visitorPaths: Array.from(visitorPaths.values())
|
|
743
|
-
.sort((a, b) => String(a.firstSeenAt || '').localeCompare(String(b.firstSeenAt || '')))
|
|
744
|
-
.slice(0, 50),
|
|
745
|
-
},
|
|
746
|
-
verdict: externalEvents > 0
|
|
747
|
-
? (excludedEvents > externalEvents ? 'polluted_but_usable' : 'usable')
|
|
748
|
-
: (events.length > 0 ? 'polluted_no_external_signal' : 'missing'),
|
|
749
|
-
};
|
|
750
|
-
}
|
|
751
|
-
|
|
752
432
|
function getTelemetrySummary(feedbackDir, options = {}) {
|
|
753
433
|
const analyticsWindow = resolveAnalyticsWindow(options);
|
|
754
434
|
const telemetryLoadOptions = analyticsWindow.bounded
|
|
@@ -759,7 +439,6 @@ function getTelemetrySummary(feedbackDir, options = {}) {
|
|
|
759
439
|
analyticsWindow,
|
|
760
440
|
(entry) => entry && (entry.receivedAt || entry.timestamp)
|
|
761
441
|
);
|
|
762
|
-
const trafficQuality = summarizeExternalTrafficQuality(events);
|
|
763
442
|
const byClientType = {};
|
|
764
443
|
const byEventType = {};
|
|
765
444
|
const webVisitors = new Set();
|
|
@@ -847,12 +526,6 @@ function getTelemetrySummary(feedbackDir, options = {}) {
|
|
|
847
526
|
let exitEngagementMsCount = 0;
|
|
848
527
|
let exitScrollPercentTotal = 0;
|
|
849
528
|
let exitScrollPercentCount = 0;
|
|
850
|
-
let chatgptActionCalls = 0;
|
|
851
|
-
let chatgptActionAccepted = 0;
|
|
852
|
-
const chatgptActionsByOperation = {};
|
|
853
|
-
const chatgptActionsByEndpoint = {};
|
|
854
|
-
const chatgptActionsByStatus = {};
|
|
855
|
-
const chatgptActionsByDecisionMode = {};
|
|
856
529
|
|
|
857
530
|
for (const entry of events) {
|
|
858
531
|
incrementCounter(byClientType, entry.clientType || entry.client || 'unknown');
|
|
@@ -1041,18 +714,6 @@ function getTelemetrySummary(feedbackDir, options = {}) {
|
|
|
1041
714
|
}
|
|
1042
715
|
}
|
|
1043
716
|
|
|
1044
|
-
if (
|
|
1045
|
-
String(entry.eventType || entry.event || '').startsWith('chatgpt_action_') ||
|
|
1046
|
-
entry.integration === 'chatgpt_gpt'
|
|
1047
|
-
) {
|
|
1048
|
-
chatgptActionCalls += 1;
|
|
1049
|
-
if (entry.accepted === true) chatgptActionAccepted += 1;
|
|
1050
|
-
incrementCounter(chatgptActionsByOperation, entry.actionOperation);
|
|
1051
|
-
incrementCounter(chatgptActionsByEndpoint, entry.endpoint);
|
|
1052
|
-
incrementCounter(chatgptActionsByStatus, entry.actionStatus);
|
|
1053
|
-
incrementCounter(chatgptActionsByDecisionMode, entry.decisionMode);
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
717
|
if ((entry.clientType || entry.client) === 'cli') {
|
|
1057
718
|
if (entry.installId) cliInstalls.add(entry.installId);
|
|
1058
719
|
incrementCounter(cliByPlatform, entry.platform);
|
|
@@ -1103,14 +764,12 @@ function getTelemetrySummary(feedbackDir, options = {}) {
|
|
|
1103
764
|
window: serializeAnalyticsWindow(analyticsWindow),
|
|
1104
765
|
totalEvents: events.length,
|
|
1105
766
|
latestSeenAt,
|
|
1106
|
-
trafficQuality,
|
|
1107
767
|
byClientType,
|
|
1108
768
|
byEventType,
|
|
1109
769
|
conversionFunnel: {
|
|
1110
770
|
landingViews: pageViews,
|
|
1111
771
|
installCopies,
|
|
1112
772
|
gptOpens,
|
|
1113
|
-
gptActionCalls: chatgptActionCalls,
|
|
1114
773
|
checkoutStarts,
|
|
1115
774
|
checkoutInterstitialViews,
|
|
1116
775
|
checkoutInterstitialClicks,
|
|
@@ -1118,7 +777,6 @@ function getTelemetrySummary(feedbackDir, options = {}) {
|
|
|
1118
777
|
proConversions,
|
|
1119
778
|
landingToInstallCopyRate: safeRate(installCopies, pageViews),
|
|
1120
779
|
landingToGptOpenRate: safeRate(gptOpens, pageViews),
|
|
1121
|
-
gptOpenToActionRate: safeRate(chatgptActionCalls, gptOpens),
|
|
1122
780
|
landingToCheckoutRate: safeRate(checkoutStarts, pageViews),
|
|
1123
781
|
checkoutInterstitialClickRate: safeRate(checkoutInterstitialClicks, checkoutInterstitialViews),
|
|
1124
782
|
checkoutInterstitialProConfirmRate: safeRate(checkoutInterstitialProConfirms, checkoutInterstitialViews),
|
|
@@ -1177,17 +835,6 @@ function getTelemetrySummary(feedbackDir, options = {}) {
|
|
|
1177
835
|
byPlatform: cliByPlatform,
|
|
1178
836
|
byVersion: cliByVersion,
|
|
1179
837
|
},
|
|
1180
|
-
chatgpt: {
|
|
1181
|
-
gptOpens,
|
|
1182
|
-
actionCalls: chatgptActionCalls,
|
|
1183
|
-
acceptedActionCalls: chatgptActionAccepted,
|
|
1184
|
-
openToActionRate: safeRate(chatgptActionCalls, gptOpens),
|
|
1185
|
-
acceptedActionRate: safeRate(chatgptActionAccepted, chatgptActionCalls),
|
|
1186
|
-
byOperation: chatgptActionsByOperation,
|
|
1187
|
-
byEndpoint: chatgptActionsByEndpoint,
|
|
1188
|
-
byStatus: chatgptActionsByStatus,
|
|
1189
|
-
byDecisionMode: chatgptActionsByDecisionMode,
|
|
1190
|
-
},
|
|
1191
838
|
marketing: {
|
|
1192
839
|
pageViewsBySource,
|
|
1193
840
|
pageViewsByCampaign,
|
|
@@ -1279,12 +926,9 @@ function getTelemetryAnalytics(feedbackDir, options = {}) {
|
|
|
1279
926
|
window: summary.window,
|
|
1280
927
|
totalEvents: summary.totalEvents,
|
|
1281
928
|
latestSeenAt: summary.latestSeenAt,
|
|
1282
|
-
trafficQuality: summary.trafficQuality,
|
|
1283
|
-
qualified: summary.trafficQuality.external,
|
|
1284
929
|
byClientType: summary.byClientType,
|
|
1285
930
|
byEventType: summary.byEventType,
|
|
1286
931
|
conversionFunnel: summary.conversionFunnel,
|
|
1287
|
-
chatgpt: summary.chatgpt,
|
|
1288
932
|
visitors: {
|
|
1289
933
|
totalEvents: summary.web.totalEvents,
|
|
1290
934
|
uniqueVisitors: summary.web.uniqueVisitors,
|
|
@@ -1460,7 +1104,6 @@ const appendTelemetryPing = appendTelemetryEvent;
|
|
|
1460
1104
|
module.exports = {
|
|
1461
1105
|
TELEMETRY_FILE_NAME,
|
|
1462
1106
|
sanitizeTelemetryPayload,
|
|
1463
|
-
classifyTelemetryAudience,
|
|
1464
1107
|
appendTelemetryPing,
|
|
1465
1108
|
appendTelemetryEvent,
|
|
1466
1109
|
getTelemetrySourceDiagnostics,
|
|
@@ -302,44 +302,24 @@ function getCalibration(model) {
|
|
|
302
302
|
// ---------------------------------------------------------------------------
|
|
303
303
|
|
|
304
304
|
/**
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
* lower temperatures sharpen the posterior, higher temperatures flatten it.
|
|
305
|
+
* Draw one sample from the Beta posterior for each category via the
|
|
306
|
+
* Marsaglia-Tsang (2000) gamma ratio method. No external library needed.
|
|
308
307
|
*
|
|
309
|
-
*
|
|
310
|
-
* @param {number} temperature - Scaling factor (default 1.0)
|
|
311
|
-
* @returns {{ alpha: number, beta: number }}
|
|
312
|
-
*/
|
|
313
|
-
function getTemperatureScaledPosteriorParams(params, temperature = 1.0) {
|
|
314
|
-
const T = Math.max(0.01, Number(temperature) || 1.0);
|
|
315
|
-
const invT = 1.0 / T;
|
|
316
|
-
return {
|
|
317
|
-
alpha: Math.max(params.alpha * invT, 0.01),
|
|
318
|
-
beta: Math.max(params.beta * invT, 0.01),
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* Draw one sample from the Beta posterior for each category.
|
|
324
|
-
* Supports temperature scaling to adjust exploitation vs exploration.
|
|
308
|
+
* betaSample(alpha, beta) = gammaSample(alpha) / (gammaSample(alpha) + gammaSample(beta))
|
|
325
309
|
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
* T < 1.0 — Sharper distribution, favors high-reliability categories (exploit).
|
|
329
|
-
* T > 1.0 — Flatter distribution, increases uncertainty and exploration.
|
|
330
|
-
*
|
|
331
|
-
* Implementation: Scales alpha and beta by 1/T.
|
|
310
|
+
* This is the JS equivalent of Python's random.betavariate(alpha, beta).
|
|
311
|
+
* Used for Thompson Sampling action selection (explore via uncertainty).
|
|
332
312
|
*
|
|
333
313
|
* @param {Object} model - Model object containing categories
|
|
334
|
-
* @param {number} temperature - Scaling factor (default 1.0)
|
|
335
314
|
* @returns {Object} Map of category → float sample in [0, 1]
|
|
336
315
|
*/
|
|
337
|
-
function samplePosteriors(model
|
|
316
|
+
function samplePosteriors(model) {
|
|
338
317
|
const samples = {};
|
|
339
|
-
|
|
340
318
|
for (const [cat, params] of Object.entries(model.categories || {})) {
|
|
341
|
-
|
|
342
|
-
|
|
319
|
+
samples[cat] = betaSample(
|
|
320
|
+
Math.max(params.alpha, 0.01),
|
|
321
|
+
Math.max(params.beta, 0.01),
|
|
322
|
+
);
|
|
343
323
|
}
|
|
344
324
|
return samples;
|
|
345
325
|
}
|
|
@@ -471,7 +451,6 @@ module.exports = {
|
|
|
471
451
|
getReliability,
|
|
472
452
|
isCalibrated,
|
|
473
453
|
getCalibration,
|
|
474
|
-
getTemperatureScaledPosteriorParams,
|
|
475
454
|
samplePosteriors,
|
|
476
455
|
argmaxPosteriors,
|
|
477
456
|
pickBestCategory,
|