@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,175 @@
|
|
|
1
|
+
-- ============================================================
|
|
2
|
+
-- fintech.js — v1.6 human-inspection queries (DuckDB)
|
|
3
|
+
--
|
|
4
|
+
-- Every query is keyed to a story id in fintech.js's `stories` export;
|
|
5
|
+
-- the machine-checked verdicts come from:
|
|
6
|
+
-- node scripts/verify-stories.mjs dungeons/vertical/fintech/fintech.js --data-prefix verify-fintech
|
|
7
|
+
-- Generate first:
|
|
8
|
+
-- node scripts/verify-runner.mjs dungeons/vertical/fintech/fintech.js verify-fintech
|
|
9
|
+
-- Run this file:
|
|
10
|
+
-- duckdb -c ".read dungeons/vertical/fintech/fintech.sql"
|
|
11
|
+
-- ============================================================
|
|
12
|
+
|
|
13
|
+
-- ── identity-resolution prelude ─────────────────────────────
|
|
14
|
+
-- avgDevicePerUser: 2 — resolve device_id → canonical distinct_id via the
|
|
15
|
+
-- USERS shards so uniques are identity-correct even for device-only rows.
|
|
16
|
+
CREATE OR REPLACE VIEW users AS
|
|
17
|
+
SELECT * FROM read_json_auto('data/verify-fintech-USERS*.json', sample_size=-1, union_by_name=true);
|
|
18
|
+
|
|
19
|
+
CREATE OR REPLACE VIEW device_map AS
|
|
20
|
+
-- profiles store the device pool under the legacy "anonymousIds" key
|
|
21
|
+
-- (buildIdentityMap in lib/verify/identity.js reads the same field)
|
|
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-fintech-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
|
+
|
|
34
|
+
-- ── H1-business-txn-4x ──────────────────────────────────────
|
|
35
|
+
-- user mix ~ 80/20 personal/business; business avg txn amount ~ 4x personal
|
|
36
|
+
SELECT account_segment, count(*) AS users FROM users GROUP BY 1 ORDER BY 1;
|
|
37
|
+
|
|
38
|
+
SELECT u.account_segment, count(*) AS txns, round(avg(e.amount), 0) AS avg_amount
|
|
39
|
+
FROM ev e JOIN users u ON e.uid = u.distinct_id
|
|
40
|
+
WHERE e.event = 'transaction completed' AND e.amount IS NOT NULL
|
|
41
|
+
GROUP BY 1 ORDER BY avg_amount DESC;
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
-- ── H2-payday-amounts ───────────────────────────────────────
|
|
45
|
+
-- direct deposits 3x on the 1st/15th; transfers ~1.6x on days 1-3 / 15-17
|
|
46
|
+
SELECT EXTRACT(DAY FROM t) AS dom, count(*) AS deposits, round(avg(amount), 0) AS avg_amount
|
|
47
|
+
FROM ev WHERE event = 'transaction completed' AND transaction_type = 'direct_deposit'
|
|
48
|
+
GROUP BY 1 ORDER BY 1;
|
|
49
|
+
|
|
50
|
+
SELECT CASE WHEN EXTRACT(DAY FROM t) IN (1, 2, 3, 15, 16, 17) THEN 'payday_window' ELSE 'other' END AS bucket,
|
|
51
|
+
count(*) AS transfers, round(avg(amount), 0) AS avg_amount
|
|
52
|
+
FROM ev WHERE event = 'transfer sent' GROUP BY 1 ORDER BY 1;
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
-- ── H3-fraud-cohort-share ───────────────────────────────────
|
|
56
|
+
-- ~3% of SUPPORT-HISTORY users carry the full burst signature: 3+ rapid
|
|
57
|
+
-- credit purchases, then suspicious lock + unauthorized dispute within 1h.
|
|
58
|
+
-- The bare lock+dispute pair is NOT the detector (organic Support-funnel
|
|
59
|
+
-- reason collisions outnumber the hook ~4:1).
|
|
60
|
+
WITH locks AS (SELECT uid, epoch(t) AS ts FROM ev WHERE event = 'card locked' AND reason = 'suspicious_activity'),
|
|
61
|
+
disputes AS (SELECT uid, epoch(t) AS ts FROM ev WHERE event = 'dispute filed' AND reason = 'unauthorized'),
|
|
62
|
+
txns AS (SELECT uid, epoch(t) AS ts FROM ev WHERE event = 'transaction completed' AND transaction_type = 'purchase' AND payment_method = 'credit'),
|
|
63
|
+
burst_locks AS (
|
|
64
|
+
SELECT l.uid, l.ts FROM locks l JOIN txns x ON x.uid = l.uid AND x.ts BETWEEN l.ts - 3900 AND l.ts
|
|
65
|
+
GROUP BY 1, 2 HAVING count(*) >= 3
|
|
66
|
+
),
|
|
67
|
+
sig AS (SELECT DISTINCT b.uid FROM burst_locks b JOIN disputes d ON d.uid = b.uid AND d.ts - b.ts BETWEEN 0 AND 3600),
|
|
68
|
+
hist AS (SELECT count(*) AS n FROM (
|
|
69
|
+
SELECT uid FROM ev WHERE event = 'card locked' GROUP BY 1
|
|
70
|
+
INTERSECT
|
|
71
|
+
SELECT uid FROM ev WHERE event = 'dispute filed' GROUP BY 1
|
|
72
|
+
))
|
|
73
|
+
SELECT (SELECT count(*) FROM sig) AS fraud_users,
|
|
74
|
+
(SELECT n FROM hist) AS support_history_users,
|
|
75
|
+
round((SELECT count(*) FROM sig)::DOUBLE / (SELECT n FROM hist), 4) AS fraction;
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
-- ── H4-lowbal-churn-suppression ─────────────────────────────
|
|
79
|
+
-- 3+ balance checks under $8K → post-d30/pre-d30 event ratio ~ 0.5x healthy
|
|
80
|
+
WITH low AS (
|
|
81
|
+
SELECT uid, count(*) FILTER (WHERE event = 'balance checked' AND account_balance < 8000) AS low_checks
|
|
82
|
+
FROM ev GROUP BY 1
|
|
83
|
+
),
|
|
84
|
+
cutoff AS (SELECT min(t) + INTERVAL 30 DAY AS c FROM ev)
|
|
85
|
+
SELECT CASE WHEN l.low_checks >= 3 THEN 'lowbal' ELSE 'healthy' END AS cohort,
|
|
86
|
+
count(DISTINCT e.uid) AS users,
|
|
87
|
+
round((count(*) FILTER (WHERE e.t >= (SELECT c FROM cutoff)))::DOUBLE
|
|
88
|
+
/ nullif(count(*) FILTER (WHERE e.t < (SELECT c FROM cutoff)), 0), 2) AS post_pre
|
|
89
|
+
FROM ev e JOIN low l USING (uid) GROUP BY 1 ORDER BY 1;
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
-- ── H5-budget-discipline ────────────────────────────────────
|
|
93
|
+
-- disciplined budgeters (3+ budgets, ~77% of users): 2x savings
|
|
94
|
+
-- contributions, 1.5x investment amounts vs light budgeters (0-2, ~14%)
|
|
95
|
+
WITH cohort AS (SELECT uid, count(*) FILTER (WHERE event = 'budget created') >= 3 AS disciplined FROM ev GROUP BY 1)
|
|
96
|
+
SELECT CASE WHEN c.disciplined THEN 'disciplined' ELSE 'light' END AS cohort, count(*) AS n,
|
|
97
|
+
round(avg(e.monthly_contribution), 0) AS avg_contribution
|
|
98
|
+
FROM ev e JOIN cohort c USING (uid)
|
|
99
|
+
WHERE e.event = 'savings goal set' AND e.monthly_contribution IS NOT NULL
|
|
100
|
+
GROUP BY 1 ORDER BY 1;
|
|
101
|
+
|
|
102
|
+
WITH cohort AS (SELECT uid, count(*) FILTER (WHERE event = 'budget created') >= 3 AS disciplined FROM ev GROUP BY 1)
|
|
103
|
+
SELECT CASE WHEN c.disciplined THEN 'disciplined' ELSE 'light' END AS cohort, count(*) AS n,
|
|
104
|
+
round(avg(e.amount), 0) AS avg_amount
|
|
105
|
+
FROM ev e JOIN cohort c USING (uid)
|
|
106
|
+
WHERE e.event = 'investment made' AND e.amount IS NOT NULL
|
|
107
|
+
GROUP BY 1 ORDER BY 1;
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
-- ── H6-autopay-missed-share ─────────────────────────────────
|
|
111
|
+
-- missed/paid ~ 0.22 (60% manual x 30% missed → 0.18/0.82); missed rows are
|
|
112
|
+
-- always auto_pay = false
|
|
113
|
+
SELECT event, auto_pay, count(*) AS n
|
|
114
|
+
FROM ev WHERE event IN ('bill paid', 'bill payment missed')
|
|
115
|
+
GROUP BY 1, 2 ORDER BY 1, 2;
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
-- ── H7-premium-tier-value ───────────────────────────────────
|
|
119
|
+
-- premium 3x reward value, plus 1.5x; premium 2x investment-sell amount
|
|
120
|
+
SELECT account_tier, count(*) AS redemptions, round(avg("value"), 1) AS avg_value
|
|
121
|
+
FROM ev WHERE event = 'reward redeemed' GROUP BY 1 ORDER BY avg_value DESC;
|
|
122
|
+
|
|
123
|
+
SELECT account_tier, count(*) AS sells, round(avg(amount), 0) AS avg_amount
|
|
124
|
+
FROM ev WHERE event = 'investment made' AND action = 'sell'
|
|
125
|
+
GROUP BY 1 ORDER BY avg_amount DESC;
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
-- ── H8-month-end-anxiety ────────────────────────────────────
|
|
129
|
+
-- days >= 28: session durations 1.4x, reported balances 0.7x
|
|
130
|
+
SELECT CASE WHEN EXTRACT(DAY FROM t) >= 28 THEN 'monthEnd' ELSE 'other' END AS bucket,
|
|
131
|
+
round(avg(session_duration_sec), 0) AS avg_session_sec, count(*) AS sessions
|
|
132
|
+
FROM ev WHERE event = 'app session' GROUP BY 1 ORDER BY 1;
|
|
133
|
+
|
|
134
|
+
SELECT CASE WHEN EXTRACT(DAY FROM t) >= 28 THEN 'monthEnd' ELSE 'other' END AS bucket,
|
|
135
|
+
round(avg(account_balance), 0) AS avg_balance, count(*) AS checks
|
|
136
|
+
FROM ev WHERE event = 'balance checked' GROUP BY 1 ORDER BY 1;
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
-- ── H9-onboarding-ttc-by-tier ───────────────────────────────
|
|
140
|
+
-- cross-event MIN→MIN TTC (account opened → first balance checked after it):
|
|
141
|
+
-- MEDIAN basic ~ 2x premium (factors 1.33 vs 0.67), plus between. Median,
|
|
142
|
+
-- not avg — TTC is heavy-tailed and a single multi-day straggler dominates
|
|
143
|
+
-- the mean of these small cohorts (only born-in users have "account opened").
|
|
144
|
+
WITH ao AS (SELECT uid, min(t) AS t0 FROM ev WHERE event = 'account opened' GROUP BY 1),
|
|
145
|
+
bc AS (
|
|
146
|
+
SELECT e.uid, min(e.t) AS t1
|
|
147
|
+
FROM ev e JOIN ao ON e.uid = ao.uid AND e.t >= ao.t0
|
|
148
|
+
WHERE e.event = 'balance checked' GROUP BY 1
|
|
149
|
+
)
|
|
150
|
+
SELECT u.account_tier, count(*) AS converters,
|
|
151
|
+
round(median(epoch(t1 - t0)) / 60, 1) AS median_ttc_min,
|
|
152
|
+
round(avg(epoch(t1 - t0)) / 60, 1) AS avg_ttc_min
|
|
153
|
+
FROM ao JOIN bc USING (uid) JOIN users u ON u.distinct_id = ao.uid
|
|
154
|
+
GROUP BY 1 ORDER BY median_ttc_min DESC;
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
-- ── H10-txn-magic-number ────────────────────────────────────
|
|
158
|
+
-- sweet band (12-19 txns, measured: median 12 / p75 17 / p90 20) invests
|
|
159
|
+
-- 1.4x the 1-11 band; over band (20+) has 0.8x the premium-upgrade share of
|
|
160
|
+
-- NON-TXN events (raw per-user counts RISE with activity, and the over band
|
|
161
|
+
-- is selected for high txn counts — normalizing by non-txn events removes
|
|
162
|
+
-- both distortions)
|
|
163
|
+
WITH txn AS (SELECT uid, count(*) AS n FROM ev WHERE event = 'transaction completed' GROUP BY 1),
|
|
164
|
+
bands AS (SELECT uid, CASE WHEN n BETWEEN 12 AND 19 THEN 'sweet' WHEN n < 12 THEN 'low' ELSE 'over' END AS band FROM txn)
|
|
165
|
+
SELECT b.band, count(DISTINCT e.uid) AS users, round(avg(e.amount), 0) AS avg_invest_amount
|
|
166
|
+
FROM ev e JOIN bands b USING (uid)
|
|
167
|
+
WHERE e.event = 'investment made' AND e.amount IS NOT NULL
|
|
168
|
+
GROUP BY 1 ORDER BY 1;
|
|
169
|
+
|
|
170
|
+
WITH txn AS (SELECT uid, count(*) AS n FROM ev WHERE event = 'transaction completed' GROUP BY 1),
|
|
171
|
+
bands AS (SELECT uid, CASE WHEN n BETWEEN 12 AND 19 THEN 'sweet' WHEN n < 12 THEN 'low' ELSE 'over' END AS band FROM txn)
|
|
172
|
+
SELECT b.band, count(DISTINCT e.uid) AS users,
|
|
173
|
+
round((count(*) FILTER (WHERE e.event = 'premium upgraded'))::DOUBLE
|
|
174
|
+
/ (count(*) FILTER (WHERE e.event != 'transaction completed')), 5) AS upgrade_share
|
|
175
|
+
FROM ev e JOIN bands b USING (uid) GROUP BY 1 ORDER BY 1;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* fintech.verify.mjs — thin wrapper around the story runner.
|
|
4
|
+
*
|
|
5
|
+
* All verification logic lives in the `stories` export of ./fintech.js;
|
|
6
|
+
* this script just streams the shards and delegates. It is equivalent to:
|
|
7
|
+
*
|
|
8
|
+
* node scripts/verify-stories.mjs dungeons/vertical/fintech/fintech.js --data-prefix verify-fintech
|
|
9
|
+
*
|
|
10
|
+
* Generate first:
|
|
11
|
+
* node scripts/verify-runner.mjs dungeons/vertical/fintech/fintech.js verify-fintech
|
|
12
|
+
* Run:
|
|
13
|
+
* node dungeons/vertical/fintech/fintech.verify.mjs [prefix] # default verify-fintech
|
|
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 './fintech.js';
|
|
22
|
+
|
|
23
|
+
const PREFIX = process.argv[2] || 'verify-fintech';
|
|
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/fintech/fintech.js ${PREFIX}`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
console.log(`fintech — 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) — every timeToConvert story carries an
|
|
55
|
+
// explicit conversionWindowMs, so no funnel-default threading is needed
|
|
56
|
+
const results = await evaluateStories(stories, events, {
|
|
57
|
+
profiles,
|
|
58
|
+
funnels: config.funnels,
|
|
59
|
+
identityMap: buildIdentityMap(profiles),
|
|
60
|
+
runSql,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
let worst = 'NAILED';
|
|
64
|
+
for (const r of results) {
|
|
65
|
+
console.log(`${r.verdict.padEnd(7)} ${r.id}`);
|
|
66
|
+
for (const a of r.assertions) console.log(` ${a.verdict.padEnd(7)} ${a.detail}`);
|
|
67
|
+
if (VERDICT_RANK[r.verdict] < VERDICT_RANK[worst]) worst = r.verdict;
|
|
68
|
+
}
|
|
69
|
+
console.log(`\nworst verdict: ${worst}`);
|
|
70
|
+
process.exit(VERDICT_RANK[worst] >= VERDICT_RANK.STRONG ? 0 : 1);
|