marketing-mindset 1.5.0 → 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/README.md +7 -0
- package/bin/cli.js +96 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -75,3 +75,10 @@ npx -y marketing-mindset@1.5.0 budget --base 0.03 --lift 0.2 --cpl 4 --daily 500
|
|
|
75
75
|
```
|
|
76
76
|
|
|
77
77
|
The money side of the same floor `sample` computes: contacts needed in both arms, cost per arm and cost to read the test at your cost per contact, days to the floor at a given daily volume, and the spend guard (below the floor the difference is noise, not a result). `--json` for agents. Work with me: https://axelfreeman.com/marketing-engineer.html
|
|
78
|
+
### Dated plan: `plan`
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
npx -y marketing-mindset@1.6.0 plan --base 0.03 --lift 0.2 --daily 500 --weeks 4
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The floor from `sample`/`budget` laid out on a calendar: contacts per week, cumulative volume against the floor, the first date the test can honestly be read, the mechanics-only first week, and an honest verdict when the volume cannot reach the floor inside the plan. `--json` for agents. Work with me: https://axelfreeman.com/marketing-engineer.html
|
package/bin/cli.js
CHANGED
|
@@ -290,6 +290,96 @@ function runBudget(opts) {
|
|
|
290
290
|
return 0;
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
+
|
|
294
|
+
/* plan — the same floor as `sample`/`budget`, laid out on a calendar: weekly volume, the date the
|
|
295
|
+
* test can first be read, the mechanics-only week, and an honest verdict when it does not fit. */
|
|
296
|
+
function runPlan(opts) {
|
|
297
|
+
const base = Number(opts.base === undefined ? 0 : opts.base);
|
|
298
|
+
const lift = Number(opts.lift === undefined ? 0 : opts.lift);
|
|
299
|
+
if (!isFinite(base) || base <= 0 || base >= 1) {
|
|
300
|
+
console.error("plan: --base must be a rate between 0 and 1 (got: " + JSON.stringify(opts.base) + ")");
|
|
301
|
+
return 1;
|
|
302
|
+
}
|
|
303
|
+
if (!isFinite(lift) || lift <= 0) {
|
|
304
|
+
console.error("plan: --lift must be a positive relative lift, e.g. 0.2 for +20%");
|
|
305
|
+
return 1;
|
|
306
|
+
}
|
|
307
|
+
const daily = Number(opts.daily === undefined ? 0 : opts.daily);
|
|
308
|
+
if (!isFinite(daily) || daily <= 0) {
|
|
309
|
+
console.error("plan: --daily must be a positive number of contacts per day (got: " + JSON.stringify(opts.daily) + ")");
|
|
310
|
+
return 1;
|
|
311
|
+
}
|
|
312
|
+
const weeks = opts.weeks === undefined ? 4 : Number(opts.weeks);
|
|
313
|
+
if (!isFinite(weeks) || weeks < 1 || weeks > 52 || Math.floor(weeks) !== weeks) {
|
|
314
|
+
console.error("plan: --weeks must be a whole number between 1 and 52");
|
|
315
|
+
return 1;
|
|
316
|
+
}
|
|
317
|
+
const power = opts.power === undefined ? 0.8 : opts.power;
|
|
318
|
+
const alpha = opts.alpha === undefined ? 0.05 : opts.alpha;
|
|
319
|
+
const start = typeof opts.start === "string" && /^\d{4}-\d{2}-\d{2}$/.test(opts.start)
|
|
320
|
+
? opts.start : new Date().toISOString().slice(0, 10);
|
|
321
|
+
const t0 = Date.parse(start + "T00:00:00Z");
|
|
322
|
+
const day = (n) => new Date(t0 + n * 86400000).toISOString().slice(0, 10);
|
|
323
|
+
|
|
324
|
+
const perArm = sampleSize(base, lift, power, alpha);
|
|
325
|
+
const need = perArm * 2;
|
|
326
|
+
const daysToRead = Math.ceil(need / daily);
|
|
327
|
+
const weekRows = [];
|
|
328
|
+
for (let w = 1; w <= weeks; w++) {
|
|
329
|
+
const confirmed = Math.min(daily * 7 * w, need);
|
|
330
|
+
weekRows.push({
|
|
331
|
+
week: w,
|
|
332
|
+
start: day((w - 1) * 7),
|
|
333
|
+
end: day(w * 7 - 1),
|
|
334
|
+
planned_contacts: daily * 7,
|
|
335
|
+
cumulative: daily * 7 * w,
|
|
336
|
+
against_floor_pct: Math.round(Math.min(100, (daily * 7 * w / need) * 1000) / 10)
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
const capacity = daily * 7 * weeks;
|
|
340
|
+
const readable = capacity >= need;
|
|
341
|
+
const p2 = base * (1 + lift);
|
|
342
|
+
const out = {
|
|
343
|
+
start_date: start,
|
|
344
|
+
base_rate: base, target_rate: Math.round(p2 * 10000) / 10000, relative_lift: lift,
|
|
345
|
+
power: power, alpha: alpha,
|
|
346
|
+
per_arm: perArm, contacts_needed: need, daily_contacts: daily, weeks_planned: weeks,
|
|
347
|
+
weekly_volume: daily * 7, planned_capacity: capacity,
|
|
348
|
+
capacity_vs_floor_pct: Math.round((capacity / need) * 1000) / 10,
|
|
349
|
+
days_to_read: daysToRead,
|
|
350
|
+
read_date: day(daysToRead - 1),
|
|
351
|
+
readable_within_plan: readable,
|
|
352
|
+
plan: weekRows,
|
|
353
|
+
gates: [
|
|
354
|
+
"Week 1 is mechanics only: delivery, bounces, replies per 100 sent. Do not read a conversion rate.",
|
|
355
|
+
"First honest read: " + day(daysToRead - 1) + " - before that date a difference between variants is noise.",
|
|
356
|
+
"Every week: if the delivered volume is under half of the planned volume, fix the channel first; the test is not running."
|
|
357
|
+
],
|
|
358
|
+
kill_rule: "What result ends the test and what result doubles it is decided before the first send, in writing.",
|
|
359
|
+
verdict: readable
|
|
360
|
+
? "Readable in " + weeks + " week(s): the floor lands on " + day(daysToRead - 1) + " at " + daily + " contacts/day."
|
|
361
|
+
: "Not readable in " + weeks + " week(s): " + capacity + " contacts planned against a floor of " + need +
|
|
362
|
+
". At " + daily + " contacts/day it needs " + daysToRead + " days - extend the plan, raise the volume, or change the offer.",
|
|
363
|
+
method: "two-proportion sample size; same formula as `marketing-mindset sample`"
|
|
364
|
+
};
|
|
365
|
+
if (opts.json) {
|
|
366
|
+
console.log(JSON.stringify(out, null, 2));
|
|
367
|
+
return 0;
|
|
368
|
+
}
|
|
369
|
+
console.log("Plan from " + out.start_date + ": base " + (base * 100).toFixed(2) + "% -> " +
|
|
370
|
+
(p2 * 100).toFixed(2) + "% (lift " + lift + "), " + daily + " contacts/day, " + weeks + " week(s)");
|
|
371
|
+
console.log("Floor: " + perArm + " per arm, " + need + " contacts in total. Weekly volume: " + daily * 7 + ".");
|
|
372
|
+
weekRows.forEach((w) => {
|
|
373
|
+
console.log(" Week " + w + " (" + w.start + " .. " + w.end + "): " + w.planned_contacts +
|
|
374
|
+
" contacts, cumulative " + w.cumulative + " (" + w.against_floor_pct + "% of the floor)");
|
|
375
|
+
});
|
|
376
|
+
console.log("First honest read: " + out.read_date + " (day " + daysToRead + ").");
|
|
377
|
+
console.log(out.verdict);
|
|
378
|
+
out.gates.forEach((g) => console.log(" gate: " + g));
|
|
379
|
+
console.log("Packages and the walkthrough: https://axelfreeman.com/marketing-engineer.html");
|
|
380
|
+
return 0;
|
|
381
|
+
}
|
|
382
|
+
|
|
293
383
|
const cmd = (process.argv[2] || "").toLowerCase();
|
|
294
384
|
const rest = process.argv.slice(3);
|
|
295
385
|
|
|
@@ -324,6 +414,8 @@ if (cmd === "sample") {
|
|
|
324
414
|
}, null, 2));
|
|
325
415
|
} else if (cmd === "aeo") {
|
|
326
416
|
process.exit(runAeo(flags(rest)));
|
|
417
|
+
} else if (cmd === "plan") {
|
|
418
|
+
process.exit(runPlan(flags(rest)));
|
|
327
419
|
} else if (cmd === "budget") {
|
|
328
420
|
process.exit(runBudget(flags(rest)));
|
|
329
421
|
} else if (cmd === "warmup") {
|
|
@@ -354,6 +446,10 @@ Usage:
|
|
|
354
446
|
--base 0.03 --n 1500 --lift 0.2
|
|
355
447
|
npx marketing-mindset budget what an honest test costs before you can read it
|
|
356
448
|
--base 0.03 --lift 0.2 --cpl 4 [--daily 500]
|
|
449
|
+
npx marketing-mindset plan dated plan: weekly volume, gates, first honest read
|
|
450
|
+
--base 0.03 --lift 0.2 --daily 500 [--weeks 4] [--start 2026-09-21]
|
|
451
|
+
npx marketing-mindset plan dated plan: weekly volume, gates, first honest read
|
|
452
|
+
--base 0.03 --lift 0.2 --daily 500 [--weeks 4] [--start 2026-09-21]
|
|
357
453
|
npx marketing-mindset warmup day-by-day sending ramp with stop rules
|
|
358
454
|
--start 2026-09-21 --target 200 --days 21 --mailboxes 4
|
|
359
455
|
npx marketing-mindset jd marketing engineer job description
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "marketing-mindset",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "A marketer's operating mindset as an installable agent skill and CLI: test-size floors, kill rules, agent-readiness checklist, marketing-engineer job description. npx marketing-mindset sample|kill|budget|warmup|aeo|jd|sections|read|install.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-skill",
|