@ak--47/dungeon-master 1.5.3 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/analyze-soup/SKILL.md +21 -12
- package/.claude/skills/create-dungeon/SKILL.md +65 -5
- package/.claude/skills/create-project/SKILL.md +96 -0
- package/.claude/skills/create-project/provision.mjs +336 -0
- package/.claude/skills/verify-dungeon/SKILL.md +56 -25
- package/.claude/skills/verify-dungeon/references/counting-semantics.md +26 -6
- package/.claude/skills/verify-dungeon/references/report-format.md +14 -8
- package/.claude/skills/write-hooks/SKILL.md +156 -18
- package/CHANGELOG.md +211 -0
- package/HOOKS.md +566 -112
- package/README.md +28 -9
- package/docs/guides/1.3.0-upgrade-guide.md +262 -0
- package/docs/guides/1.3.1-upgrade-guide.md +328 -0
- package/docs/guides/1.4.0-upgrade-guide.md +130 -0
- package/docs/guides/1.4.1-upgrade-guide.md +125 -0
- package/docs/guides/1.4.3-upgrade-guide.md +200 -0
- package/docs/guides/1.4.4-upgrade-guide.md +95 -0
- package/docs/guides/1.5.0-upgrade-guide.md +426 -0
- package/docs/guides/1.5.1-upgrade-guide.md +178 -0
- package/docs/guides/1.5.3-upgrade-guide.md +117 -0
- package/docs/guides/1.6.0-upgrade-guide.md +204 -0
- package/dungeons/technical/stories-verify.js +152 -0
- package/dungeons/vertical/README.md +154 -0
- package/dungeons/vertical/ai-platform/ai-platform.js +1424 -0
- package/dungeons/vertical/ai-platform/ai-platform.sql +184 -0
- package/dungeons/vertical/ai-platform/ai-platform.verify.mjs +71 -0
- package/dungeons/vertical/community/community.js +1589 -0
- package/dungeons/vertical/community/community.sql +216 -0
- package/dungeons/vertical/community/community.verify.mjs +72 -0
- package/dungeons/vertical/crypto/crypto.js +1483 -0
- package/dungeons/vertical/crypto/crypto.sql +194 -0
- package/dungeons/vertical/crypto/crypto.verify.mjs +78 -0
- package/dungeons/vertical/dating/dating.js +1540 -0
- package/dungeons/vertical/dating/dating.sql +288 -0
- package/dungeons/vertical/dating/dating.verify.mjs +72 -0
- package/dungeons/vertical/devtools/devtools.js +1524 -0
- package/dungeons/vertical/devtools/devtools.sql +161 -0
- package/dungeons/vertical/devtools/devtools.verify.mjs +73 -0
- package/dungeons/vertical/ecommerce/ecommerce.js +1480 -0
- package/dungeons/vertical/ecommerce/ecommerce.sql +218 -0
- package/dungeons/vertical/ecommerce/ecommerce.verify.mjs +70 -0
- package/dungeons/vertical/education/education.js +1763 -0
- package/dungeons/vertical/education/education.sql +243 -0
- package/dungeons/vertical/education/education.verify.mjs +75 -0
- package/dungeons/vertical/fintech/fintech.js +1410 -0
- package/dungeons/vertical/fintech/fintech.sql +175 -0
- package/dungeons/vertical/fintech/fintech.verify.mjs +70 -0
- package/dungeons/vertical/fitness/fitness.js +1495 -0
- package/dungeons/vertical/fitness/fitness.sql +206 -0
- package/dungeons/vertical/fitness/fitness.verify.mjs +71 -0
- package/dungeons/vertical/food-delivery/food-delivery.js +1641 -0
- package/dungeons/vertical/food-delivery/food-delivery.sql +183 -0
- package/dungeons/vertical/food-delivery/food-delivery.verify.mjs +75 -0
- package/dungeons/vertical/gaming/gaming.js +1741 -0
- package/dungeons/vertical/gaming/gaming.sql +249 -0
- package/dungeons/vertical/gaming/gaming.verify.mjs +70 -0
- package/dungeons/vertical/healthcare/healthcare.js +1488 -0
- package/dungeons/vertical/healthcare/healthcare.sql +193 -0
- package/dungeons/vertical/healthcare/healthcare.verify.mjs +72 -0
- package/dungeons/vertical/insurance-application/insurance-application.js +1852 -0
- package/dungeons/vertical/insurance-application/insurance-application.sql +174 -0
- package/dungeons/vertical/insurance-application/insurance-application.verify.mjs +74 -0
- package/dungeons/vertical/logistics/logistics.js +1541 -0
- package/dungeons/vertical/logistics/logistics.sql +205 -0
- package/dungeons/vertical/logistics/logistics.verify.mjs +73 -0
- package/dungeons/vertical/marketplace/marketplace.js +1438 -0
- package/dungeons/vertical/marketplace/marketplace.sql +148 -0
- package/dungeons/vertical/marketplace/marketplace.verify.mjs +74 -0
- package/dungeons/vertical/{media.js → media/media.js} +557 -58
- package/dungeons/vertical/media/media.sql +173 -0
- package/dungeons/vertical/media/media.verify.mjs +74 -0
- package/dungeons/vertical/real-estate/real-estate.js +1629 -0
- package/dungeons/vertical/real-estate/real-estate.sql +167 -0
- package/dungeons/vertical/real-estate/real-estate.verify.mjs +75 -0
- package/dungeons/vertical/sass/sass.js +1733 -0
- package/dungeons/vertical/sass/sass.sql +173 -0
- package/dungeons/vertical/sass/sass.verify.mjs +74 -0
- package/dungeons/vertical/social/social.js +1456 -0
- package/dungeons/vertical/social/social.sql +160 -0
- package/dungeons/vertical/social/social.verify.mjs +76 -0
- package/dungeons/vertical/streaming/streaming.js +677 -0
- package/dungeons/vertical/streaming/streaming.sql +101 -0
- package/dungeons/vertical/streaming/streaming.verify.mjs +75 -0
- package/dungeons/vertical/support-desk/support-desk.js +741 -0
- package/dungeons/vertical/support-desk/support-desk.sql +137 -0
- package/dungeons/vertical/support-desk/support-desk.verify.mjs +75 -0
- package/dungeons/vertical/travel/travel.js +1603 -0
- package/dungeons/vertical/travel/travel.sql +236 -0
- package/dungeons/vertical/travel/travel.verify.mjs +72 -0
- package/index.js +2 -2
- package/lib/core/config-validator.js +37 -2
- package/lib/core/context.js +4 -2
- package/lib/core/dungeon-loader.js +20 -15
- package/lib/core/dungeon-to-json.js +22 -4
- package/lib/core/storage.js +3 -3
- package/lib/generators/funnels.js +36 -9
- package/lib/hook-helpers/cohort.js +53 -0
- package/lib/hook-helpers/index.js +8 -0
- package/lib/hook-helpers/mutate.js +1 -1
- package/lib/hook-helpers/shape.js +319 -0
- package/lib/hook-patterns/aggregate-per-user-by-bin.js +13 -5
- package/lib/hook-patterns/attributed-by-source.js +60 -58
- package/lib/hook-patterns/frequency-by-frequency.js +14 -4
- package/lib/hook-patterns/funnel-frequency-breakdown.js +24 -12
- package/lib/hook-patterns/index.js +1 -1
- package/lib/hook-patterns/time-to-convert-by-segment.js +75 -13
- package/lib/orchestrators/user-loop.js +41 -0
- package/lib/templates/story-spec.schema.json +175 -0
- package/lib/utils/json-evaluator.js +1 -1
- package/lib/utils/utils.js +5 -4
- package/lib/verify/coerce.js +186 -0
- package/lib/verify/counting.js +270 -32
- package/lib/verify/emulate-breakdown.js +1285 -203
- package/lib/verify/first-time.js +89 -0
- package/lib/verify/flows.js +679 -0
- package/lib/verify/formula.js +259 -0
- package/lib/verify/funnel-engine.js +697 -113
- package/lib/verify/index.js +32 -4
- package/lib/verify/sessionize.js +275 -0
- package/lib/verify/story-runner.js +483 -0
- package/lib/verify/verify-dungeon.js +59 -37
- package/package.json +3 -2
- package/scripts/extract-dungeon-schema.mjs +2 -2
- package/scripts/run-many.mjs +2 -2
- package/scripts/verify-runner.mjs +2 -2
- package/scripts/verify-stories.mjs +244 -0
- package/types.d.ts +296 -30
- package/dungeons/vertical/ai-platform.js +0 -818
- package/dungeons/vertical/community.js +0 -966
- package/dungeons/vertical/crypto.js +0 -895
- package/dungeons/vertical/dating.js +0 -830
- package/dungeons/vertical/devtools.js +0 -1050
- package/dungeons/vertical/ecommerce.js +0 -811
- package/dungeons/vertical/education.js +0 -964
- package/dungeons/vertical/fintech.js +0 -946
- package/dungeons/vertical/fitness.js +0 -935
- package/dungeons/vertical/food-delivery.js +0 -934
- package/dungeons/vertical/gaming.js +0 -1120
- package/dungeons/vertical/healthcare.js +0 -945
- package/dungeons/vertical/insurance-application.js +0 -1039
- package/dungeons/vertical/logistics.js +0 -986
- package/dungeons/vertical/marketplace.js +0 -1019
- package/dungeons/vertical/real-estate.js +0 -872
- package/dungeons/vertical/sass.js +0 -995
- package/dungeons/vertical/social.js +0 -904
- package/dungeons/vertical/travel.js +0 -930
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
-- ============================================================
|
|
2
|
+
-- ai-platform.js — v1.6 human-inspection queries (DuckDB)
|
|
3
|
+
--
|
|
4
|
+
-- Every query is keyed to a story id in ai-platform.js's `stories` export;
|
|
5
|
+
-- the machine-checked verdicts come from:
|
|
6
|
+
-- node scripts/verify-stories.mjs dungeons/vertical/ai-platform/ai-platform.js --data-prefix verify-ai-platform
|
|
7
|
+
-- Generate first:
|
|
8
|
+
-- node scripts/verify-runner.mjs dungeons/vertical/ai-platform/ai-platform.js verify-ai-platform
|
|
9
|
+
-- Run this file:
|
|
10
|
+
-- duckdb -c ".read dungeons/vertical/ai-platform/ai-platform.sql"
|
|
11
|
+
-- ============================================================
|
|
12
|
+
|
|
13
|
+
-- ── identity-resolution prelude ─────────────────────────────
|
|
14
|
+
-- avgDevicePerUser: 2 + organization created is both isAuthEvent and
|
|
15
|
+
-- isFirstEvent, so born users auth on their first event; the device-pool
|
|
16
|
+
-- resolve is belt-and-braces for any device-only edge.
|
|
17
|
+
CREATE OR REPLACE VIEW users AS
|
|
18
|
+
SELECT * FROM read_json_auto('data/verify-ai-platform-USERS*.json', sample_size=-1, union_by_name=true);
|
|
19
|
+
|
|
20
|
+
CREATE OR REPLACE VIEW device_map AS
|
|
21
|
+
-- profiles store the device pool under the legacy "anonymousIds" key
|
|
22
|
+
SELECT unnest("anonymousIds") AS device_id, distinct_id FROM users;
|
|
23
|
+
|
|
24
|
+
CREATE OR REPLACE VIEW ev AS
|
|
25
|
+
-- ::VARCHAR casts — user_id sniffs as UUID, device_id as VARCHAR; DuckDB
|
|
26
|
+
-- refuses to coalesce mixed types
|
|
27
|
+
SELECT coalesce(m.distinct_id::VARCHAR, e.user_id::VARCHAR, e.device_id::VARCHAR) AS uid,
|
|
28
|
+
e.time::TIMESTAMP AS t,
|
|
29
|
+
e.*
|
|
30
|
+
FROM read_json_auto('data/verify-ai-platform-EVENTS*.json', sample_size=-1, union_by_name=true) e
|
|
31
|
+
LEFT JOIN device_map m ON e.device_id = m.device_id;
|
|
32
|
+
|
|
33
|
+
-- H3/H7 four-cell cohort: agentic = 3+ tool use calls AND 3+ multi-turn
|
|
34
|
+
-- api calls; batch = any batch job submitted. Filters (H4/H8) run before
|
|
35
|
+
-- the cohort mutators, so output-side classification reproduces the
|
|
36
|
+
-- hook's cohorts 1:1.
|
|
37
|
+
CREATE OR REPLACE VIEW cells AS
|
|
38
|
+
WITH coh AS (SELECT e.uid,
|
|
39
|
+
(count(*) FILTER (WHERE e.event = 'tool use call') >= 3
|
|
40
|
+
AND count(*) FILTER (WHERE e.event = 'api call' AND e.multi_turn = true) >= 3) AS agentic,
|
|
41
|
+
bool_or(e.event = 'batch job submitted') AS batch
|
|
42
|
+
FROM ev e GROUP BY 1)
|
|
43
|
+
SELECT uid, CASE WHEN agentic AND batch THEN 'both' WHEN agentic THEN 'agentic'
|
|
44
|
+
WHEN batch THEN 'batch' ELSE 'neither' END AS cell FROM coh;
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
-- ── H1-prompt-caching ───────────────────────────────────────
|
|
48
|
+
-- ~25% of users flip cache_enabled=true ~30% into their stream; cached
|
|
49
|
+
-- api calls carry cost_usd × 0.3.
|
|
50
|
+
SELECT CASE WHEN cache_enabled = true THEN 'cached' ELSE 'uncached' END AS grp,
|
|
51
|
+
count(*) AS calls, round(avg(cost_usd), 4) AS avg_cost
|
|
52
|
+
FROM ev WHERE event = 'api call' GROUP BY 1 ORDER BY 1;
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
-- ── H2-model-migration ──────────────────────────────────────
|
|
56
|
+
-- opus-4-7 releases day 60 (2026-03-02): 35% of post-release
|
|
57
|
+
-- Build/Enterprise api calls migrate at 1.5x tokens. Purity is exact —
|
|
58
|
+
-- the hook scrubs engine-sampled opus-4-7 back to the pre-release mix.
|
|
59
|
+
SELECT
|
|
60
|
+
count(*) FILTER (WHERE model = 'opus-4-7' AND (t < TIMESTAMP '2026-03-02' OR api_tier = 'Free' OR event <> 'api call')) AS impure_rows,
|
|
61
|
+
count(*) FILTER (WHERE model = 'opus-4-7') AS opus_calls,
|
|
62
|
+
round(count(*) FILTER (WHERE model = 'opus-4-7' AND event = 'api call' AND api_tier IN ('Build', 'Enterprise') AND t >= TIMESTAMP '2026-03-02')::DOUBLE
|
|
63
|
+
/ nullif(count(*) FILTER (WHERE event = 'api call' AND api_tier IN ('Build', 'Enterprise') AND t >= TIMESTAMP '2026-03-02'), 0), 4) AS post_paid_share
|
|
64
|
+
FROM ev WHERE model IS NOT NULL;
|
|
65
|
+
|
|
66
|
+
-- tokens 1.5x on neither-cell users (H3's 8x / H7's 2x excluded by cell)
|
|
67
|
+
SELECT CASE WHEN e.model = 'opus-4-7' THEN 'opus' ELSE 'other' END AS grp,
|
|
68
|
+
count(*) AS calls, round(avg(e.tokens_used), 0) AS avg_tokens
|
|
69
|
+
FROM ev e JOIN cells c ON c.uid = e.uid AND c.cell = 'neither'
|
|
70
|
+
WHERE e.event = 'api call' AND e.api_tier IN ('Build', 'Enterprise')
|
|
71
|
+
AND e.t >= TIMESTAMP '2026-03-02'
|
|
72
|
+
GROUP BY 1 ORDER BY 1;
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
-- ── H3-agentic-power-users / H7-batch-discount ──────────────
|
|
76
|
+
-- Four-cell token design: neither 1x / agentic 8x / batch 2x / both 16x.
|
|
77
|
+
-- Agentic clones stamp multi_turn=true → agentic mt_share ≈ 0.75
|
|
78
|
+
-- ((0.25n + 2n) / 3n against the declared 1-in-4 mix).
|
|
79
|
+
SELECT c.cell, count(DISTINCT e.uid) AS users, count(*) AS calls,
|
|
80
|
+
round(avg(e.tokens_used), 0) AS avg_tokens,
|
|
81
|
+
round(count(*) FILTER (WHERE e.multi_turn = true)::DOUBLE / count(*), 4) AS mt_share
|
|
82
|
+
FROM cells c JOIN ev e ON e.uid = c.uid
|
|
83
|
+
WHERE e.event = 'api call' GROUP BY 1 ORDER BY avg_tokens;
|
|
84
|
+
|
|
85
|
+
-- H7: cost_per_token × 0.5 for batch users (no other hook touches it)
|
|
86
|
+
SELECT CASE WHEN c.cell IN ('batch', 'both') THEN 'batch' ELSE 'rest' END AS grp,
|
|
87
|
+
count(*) AS calls, round(avg(e.cost_per_token), 6) AS avg_cpt
|
|
88
|
+
FROM cells c JOIN ev e ON e.uid = c.uid
|
|
89
|
+
WHERE e.event = 'api call' GROUP BY 1 ORDER BY 1;
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
-- ── H4-rate-limit-churn ─────────────────────────────────────
|
|
93
|
+
-- 60% of users with 2+ rate limit errors in week 1 lose ALL post-week-1
|
|
94
|
+
-- events (per-user cliff). Signal: zero-post-week-1 share, flagged vs
|
|
95
|
+
-- rest — the DIFFERENCE cancels the natural-quiet baseline. Eligibility:
|
|
96
|
+
-- t0 ≤ datasetEnd − 21d (≥14d of post-week-1 runway).
|
|
97
|
+
WITH t0 AS (SELECT uid, min(t) AS t0 FROM ev GROUP BY 1),
|
|
98
|
+
rl AS (SELECT e.uid FROM ev e JOIN t0 ON t0.uid = e.uid
|
|
99
|
+
WHERE e.event = 'rate limit error' AND e.t < t0.t0 + INTERVAL 7 DAY
|
|
100
|
+
GROUP BY 1 HAVING count(*) >= 2),
|
|
101
|
+
per AS (SELECT t0.uid, (t0.uid IN (SELECT uid FROM rl)) AS flagged,
|
|
102
|
+
count(*) FILTER (WHERE e.t > t0.t0 + INTERVAL 7 DAY) AS post_ct
|
|
103
|
+
FROM t0 JOIN ev e ON e.uid = t0.uid
|
|
104
|
+
WHERE t0.t0 <= TIMESTAMP '2026-04-10 23:59:59' GROUP BY 1, 2)
|
|
105
|
+
SELECT CASE WHEN flagged THEN 'flagged' ELSE 'rest' END AS grp,
|
|
106
|
+
count(*) AS users,
|
|
107
|
+
round(count(*) FILTER (WHERE post_ct = 0)::DOUBLE / count(*), 4) AS zero_post_share
|
|
108
|
+
FROM per GROUP BY 1 ORDER BY 1;
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
-- ── H5-tier-context-window ──────────────────────────────────
|
|
112
|
+
-- input_tokens × 1/2/4 by tier; context_window pinned to the tier
|
|
113
|
+
-- constant (200K / 1M / 2M) on every api call — min = max proves it.
|
|
114
|
+
SELECT api_tier, count(*) AS calls,
|
|
115
|
+
round(avg(input_tokens), 0) AS avg_input,
|
|
116
|
+
min(context_window) AS min_cw, max(context_window) AS max_cw
|
|
117
|
+
FROM ev WHERE event = 'api call' GROUP BY 1 ORDER BY avg_input;
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
-- ── H6-outage-day ───────────────────────────────────────────
|
|
121
|
+
-- days 40-41 (2026-02-10 → 2026-02-12): 40% of api calls flagged
|
|
122
|
+
-- is_error + 3x latency. is_error declares [false] → zero errors outside
|
|
123
|
+
-- the window by schema.
|
|
124
|
+
SELECT
|
|
125
|
+
CASE WHEN t >= TIMESTAMP '2026-02-10' AND t < TIMESTAMP '2026-02-12' THEN 'outage' ELSE 'normal' END AS bucket,
|
|
126
|
+
count(*) AS calls,
|
|
127
|
+
round(count(*) FILTER (WHERE is_error = true)::DOUBLE / count(*), 4) AS error_share,
|
|
128
|
+
round(avg(latency_ms), 0) AS avg_latency
|
|
129
|
+
FROM ev WHERE event = 'api call' GROUP BY 1 ORDER BY 1;
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
-- ── H8-eval-retention ───────────────────────────────────────
|
|
133
|
+
-- users without an eval job in week 1 keep 25% of post-day-30 events.
|
|
134
|
+
-- Ratio-of-ratios (noneval post/pre vs eval post/pre) cancels window
|
|
135
|
+
-- lengths and the growth soup.
|
|
136
|
+
WITH t0 AS (SELECT uid, min(t) AS t0 FROM ev GROUP BY 1),
|
|
137
|
+
ev_users AS (SELECT e.uid FROM ev e JOIN t0 ON t0.uid = e.uid
|
|
138
|
+
WHERE e.event = 'eval job' AND e.t < t0.t0 + INTERVAL 7 DAY GROUP BY 1),
|
|
139
|
+
per AS (SELECT t0.uid, (t0.uid IN (SELECT uid FROM ev_users)) AS eval_user,
|
|
140
|
+
count(*) FILTER (WHERE e.t <= t0.t0 + INTERVAL 30 DAY) AS pre_ct,
|
|
141
|
+
count(*) FILTER (WHERE e.t > t0.t0 + INTERVAL 30 DAY) AS post_ct
|
|
142
|
+
FROM t0 JOIN ev e ON e.uid = t0.uid GROUP BY 1, 2)
|
|
143
|
+
SELECT CASE WHEN eval_user THEN 'eval' ELSE 'noneval' END AS grp,
|
|
144
|
+
count(*) AS users, round(avg(pre_ct), 1) AS avg_pre, round(avg(post_ct), 1) AS avg_post,
|
|
145
|
+
round(avg(post_ct) / nullif(avg(pre_ct), 0), 4) AS post_pre
|
|
146
|
+
FROM per GROUP BY 1 ORDER BY 1;
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
-- ── H9-api-to-eval-ttc ──────────────────────────────────────
|
|
150
|
+
-- funnel-post scales API-to-Eval step gaps: Enterprise × 0.5, Free × 2.0.
|
|
151
|
+
-- CAUTION: this nearest-preceding-pair SQL is shown for inspection only —
|
|
152
|
+
-- it is censored by its lookback window (stretched Free pairs fall out and
|
|
153
|
+
-- organic events intercept), which can INVERT the direction. The story
|
|
154
|
+
-- asserts through the Mixpanel-aligned funnel emulator at a 336h window
|
|
155
|
+
-- (= 2.0 × the funnel's 168h generative window, covering the stretched
|
|
156
|
+
-- support); trust the story verdict, not this query's ratio.
|
|
157
|
+
WITH ej AS (SELECT uid, t, api_tier FROM ev WHERE event = 'eval job'),
|
|
158
|
+
gap AS (SELECT ej.uid, ej.api_tier, epoch(ej.t - max(tc.t)) / 3600.0 AS gap_h
|
|
159
|
+
FROM ej JOIN ev tc ON tc.uid = ej.uid AND tc.event = 'tool use call'
|
|
160
|
+
AND tc.t < ej.t AND tc.t >= ej.t - INTERVAL 336 HOUR
|
|
161
|
+
GROUP BY ej.uid, ej.api_tier, ej.t)
|
|
162
|
+
SELECT api_tier, count(*) AS pairs, count(DISTINCT uid) AS users,
|
|
163
|
+
round(median(gap_h), 1) AS med_gap_h
|
|
164
|
+
FROM gap GROUP BY 1 ORDER BY med_gap_h;
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
-- ── H10-docs-magic-number ───────────────────────────────────
|
|
168
|
+
-- docs searched strictly between earliest org-created and earliest
|
|
169
|
+
-- billing payment: 1-2 (sweet) → amount_usd × 1.35 on ALL billing
|
|
170
|
+
-- payments; 3+ (over) → × 0.75. Both branches amount-only (iid draw,
|
|
171
|
+
-- selection-free); median ratios vs the untouched zero-docs cohort read
|
|
172
|
+
-- the knobs directly.
|
|
173
|
+
WITH org AS (SELECT uid, min(t) AS org_t FROM ev WHERE event = 'organization created' GROUP BY 1),
|
|
174
|
+
bill AS (SELECT uid, min(t) AS bill_t FROM ev WHERE event = 'billing payment' GROUP BY 1),
|
|
175
|
+
docs AS (SELECT o.uid, count(e.uid) AS docs_ct
|
|
176
|
+
FROM org o JOIN bill b ON b.uid = o.uid
|
|
177
|
+
LEFT JOIN ev e ON e.uid = o.uid AND e.event = 'docs searched' AND e.t > o.org_t AND e.t < b.bill_t
|
|
178
|
+
GROUP BY 1, o.org_t, b.bill_t),
|
|
179
|
+
dcoh AS (SELECT uid, CASE WHEN docs_ct BETWEEN 1 AND 2 THEN 'sweet'
|
|
180
|
+
WHEN docs_ct >= 3 THEN 'over' ELSE 'zero' END AS grp FROM docs)
|
|
181
|
+
SELECT d.grp, count(DISTINCT d.uid) AS users, count(*) AS payments,
|
|
182
|
+
round(median(e.amount_usd), 0) AS med_amount
|
|
183
|
+
FROM dcoh d JOIN ev e ON e.uid = d.uid AND e.event = 'billing payment'
|
|
184
|
+
GROUP BY 1 ORDER BY med_amount;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ai-platform.verify.mjs — thin wrapper around the story runner.
|
|
4
|
+
*
|
|
5
|
+
* All verification logic lives in the `stories` export of ./ai-platform.js;
|
|
6
|
+
* this script just streams the shards and delegates. It is equivalent to:
|
|
7
|
+
*
|
|
8
|
+
* node scripts/verify-stories.mjs dungeons/vertical/ai-platform/ai-platform.js --data-prefix verify-ai-platform
|
|
9
|
+
*
|
|
10
|
+
* Generate first:
|
|
11
|
+
* node scripts/verify-runner.mjs dungeons/vertical/ai-platform/ai-platform.js verify-ai-platform
|
|
12
|
+
* Run:
|
|
13
|
+
* node dungeons/vertical/ai-platform/ai-platform.verify.mjs [prefix] # default verify-ai-platform
|
|
14
|
+
*/
|
|
15
|
+
import fs from 'node:fs';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import readline from 'node:readline';
|
|
18
|
+
import { execFile } from 'node:child_process';
|
|
19
|
+
import { promisify } from 'node:util';
|
|
20
|
+
import { buildIdentityMap, evaluateStories, VERDICT_RANK } from '@ak--47/dungeon-master/verify';
|
|
21
|
+
import config, { stories } from './ai-platform.js';
|
|
22
|
+
|
|
23
|
+
const PREFIX = process.argv[2] || 'verify-ai-platform';
|
|
24
|
+
const prefixPath = path.join('data', PREFIX);
|
|
25
|
+
|
|
26
|
+
async function loadShards(suffix) {
|
|
27
|
+
// streaming load: full-fidelity EVENTS shards exceed the readFileSync cap
|
|
28
|
+
const dir = path.dirname(prefixPath), base = path.basename(prefixPath);
|
|
29
|
+
const out = [];
|
|
30
|
+
if (!fs.existsSync(dir)) return out;
|
|
31
|
+
for (const f of fs.readdirSync(dir).filter(f => f.startsWith(`${base}-${suffix}`) && f.endsWith('.json')).sort()) {
|
|
32
|
+
const rl = readline.createInterface({ input: fs.createReadStream(path.join(dir, f)), crlfDelay: Infinity });
|
|
33
|
+
for await (const line of rl) {
|
|
34
|
+
if (line.trim()) out.push(JSON.parse(line));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const events = await loadShards('EVENTS');
|
|
41
|
+
const profiles = await loadShards('USERS');
|
|
42
|
+
if (!events.length) {
|
|
43
|
+
console.error(`no shards at ${prefixPath}-EVENTS*.json — run: node scripts/verify-runner.mjs dungeons/vertical/ai-platform/ai-platform.js ${PREFIX}`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
console.log(`ai-platform — events=${events.length} users=${profiles.length} (${prefixPath})`);
|
|
47
|
+
|
|
48
|
+
const execFileP = promisify(execFile);
|
|
49
|
+
const runSql = async (sql) => {
|
|
50
|
+
const { stdout } = await execFileP('duckdb', ['-json', '-c', sql.replaceAll('{{PREFIX}}', prefixPath)], { maxBuffer: 64 * 1024 * 1024 });
|
|
51
|
+
return stdout.trim() ? JSON.parse(stdout) : [];
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// funnels passed raw (unvalidated) — the H9 story carries its own explicit
|
|
55
|
+
// 336-hour conversion window (2x the generative window, covering the
|
|
56
|
+
// stretched support), so no funnel-default threading is needed
|
|
57
|
+
const results = await evaluateStories(stories, events, {
|
|
58
|
+
profiles,
|
|
59
|
+
funnels: config.funnels,
|
|
60
|
+
identityMap: buildIdentityMap(profiles),
|
|
61
|
+
runSql,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
let worst = 'NAILED';
|
|
65
|
+
for (const r of results) {
|
|
66
|
+
console.log(`${r.verdict.padEnd(7)} ${r.id}`);
|
|
67
|
+
for (const a of r.assertions) console.log(` ${a.verdict.padEnd(7)} ${a.detail}`);
|
|
68
|
+
if (VERDICT_RANK[r.verdict] < VERDICT_RANK[worst]) worst = r.verdict;
|
|
69
|
+
}
|
|
70
|
+
console.log(`\nworst verdict: ${worst}`);
|
|
71
|
+
process.exit(VERDICT_RANK[worst] >= VERDICT_RANK.STRONG ? 0 : 1);
|