marketing-mindset 1.11.0 → 1.13.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 +48 -0
- package/bin/cli.js +233 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -177,3 +177,51 @@ npx marketing-mindset cost --cost 0.02 --daily 500 --budget 5000
|
|
|
177
177
|
assumed. The floor uses the same formula as `sample`, so a budget planned here, a queue planned with `queue` and a
|
|
178
178
|
test read with `verdict` stay consistent. Two things it refuses to hide: stopping before the floor buys a sample
|
|
179
179
|
rather than a result, and money buys tests while volume limits how many can run at once.
|
|
180
|
+
|
|
181
|
+
## `unit` — which step of your funnel can fund a verdict
|
|
182
|
+
|
|
183
|
+
A funnel is a list of counted events per month. `unit` answers the only question that decides whether a
|
|
184
|
+
channel can be judged: which of those steps happens often enough to reach a test floor, and how many
|
|
185
|
+
months it needs.
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
npx marketing-mindset unit --steps visits:12000,signups:900,activations:270,paid:40
|
|
189
|
+
```
|
|
190
|
+
```
|
|
191
|
+
unit visits -> signups: rate 7.50%, floor 5,280 per arm, 6,000 events per arm a month -> 1 month(s) to read
|
|
192
|
+
unit signups -> activations: rate 30.00%, floor 963 per arm, 450 events per arm a month -> 3 month(s) to read
|
|
193
|
+
unit activations -> paid: rate 14.81%, floor 2,438 per arm, 135 events per arm a month -> 19 month(s) to read
|
|
194
|
+
readable at this volume: visits -> signups in 1 month(s)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`--json` returns the same object: one entry per consecutive pair with `rate`, `floor_per_arm`,
|
|
198
|
+
`arm_events_per_month` and `months_to_read`, plus `readable_unit`, `unreadable_units` and three gates.
|
|
199
|
+
The floor comes from the same formula as `sample`, and the arms split the earlier step's volume in half,
|
|
200
|
+
so a unit chosen here, a queue planned with `queue` and a result read with `verdict` stay consistent.
|
|
201
|
+
Two things it refuses to hide: the earliest step reads soonest and owns the least, and money always reads
|
|
202
|
+
one step behind whatever unit you chose.
|
|
203
|
+
|
|
204
|
+
## `fit` — which channel can actually fund a verdict
|
|
205
|
+
|
|
206
|
+
Two channels can both be worth testing and only one of them can be read this quarter. `fit` takes
|
|
207
|
+
`name:contacts_per_month:cost_per_contact` for each channel, applies the same floor as `sample`, and
|
|
208
|
+
says which channel reaches that floor inside the horizon and what one readable test costs there.
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
npx marketing-mindset fit --channels email:9000:0.02,linkedin:1200:0.35,seo:30000:0.005
|
|
212
|
+
```
|
|
213
|
+
```
|
|
214
|
+
email: 9,000 contacts a month at 0.02 per contact -> floor 13,914 per arm, 4,500 events per arm a month, 4 month(s) to a verdict, one test 556.56, fits: yes
|
|
215
|
+
linkedin: 1,200 contacts a month at 0.35 per contact -> floor 13,914 per arm, 600 events per arm a month, 24 month(s) to a verdict, one test 9739.8, fits: no
|
|
216
|
+
seo: 30,000 contacts a month at 0.005 per contact -> floor 13,914 per arm, 15,000 events per arm a month, 1 month(s) to a verdict, one test 139.14, fits: yes
|
|
217
|
+
first to run: seo - 139.14 for one readable test in 1 month(s)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
At a 3% base rate and a +20% relative lift the floor is 13,914 contacts per arm on every channel, so the
|
|
221
|
+
channel is not chosen by a better conversion rate but by how fast it can deliver those contacts and what
|
|
222
|
+
each contact costs. `--json` returns the same object: one entry per channel with `floor_per_arm`,
|
|
223
|
+
`events_per_arm_per_month`, `months_to_verdict`, `cost_per_arm`, `cost_per_test` and `fits`, plus
|
|
224
|
+
`first_to_run`, `unreadable_channels` and three gates. `first_to_run` is the fitting channel with the
|
|
225
|
+
lowest `cost_per_test`, ties broken by fewest months; when no channel fits it is a sentence saying so.
|
|
226
|
+
Two things it refuses to hide: the floor is set by the base rate and the lift rather than by the channel,
|
|
227
|
+
and a cheap contact list that cannot reach the floor inside the horizon funds nothing.
|
package/bin/cli.js
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* marketing-mindset verdict read a finished test: interval, floor, honest verdict
|
|
8
8
|
* marketing-mindset queue volume into a queue of honest tests with read-out dates
|
|
9
9
|
* marketing-mindset cost the cost of one readable verdict, and funded tests per budget
|
|
10
|
+
* marketing-mindset fit which channel can actually fund a verdict inside a horizon
|
|
11
|
+
* marketing-mindset unit which funnel step can fund a verdict, and in how many months
|
|
10
12
|
* marketing-mindset sections list every section of SKILL.md
|
|
11
13
|
* marketing-mindset read <text> print one section by fuzzy title match
|
|
12
14
|
* marketing-mindset install copy the skill into your agent's skills directory
|
|
@@ -625,6 +627,229 @@ function runCost(opts) {
|
|
|
625
627
|
return 0;
|
|
626
628
|
}
|
|
627
629
|
|
|
630
|
+
var UN_ZA = { "0.05": 1.9599639845, "0.01": 2.5758293036, "0.1": 1.6448536270 };
|
|
631
|
+
var UN_ZB = { "0.8": 0.8416212336, "0.9": 1.2815515655 };
|
|
632
|
+
function unRound6(x) { return Math.round(x * 1000000) / 1000000; }
|
|
633
|
+
function unSampleSize(rate, lift, power, alpha) {
|
|
634
|
+
const p1 = Math.min(Math.max(rate, 0.000001), 0.999999);
|
|
635
|
+
const p2 = Math.min(p1 * (1 + lift), 0.999999);
|
|
636
|
+
const pbar = (p1 + p2) / 2;
|
|
637
|
+
const za = UN_ZA[String(alpha)] || UN_ZA["0.05"];
|
|
638
|
+
const zb = UN_ZB[String(power)] || UN_ZB["0.8"];
|
|
639
|
+
const num = Math.pow(za * Math.sqrt(2 * pbar * (1 - pbar)) + zb * Math.sqrt(p1 * (1 - p1) + p2 * (1 - p2)), 2);
|
|
640
|
+
return Math.ceil(num / Math.pow(p2 - p1, 2));
|
|
641
|
+
}
|
|
642
|
+
function planUnits(input) {
|
|
643
|
+
const steps = input.steps || [];
|
|
644
|
+
const lift = Number(input.lift !== undefined ? input.lift : 0.2);
|
|
645
|
+
const power = Number(input.power !== undefined ? input.power : 0.8);
|
|
646
|
+
const alpha = Number(input.alpha !== undefined ? input.alpha : 0.05);
|
|
647
|
+
const horizon = Number(input.horizon !== undefined ? input.horizon : 6);
|
|
648
|
+
const problems = [];
|
|
649
|
+
if (steps.length < 2) { problems.push("steps: pass at least two steps, e.g. visits:12000,signups:900"); }
|
|
650
|
+
steps.forEach(function (s) {
|
|
651
|
+
if (!s || !s.name) { problems.push("steps: every step needs a name"); }
|
|
652
|
+
if (!(Number(s.count) > 0)) { problems.push("steps: count for '" + ((s && s.name) || "?") + "' must be a positive number of events per month"); }
|
|
653
|
+
});
|
|
654
|
+
if (!(lift > 0) || !(lift < 5)) { problems.push("lift worth detecting: a fraction, 0.2 = +20% relative"); }
|
|
655
|
+
if (!UN_ZB[String(power)]) { problems.push("power must be 0.8 or 0.9"); }
|
|
656
|
+
if (!UN_ZA[String(alpha)]) { problems.push("alpha must be 0.05, 0.01 or 0.1"); }
|
|
657
|
+
if (!(horizon > 0)) { problems.push("horizon: months you are willing to wait, a positive number"); }
|
|
658
|
+
if (problems.length) { return { problems: problems }; }
|
|
659
|
+
|
|
660
|
+
const units = [];
|
|
661
|
+
for (let i = 0; i < steps.length - 1; i++) {
|
|
662
|
+
const a = steps[i], b = steps[i + 1];
|
|
663
|
+
const rate = Number(b.count) / Number(a.count);
|
|
664
|
+
const per_arm = unSampleSize(rate, lift, power, alpha);
|
|
665
|
+
const arm_events_per_month = Number(a.count) / 2;
|
|
666
|
+
const months = Math.ceil(per_arm / arm_events_per_month);
|
|
667
|
+
units.push({
|
|
668
|
+
unit: a.name + " -> " + b.name, from: a.name, to: b.name, rate: unRound6(rate),
|
|
669
|
+
floor_per_arm: per_arm, months_to_read: months,
|
|
670
|
+
arm_events_per_month: arm_events_per_month
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
let chosen = null;
|
|
674
|
+
units.forEach(function (u) { if (!chosen && u.months_to_read <= horizon) { chosen = u; } });
|
|
675
|
+
const unreadable = units.filter(function (u) { return u.months_to_read > horizon; }).map(function (u) { return u.unit; });
|
|
676
|
+
const last = units[units.length - 1];
|
|
677
|
+
const gates = [
|
|
678
|
+
"the earliest step is the cheapest to read and the least able to own the outcome: " + units[0].unit +
|
|
679
|
+
" reads in " + units[0].months_to_read + " month(s), which is why it is usually the number already in the dashboard",
|
|
680
|
+
"the money step has no next step at this volume: " + last.unit + " is the last pair in this funnel, so money always reads one step behind the unit you chose",
|
|
681
|
+
chosen
|
|
682
|
+
? "readable at this volume: " + chosen.unit + " in " + chosen.months_to_read + " month(s) - fund that one test and set the read-out date before it starts"
|
|
683
|
+
: "nothing in this funnel is readable within " + horizon + " month(s): pick a more frequent unit or write down that the month produces a progress report"
|
|
684
|
+
];
|
|
685
|
+
return {
|
|
686
|
+
steps: steps.length, lift: lift, power: power, alpha: alpha, horizon_months: horizon,
|
|
687
|
+
units: units,
|
|
688
|
+
readable_unit: chosen ? chosen.unit : "",
|
|
689
|
+
readable_months: chosen ? chosen.months_to_read : 0,
|
|
690
|
+
unreadable_units: unreadable,
|
|
691
|
+
gates: gates
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function runUnit(opts) {
|
|
696
|
+
const spec = opts.steps;
|
|
697
|
+
const lift = Number(opts.lift !== undefined ? opts.lift : 0.2);
|
|
698
|
+
const power = Number(opts.power !== undefined ? opts.power : 0.8);
|
|
699
|
+
const alpha = Number(opts.alpha !== undefined ? opts.alpha : 0.05);
|
|
700
|
+
const horizon = Number(opts.horizon !== undefined ? opts.horizon : 6);
|
|
701
|
+
const problems = [];
|
|
702
|
+
let steps = [];
|
|
703
|
+
if (typeof spec !== "string" || spec.length === 0) {
|
|
704
|
+
problems.push("steps: pass --steps visits:12000,signups:900,activations:270,paid:40");
|
|
705
|
+
} else {
|
|
706
|
+
spec.split(",").forEach(function (part) {
|
|
707
|
+
const bits = part.split(":");
|
|
708
|
+
if (bits.length !== 2 || !bits[0] || !(Number(bits[1]) > 0)) {
|
|
709
|
+
problems.push("steps: '" + part.trim() + "' must look like name:events_per_month");
|
|
710
|
+
} else {
|
|
711
|
+
steps.push({ name: bits[0].trim(), count: Number(bits[1]) });
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
if (problems.length) { console.error("unit: " + problems.join("; ")); return 1; }
|
|
716
|
+
const out = planUnits({ steps: steps, lift: lift, power: power, alpha: alpha, horizon: horizon });
|
|
717
|
+
if (out.problems) { console.error("unit: " + out.problems.join("; ")); return 1; }
|
|
718
|
+
if (opts.json) { console.log(JSON.stringify(out, null, 2)); } else {
|
|
719
|
+
out.units.forEach(function (u) {
|
|
720
|
+
console.log("unit " + u.unit + ": rate " + (u.rate * 100).toFixed(2) + "%, floor " + fmtNum(u.floor_per_arm) +
|
|
721
|
+
" per arm, " + fmtNum(u.arm_events_per_month) + " events per arm a month -> " + u.months_to_read + " month(s) to read");
|
|
722
|
+
});
|
|
723
|
+
if (out.readable_unit) {
|
|
724
|
+
console.log("readable at this volume: " + out.readable_unit + " in " + out.readable_months + " month(s)");
|
|
725
|
+
} else {
|
|
726
|
+
console.log("readable at this volume: nothing within " + horizon + " month(s)");
|
|
727
|
+
}
|
|
728
|
+
if (out.unreadable_units.length) { console.log("out of reach: " + out.unreadable_units.join(", ")); }
|
|
729
|
+
out.gates.forEach(function (g) { console.log("gate: " + g); });
|
|
730
|
+
}
|
|
731
|
+
return 0;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
var FIT_ZA = { "0.05": 1.9599639845, "0.01": 2.5758293036, "0.1": 1.6448536270 };
|
|
735
|
+
var FIT_ZB = { "0.8": 0.8416212336, "0.9": 1.2815515655 };
|
|
736
|
+
function fitRound2(x) { return Math.round(x * 100) / 100; }
|
|
737
|
+
function fitFloor(rate, lift, power, alpha) {
|
|
738
|
+
const p1 = Math.min(Math.max(rate, 0.000001), 0.999999);
|
|
739
|
+
const p2 = Math.min(p1 * (1 + lift), 0.999999);
|
|
740
|
+
const pbar = (p1 + p2) / 2;
|
|
741
|
+
const za = FIT_ZA[String(alpha)] || FIT_ZA["0.05"];
|
|
742
|
+
const zb = FIT_ZB[String(power)] || FIT_ZB["0.8"];
|
|
743
|
+
const num = Math.pow(za * Math.sqrt(2 * pbar * (1 - pbar)) + zb * Math.sqrt(p1 * (1 - p1) + p2 * (1 - p2)), 2);
|
|
744
|
+
return Math.ceil(num / Math.pow(p2 - p1, 2));
|
|
745
|
+
}
|
|
746
|
+
function planFit(input) {
|
|
747
|
+
const channels = input.channels || [];
|
|
748
|
+
const base = Number(input.base !== undefined ? input.base : 0.03);
|
|
749
|
+
const lift = Number(input.lift !== undefined ? input.lift : 0.2);
|
|
750
|
+
const power = Number(input.power !== undefined ? input.power : 0.8);
|
|
751
|
+
const alpha = Number(input.alpha !== undefined ? input.alpha : 0.05);
|
|
752
|
+
const horizon = Number(input.horizon !== undefined ? input.horizon : 6);
|
|
753
|
+
const problems = [];
|
|
754
|
+
if (channels.length === 0) { problems.push("channels: pass --channels email:9000:0.02,linkedin:1200:0.35,seo:30000:0.005"); }
|
|
755
|
+
channels.forEach(function (c) {
|
|
756
|
+
if (!(Number(c.contacts_per_month) > 0)) { problems.push("contacts per month for '" + c.name + "' must be a positive number of contacts a month"); }
|
|
757
|
+
if (!(Number(c.cost_per_contact) >= 0)) { problems.push("cost per contact for '" + c.name + "' cannot be negative"); }
|
|
758
|
+
});
|
|
759
|
+
if (!(base > 0) || !(base < 1)) { problems.push("base rate: a number between 0 and 1 (0.03 = 3%)"); }
|
|
760
|
+
if (!(lift > 0) || !(lift < 5)) { problems.push("lift worth detecting: a fraction, 0.2 = +20% relative"); }
|
|
761
|
+
if (!FIT_ZB[String(power)]) { problems.push("power must be 0.8 or 0.9"); }
|
|
762
|
+
if (!FIT_ZA[String(alpha)]) { problems.push("alpha must be 0.05, 0.01 or 0.1"); }
|
|
763
|
+
if (!(horizon > 0)) { problems.push("horizon: months you are willing to wait, a positive number"); }
|
|
764
|
+
if (problems.length) { return { problems: problems }; }
|
|
765
|
+
|
|
766
|
+
const floor = fitFloor(base, lift, power, alpha);
|
|
767
|
+
const rows = channels.map(function (c) {
|
|
768
|
+
const perMonth = Number(c.contacts_per_month);
|
|
769
|
+
const cost = Number(c.cost_per_contact);
|
|
770
|
+
const events = perMonth / 2;
|
|
771
|
+
const months = Math.ceil(floor / events);
|
|
772
|
+
const costArm = fitRound2(floor * cost);
|
|
773
|
+
return {
|
|
774
|
+
channel: c.name, contacts_per_month: perMonth, cost_per_contact: cost,
|
|
775
|
+
floor_per_arm: floor, events_per_arm_per_month: events, months_to_verdict: months,
|
|
776
|
+
cost_per_arm: costArm, cost_per_test: fitRound2(costArm * 2), fits: months <= horizon
|
|
777
|
+
};
|
|
778
|
+
});
|
|
779
|
+
let best = null;
|
|
780
|
+
rows.forEach(function (c) {
|
|
781
|
+
if (!c.fits) { return; }
|
|
782
|
+
if (!best || c.cost_per_test < best.cost_per_test ||
|
|
783
|
+
(c.cost_per_test === best.cost_per_test && c.months_to_verdict < best.months_to_verdict)) { best = c; }
|
|
784
|
+
});
|
|
785
|
+
const none = "none of the listed channels can finish a verdict inside " + horizon + " month(s)";
|
|
786
|
+
const unreadable = rows.filter(function (c) { return !c.fits; }).map(function (c) { return c.channel; });
|
|
787
|
+
const gates = [
|
|
788
|
+
"the floor does not depend on the channel: " + fmtNum(floor) + " contacts per arm in every row, because it comes from the base rate and the lift",
|
|
789
|
+
best
|
|
790
|
+
? "fund " + best.channel + " first: " + best.cost_per_test + " for one readable test (" + best.months_to_verdict + " month(s) at " + best.cost_per_contact + " per contact)"
|
|
791
|
+
: none + " - move the horizon or find a channel with more contacts",
|
|
792
|
+
unreadable.length
|
|
793
|
+
? "out of reach inside " + horizon + " month(s): " + unreadable.join(", ")
|
|
794
|
+
: "every channel listed reaches a verdict inside " + horizon + " month(s)"
|
|
795
|
+
];
|
|
796
|
+
return {
|
|
797
|
+
base_rate: base, lift: lift, power: power, alpha: alpha, horizon_months: horizon, floor_per_arm: floor,
|
|
798
|
+
channels: rows,
|
|
799
|
+
first_to_run: best ? best.channel : none,
|
|
800
|
+
first_to_run_cost_per_test: best ? best.cost_per_test : 0,
|
|
801
|
+
first_to_run_months: best ? best.months_to_verdict : 0,
|
|
802
|
+
unreadable_channels: unreadable, gates: gates
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
function runFit(opts) {
|
|
807
|
+
const spec = opts.channels;
|
|
808
|
+
const base = Number(opts.base !== undefined ? opts.base : 0.03);
|
|
809
|
+
const lift = Number(opts.lift !== undefined ? opts.lift : 0.2);
|
|
810
|
+
const power = Number(opts.power !== undefined ? opts.power : 0.8);
|
|
811
|
+
const alpha = Number(opts.alpha !== undefined ? opts.alpha : 0.05);
|
|
812
|
+
const horizon = Number(opts.horizon !== undefined ? opts.horizon : 6);
|
|
813
|
+
const problems = [];
|
|
814
|
+
let channels = [];
|
|
815
|
+
if (typeof spec !== "string" || !spec.trim()) {
|
|
816
|
+
problems.push("channels: pass --channels email:9000:0.02,linkedin:1200:0.35,seo:30000:0.005");
|
|
817
|
+
} else {
|
|
818
|
+
spec.split(",").forEach(function (part) {
|
|
819
|
+
const bits = part.trim().split(":");
|
|
820
|
+
if (bits.length !== 3 || !bits[0].trim() || !bits[1].trim() || !bits[2].trim() ||
|
|
821
|
+
!isFinite(Number(bits[1])) || !isFinite(Number(bits[2]))) {
|
|
822
|
+
problems.push("channels: '" + part.trim() + "' must look like name:contacts_per_month:cost_per_contact");
|
|
823
|
+
} else {
|
|
824
|
+
channels.push({ name: bits[0].trim(), contacts_per_month: Number(bits[1]), cost_per_contact: Number(bits[2]) });
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
if (problems.length) { console.error("fit: " + problems.join("; ")); return 1; }
|
|
829
|
+
const out = planFit({ channels: channels, base: base, lift: lift, power: power, alpha: alpha, horizon: horizon });
|
|
830
|
+
if (out.problems) { console.error("fit: " + out.problems.join("; ")); return 1; }
|
|
831
|
+
if (opts.json) { console.log(JSON.stringify(out, null, 2)); } else {
|
|
832
|
+
const head = ["channel", "contacts/mo", "cost/contact", "floor/arm", "events/arm/mo", "months", "cost/test", "fits"];
|
|
833
|
+
const rows = out.channels.map(function (c) {
|
|
834
|
+
return [c.channel, fmtNum(c.contacts_per_month), String(c.cost_per_contact), fmtNum(c.floor_per_arm),
|
|
835
|
+
fmtNum(c.events_per_arm_per_month), String(c.months_to_verdict), String(c.cost_per_test),
|
|
836
|
+
c.fits ? "yes" : "no"];
|
|
837
|
+
});
|
|
838
|
+
const widths = head.map(function (h, i) {
|
|
839
|
+
return Math.max(h.length, Math.max.apply(null, rows.map(function (r) { return r[i].length; })));
|
|
840
|
+
});
|
|
841
|
+
function pad(s, i) { return s + " ".repeat(widths[i] - s.length); }
|
|
842
|
+
console.log(head.map(pad).join(" "));
|
|
843
|
+
console.log(widths.map(function (w) { return "-".repeat(w); }).join(" "));
|
|
844
|
+
rows.forEach(function (r) { console.log(r.map(pad).join(" ")); });
|
|
845
|
+
console.log(out.first_to_run_cost_per_test
|
|
846
|
+
? "first to run: " + out.first_to_run + " - " + out.first_to_run_cost_per_test + " for one readable test in " + out.first_to_run_months + " month(s)"
|
|
847
|
+
: "first to run: " + out.first_to_run);
|
|
848
|
+
out.gates.forEach(function (g) { console.log("gate: " + g); });
|
|
849
|
+
}
|
|
850
|
+
return 0;
|
|
851
|
+
}
|
|
852
|
+
|
|
628
853
|
function fmtNum(n) {
|
|
629
854
|
return String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
630
855
|
}
|
|
@@ -759,6 +984,10 @@ if (cmd === "sample") {
|
|
|
759
984
|
process.exit(runVerdict(flags(rest)));
|
|
760
985
|
} else if (cmd === "queue") {
|
|
761
986
|
process.exit(runQueue(flags(rest)));
|
|
987
|
+
} else if (cmd === "unit") {
|
|
988
|
+
process.exit(runUnit(flags(rest)));
|
|
989
|
+
} else if (cmd === "fit") {
|
|
990
|
+
process.exit(runFit(flags(rest)));
|
|
762
991
|
} else if (cmd === "cost") {
|
|
763
992
|
process.exit(runCost(flags(rest)));
|
|
764
993
|
} else if (cmd === "aeo") {
|
|
@@ -806,6 +1035,10 @@ Usage:
|
|
|
806
1035
|
npx marketing-mindset verdict was the difference real, and was the volume ever enough?
|
|
807
1036
|
npx marketing-mindset queue how many honest tests your volume can finish, and when each reads
|
|
808
1037
|
npx marketing-mindset cost what one readable verdict costs to finish, and how many a budget funds
|
|
1038
|
+
npx marketing-mindset fit which channel can actually fund a verdict, and what one test costs there
|
|
1039
|
+
--channels email:9000:0.02,linkedin:1200:0.35,seo:30000:0.005 [--base 0.03] [--lift 0.2] [--power 0.8] [--alpha 0.05] [--horizon 6] [--json]
|
|
1040
|
+
npx marketing-mindset unit which step of your funnel can fund a verdict, and how many months it needs
|
|
1041
|
+
--steps visits:12000,signups:900,activations:270,paid:40 [--lift 0.2] [--power 0.8] [--alpha 0.05] [--horizon 6]
|
|
809
1042
|
--cost 0.02 --daily 500 [--budget 5000] [--base 0.03] [--lift 0.2] [--power 0.8] [--alpha 0.05]
|
|
810
1043
|
--daily 500 --days 90 --start 2026-09-21 [--base 0.03] [--lift 0.2] [--power 0.8] [--alpha 0.05]
|
|
811
1044
|
--a 120/4000 --b 168/4000 [--alpha 0.05] [--json]
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "marketing-mindset",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "A marketer's operating mindset as an installable agent skill and CLI: test-size floors, kill rules, honest verdicts on finished tests, the lift a volume can read, a queue of readable tests with read-out dates, the cost of one readable verdict
|
|
3
|
+
"version": "1.13.0",
|
|
4
|
+
"description": "A marketer's operating mindset as an installable agent skill and CLI: test-size floors, kill rules, honest verdicts on finished tests, which funnel step can fund a verdict, which channel can actually fund a verdict, the lift a volume can read, a queue of readable tests with read-out dates, the cost of one readable verdict. npx marketing-mindset sample|kill|verdict|unit|fit|queue|cost|mde|budget|brief|plan|warmup|aeo|jd|sections|read|install.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-skill",
|
|
7
7
|
"skill",
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
"sample-size",
|
|
16
16
|
"test-queue",
|
|
17
17
|
"cost-per-verdict",
|
|
18
|
+
"channel-fit",
|
|
19
|
+
"funnel-unit",
|
|
18
20
|
"ab-test-verdict",
|
|
19
21
|
"minimum-detectable-effect",
|
|
20
22
|
"job-description",
|