@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,183 @@
|
|
|
1
|
+
-- ============================================================
|
|
2
|
+
-- food-delivery.js — v1.6.0 hook verification queries (human eyeball)
|
|
3
|
+
-- Machine contract lives in the `stories` export of food-delivery.js;
|
|
4
|
+
-- run ./food-delivery.verify.mjs for verdicts. These queries mirror the
|
|
5
|
+
-- story reads for interactive inspection.
|
|
6
|
+
--
|
|
7
|
+
-- Generate first:
|
|
8
|
+
-- node scripts/verify-runner.mjs dungeons/vertical/food-delivery/food-delivery.js verify-food-delivery
|
|
9
|
+
--
|
|
10
|
+
-- Key derivation notes (full math in HOOK STORIES block of food-delivery.js):
|
|
11
|
+
-- - H1/H7 read delivered-per-PLACED ratios: 'order placed' shares the soup
|
|
12
|
+
-- HOD/hash distribution but is untouched by either drop, so it cancels
|
|
13
|
+
-- the confound. Raw delivered HOD volume does NOT show a clean 0.70.
|
|
14
|
+
-- - H6 zero-post share lands near 0.25 (not the 0.60 knob): the deletion
|
|
15
|
+
-- removes late 'subscription started' events themselves, so churned
|
|
16
|
+
-- users with a late trial start vanish from the visible cohort.
|
|
17
|
+
-- - H8 buckets by HOOK-TIME order count; output counts are contaminated by
|
|
18
|
+
-- H4 duplication and H6 deletion, so the clean read excludes rainy-window
|
|
19
|
+
-- orderers and users with zero post-day-14 activity.
|
|
20
|
+
-- - H10 reads reorders-per-DELIVERED: last funnel step fires only on
|
|
21
|
+
-- conversion (determineConversion, lib/generators/funnels.js), so the
|
|
22
|
+
-- ratio recovers the conversionRate scaling exactly (1.40 / 0.70).
|
|
23
|
+
-- ============================================================
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
-- H1: RUSH-HOUR KEEP RATE — delivered-per-placed off/rush ≈ 0.70
|
|
27
|
+
WITH ev AS (
|
|
28
|
+
SELECT event, hour(time::TIMESTAMP) AS hr
|
|
29
|
+
FROM read_json_auto('data/verify-food-delivery-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
30
|
+
WHERE event IN ('order delivered','order placed')
|
|
31
|
+
), agg AS (
|
|
32
|
+
SELECT (hr BETWEEN 11 AND 13) OR (hr BETWEEN 17 AND 20) AS rush,
|
|
33
|
+
count(*) FILTER (WHERE event='order delivered') AS del,
|
|
34
|
+
count(*) FILTER (WHERE event='order placed') AS placed
|
|
35
|
+
FROM ev GROUP BY 1
|
|
36
|
+
)
|
|
37
|
+
SELECT rush, del, placed, round(del::DOUBLE/placed, 3) AS del_per_placed FROM agg ORDER BY rush;
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
-- H2: COUPON INJECTION — coupons-per-checkout diff ≈ +0.30 (Free − QB+); cpu ratio ≈ 2.2
|
|
41
|
+
WITH pu AS (
|
|
42
|
+
SELECT user_id::VARCHAR AS uid, any_value(subscription_tier) AS tier,
|
|
43
|
+
count(*) FILTER (WHERE event='coupon applied') AS coupons,
|
|
44
|
+
count(*) FILTER (WHERE event='checkout started') AS checkouts
|
|
45
|
+
FROM read_json_auto('data/verify-food-delivery-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
46
|
+
WHERE event IN ('coupon applied','checkout started') GROUP BY 1
|
|
47
|
+
)
|
|
48
|
+
SELECT tier, count(*) AS users,
|
|
49
|
+
round(sum(coupons)::DOUBLE/count(*), 2) AS coupons_per_user,
|
|
50
|
+
round(sum(coupons)::DOUBLE/nullif(sum(checkouts),0), 3) AS coupons_per_checkout
|
|
51
|
+
FROM pu GROUP BY tier ORDER BY tier;
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
-- H3: LATE-NIGHT MUNCHIES — flip inversion (late−off)/(1−off) ≈ 0.70; price ratio ≈ 1.30
|
|
55
|
+
WITH ev AS (
|
|
56
|
+
SELECT event, cuisine_type, TRY_CAST(item_price AS DOUBLE) AS price,
|
|
57
|
+
hour(time::TIMESTAMP) AS hr
|
|
58
|
+
FROM read_json_auto('data/verify-food-delivery-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
59
|
+
WHERE event IN ('restaurant viewed','restaurant browsed','item added to cart')
|
|
60
|
+
)
|
|
61
|
+
SELECT
|
|
62
|
+
round(avg((cuisine_type='American')::INT) FILTER (WHERE event='restaurant viewed' AND (hr>=22 OR hr<=2)), 3) AS amer_late,
|
|
63
|
+
round(avg((cuisine_type='American')::INT) FILTER (WHERE event='restaurant viewed' AND hr BETWEEN 6 AND 18), 3) AS amer_off,
|
|
64
|
+
round(avg((cuisine_type='American')::INT) FILTER (WHERE event='restaurant browsed'), 3) AS amer_organic_ctrl,
|
|
65
|
+
round(avg(price) FILTER (WHERE event='item added to cart' AND (hr>=22 OR hr<=2)) /
|
|
66
|
+
avg(price) FILTER (WHERE event='item added to cart' AND hr BETWEEN 6 AND 18), 3) AS price_ratio
|
|
67
|
+
FROM ev;
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
-- H4: RAINY WEEK (day_idx 20-27) — dup share ≈ 0.40, fee ratio ≈ 2.0, vol RoR vs checkout ≈ 1.4
|
|
71
|
+
WITH ev AS (
|
|
72
|
+
SELECT user_id::VARCHAR AS uid, event, order_id, TRY_CAST(delivery_fee AS DOUBLE) AS fee,
|
|
73
|
+
date_diff('day', TIMESTAMP '2026-01-01 00:00:00', time::TIMESTAMP) AS day_idx
|
|
74
|
+
FROM read_json_auto('data/verify-food-delivery-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
75
|
+
WHERE event IN ('order placed','checkout started')
|
|
76
|
+
)
|
|
77
|
+
SELECT
|
|
78
|
+
round((count(*) FILTER (WHERE event='order placed' AND day_idx BETWEEN 20 AND 27)
|
|
79
|
+
- count(DISTINCT uid || '|' || order_id) FILTER (WHERE event='order placed' AND day_idx BETWEEN 20 AND 27))::DOUBLE
|
|
80
|
+
/ count(DISTINCT uid || '|' || order_id) FILTER (WHERE event='order placed' AND day_idx BETWEEN 20 AND 27), 3) AS dup_share,
|
|
81
|
+
round(avg(fee) FILTER (WHERE event='order placed' AND day_idx BETWEEN 20 AND 27) /
|
|
82
|
+
avg(fee) FILTER (WHERE event='order placed' AND (day_idx BETWEEN 10 AND 19 OR day_idx BETWEEN 28 AND 37)), 3) AS fee_ratio,
|
|
83
|
+
round((count(*) FILTER (WHERE event='order placed' AND day_idx BETWEEN 20 AND 27)::DOUBLE /
|
|
84
|
+
count(*) FILTER (WHERE event='order placed' AND (day_idx BETWEEN 10 AND 19 OR day_idx BETWEEN 28 AND 37))) /
|
|
85
|
+
(count(*) FILTER (WHERE event='checkout started' AND day_idx BETWEEN 20 AND 27)::DOUBLE /
|
|
86
|
+
count(*) FILTER (WHERE event='checkout started' AND (day_idx BETWEEN 10 AND 19 OR day_idx BETWEEN 28 AND 37))), 3) AS vol_ror
|
|
87
|
+
FROM ev;
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
-- H5: REFERRAL POWER USERS (born-in only) — reorders/user ratio ≈ 1.4; ratings 4.4 vs 2.8
|
|
91
|
+
WITH ev AS (
|
|
92
|
+
SELECT user_id::VARCHAR AS uid, event, referral_code, TRY_CAST(food_rating AS DOUBLE) AS rating
|
|
93
|
+
FROM read_json_auto('data/verify-food-delivery-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
94
|
+
WHERE event IN ('account created','reorder initiated','order rated')
|
|
95
|
+
), born AS (
|
|
96
|
+
SELECT uid, bool_or(referral_code = true) AS referred
|
|
97
|
+
FROM ev WHERE event='account created' GROUP BY 1
|
|
98
|
+
), pu AS (
|
|
99
|
+
SELECT b.uid, b.referred,
|
|
100
|
+
count(*) FILTER (WHERE e.event='reorder initiated') AS reorders,
|
|
101
|
+
avg(e.rating) FILTER (WHERE e.event='order rated') AS user_rating
|
|
102
|
+
FROM born b LEFT JOIN ev e ON e.uid=b.uid GROUP BY 1,2
|
|
103
|
+
)
|
|
104
|
+
SELECT referred, count(*) AS users,
|
|
105
|
+
round(sum(reorders)::DOUBLE/count(*), 2) AS reorders_per_user,
|
|
106
|
+
round(avg(user_rating), 2) AS mean_user_rating
|
|
107
|
+
FROM pu GROUP BY referred ORDER BY referred;
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
-- H6: TRIAL CHURN (survivor-biased view) — zero-post share ~0.25 nonactivated vs ~0.04 activated
|
|
111
|
+
WITH ev AS (
|
|
112
|
+
SELECT user_id::VARCHAR AS uid, time::TIMESTAMP AS t, event, trial
|
|
113
|
+
FROM read_json_auto('data/verify-food-delivery-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
114
|
+
), fu AS (SELECT uid, min(t) AS first_t FROM ev GROUP BY 1),
|
|
115
|
+
trial AS (SELECT uid FROM ev WHERE event='subscription started' AND trial = true GROUP BY 1),
|
|
116
|
+
pux AS (
|
|
117
|
+
SELECT f.uid,
|
|
118
|
+
count(*) FILTER (WHERE e.event='order placed' AND e.t <= f.first_t + INTERVAL '14 days') AS early_orders,
|
|
119
|
+
count(*) FILTER (WHERE e.t > f.first_t + INTERVAL '14 days') AS post_n
|
|
120
|
+
FROM fu f JOIN trial tr ON tr.uid=f.uid JOIN ev e ON e.uid=f.uid GROUP BY 1
|
|
121
|
+
)
|
|
122
|
+
SELECT (early_orders >= 3) AS activated, count(*) AS users,
|
|
123
|
+
round(avg(CASE WHEN post_n = 0 THEN 1.0 ELSE 0 END), 3) AS zero_post_share
|
|
124
|
+
FROM pux GROUP BY 1 ORDER BY 1;
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
-- H7: HASH-BUCKET DROP — delivered-per-placed odd/even ≈ 0.70
|
|
128
|
+
WITH pu AS (
|
|
129
|
+
SELECT user_id::VARCHAR AS uid, (ascii(substr(user_id::VARCHAR,1,1)) % 2 = 0) AS even_bucket,
|
|
130
|
+
count(*) FILTER (WHERE event='order placed') AS placed,
|
|
131
|
+
count(*) FILTER (WHERE event='order delivered') AS delivered
|
|
132
|
+
FROM read_json_auto('data/verify-food-delivery-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
133
|
+
WHERE event IN ('order placed','order delivered') GROUP BY 1,2
|
|
134
|
+
)
|
|
135
|
+
SELECT even_bucket, count(*) AS users,
|
|
136
|
+
round(sum(delivered)::DOUBLE/nullif(sum(placed),0), 3) AS del_per_placed
|
|
137
|
+
FROM pu GROUP BY even_bucket ORDER BY even_bucket;
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
-- H8: ORDER-COUNT MAGIC NUMBER (clean population) — sweet/base ≈ 1.40, over/sweet ≈ 0.46
|
|
141
|
+
WITH raw AS (
|
|
142
|
+
SELECT user_id::VARCHAR AS uid, time::TIMESTAMP AS t, event,
|
|
143
|
+
TRY_CAST(order_total AS DOUBLE) AS ot,
|
|
144
|
+
date_diff('day', TIMESTAMP '2026-01-01 00:00:00', time::TIMESTAMP) AS day_idx
|
|
145
|
+
FROM read_json_auto('data/verify-food-delivery-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
146
|
+
), fu AS (SELECT uid, min(t) AS first_t FROM raw GROUP BY 1),
|
|
147
|
+
flags AS (
|
|
148
|
+
SELECT r.uid,
|
|
149
|
+
max(CASE WHEN r.event='order placed' AND r.day_idx BETWEEN 20 AND 27 THEN 1 ELSE 0 END) AS rainy,
|
|
150
|
+
max(CASE WHEN r.t > f.first_t + INTERVAL '14 days' THEN 1 ELSE 0 END) AS alive,
|
|
151
|
+
count(*) FILTER (WHERE r.event='order placed') AS orders,
|
|
152
|
+
sum(r.ot) FILTER (WHERE r.event='order placed') AS spend
|
|
153
|
+
FROM raw r JOIN fu f USING(uid) GROUP BY 1
|
|
154
|
+
)
|
|
155
|
+
SELECT CASE WHEN orders BETWEEN 4 AND 8 THEN 'sweet' WHEN orders >= 9 THEN 'over' ELSE 'base' END AS bucket,
|
|
156
|
+
count(*) AS users, round(sum(spend)/nullif(sum(orders),0), 1) AS avg_order_total
|
|
157
|
+
FROM flags WHERE rainy = 0 AND alive = 1 AND orders > 0
|
|
158
|
+
GROUP BY 1 ORDER BY 1;
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
-- H9: TIER DELIVERY SPEED — QB+/Free property ratio ≈ 0.479 (= 0.67/1.4) on both props
|
|
162
|
+
SELECT
|
|
163
|
+
round(avg(TRY_CAST(actual_delivery_mins AS DOUBLE)) FILTER (WHERE subscription_tier='QuickBite+' AND event='order delivered') /
|
|
164
|
+
avg(TRY_CAST(actual_delivery_mins AS DOUBLE)) FILTER (WHERE subscription_tier='Free' AND event='order delivered'), 3) AS adm_ratio,
|
|
165
|
+
round(avg(TRY_CAST(eta_mins AS DOUBLE)) FILTER (WHERE subscription_tier='QuickBite+' AND event='order tracked') /
|
|
166
|
+
avg(TRY_CAST(eta_mins AS DOUBLE)) FILTER (WHERE subscription_tier='Free' AND event='order tracked'), 3) AS eta_ratio
|
|
167
|
+
FROM read_json_auto('data/verify-food-delivery-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
168
|
+
WHERE event IN ('order delivered','order tracked');
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
-- H10: CITY DENSITY — reorders-per-delivered dense/base ≈ 1.40, sprawl/base ≈ 0.70
|
|
172
|
+
WITH pu AS (
|
|
173
|
+
SELECT user_id::VARCHAR AS uid, any_value(city) AS city,
|
|
174
|
+
count(*) FILTER (WHERE event='order delivered') AS delivered,
|
|
175
|
+
count(*) FILTER (WHERE event='reorder initiated') AS reorders
|
|
176
|
+
FROM read_json_auto('data/verify-food-delivery-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
177
|
+
WHERE event IN ('order delivered','reorder initiated') GROUP BY 1
|
|
178
|
+
)
|
|
179
|
+
SELECT CASE WHEN city IN ('San Francisco','New York') THEN 'dense'
|
|
180
|
+
WHEN city IN ('Houston','Phoenix') THEN 'sprawl' ELSE 'base' END AS grp,
|
|
181
|
+
count(*) AS users,
|
|
182
|
+
round(sum(reorders)::DOUBLE/nullif(sum(delivered),0), 4) AS reorders_per_delivered
|
|
183
|
+
FROM pu GROUP BY 1 ORDER BY 1;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* food-delivery.verify.mjs — thin wrapper around the story runner.
|
|
4
|
+
*
|
|
5
|
+
* All verification logic lives in the `stories` export of
|
|
6
|
+
* ./food-delivery.js; this script just streams the shards and
|
|
7
|
+
* delegates. It is equivalent to:
|
|
8
|
+
*
|
|
9
|
+
* node scripts/verify-stories.mjs dungeons/vertical/food-delivery/food-delivery.js --data-prefix verify-food-delivery
|
|
10
|
+
*
|
|
11
|
+
* Generate first:
|
|
12
|
+
* node scripts/verify-runner.mjs dungeons/vertical/food-delivery/food-delivery.js verify-food-delivery
|
|
13
|
+
* Run:
|
|
14
|
+
* node dungeons/vertical/food-delivery/food-delivery.verify.mjs [prefix] # default verify-food-delivery
|
|
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 './food-delivery.js';
|
|
23
|
+
|
|
24
|
+
const PREFIX = process.argv[2] || 'verify-food-delivery';
|
|
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/food-delivery/food-delivery.js ${PREFIX}`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
console.log(`food-delivery — 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. Every read here is a ratio-of-ratios
|
|
56
|
+
// (delivered-per-placed, reorders-per-delivered, coupons-per-checkout) or a
|
|
57
|
+
// behavioral-cohort aggregation (hash bucket, hook-time order-count bucket,
|
|
58
|
+
// trial-activation split) — shapes emulateBreakdown's segmentation types
|
|
59
|
+
// don't model, and the RoR normalization is exactly what cancels the soup
|
|
60
|
+
// confounds a raw emulator read would inherit.
|
|
61
|
+
const results = await evaluateStories(stories, events, {
|
|
62
|
+
profiles,
|
|
63
|
+
funnels: config.funnels,
|
|
64
|
+
identityMap: buildIdentityMap(profiles),
|
|
65
|
+
runSql,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
let worst = 'NAILED';
|
|
69
|
+
for (const r of results) {
|
|
70
|
+
console.log(`${r.verdict.padEnd(7)} ${r.id}`);
|
|
71
|
+
for (const a of r.assertions) console.log(` ${a.verdict.padEnd(7)} ${a.detail}`);
|
|
72
|
+
if (VERDICT_RANK[r.verdict] < VERDICT_RANK[worst]) worst = r.verdict;
|
|
73
|
+
}
|
|
74
|
+
console.log(`\nworst verdict: ${worst}`);
|
|
75
|
+
process.exit(VERDICT_RANK[worst] >= VERDICT_RANK.STRONG ? 0 : 1);
|