@ak--47/dungeon-master 1.5.4 → 1.6.1
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/powertools/SKILL.md +75 -0
- package/.claude/skills/powertools/pt.mjs +91 -0
- package/.claude/skills/powertools/snapshot-project.mjs +124 -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 +224 -0
- package/HOOKS.md +566 -112
- package/README.md +30 -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 +7 -3
- 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 +151 -59
- 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 +298 -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,205 @@
|
|
|
1
|
+
-- ============================================================
|
|
2
|
+
-- logistics.js — v1.6 human-inspection queries (DuckDB)
|
|
3
|
+
--
|
|
4
|
+
-- Every query is keyed to a story id in logistics.js's `stories` export;
|
|
5
|
+
-- the machine-checked verdicts come from:
|
|
6
|
+
-- node scripts/verify-stories.mjs dungeons/vertical/logistics/logistics.js --data-prefix verify-logistics
|
|
7
|
+
-- Generate first:
|
|
8
|
+
-- node scripts/verify-runner.mjs dungeons/vertical/logistics/logistics.js verify-logistics
|
|
9
|
+
-- Run this file:
|
|
10
|
+
-- duckdb -c ".read dungeons/vertical/logistics/logistics.sql"
|
|
11
|
+
-- ============================================================
|
|
12
|
+
|
|
13
|
+
-- ── identity-resolution prelude ─────────────────────────────
|
|
14
|
+
-- avgDevicePerUser: 2 + account 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-logistics-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-logistics-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
|
+
-- Per-user counts. Inventory checks are ONE-SIDED (only H6 deletes them,
|
|
34
|
+
-- for trial users, and H9 reads them AFTER H6 ran) — output counts equal
|
|
35
|
+
-- H9's hook-time counts exactly. POs are deleted only for 16+ checkers
|
|
36
|
+
-- (H9); stockout alerts only for enterprise (H3); clones are identified
|
|
37
|
+
-- by report_type = 'integration_summary' (outside the organic pool).
|
|
38
|
+
CREATE OR REPLACE VIEW per_user AS
|
|
39
|
+
SELECT uid,
|
|
40
|
+
count(*) AS total,
|
|
41
|
+
count(*) FILTER (WHERE event = 'inventory checked') AS inv,
|
|
42
|
+
count(*) FILTER (WHERE event = 'purchase order created') AS po,
|
|
43
|
+
count(*) FILTER (WHERE event = 'stockout alert') AS so,
|
|
44
|
+
count(*) FILTER (WHERE event = 'integration connected') AS ic,
|
|
45
|
+
count(*) FILTER (WHERE event = 'report generated' AND report_type = 'integration_summary') AS clones,
|
|
46
|
+
min(t) AS first_t
|
|
47
|
+
FROM ev GROUP BY 1;
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
-- ── H1-month-end-pages ──────────────────────────────────────
|
|
51
|
+
-- Reports on calendar days >= 28 get report_pages ×2.5 (floored). H4's
|
|
52
|
+
-- clones (report_type 'integration_summary', uniform [5,25] pages stamped
|
|
53
|
+
-- AFTER H1) dilute a pooled read — exclude them; they are the placebo arm.
|
|
54
|
+
SELECT (report_type = 'integration_summary') AS is_clone,
|
|
55
|
+
CASE WHEN extract(day FROM t) >= 28 THEN 'month_end' ELSE 'mid_month' END AS bucket,
|
|
56
|
+
count(*) AS n, round(avg(report_pages), 1) AS avg_pages
|
|
57
|
+
FROM ev WHERE event = 'report generated'
|
|
58
|
+
GROUP BY 1, 2 ORDER BY 1, 2;
|
|
59
|
+
-- read: organic month_end/mid_month ≈ 2.4-2.5 (floor loss ~1%);
|
|
60
|
+
-- clone ratio ≈ 1.0 (placebo)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
-- ── H2-rush-order-premium ───────────────────────────────────
|
|
64
|
+
-- 'urgent' POs get unit_cost ×1.5 (floored); 'expedited' is untreated.
|
|
65
|
+
SELECT priority, count(*) AS n, round(avg(unit_cost), 1) AS avg_cost
|
|
66
|
+
FROM ev WHERE event = 'purchase order created'
|
|
67
|
+
GROUP BY 1 ORDER BY 1;
|
|
68
|
+
-- read: urgent/standard ≈ 1.5; expedited/standard ≈ 1.0 (placebo)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
-- ── H3-stockout-by-tier ─────────────────────────────────────
|
|
72
|
+
-- Enterprise loses 10% of stockout alerts. Per-user LEVELS are dominated
|
|
73
|
+
-- by persona multipliers (enterprise 5x) — read the stockout-per-
|
|
74
|
+
-- inventory-check ratio instead; the supply-chain worldEvent (×3, days
|
|
75
|
+
-- 35-40) hits all tiers alike and cancels cross-tier.
|
|
76
|
+
SELECT u.company_tier AS tier, count(*) AS users,
|
|
77
|
+
round(sum(p.so)::DOUBLE / sum(p.inv), 4) AS so_per_inv
|
|
78
|
+
FROM per_user p JOIN users u ON u.distinct_id::VARCHAR = p.uid
|
|
79
|
+
GROUP BY 1 ORDER BY 1;
|
|
80
|
+
-- read: enterprise/small_business ≈ 0.88; mid_market/small_business ≈ 1.0
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
-- ── H4-integration-reports ──────────────────────────────────
|
|
84
|
+
-- Users with >= 3 'integration connected' get a cloned 'report generated'
|
|
85
|
+
-- per integration at 65% (+1-5d later, report_type 'integration_summary').
|
|
86
|
+
-- H6 (the only integration-deleter) runs BEFORE H4 → output counts equal
|
|
87
|
+
-- H4's hook-time counts exactly; the treated cohort is fully recoverable.
|
|
88
|
+
SELECT (p.ic >= 3) AS treated, count(*) AS users,
|
|
89
|
+
round(sum(p.clones)::DOUBLE / nullif(sum(p.ic), 0), 4) AS clones_per_integration,
|
|
90
|
+
round(avg((p.clones > 0)::INT), 4) AS any_clone_share
|
|
91
|
+
FROM per_user p GROUP BY 1 ORDER BY 1;
|
|
92
|
+
-- read: treated clones_per_integration ≈ 0.63 (0.65 × ~2% future-guard
|
|
93
|
+
-- loss); untreated any_clone_share = 0 (structural — leakage would
|
|
94
|
+
-- mean the hook-order invariant broke)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
-- ── H5-alert-fatigue ────────────────────────────────────────
|
|
98
|
+
-- Users with > 30 stockout alerts: response_time_hours scaled from alert
|
|
99
|
+
-- index 20 on, ×(1.5 + 1.5×(idx-20)/n). Hook index = record order; time
|
|
100
|
+
-- order matches exactly (iteration placebo 1.000). Control = 20-30-alert
|
|
101
|
+
-- users (never treated, same iid response_time pool).
|
|
102
|
+
WITH al AS (
|
|
103
|
+
SELECT uid, response_time_hours AS rt,
|
|
104
|
+
row_number() OVER (PARTITION BY uid ORDER BY t) - 1 AS idx,
|
|
105
|
+
count(*) OVER (PARTITION BY uid) AS n
|
|
106
|
+
FROM ev WHERE event = 'stockout alert'
|
|
107
|
+
)
|
|
108
|
+
SELECT arm, count(DISTINCT uid) AS users, round(avg(rt), 2) AS avg_rt FROM (
|
|
109
|
+
SELECT uid, rt, 'late_treated' AS arm FROM al WHERE n > 30 AND idx >= 25
|
|
110
|
+
UNION ALL
|
|
111
|
+
SELECT uid, rt, 'early_untreated' FROM al WHERE n > 30 AND idx <= 14
|
|
112
|
+
UNION ALL
|
|
113
|
+
SELECT uid, rt, 'control_20_30' FROM al WHERE n BETWEEN 20 AND 30
|
|
114
|
+
) GROUP BY 1 ORDER BY 1;
|
|
115
|
+
-- read: late_treated/control ≈ 2.0; early_untreated/control ≈ 1.0
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
-- ── H6-trial-churn ──────────────────────────────────────────
|
|
119
|
+
-- Trial-tier users lose 50% of events after day 7 from first event
|
|
120
|
+
-- (v1.6 behavior change: v1.5 keyed on record.length < 10 — matched ~0.9%
|
|
121
|
+
-- of users, never touched trials). Cross-tier levels are incomparable
|
|
122
|
+
-- (activeWindow 14d, 0.4x multiplier) — read each tier's own
|
|
123
|
+
-- rate(day 8-13)/rate(day 1-6); the ratio cancels the level.
|
|
124
|
+
WITH fe AS (SELECT uid, min(t) AS f FROM ev GROUP BY 1),
|
|
125
|
+
rd AS (SELECT e.uid, date_diff('day', fe.f, e.t) AS d FROM ev e JOIN fe ON fe.uid = e.uid)
|
|
126
|
+
SELECT u.company_tier AS tier, count(DISTINCT r.uid) AS users,
|
|
127
|
+
round(count(*) FILTER (WHERE d BETWEEN 8 AND 13)::DOUBLE
|
|
128
|
+
/ count(*) FILTER (WHERE d BETWEEN 1 AND 6), 4) AS wk2_over_wk1
|
|
129
|
+
FROM rd r JOIN users u ON u.distinct_id::VARCHAR = r.uid
|
|
130
|
+
GROUP BY 1 ORDER BY 1;
|
|
131
|
+
-- read: trial ratio ≈ 0.5 × small_business ratio (DiD ≈ 0.55);
|
|
132
|
+
-- mid_market ≈ small_business (placebo)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
-- ── H7-enterprise-profiles ──────────────────────────────────
|
|
136
|
+
-- user hook overwrites warehouse_count/employee_count per tier with
|
|
137
|
+
-- disjoint uniform ranges. Personas cover 100% of users → ranges EXACT.
|
|
138
|
+
SELECT company_tier AS tier, count(*) AS users,
|
|
139
|
+
min(warehouse_count) AS min_wh, max(warehouse_count) AS max_wh,
|
|
140
|
+
min(employee_count) AS min_emp, max(employee_count) AS max_emp
|
|
141
|
+
FROM users GROUP BY 1 ORDER BY 1;
|
|
142
|
+
-- read: enterprise wh [5,15] emp [200,2000]; mid_market wh [2,6]
|
|
143
|
+
-- emp [20,200]; small_business wh [1,3] emp [5,80];
|
|
144
|
+
-- trial wh = 1 emp [1,10] — zero out-of-range rows
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
-- ── H8-smb-conversion-drop ──────────────────────────────────
|
|
148
|
+
-- small_business loses 35% of 'alert configured' — last step of
|
|
149
|
+
-- Integration Setup. CAUTION: cross-event SQL step-pairing here is the
|
|
150
|
+
-- documented greedy-single-pass limitation; the story asserts conversion
|
|
151
|
+
-- through the emulator (timeToConvert, 48h treated / 336h Supplier
|
|
152
|
+
-- Management placebo). This query shows the RAW event-count shadow only.
|
|
153
|
+
SELECT u.company_tier AS tier,
|
|
154
|
+
count(*) FILTER (WHERE e.event = 'report generated') AS step2_events,
|
|
155
|
+
count(*) FILTER (WHERE e.event = 'alert configured') AS step3_events,
|
|
156
|
+
round(count(*) FILTER (WHERE e.event = 'alert configured')::DOUBLE
|
|
157
|
+
/ count(*) FILTER (WHERE e.event = 'report generated'), 4) AS ac_per_rg
|
|
158
|
+
FROM ev e JOIN users u ON u.distinct_id::VARCHAR = e.uid
|
|
159
|
+
WHERE u.company_tier IN ('small_business', 'mid_market')
|
|
160
|
+
GROUP BY 1 ORDER BY 1;
|
|
161
|
+
-- read: smb ac_per_rg depressed vs mid_market (raw shadow of the 35% drop;
|
|
162
|
+
-- trust the story's emulator verdict for the funnel-conversion read)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
-- ── H9-inventory-magic-number ───────────────────────────────
|
|
166
|
+
-- Sweet 5-15 inventory checks → PO quantity ×1.4; 16+ checks → 60% of POs
|
|
167
|
+
-- dropped. Value read: quantity is an iid pool draw — sweet/low mean ratio
|
|
168
|
+
-- reads the knob; unit_cost is the placebo.
|
|
169
|
+
SELECT CASE WHEN p.inv BETWEEN 5 AND 15 THEN 'sweet' WHEN p.inv <= 4 THEN 'low' END AS arm,
|
|
170
|
+
count(DISTINCT p.uid) AS users,
|
|
171
|
+
round(avg(e.quantity), 1) AS avg_qty, round(avg(e.unit_cost), 1) AS avg_cost
|
|
172
|
+
FROM per_user p JOIN ev e ON e.uid = p.uid AND e.event = 'purchase order created'
|
|
173
|
+
WHERE p.inv <= 15
|
|
174
|
+
GROUP BY 1 ORDER BY 1;
|
|
175
|
+
-- read: sweet/low avg_qty ≈ 1.4; avg_cost ≈ 1.0 (placebo)
|
|
176
|
+
|
|
177
|
+
-- volume read: PO count is activity-coupled → no cross-arm level works.
|
|
178
|
+
-- Read PO-per-inventory-check within small_business only (constant
|
|
179
|
+
-- conversionModifier), treated cliff bin 16-23 vs adjacent untreated
|
|
180
|
+
-- 12-15, with a flatness guard on the pre-cliff bins.
|
|
181
|
+
SELECT CASE WHEN p.inv BETWEEN 4 AND 7 THEN 'b04_07'
|
|
182
|
+
WHEN p.inv BETWEEN 8 AND 11 THEN 'b08_11'
|
|
183
|
+
WHEN p.inv BETWEEN 12 AND 15 THEN 'b12_15'
|
|
184
|
+
WHEN p.inv BETWEEN 16 AND 23 THEN 'b16_23'
|
|
185
|
+
WHEN p.inv BETWEEN 24 AND 40 THEN 'b24_40' END AS bin,
|
|
186
|
+
count(*) AS users, round(sum(p.po)::DOUBLE / sum(p.inv), 4) AS po_per_inv
|
|
187
|
+
FROM per_user p JOIN users u ON u.distinct_id::VARCHAR = p.uid
|
|
188
|
+
WHERE u.company_tier = 'small_business' AND p.inv BETWEEN 4 AND 40
|
|
189
|
+
GROUP BY 1 ORDER BY 1;
|
|
190
|
+
-- read: pre-cliff bins flat (~1.6, mild organic decline), then
|
|
191
|
+
-- b16_23/b12_15 ≈ 0.4 × organic gradient [0.83, 1.0] ≈ 0.37
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
-- ── H10-onboarding-ttc ──────────────────────────────────────
|
|
195
|
+
-- funnel-post scales Onboarding inter-step gaps: enterprise ×0.71,
|
|
196
|
+
-- small_business/trial ×1.3, mid_market untouched (v1.6 scopes the hook
|
|
197
|
+
-- to Onboarding only). CAUTION: cross-event TTC SQL is the documented
|
|
198
|
+
-- greedy-single-pass limitation — it pairs steps across funnel instances
|
|
199
|
+
-- and buries the signal. The story asserts TTC through the emulator
|
|
200
|
+
-- (timeToConvert, 93.6h window = 72h generative × 1.3 max stretch);
|
|
201
|
+
-- trust the story verdict, not ad-hoc pair SQL. Only born-in-dataset
|
|
202
|
+
-- users (~12%) have 'account created' in-window — cohort sanity below.
|
|
203
|
+
SELECT u.company_tier AS tier, count(*) AS account_created_events
|
|
204
|
+
FROM ev e JOIN users u ON u.distinct_id::VARCHAR = e.uid
|
|
205
|
+
WHERE e.event = 'account created' GROUP BY 1 ORDER BY 1;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* logistics.verify.mjs — thin wrapper around the story runner.
|
|
4
|
+
*
|
|
5
|
+
* All verification logic lives in the `stories` export of ./logistics.js;
|
|
6
|
+
* this script just streams the shards and delegates. It is equivalent to:
|
|
7
|
+
*
|
|
8
|
+
* node scripts/verify-stories.mjs dungeons/vertical/logistics/logistics.js --data-prefix verify-logistics
|
|
9
|
+
*
|
|
10
|
+
* Generate first:
|
|
11
|
+
* node scripts/verify-runner.mjs dungeons/vertical/logistics/logistics.js verify-logistics
|
|
12
|
+
* Run:
|
|
13
|
+
* node dungeons/vertical/logistics/logistics.verify.mjs [prefix] # default verify-logistics
|
|
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 './logistics.js';
|
|
22
|
+
|
|
23
|
+
const PREFIX = process.argv[2] || 'verify-logistics';
|
|
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/logistics/logistics.js ${PREFIX}`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
console.log(`logistics — 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 emulator stories carry their own
|
|
55
|
+
// explicit conversion windows (H8: 48h Integration Setup treated arm, 336h
|
|
56
|
+
// Supplier Management placebo; H10: 93.6h = 72h Onboarding generative
|
|
57
|
+
// window × the 1.3 small-business stretch, covering the stretched support),
|
|
58
|
+
// so no funnel-default threading is needed
|
|
59
|
+
const results = await evaluateStories(stories, events, {
|
|
60
|
+
profiles,
|
|
61
|
+
funnels: config.funnels,
|
|
62
|
+
identityMap: buildIdentityMap(profiles),
|
|
63
|
+
runSql,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
let worst = 'NAILED';
|
|
67
|
+
for (const r of results) {
|
|
68
|
+
console.log(`${r.verdict.padEnd(7)} ${r.id}`);
|
|
69
|
+
for (const a of r.assertions) console.log(` ${a.verdict.padEnd(7)} ${a.detail}`);
|
|
70
|
+
if (VERDICT_RANK[r.verdict] < VERDICT_RANK[worst]) worst = r.verdict;
|
|
71
|
+
}
|
|
72
|
+
console.log(`\nworst verdict: ${worst}`);
|
|
73
|
+
process.exit(VERDICT_RANK[worst] >= VERDICT_RANK.STRONG ? 0 : 1);
|