skilldrop-cli 1.2.0 → 1.4.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 +5 -5
- package/catalogue.json +4 -4
- package/model-routing.json +62 -70
- package/package.json +1 -1
- package/packs/ai-engineering/loops/adopt/LOOP.md +120 -0
- package/packs/ai-engineering/loops/adopt/evals/eval_queries.json +10 -0
- package/packs/ai-engineering/loops/adopt/evals/evals.json +69 -0
- package/packs/ai-engineering/loops/adopt/evals/files/adopt-first-wave-to-sign-off/ai-ideas.md +15 -0
- package/packs/ai-engineering/loops/adopt/evals/files/adopt-first-wave-to-sign-off/org-context.md +27 -0
- package/packs/ai-engineering/loops/adopt/evals/files/adopt-first-wave-to-sign-off/staff-survey.csv +11 -0
- package/packs/ai-engineering/loops/adopt/evals/files/adopt-sponsor-slice-blocked-by-client-data/ai-ideas.md +13 -0
- package/packs/ai-engineering/loops/adopt/evals/files/adopt-sponsor-slice-blocked-by-client-data/firm-context.md +23 -0
- package/packs/ai-engineering/loops/adopt/loop.json +53 -0
- package/packs/ai-engineering/loops/ship-agent/LOOP.md +128 -0
- package/packs/ai-engineering/loops/ship-agent/evals/eval_queries.json +10 -0
- package/packs/ai-engineering/loops/ship-agent/evals/evals.json +70 -0
- package/packs/ai-engineering/loops/ship-agent/evals/files/ship-agent-auto-reply-exfil-open/agent-brief.md +33 -0
- package/packs/ai-engineering/loops/ship-agent/evals/files/ship-agent-changelog-config-reopens-path/agent-brief.md +33 -0
- package/packs/ai-engineering/loops/ship-agent/evals/files/ship-agent-changelog-config-reopens-path/claude-settings.json +25 -0
- package/packs/ai-engineering/loops/ship-agent/evals/files/ship-agent-changelog-config-reopens-path/sample-merged-prs.md +30 -0
- package/packs/ai-engineering/loops/ship-agent/loop.json +53 -0
- package/packs/ai-engineering/skills/agent-adoption-stage/SKILL.md +0 -1
- package/packs/ai-engineering/skills/agent-adoption-stage/manifest.json +3 -4
- package/packs/ai-engineering/skills/ai-adoption-rollout/SKILL.md +0 -1
- package/packs/ai-engineering/skills/ai-adoption-rollout/manifest.json +3 -4
- package/packs/ai-engineering/skills/ai-readiness-assessment/SKILL.md +0 -1
- package/packs/ai-engineering/skills/ai-readiness-assessment/manifest.json +1 -2
- package/packs/ai-engineering/skills/ai-use-case-triage/manifest.json +3 -3
- package/packs/ai-engineering/skills/judge-audit/SKILL.md +1 -2
- package/packs/ai-engineering/skills/judge-audit/manifest.json +1 -2
- package/packs/ai-engineering/skills/llm-eval-harness/SKILL.md +1 -2
- package/packs/ai-engineering/skills/llm-eval-harness/manifest.json +3 -3
- package/packs/api-builder/skills/tool-use-schema-writer/manifest.json +2 -2
- package/packs/career/loops/apply/evals/evals.json +28 -0
- package/packs/career/loops/apply/evals/files/apply-candidate-decided-full-run/application-form.md +10 -0
- package/packs/career/loops/apply/evals/files/apply-candidate-decided-full-run/job-posting.md +33 -0
- package/packs/career/loops/apply/evals/files/apply-candidate-decided-full-run/resume.md +34 -0
- package/packs/career/loops/apply/evals/files/apply-fit-gate-hard-requirement/cv.md +27 -0
- package/packs/career/loops/apply/evals/files/apply-fit-gate-hard-requirement/warehouse-supervisor.md +30 -0
- package/packs/career/skills/application-form-answers/manifest.json +3 -3
- package/packs/career/skills/cover-letter/manifest.json +3 -3
- package/packs/career/skills/resume-tailor/manifest.json +3 -3
- package/packs/converters/skills/md-to-docx/manifest.json +3 -3
- package/packs/core/loops/ship-a-draft/evals/evals.json +28 -0
- package/packs/core/loops/ship-a-draft/evals/files/ship-a-draft-adr-from-thread/slack-thread.txt +47 -0
- package/packs/core/loops/ship-a-draft/evals/files/ship-a-draft-announcement-unsettled/ticket-PRD-412.md +44 -0
- package/packs/core/skills/output-hygiene/manifest.json +3 -3
- package/packs/data-analytics/loops/measure/evals/evals.json +30 -0
- package/packs/data-analytics/loops/measure/evals/files/measure-active-customers-dispute/schema.md +43 -0
- package/packs/data-analytics/loops/measure/evals/files/measure-active-customers-dispute/sql/finance_active_customers.sql +7 -0
- package/packs/data-analytics/loops/measure/evals/files/measure-active-customers-dispute/sql/growth_active_customers.sql +9 -0
- package/packs/data-analytics/loops/measure/evals/files/measure-active-customers-dispute/thread.md +30 -0
- package/packs/data-analytics/loops/measure/evals/files/measure-retention-dashboard/request.md +34 -0
- package/packs/data-analytics/loops/measure/evals/files/measure-retention-dashboard/schema.md +58 -0
- package/packs/data-analytics/loops/measure/evals/files/measure-retention-dashboard/sql/active_subscribers.sql +17 -0
- package/packs/data-analytics/loops/measure/evals/files/measure-retention-dashboard/sql/net_revenue_by_plan.sql +15 -0
- package/packs/data-analytics/loops/measure/evals/files/measure-retention-dashboard/sql/weekly_churn_rate.sql +34 -0
- package/packs/data-analytics/skills/dashboard-spec/SKILL.md +0 -1
- package/packs/data-analytics/skills/dashboard-spec/manifest.json +1 -2
- package/packs/design/skills/slide-outliner/manifest.json +2 -2
- package/packs/dev-team/loops/build/evals/evals.json +28 -0
- package/packs/dev-team/loops/build/evals/files/build-promo-codes/.github/workflows/ci.yml.fixture +19 -0
- package/packs/dev-team/loops/build/evals/files/build-promo-codes/cartcalc/__init__.py +4 -0
- package/packs/dev-team/loops/build/evals/files/build-promo-codes/cartcalc/pricing.py +47 -0
- package/packs/dev-team/loops/build/evals/files/build-promo-codes/pyproject.toml.fixture +9 -0
- package/packs/dev-team/loops/build/evals/files/build-promo-codes/story.md +61 -0
- package/packs/dev-team/loops/build/evals/files/build-promo-codes/tests/__init__.py +0 -0
- package/packs/dev-team/loops/build/evals/files/build-promo-codes/tests/test_pricing.py +49 -0
- package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/.github/workflows/ci.yml.fixture +19 -0
- package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/bug-report.md +25 -0
- package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/pr-description.md +42 -0
- package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/pyproject.toml.fixture +9 -0
- package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/ratelimit/__init__.py +4 -0
- package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/ratelimit/bucket.py +49 -0
- package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/tests/__init__.py +0 -0
- package/packs/dev-team/loops/build/evals/files/build-rate-limiter-not-ready/tests/test_bucket.py +63 -0
- package/packs/dev-team/loops/modernize/evals/evals.json +30 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/.github/workflows/ci.yml.fixture +11 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/README.md +17 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/docs/adr/0004-late-fee-calculation.md +25 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/legacy/feecalc.py +74 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/samples/accounts_2026-10.txt +10 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/samples/accounts_edge.txt +11 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-batch-port/tests/test_feecalc_smoke.py +21 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/.github/workflows/ci.yml.fixture +11 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/PR-412.md +24 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/README.md +17 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/docs/adr/0004-late-fee-calculation.md +25 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/hartwell_fees/__init__.py +1 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/hartwell_fees/cli.py +33 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/hartwell_fees/extract.py +33 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/hartwell_fees/rules.py +39 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/legacy/feecalc.py +74 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/samples/accounts_2026-10.txt +10 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/samples/accounts_edge.txt +11 -0
- package/packs/dev-team/loops/modernize/evals/files/modernize-late-fee-rewrite-not-equivalent/tests/test_feecalc_smoke.py +21 -0
- package/packs/dev-team/loops/release/evals/eval_queries.json +10 -0
- package/packs/dev-team/loops/release/evals/evals.json +34 -0
- package/packs/dev-team/loops/release/evals/files/release-irreversible-tomorrow/0061_switch_to_haldrin_tokens.sql +22 -0
- package/packs/dev-team/loops/release/evals/files/release-irreversible-tomorrow/eng-notes.md +40 -0
- package/packs/dev-team/loops/release/evals/files/release-irreversible-tomorrow/merged-prs.md +13 -0
- package/packs/dev-team/loops/release/evals/files/release-saved-searches/merged-prs.md +13 -0
- package/packs/dev-team/loops/release/evals/files/release-saved-searches/readiness-evidence.md +27 -0
- package/packs/dev-team/loops/release/evals/files/release-saved-searches/traffic-baseline.md +23 -0
- package/packs/dev-team/skills/feature-implement-loop/manifest.json +2 -2
- package/packs/dev-team/skills/pr-description-writer/manifest.json +3 -3
- package/packs/dev-team/skills/pre-merge-review/manifest.json +2 -2
- package/packs/dev-team/skills/release-notes/manifest.json +2 -2
- package/packs/dev-team/skills/test-plan-generator/manifest.json +2 -2
- package/packs/experience-design/loops/clarify/LOOP.md +114 -0
- package/packs/experience-design/loops/clarify/evals/eval_queries.json +10 -0
- package/packs/experience-design/loops/clarify/evals/evals.json +68 -0
- package/packs/experience-design/loops/clarify/evals/files/clarify-garden-waste-no-complaint-data/form-fields.csv +12 -0
- package/packs/experience-design/loops/clarify/evals/files/clarify-garden-waste-no-complaint-data/garden-waste-page.md +29 -0
- package/packs/experience-design/loops/clarify/evals/files/clarify-parking-permit-with-call-data/backoffice-notes.md +14 -0
- package/packs/experience-design/loops/clarify/evals/files/clarify-parking-permit-with-call-data/calls-q3.csv +8 -0
- package/packs/experience-design/loops/clarify/evals/files/clarify-parking-permit-with-call-data/form-fields.csv +12 -0
- package/packs/experience-design/loops/clarify/evals/files/clarify-parking-permit-with-call-data/permit-page.md +27 -0
- package/packs/experience-design/loops/clarify/loop.json +35 -0
- package/packs/experience-design/skills/service-blueprint/manifest.json +3 -3
- package/packs/experience-design/skills/ux-writing/manifest.json +3 -3
- package/packs/grc/loops/comply/evals/evals.json +29 -0
- package/packs/grc/loops/comply/evals/files/comply-chat-vendor-launch/vendor-brief.md +72 -0
- package/packs/grc/loops/comply/evals/files/comply-loyalty-app-unowned-risks/launch-note.md +31 -0
- package/packs/grc/loops/comply/evals/files/comply-loyalty-app-unowned-risks/risk-register.csv +6 -0
- package/packs/infra-as-code/loops/provision/LOOP.md +128 -0
- package/packs/infra-as-code/loops/provision/evals/eval_queries.json +9 -0
- package/packs/infra-as-code/loops/provision/evals/evals.json +67 -0
- package/packs/infra-as-code/loops/provision/evals/files/provision-rds-no-plan-yet/.terraform.lock.hcl.fixture +24 -0
- package/packs/infra-as-code/loops/provision/evals/files/provision-rds-no-plan-yet/network.md +36 -0
- package/packs/infra-as-code/loops/provision/evals/files/provision-replica-plan-replaces-primary/main.tf +74 -0
- package/packs/infra-as-code/loops/provision/evals/files/provision-replica-plan-replaces-primary/plan.json +901 -0
- package/packs/infra-as-code/loops/provision/evals/files/provision-replica-plan-replaces-primary/variables.tf +47 -0
- package/packs/infra-as-code/loops/provision/loop.json +41 -0
- package/packs/infra-as-code/skills/terraform-module/manifest.json +3 -3
- package/packs/product-manager/loops/discover/evals/evals.json +28 -0
- package/packs/product-manager/loops/discover/evals/files/discover-no-show-signal/interview-notes.md +63 -0
- package/packs/product-manager/loops/discover/evals/files/discover-no-show-signal/support-tickets.csv +13 -0
- package/packs/product-manager/loops/discover/evals/files/discover-vp-loyalty-no-evidence/vp-message.txt +13 -0
- package/packs/product-manager/skills/user-journey-map/manifest.json +2 -2
- package/packs/research/loops/research/evals/evals.json +28 -0
- package/packs/research/loops/research/evals/files/research-ticket-spike-confounded/sources/t1-ticket-volume.md +15 -0
- package/packs/research/loops/research/evals/files/research-ticket-spike-confounded/sources/t2-release-notes-4-2.md +11 -0
- package/packs/research/loops/research/evals/files/research-ticket-spike-confounded/sources/t3-billing-migration.md +12 -0
- package/packs/research/loops/research/evals/files/research-ticket-spike-confounded/sources/t4-agent-poll.md +11 -0
- package/packs/research/loops/research/evals/files/research-ticket-spike-confounded/sources/t5-app-store-reviews.md +12 -0
- package/packs/research/loops/research/evals/files/research-trial-conversion-drop/sources/s1-analytics-export.md +29 -0
- package/packs/research/loops/research/evals/files/research-trial-conversion-drop/sources/s2-email-delivery-report.md +25 -0
- package/packs/research/loops/research/evals/files/research-trial-conversion-drop/sources/s3-pricing-changelog.md +9 -0
- package/packs/research/loops/research/evals/files/research-trial-conversion-drop/sources/s4-trial-interview-notes.md +21 -0
- package/packs/research/loops/research/evals/files/research-trial-conversion-drop/sources/s5-competitor-note.md +9 -0
- package/packs/research/loops/research/evals/files/research-trial-conversion-drop/sources/s6-weekly-growth-recap.md +7 -0
- package/packs/research/skills/research-plan/manifest.json +3 -3
- package/packs/research/skills/source-synthesis/manifest.json +3 -3
- package/packs/skill-engineering/skills/skill-author/manifest.json +3 -3
- package/packs/solution-architect/loops/design/evals/evals.json +28 -0
- package/packs/solution-architect/loops/design/evals/files/design-bulk-export/adr/0001-postgres-system-of-record.md +23 -0
- package/packs/solution-architect/loops/design/evals/files/design-bulk-export/adr/0002-background-work-via-job-queue.md +23 -0
- package/packs/solution-architect/loops/design/evals/files/design-bulk-export/adr/0003-tenant-isolation-row-level.md +25 -0
- package/packs/solution-architect/loops/design/evals/files/design-bulk-export/prd-bulk-export.md +50 -0
- package/packs/solution-architect/loops/design/evals/files/design-draft-prd-webhooks/adr/0002-background-work-via-job-queue.md +23 -0
- package/packs/solution-architect/loops/design/evals/files/design-draft-prd-webhooks/adr/0004-new-stateful-infra-needs-platform-signoff.md +22 -0
- package/packs/solution-architect/loops/design/evals/files/design-draft-prd-webhooks/prd-partner-webhooks.md +36 -0
- package/packs/solution-architect/skills/api-contract-draft/manifest.json +3 -3
- package/packs/solution-architect/skills/architecture-diagrams/manifest.json +3 -3
- package/packs/solution-architect/skills/design-doc/manifest.json +2 -2
- package/packs/solution-architect/skills/figma-diagrams/manifest.json +3 -3
- package/packs/sre-oncall/loops/operate/evals/evals.json +28 -0
- package/packs/sre-oncall/loops/operate/evals/files/operate-export-service-first-outage/alerts.log +22 -0
- package/packs/sre-oncall/loops/operate/evals/files/operate-export-service-first-outage/incident-chat.txt +27 -0
- package/packs/sre-oncall/loops/operate/evals/files/operate-export-service-first-outage/service-notes.md +46 -0
- package/packs/sre-oncall/loops/operate/evals/files/operate-incident-still-open/oncall-log.txt +17 -0
- package/packs/sre-oncall/loops/operate/evals/files/operate-incident-still-open/runbook-webhooks.md +38 -0
- package/packs/sre-oncall/skills/cloud-cost-review/SKILL.md +0 -1
- package/packs/sre-oncall/skills/cloud-cost-review/manifest.json +1 -2
- package/packs/sre-oncall/skills/delivery-metrics-report/SKILL.md +0 -1
- package/packs/sre-oncall/skills/delivery-metrics-report/manifest.json +1 -2
- package/packs/sre-oncall/skills/incident-comms/manifest.json +2 -2
- package/packs/sre-oncall/skills/postmortem-generator/manifest.json +2 -2
- package/packs/stakeholder-comms/skills/decision-log/manifest.json +2 -2
- package/packs/trackers/skills/team-status-report/manifest.json +3 -3
- package/packs/ai-engineering/skills/ai-usage-report/SKILL.md +0 -111
- package/packs/ai-engineering/skills/ai-usage-report/evals/eval_queries.json +0 -30
- package/packs/ai-engineering/skills/ai-usage-report/evals/evals.json +0 -46
- package/packs/ai-engineering/skills/ai-usage-report/evals/files/1/northwind-ai-events.csv +0 -4213
- package/packs/ai-engineering/skills/ai-usage-report/evals/files/2/draftwell-events.csv +0 -249
- package/packs/ai-engineering/skills/ai-usage-report/evals/files/2/export-note.md +0 -9
- package/packs/ai-engineering/skills/ai-usage-report/evals/files/2/published-assets.csv +0 -44
- package/packs/ai-engineering/skills/ai-usage-report/evals/files/2/team-roster.md +0 -10
- package/packs/ai-engineering/skills/ai-usage-report/examples/weekly-engineering-team.md +0 -133
- package/packs/ai-engineering/skills/ai-usage-report/manifest.json +0 -12
- package/packs/ai-engineering/skills/ai-usage-report/scripts/build_report.py +0 -453
- package/packs/ai-engineering/skills/ai-usage-report/templates/report-per-user.md +0 -42
- package/packs/ai-engineering/skills/ai-usage-report/templates/report-team-rollup.md +0 -49
- package/packs/ai-engineering/skills/ai-usage-report/templates/usage-event-schema.md +0 -76
- package/packs/product-manager/skills/lean-canvas/SKILL.md +0 -162
- package/packs/product-manager/skills/lean-canvas/evals/eval_queries.json +0 -42
- package/packs/product-manager/skills/lean-canvas/evals/evals.json +0 -82
- package/packs/product-manager/skills/lean-canvas/evals/files/1/pawpass-notes.md +0 -54
- package/packs/product-manager/skills/lean-canvas/evals/files/3/lunchloop-pitch.md +0 -30
- package/packs/product-manager/skills/lean-canvas/evals/files/4/clubkit-notes.md +0 -44
- package/packs/product-manager/skills/lean-canvas/evals/files/5/cost-model.csv +0 -5
- package/packs/product-manager/skills/lean-canvas/evals/files/5/proposal.md +0 -40
- package/packs/product-manager/skills/lean-canvas/evals/files/5/stakeholder-emails.txt +0 -18
- package/packs/product-manager/skills/lean-canvas/examples/bike-repair.md +0 -72
- package/packs/product-manager/skills/lean-canvas/manifest.json +0 -34
- package/packs/product-manager/skills/lean-canvas/reference.md +0 -102
- package/packs/product-manager/skills/lean-canvas/scripts/unit_economics.py +0 -211
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
# lean-canvas reference
|
|
2
|
-
|
|
3
|
-
Long-form material for the steps in `SKILL.md`. The canvas is Ash Maurya's adaptation of the
|
|
4
|
-
Business Model Canvas (*Running Lean*); the interview rules follow Rob Fitzpatrick's *The Mom
|
|
5
|
-
Test*.
|
|
6
|
-
|
|
7
|
-
## The nine boxes, in the order to fill them
|
|
8
|
-
|
|
9
|
-
| Box | What goes in | Common wrong entry |
|
|
10
|
-
|---|---|---|
|
|
11
|
-
| Problem | The top 1–3 problems, and how the early adopter copes today (existing alternatives) | A solution phrased as a problem: "no app for X" |
|
|
12
|
-
| Customer segments | Who has the problem; the **early adopter** named separately, narrow enough to find by name | "Small businesses", "everyone who…" |
|
|
13
|
-
| Unique value proposition | One sentence: why this, for the early adopter, instead of what they do today | A feature list |
|
|
14
|
-
| Solution | The smallest thing that addresses each problem | A roadmap |
|
|
15
|
-
| Channels | The paths to the early adopter, free and paid | Channels that reach the later segment |
|
|
16
|
-
| Revenue streams | Price, who pays, how often | "Freemium" with no paid tier defined |
|
|
17
|
-
| Cost structure | Cost to serve one customer, fixed costs, acquisition cost | Fixed costs only |
|
|
18
|
-
| Key metrics | The 1–3 numbers that show the model works (activation, retention, paid conversion) | Downloads, page views |
|
|
19
|
-
| Unfair advantage | What a funded competitor can't copy or buy within a year | A feature, "AI", "first mover", "passion" |
|
|
20
|
-
|
|
21
|
-
**Internal products.** Revenue streams = the budget line or measured saving that keeps it
|
|
22
|
-
funded. Customer = the team that has to change how it works; early adopter = the one team
|
|
23
|
-
with the worst version of the problem and a lead who asked for help. Channel = how that team
|
|
24
|
-
hears about and gets onto the tool (a mandate, a team lead, an existing workflow it plugs
|
|
25
|
-
into). Unfair advantage can be real here: access to internal data or a mandate no vendor
|
|
26
|
-
has. Still test whether the team will change its habits.
|
|
27
|
-
|
|
28
|
-
## Grading evidence
|
|
29
|
-
|
|
30
|
-
| Signal | Grade |
|
|
31
|
-
|---|---|
|
|
32
|
-
| Paid, pre-ordered, signed a letter of intent, or switched from a current tool | Behaviour — strongest |
|
|
33
|
-
| Describes a recent, specific instance of the problem unprompted, and what it cost | Behaviour about the past — strong |
|
|
34
|
-
| Built a workaround (spreadsheet, hired someone) | Behaviour — strong |
|
|
35
|
-
| "I'd definitely use it", "great idea", survey interest | Opinion — weak, counts as assumption |
|
|
36
|
-
| Industry statistic or report | Sizes the market; says nothing about this segment's willingness to pay |
|
|
37
|
-
| Any of the above from a segment other than the early adopter | Counts as assumption for the early adopter |
|
|
38
|
-
|
|
39
|
-
## Scoring risk
|
|
40
|
-
|
|
41
|
-
| | Fatal (3) | Pivot (2) | Tune (1) |
|
|
42
|
-
|---|---|---|---|
|
|
43
|
-
| **High (3)** — no evidence, wrong segment, opinion only | 9 | 6 | 3 |
|
|
44
|
-
| **Medium (2)** — right segment, few people or indirect | 6 | 4 | 2 |
|
|
45
|
-
| **Low (1)** — observed behaviour, right segment | 3 | 2 | 1 |
|
|
46
|
-
|
|
47
|
-
Ties: rank the assumption whose test is cheaper first; it clears the way for the rest.
|
|
48
|
-
|
|
49
|
-
Why problem, customer and channel usually top the list: a wrong solution is fixed by
|
|
50
|
-
rebuilding it, which a team can do in weeks. A wrong problem or an unreachable customer means
|
|
51
|
-
the next build lands on nobody, and no amount of engineering moves it.
|
|
52
|
-
|
|
53
|
-
## Test menu, cheapest first
|
|
54
|
-
|
|
55
|
-
| Test | Proves wrong | Typical sample | Typical cost and time |
|
|
56
|
-
|---|---|---|---|
|
|
57
|
-
| Problem interview | The problem, the segment | 10–20 early adopters | $0–$200, 1–2 weeks |
|
|
58
|
-
| Channel smoke test | The channel and its cost | $100–$500 of spend or one week of outreach on one channel | the spend, 1 week |
|
|
59
|
-
| Landing page with a price | The UVP and the price | 100+ visitors from the stated channel | $100–$500, 1–2 weeks |
|
|
60
|
-
| Concierge run (deliver it by hand) | The solution and retention | 3–10 customers | founder time, 2–4 weeks |
|
|
61
|
-
| Pre-sale or letter of intent | Willingness to pay | 5–20 asks | $0, 2–4 weeks |
|
|
62
|
-
| Wizard of Oz (looks automated, done by hand) | The solution at small volume | 10–30 uses | founder time, 2–4 weeks |
|
|
63
|
-
|
|
64
|
-
A survey and "build an MVP and see" are not on the menu: the first measures opinion, the
|
|
65
|
-
second costs months to learn what an interview learns in a week.
|
|
66
|
-
|
|
67
|
-
## Setting the pass/fail line
|
|
68
|
-
|
|
69
|
-
- **Write it before the test runs**, as a count: "fewer than 4 of 10". A line set after the
|
|
70
|
-
results arrive moves to fit them.
|
|
71
|
-
- **Take it from the numbers when they exist.** Channel test fail line = the most a customer
|
|
72
|
-
can cost to acquire (lifetime margin ÷ 3 for a business that has to cover fixed costs;
|
|
73
|
-
lifetime margin at break-even). Landing-page fail line = the conversion that would
|
|
74
|
-
produce that cost per customer at the channel's cost per visitor.
|
|
75
|
-
- **No numbers yet:** a problem interview passes when at least 4 in 10 describe a recent
|
|
76
|
-
instance unprompted; a pre-sale passes when at least 1 in 5 pays or signs.
|
|
77
|
-
- **Say what failure changes:** which box is rewritten, and what the next test is.
|
|
78
|
-
|
|
79
|
-
## Problem-interview rules
|
|
80
|
-
|
|
81
|
-
1. Ask about the past, never the future. ✅ "When did that last happen? What did you do?"
|
|
82
|
-
❌ "Would you use an app that…?"
|
|
83
|
-
2. Don't pitch until the end, if at all. The moment the idea is described, people are polite.
|
|
84
|
-
3. Ask what it cost them: time, money, a lost customer.
|
|
85
|
-
4. Ask what they do about it today and what that costs. No workaround is a warning sign.
|
|
86
|
-
5. Count who raised the problem **before** it was named, separately from who agreed once asked.
|
|
87
|
-
|
|
88
|
-
Five-question script: "Walk me through your last [day/week/job]." / "What was the hardest
|
|
89
|
-
part?" / "When did [problem area] last go wrong? What happened?" / "What did you do about
|
|
90
|
-
it, and what did that cost?" / "What have you tried to fix it?"
|
|
91
|
-
|
|
92
|
-
## What counts as an unfair advantage
|
|
93
|
-
|
|
94
|
-
Counts: insider information, an existing audience or customer base, a network effect,
|
|
95
|
-
a community, exclusive contracts or access, regulatory permission others lack, a team
|
|
96
|
-
member whose name opens doors in this market, internal data or a mandate (internal products).
|
|
97
|
-
|
|
98
|
-
Doesn't count: a feature, an algorithm, "AI", a patent pending on a feature, first-mover,
|
|
99
|
-
better UX, lower price, hard work, passion. These go in Solution or UVP.
|
|
100
|
-
|
|
101
|
-
"None yet" is the most common correct answer at this stage. Name a candidate the idea
|
|
102
|
-
could grow into, if the input supports one.
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Unit-economics sanity check for a lean canvas.
|
|
3
|
-
|
|
4
|
-
Stdlib only; runs on Python 3.9+. Computes, never estimates:
|
|
5
|
-
|
|
6
|
-
* contribution (gross margin) per customer per period, and as a % of price;
|
|
7
|
-
* customer lifetime in periods (given directly, or 1 / churn);
|
|
8
|
-
* lifetime gross margin (LTV) and LTV:CAC;
|
|
9
|
-
* CAC payback in periods, and whether it is longer than the lifetime;
|
|
10
|
-
* the most a customer can cost to acquire: at break-even (LTV) and at 3:1 (LTV / 3);
|
|
11
|
-
* break-even customers for the fixed costs, and the customers a stated
|
|
12
|
-
market can supply, when those are given.
|
|
13
|
-
|
|
14
|
-
All money figures share one currency and one period (default "month"). Convert
|
|
15
|
-
an annual price to monthly before passing it, or pass --period year with
|
|
16
|
-
annual figures throughout.
|
|
17
|
-
|
|
18
|
-
Usage:
|
|
19
|
-
python3 unit_economics.py --price 29 --variable-cost 6 --cac 400 \
|
|
20
|
-
--lifetime 9 --fixed 4300
|
|
21
|
-
python3 unit_economics.py --price 29 --variable-cost 6 --cac 400 --churn 0.11
|
|
22
|
-
python3 unit_economics.py --json inputs.json [-o out.md]
|
|
23
|
-
echo '{"price": 29, "variable_cost": 6, "cac": 400, "lifetime": 9}' | \
|
|
24
|
-
python3 unit_economics.py --json -
|
|
25
|
-
|
|
26
|
-
JSON keys mirror the flags: price, variable_cost, cac, lifetime, churn,
|
|
27
|
-
fixed, market_customers, period. Flags given on the command line override
|
|
28
|
-
the JSON.
|
|
29
|
-
|
|
30
|
-
Exit codes: 0 ok, 2 bad input.
|
|
31
|
-
"""
|
|
32
|
-
|
|
33
|
-
from __future__ import annotations
|
|
34
|
-
|
|
35
|
-
import argparse
|
|
36
|
-
import json
|
|
37
|
-
import sys
|
|
38
|
-
|
|
39
|
-
KEYS = ("price", "variable_cost", "cac", "lifetime", "churn", "fixed", "market_customers")
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def fail(msg: str) -> None:
|
|
43
|
-
print(f"error: {msg}", file=sys.stderr)
|
|
44
|
-
sys.exit(2)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def load_json(path: str) -> dict:
|
|
48
|
-
try:
|
|
49
|
-
if path == "-":
|
|
50
|
-
data = json.load(sys.stdin)
|
|
51
|
-
else:
|
|
52
|
-
with open(path, encoding="utf-8") as fh:
|
|
53
|
-
data = json.load(fh)
|
|
54
|
-
except FileNotFoundError:
|
|
55
|
-
fail(f"no such file: {path}")
|
|
56
|
-
except json.JSONDecodeError as exc:
|
|
57
|
-
fail(f"not valid JSON ({exc})")
|
|
58
|
-
if not isinstance(data, dict):
|
|
59
|
-
fail("JSON input must be one object")
|
|
60
|
-
return data
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def number(name: str, value, minimum: float = 0.0, allow_equal: bool = True):
|
|
64
|
-
if value is None:
|
|
65
|
-
return None
|
|
66
|
-
try:
|
|
67
|
-
val = float(value)
|
|
68
|
-
except (TypeError, ValueError):
|
|
69
|
-
fail(f"{name} must be a number, got {value!r}")
|
|
70
|
-
if val < minimum or (not allow_equal and val == minimum):
|
|
71
|
-
op = ">=" if allow_equal else ">"
|
|
72
|
-
fail(f"{name} must be {op} {minimum:g}, got {val:g}")
|
|
73
|
-
return val
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def money(x: float) -> str:
|
|
77
|
-
return f"{x:,.2f}"
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def compute(inp: dict) -> tuple[list[str], list[str]]:
|
|
81
|
-
period = str(inp.get("period") or "month")
|
|
82
|
-
price = number("price", inp.get("price"), 0.0, allow_equal=False)
|
|
83
|
-
if price is None:
|
|
84
|
-
fail("price is required (per customer per period)")
|
|
85
|
-
vcost = number("variable_cost", inp.get("variable_cost")) or 0.0
|
|
86
|
-
cac = number("cac", inp.get("cac"))
|
|
87
|
-
lifetime = number("lifetime", inp.get("lifetime"), 0.0, allow_equal=False)
|
|
88
|
-
churn = inp.get("churn")
|
|
89
|
-
fixed = number("fixed", inp.get("fixed"))
|
|
90
|
-
market = number("market_customers", inp.get("market_customers"))
|
|
91
|
-
|
|
92
|
-
if churn is not None:
|
|
93
|
-
churn = number("churn", churn, 0.0, allow_equal=False)
|
|
94
|
-
if churn > 1:
|
|
95
|
-
if churn <= 100:
|
|
96
|
-
churn = churn / 100.0 # "11" meaning 11%
|
|
97
|
-
else:
|
|
98
|
-
fail("churn must be a fraction (0.11) or a percent (11)")
|
|
99
|
-
if lifetime is None:
|
|
100
|
-
lifetime = 1.0 / churn
|
|
101
|
-
|
|
102
|
-
lines: list[str] = []
|
|
103
|
-
flags: list[str] = []
|
|
104
|
-
|
|
105
|
-
margin = price - vcost
|
|
106
|
-
margin_pct = margin / price * 100
|
|
107
|
-
lines.append(f"Price per customer per {period}: {money(price)}")
|
|
108
|
-
if inp.get("variable_cost") is None:
|
|
109
|
-
lines.append(f"Variable cost per customer per {period}: not given, treated as 0")
|
|
110
|
-
flags.append("No cost to serve was given, so every margin figure here is an upper bound.")
|
|
111
|
-
else:
|
|
112
|
-
lines.append(f"Variable cost per customer per {period}: {money(vcost)}")
|
|
113
|
-
lines.append(f"Contribution per customer per {period}: {money(margin)} ({margin_pct:.1f}% gross margin)")
|
|
114
|
-
if margin <= 0:
|
|
115
|
-
flags.append(f"Each customer costs more to serve than they pay ({money(margin)} per {period}). No volume fixes this.")
|
|
116
|
-
|
|
117
|
-
ltv = None
|
|
118
|
-
if lifetime is not None:
|
|
119
|
-
src = f"1 / churn of {churn * 100:.1f}%" if churn is not None and inp.get("lifetime") is None else "as given"
|
|
120
|
-
lines.append(f"Customer lifetime: {lifetime:.1f} {period}s ({src})")
|
|
121
|
-
ltv = margin * lifetime
|
|
122
|
-
lines.append(f"Lifetime contribution (LTV): {money(ltv)}")
|
|
123
|
-
|
|
124
|
-
if cac is not None:
|
|
125
|
-
lines.append(f"Customer acquisition cost (CAC): {money(cac)}")
|
|
126
|
-
if margin > 0:
|
|
127
|
-
payback = cac / margin
|
|
128
|
-
lines.append(f"CAC payback: {payback:.1f} {period}s")
|
|
129
|
-
if lifetime is not None and payback > lifetime:
|
|
130
|
-
flags.append(
|
|
131
|
-
f"CAC payback ({payback:.1f} {period}s) is longer than the customer lifetime "
|
|
132
|
-
f"({lifetime:.1f} {period}s): every acquired customer loses {money(cac - ltv)}."
|
|
133
|
-
)
|
|
134
|
-
if ltv is not None:
|
|
135
|
-
ratio = ltv / cac if cac > 0 else float("inf")
|
|
136
|
-
lines.append(f"LTV:CAC: {ratio:.2f}" if cac > 0 else "LTV:CAC: no acquisition cost given as 0")
|
|
137
|
-
if cac > 0 and ratio < 1:
|
|
138
|
-
flags.append(f"LTV:CAC is {ratio:.2f}: acquisition destroys money before fixed costs.")
|
|
139
|
-
elif cac > 0 and ratio < 3:
|
|
140
|
-
flags.append(f"LTV:CAC is {ratio:.2f}, under the 3:1 most subscription businesses need to cover fixed costs.")
|
|
141
|
-
|
|
142
|
-
if ltv is not None and ltv > 0:
|
|
143
|
-
lines.append(f"Most a customer can cost to acquire: {money(ltv)} at break-even, {money(ltv / 3)} at 3:1")
|
|
144
|
-
|
|
145
|
-
if fixed is not None:
|
|
146
|
-
lines.append(f"Fixed costs per {period}: {money(fixed)}")
|
|
147
|
-
if margin > 0:
|
|
148
|
-
be = fixed / margin
|
|
149
|
-
be_whole = int(be) if be == int(be) else int(be) + 1
|
|
150
|
-
lines.append(f"Break-even customers (active at once): {be_whole:,}")
|
|
151
|
-
if market is not None:
|
|
152
|
-
lines.append(f"Customers the stated market holds: {int(market):,}")
|
|
153
|
-
if be_whole > market:
|
|
154
|
-
flags.append(
|
|
155
|
-
f"Break-even needs {be_whole:,} active customers but the stated market holds {int(market):,}."
|
|
156
|
-
)
|
|
157
|
-
else:
|
|
158
|
-
share = be_whole / market * 100 if market else 0.0 # 0 needed of a 0 market
|
|
159
|
-
lines.append(f"Break-even share of the stated market: {share:.1f}%")
|
|
160
|
-
if share >= 25:
|
|
161
|
-
flags.append(
|
|
162
|
-
f"Break-even needs {share:.0f}% of the stated market as paying customers at the same time."
|
|
163
|
-
)
|
|
164
|
-
if cac is not None and ltv is not None and ltv <= cac:
|
|
165
|
-
flags.append("Break-even customers cannot be reached through paid acquisition at this CAC: each one adds a loss.")
|
|
166
|
-
|
|
167
|
-
missing = [k for k in ("variable_cost", "cac", "lifetime/churn", "fixed")
|
|
168
|
-
if (k == "lifetime/churn" and lifetime is None)
|
|
169
|
-
or (k != "lifetime/churn" and inp.get(k) is None)]
|
|
170
|
-
if missing:
|
|
171
|
-
lines.append("Not computed (input missing): " + ", ".join(missing))
|
|
172
|
-
return lines, flags
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
def main() -> None:
|
|
176
|
-
ap = argparse.ArgumentParser(description="Unit-economics sanity check for a lean canvas.")
|
|
177
|
-
ap.add_argument("--json", metavar="PATH", help="JSON object of inputs, or - for stdin")
|
|
178
|
-
ap.add_argument("--price", help="price per customer per period")
|
|
179
|
-
ap.add_argument("--variable-cost", dest="variable_cost", help="cost to serve one customer per period")
|
|
180
|
-
ap.add_argument("--cac", help="cost to acquire one paying customer")
|
|
181
|
-
ap.add_argument("--lifetime", help="average customer lifetime in periods")
|
|
182
|
-
ap.add_argument("--churn", help="churn per period, as 0.11 or 11")
|
|
183
|
-
ap.add_argument("--fixed", help="fixed costs per period")
|
|
184
|
-
ap.add_argument("--market-customers", dest="market_customers", help="customers the stated market holds")
|
|
185
|
-
ap.add_argument("--period", help='period label, default "month"')
|
|
186
|
-
ap.add_argument("-o", "--output", help="write the report to this path instead of stdout")
|
|
187
|
-
args = ap.parse_args()
|
|
188
|
-
|
|
189
|
-
inp: dict = load_json(args.json) if args.json else {}
|
|
190
|
-
unknown = set(inp) - set(KEYS) - {"period"}
|
|
191
|
-
if unknown:
|
|
192
|
-
fail("unknown JSON keys: " + ", ".join(sorted(unknown)))
|
|
193
|
-
for key in KEYS + ("period",):
|
|
194
|
-
val = getattr(args, key)
|
|
195
|
-
if val is not None:
|
|
196
|
-
inp[key] = val
|
|
197
|
-
|
|
198
|
-
lines, flags = compute(inp)
|
|
199
|
-
out = ["Unit economics", ""] + [f"- {ln}" for ln in lines]
|
|
200
|
-
out += ["", "Where the numbers don't close:" if flags else "Where the numbers don't close: none found."]
|
|
201
|
-
out += [f"- {f}" for f in flags]
|
|
202
|
-
text = "\n".join(out) + "\n"
|
|
203
|
-
if args.output:
|
|
204
|
-
with open(args.output, "w", encoding="utf-8") as fh:
|
|
205
|
-
fh.write(text)
|
|
206
|
-
else:
|
|
207
|
-
sys.stdout.write(text)
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if __name__ == "__main__":
|
|
211
|
-
main()
|