@ak--47/dungeon-master 1.5.4 → 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 +1 -1
- package/.claude/skills/create-project/provision.mjs +57 -10
- 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 +183 -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 +2 -1
- 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,174 @@
|
|
|
1
|
+
-- ============================================================
|
|
2
|
+
-- insurance-application.js — v1.6 human-inspection queries (DuckDB)
|
|
3
|
+
--
|
|
4
|
+
-- Every query is keyed to a story id in insurance-application.js's
|
|
5
|
+
-- `stories` export; the machine-checked verdicts come from:
|
|
6
|
+
-- node scripts/verify-stories.mjs dungeons/vertical/insurance-application/insurance-application.js --data-prefix verify-insurance-application
|
|
7
|
+
-- Generate first:
|
|
8
|
+
-- node scripts/verify-runner.mjs dungeons/vertical/insurance-application/insurance-application.js verify-insurance-application
|
|
9
|
+
-- Run this file:
|
|
10
|
+
-- duckdb -c ".read dungeons/vertical/insurance-application/insurance-application.sql"
|
|
11
|
+
--
|
|
12
|
+
-- NOTE: this dungeon uses a LITERAL historical window (datasetStart
|
|
13
|
+
-- 2026-01-01 → datasetEnd 2026-05-01T23:59:59Z, no forward shift), so
|
|
14
|
+
-- day_idx = date_diff('day', DATE '2026-01-01', t::DATE) matches the
|
|
15
|
+
-- hook's day offsets exactly. Version boundaries are exact timestamps:
|
|
16
|
+
-- v2.11 @ 2026-01-31 00:00:00, v2.12 @ 2026-03-02 00:00:00,
|
|
17
|
+
-- v2.13 @ 2026-04-21 23:59:59 (end − 10d — NOT midnight).
|
|
18
|
+
-- ============================================================
|
|
19
|
+
|
|
20
|
+
-- ── identity-resolution prelude ─────────────────────────────
|
|
21
|
+
-- 'account created' is both isAuthEvent and isFirstEvent, so every user
|
|
22
|
+
-- auths on their very first event — user_id is present on ALL events and
|
|
23
|
+
-- there are zero device-only rows (asserted by story H1).
|
|
24
|
+
CREATE OR REPLACE VIEW us AS
|
|
25
|
+
SELECT distinct_id::VARCHAR AS duid, risk_profile
|
|
26
|
+
FROM read_json_auto('data/verify-insurance-application-USERS*.json', sample_size=-1, union_by_name=true);
|
|
27
|
+
|
|
28
|
+
CREATE OR REPLACE VIEW ev AS
|
|
29
|
+
SELECT e.user_id::VARCHAR AS uid, e.device_id::VARCHAR AS did, e.time::TIMESTAMP AS t,
|
|
30
|
+
date_diff('day', DATE '2026-01-01', e.time::TIMESTAMP::DATE) AS day_idx,
|
|
31
|
+
e.*
|
|
32
|
+
FROM read_json_auto('data/verify-insurance-application-EVENTS*.json', sample_size=-1, union_by_name=true) e;
|
|
33
|
+
|
|
34
|
+
-- Per-user aggregates. H8 deletes 75% of post-day-30 events for born
|
|
35
|
+
-- non-uploaders, so reads needing undistorted per-user tallies (H4, H10)
|
|
36
|
+
-- restrict to the H8-untouched population: (NOT born) OR uploader.
|
|
37
|
+
CREATE OR REPLACE VIEW pu AS
|
|
38
|
+
SELECT uid, min(t) AS first_t,
|
|
39
|
+
count(*) FILTER (WHERE event = 'account created') AS acct_created,
|
|
40
|
+
count(*) FILTER (WHERE event = 'application step completed') AS steps,
|
|
41
|
+
count(*) FILTER (WHERE event = 'application approved') AS approvals,
|
|
42
|
+
count(*) FILTER (WHERE event = 'application submitted') AS submits,
|
|
43
|
+
count(*) FILTER (WHERE event = 'claim filed') AS claims,
|
|
44
|
+
count(*) FILTER (WHERE event = 'payment made') AS payments
|
|
45
|
+
FROM ev GROUP BY 1;
|
|
46
|
+
|
|
47
|
+
CREATE OR REPLACE VIEW puu AS
|
|
48
|
+
SELECT p.*, p.acct_created > 0 AS born,
|
|
49
|
+
((SELECT count(*) FROM ev e WHERE e.uid = p.uid AND e.event = 'document uploaded'
|
|
50
|
+
AND e.t <= p.first_t + INTERVAL '14 days') >= 3) AS uploader
|
|
51
|
+
FROM pu p;
|
|
52
|
+
|
|
53
|
+
CREATE OR REPLACE VIEW h4pop AS
|
|
54
|
+
SELECT * FROM puu WHERE (NOT born) OR uploader;
|
|
55
|
+
|
|
56
|
+
-- ── H1-version-bands: app_version is a pure function of timestamp ──
|
|
57
|
+
-- Expect: zero violations, four crisp bands.
|
|
58
|
+
SELECT app_version, count(*) AS events,
|
|
59
|
+
min(t) AS first_seen, max(t) AS last_seen
|
|
60
|
+
FROM ev GROUP BY 1 ORDER BY 1;
|
|
61
|
+
|
|
62
|
+
SELECT count(*) AS version_violations FROM ev
|
|
63
|
+
WHERE app_version != CASE
|
|
64
|
+
WHEN t < TIMESTAMP '2026-01-31 00:00:00' THEN '2.10'
|
|
65
|
+
WHEN t < TIMESTAMP '2026-03-02 00:00:00' THEN '2.11'
|
|
66
|
+
WHEN t < TIMESTAMP '2026-04-21 23:59:59' THEN '2.12'
|
|
67
|
+
ELSE '2.13' END;
|
|
68
|
+
|
|
69
|
+
-- ── H2-ticket-drop: volume collapse + bug categories vanish ──
|
|
70
|
+
-- Expect: v2.13 daily rate ~0.3x the final-v2.12 rate; bug-category share
|
|
71
|
+
-- ~0.67 pre-release, EXACTLY ZERO post (survivors are recategorized).
|
|
72
|
+
SELECT
|
|
73
|
+
count(*) FILTER (WHERE day_idx BETWEEN 101 AND 110) / 10.0 AS v212_last10_per_day,
|
|
74
|
+
count(*) FILTER (WHERE day_idx BETWEEN 111 AND 120) / 10.0 AS v213_per_day
|
|
75
|
+
FROM ev WHERE event = 'support ticket created';
|
|
76
|
+
|
|
77
|
+
SELECT
|
|
78
|
+
avg((issue_category IN ('form_crash','login_error','page_timeout','payment_failure'))::INT)
|
|
79
|
+
FILTER (WHERE t < TIMESTAMP '2026-04-21 23:59:59') AS bug_share_pre,
|
|
80
|
+
count(*) FILTER (WHERE issue_category IN ('form_crash','login_error','page_timeout','payment_failure')
|
|
81
|
+
AND t >= TIMESTAMP '2026-04-21 23:59:59') AS bug_tickets_post
|
|
82
|
+
FROM ev WHERE event = 'support ticket created';
|
|
83
|
+
|
|
84
|
+
-- ── H3-activation-gap: policy activated flatlines pre-v2.13 ──
|
|
85
|
+
-- Expect: ~28x per-day step-up on release (1/0.05 gate x organic drift);
|
|
86
|
+
-- approvals are untouched (H5 pins them near each user's start).
|
|
87
|
+
SELECT
|
|
88
|
+
count(*) FILTER (WHERE t < TIMESTAMP '2026-04-21 23:59:59') / 111.0 AS act_pre_per_day,
|
|
89
|
+
count(*) FILTER (WHERE t >= TIMESTAMP '2026-04-21 23:59:59') / 10.0 AS act_post_per_day
|
|
90
|
+
FROM ev WHERE event = 'policy activated';
|
|
91
|
+
|
|
92
|
+
-- ── H4-step-magic: sweet-spot premium boost + over-engagement drop ──
|
|
93
|
+
-- Expect: sweet/base avg approved_premium ~1.35x; over/sweet
|
|
94
|
+
-- approvals-per-submit ~0.5x (raw per-user is activity-confounded).
|
|
95
|
+
SELECT
|
|
96
|
+
(SELECT avg(e.approved_premium) FROM ev e JOIN h4pop p ON e.uid = p.uid
|
|
97
|
+
WHERE e.event = 'application approved' AND p.steps BETWEEN 8 AND 14) AS sweet_avg_premium,
|
|
98
|
+
(SELECT avg(e.approved_premium) FROM ev e JOIN h4pop p ON e.uid = p.uid
|
|
99
|
+
WHERE e.event = 'application approved' AND p.steps < 8) AS base_avg_premium,
|
|
100
|
+
(SELECT sum(approvals)::DOUBLE / nullif(sum(submits), 0) FROM h4pop WHERE steps >= 15) AS over_app_per_submit,
|
|
101
|
+
(SELECT sum(approvals)::DOUBLE / nullif(sum(submits), 0) FROM h4pop WHERE steps BETWEEN 8 AND 14) AS sweet_app_per_submit;
|
|
102
|
+
|
|
103
|
+
-- ── H5-ttc-account-type: started→approved gap pinned per account type ──
|
|
104
|
+
-- Expect: medians ~37h (business) / ~50h (individual) / ~65h (family);
|
|
105
|
+
-- support exactly [target, target+4h). Born users only (account_type
|
|
106
|
+
-- lives on the "account created" event).
|
|
107
|
+
WITH acct AS (SELECT uid, min(account_type) AS account_type FROM ev WHERE event = 'account created' GROUP BY 1),
|
|
108
|
+
fs AS (SELECT uid, min(t) AS first_started FROM ev WHERE event = 'application started' GROUP BY 1),
|
|
109
|
+
gaps AS (SELECT a.account_type, date_diff('second', f.first_started, e.t) / 3600.0 AS gap_h
|
|
110
|
+
FROM ev e JOIN fs f ON e.uid = f.uid JOIN acct a ON e.uid = a.uid
|
|
111
|
+
WHERE e.event = 'application approved')
|
|
112
|
+
SELECT account_type, count(*) AS n, median(gap_h) AS med_h, min(gap_h) AS min_h, max(gap_h) AS max_h
|
|
113
|
+
FROM gaps GROUP BY 1 ORDER BY med_h;
|
|
114
|
+
|
|
115
|
+
-- ── H6-claims-experiment: engine-native A/B on Claims Process ──
|
|
116
|
+
-- Expect: ~50/50 split, zero exposures before end−35d, Simplified ~1.3x
|
|
117
|
+
-- completion (49h window covers the 0.8x-compressed variant TTC).
|
|
118
|
+
SELECT "Variant name" AS variant, count(*) AS exposures, count(DISTINCT uid) AS users,
|
|
119
|
+
count(*) FILTER (WHERE t < TIMESTAMP '2026-03-27 23:59:59') AS pre_start_exposures
|
|
120
|
+
FROM ev WHERE event = '$experiment_started' GROUP BY 1;
|
|
121
|
+
|
|
122
|
+
WITH expusers AS (SELECT uid, min("Variant name") AS variant FROM ev WHERE event = '$experiment_started'
|
|
123
|
+
GROUP BY 1 HAVING count(DISTINCT "Variant name") = 1),
|
|
124
|
+
claimwin AS (SELECT c.uid,
|
|
125
|
+
(EXISTS (SELECT 1 FROM ev s WHERE s.uid = c.uid AND s.event = 'claim status checked'
|
|
126
|
+
AND s.t > c.t AND s.t <= c.t + INTERVAL 49 HOUR
|
|
127
|
+
AND EXISTS (SELECT 1 FROM ev k WHERE k.uid = c.uid AND k.event = 'support ticket created'
|
|
128
|
+
AND k.t > s.t AND k.t <= c.t + INTERVAL 49 HOUR))) AS completed
|
|
129
|
+
FROM ev c WHERE c.event = 'claim filed' AND c.t >= TIMESTAMP '2026-03-27 23:59:59')
|
|
130
|
+
SELECT x.variant, count(*) AS instances, avg(w.completed::INT) AS completion_49h
|
|
131
|
+
FROM claimwin w JOIN expusers x ON w.uid = x.uid GROUP BY 1;
|
|
132
|
+
|
|
133
|
+
-- ── H7-risk-approval: funnel-pre conversion multipliers ──
|
|
134
|
+
-- Expect approvals-per-submit low/med ~1.15x, high/med ~0.71x (partial-step
|
|
135
|
+
-- walk compresses the 1.8x/0.3x knobs: p = c + (1−c)/2 → 0.975/0.85/0.605).
|
|
136
|
+
SELECT u.risk_profile, count(*) AS users,
|
|
137
|
+
sum(p.approvals)::DOUBLE / nullif(sum(p.submits), 0) AS approvals_per_submit
|
|
138
|
+
FROM puu p JOIN us u ON p.uid = u.duid GROUP BY 1 ORDER BY 1;
|
|
139
|
+
|
|
140
|
+
-- ── H8-doc-retention: churn for born non-uploaders ──
|
|
141
|
+
-- Expect DiD (nonup post/pre ÷ up post/pre) ~0.25-0.30. Small cohorts even
|
|
142
|
+
-- at 15K (~240 non-uploaders / ~45 uploaders born before Mar 2).
|
|
143
|
+
WITH h8pop AS (SELECT p.*,
|
|
144
|
+
(SELECT count(*) FROM ev e WHERE e.uid = p.uid AND e.t <= p.first_t + INTERVAL '30 days') AS pre_n,
|
|
145
|
+
(SELECT count(*) FROM ev e WHERE e.uid = p.uid AND e.t > p.first_t + INTERVAL '30 days') AS post_n
|
|
146
|
+
FROM puu p WHERE p.born AND p.first_t < TIMESTAMP '2026-03-02')
|
|
147
|
+
SELECT
|
|
148
|
+
(SELECT sum(post_n)::DOUBLE / sum(pre_n) FROM h8pop WHERE NOT uploader) AS nonup_post_pre,
|
|
149
|
+
(SELECT sum(post_n)::DOUBLE / sum(pre_n) FROM h8pop WHERE uploader) AS up_post_pre,
|
|
150
|
+
(SELECT count(*) FROM h8pop WHERE NOT uploader) AS nonup_users,
|
|
151
|
+
(SELECT count(*) FROM h8pop WHERE uploader) AS up_users;
|
|
152
|
+
|
|
153
|
+
-- ── H9-renewal-spike: end-of-quarter clone burst, days 85-94 ──
|
|
154
|
+
-- Expect renewals ~3x and coverage reviews ~1.8-2x a LOCAL baseline
|
|
155
|
+
-- (days 75-84 ∪ 96-105; day 95 skipped — clone jitter bleeds into Apr 6).
|
|
156
|
+
SELECT event,
|
|
157
|
+
count(*) FILTER (WHERE t > TIMESTAMP '2026-03-27' AND t < TIMESTAMP '2026-04-06') / 10.0 AS spike_per_day,
|
|
158
|
+
count(*) FILTER (WHERE (day_idx BETWEEN 75 AND 84) OR (day_idx BETWEEN 96 AND 105)) / 20.0 AS local_base_per_day
|
|
159
|
+
FROM ev WHERE event IN ('renewal completed', 'coverage reviewed') GROUP BY 1;
|
|
160
|
+
|
|
161
|
+
-- ── H10-claim-premium: one-shot doubling after claim ──
|
|
162
|
+
-- Expect: zero payments > 600 among H8-untouched non-claimants (organic
|
|
163
|
+
-- premium caps at 600); claimant/non-claimant avg ~1.5x (only ~half of a
|
|
164
|
+
-- claimant's payments double — the 2.0x lives on the doubled payments).
|
|
165
|
+
SELECT
|
|
166
|
+
(SELECT count(*) FROM ev e JOIN puu p ON e.uid = p.uid
|
|
167
|
+
WHERE e.event = 'payment made' AND e.premium_amount > 600 AND p.claims = 0
|
|
168
|
+
AND ((NOT p.born) OR p.uploader)) AS gt600_untouched_nonclaimants,
|
|
169
|
+
(SELECT count(*) FROM ev e JOIN puu p ON e.uid = p.uid
|
|
170
|
+
WHERE e.event = 'payment made' AND e.premium_amount > 600 AND p.claims > 0) AS gt600_claimants,
|
|
171
|
+
(SELECT avg(e.premium_amount) FROM ev e JOIN puu p ON e.uid = p.uid
|
|
172
|
+
WHERE e.event = 'payment made' AND p.claims > 0) AS claimant_avg,
|
|
173
|
+
(SELECT avg(e.premium_amount) FROM ev e JOIN puu p ON e.uid = p.uid
|
|
174
|
+
WHERE e.event = 'payment made' AND p.claims = 0) AS nonclaimant_avg;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* insurance-application.verify.mjs — thin wrapper around the story runner.
|
|
4
|
+
*
|
|
5
|
+
* All verification logic lives in the `stories` export of
|
|
6
|
+
* ./insurance-application.js; this script just streams the shards and
|
|
7
|
+
* delegates. It is equivalent to:
|
|
8
|
+
*
|
|
9
|
+
* node scripts/verify-stories.mjs dungeons/vertical/insurance-application/insurance-application.js --data-prefix verify-insurance-application
|
|
10
|
+
*
|
|
11
|
+
* Generate first:
|
|
12
|
+
* node scripts/verify-runner.mjs dungeons/vertical/insurance-application/insurance-application.js verify-insurance-application
|
|
13
|
+
* Run:
|
|
14
|
+
* node dungeons/vertical/insurance-application/insurance-application.verify.mjs [prefix] # default verify-insurance-application
|
|
15
|
+
*/
|
|
16
|
+
import fs from 'node:fs';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import readline from 'node:readline';
|
|
19
|
+
import { execFile } from 'node:child_process';
|
|
20
|
+
import { promisify } from 'node:util';
|
|
21
|
+
import { buildIdentityMap, evaluateStories, VERDICT_RANK } from '@ak--47/dungeon-master/verify';
|
|
22
|
+
import config, { stories } from './insurance-application.js';
|
|
23
|
+
|
|
24
|
+
const PREFIX = process.argv[2] || 'verify-insurance-application';
|
|
25
|
+
const prefixPath = path.join('data', PREFIX);
|
|
26
|
+
|
|
27
|
+
async function loadShards(suffix) {
|
|
28
|
+
// streaming load: full-fidelity EVENTS shards exceed the readFileSync cap
|
|
29
|
+
const dir = path.dirname(prefixPath), base = path.basename(prefixPath);
|
|
30
|
+
const out = [];
|
|
31
|
+
if (!fs.existsSync(dir)) return out;
|
|
32
|
+
for (const f of fs.readdirSync(dir).filter(f => f.startsWith(`${base}-${suffix}`) && f.endsWith('.json')).sort()) {
|
|
33
|
+
const rl = readline.createInterface({ input: fs.createReadStream(path.join(dir, f)), crlfDelay: Infinity });
|
|
34
|
+
for await (const line of rl) {
|
|
35
|
+
if (line.trim()) out.push(JSON.parse(line));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const events = await loadShards('EVENTS');
|
|
42
|
+
const profiles = await loadShards('USERS');
|
|
43
|
+
if (!events.length) {
|
|
44
|
+
console.error(`no shards at ${prefixPath}-EVENTS*.json — run: node scripts/verify-runner.mjs dungeons/vertical/insurance-application/insurance-application.js ${PREFIX}`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
console.log(`insurance-application — events=${events.length} users=${profiles.length} (${prefixPath})`);
|
|
48
|
+
|
|
49
|
+
const execFileP = promisify(execFile);
|
|
50
|
+
const runSql = async (sql) => {
|
|
51
|
+
const { stdout } = await execFileP('duckdb', ['-json', '-c', sql.replaceAll('{{PREFIX}}', prefixPath)], { maxBuffer: 64 * 1024 * 1024 });
|
|
52
|
+
return stdout.trim() ? JSON.parse(stdout) : [];
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// All stories are DuckDB-backed. H5's TTC breakdown segments by account_type,
|
|
56
|
+
// which lives on the "account created" EVENT (not a profile prop), so
|
|
57
|
+
// emulateBreakdown's breakdownByUserProperty can't serve it — the SQL read
|
|
58
|
+
// derives the started→approved gap directly, which is exact for this hook
|
|
59
|
+
// because H5 pins approvals to firstStart + target + [0,4h) by construction.
|
|
60
|
+
const results = await evaluateStories(stories, events, {
|
|
61
|
+
profiles,
|
|
62
|
+
funnels: config.funnels,
|
|
63
|
+
identityMap: buildIdentityMap(profiles),
|
|
64
|
+
runSql,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
let worst = 'NAILED';
|
|
68
|
+
for (const r of results) {
|
|
69
|
+
console.log(`${r.verdict.padEnd(7)} ${r.id}`);
|
|
70
|
+
for (const a of r.assertions) console.log(` ${a.verdict.padEnd(7)} ${a.detail}`);
|
|
71
|
+
if (VERDICT_RANK[r.verdict] < VERDICT_RANK[worst]) worst = r.verdict;
|
|
72
|
+
}
|
|
73
|
+
console.log(`\nworst verdict: ${worst}`);
|
|
74
|
+
process.exit(VERDICT_RANK[worst] >= VERDICT_RANK.STRONG ? 0 : 1);
|