From 4fd0cb0b2c2061ccef23cd350ec0ff67b5f58f5f Mon Sep 17 00:00:00 2001 From: Leo Date: Sun, 8 Mar 2026 15:34:31 +0100 Subject: [PATCH] feat: add product discovery skill with assumption mapper --- product-team/product-discovery/SKILL.md | 114 ++++++++++++++++ .../references/discovery-frameworks.md | 72 ++++++++++ .../scripts/assumption_mapper.py | 123 ++++++++++++++++++ 3 files changed, 309 insertions(+) create mode 100644 product-team/product-discovery/SKILL.md create mode 100644 product-team/product-discovery/references/discovery-frameworks.md create mode 100755 product-team/product-discovery/scripts/assumption_mapper.py diff --git a/product-team/product-discovery/SKILL.md b/product-team/product-discovery/SKILL.md new file mode 100644 index 0000000..17265ee --- /dev/null +++ b/product-team/product-discovery/SKILL.md @@ -0,0 +1,114 @@ +--- +name: product-discovery +description: Use when validating product opportunities, mapping assumptions, planning discovery sprints, or testing problem-solution fit before committing delivery resources. +--- + +# Product Discovery + +Run structured discovery to identify high-value opportunities and de-risk product bets. + +## When To Use + +Use this skill for: +- Opportunity Solution Tree facilitation +- Assumption mapping and test planning +- Problem validation interviews and evidence synthesis +- Solution validation with prototypes/experiments +- Discovery sprint planning and outputs + +## Core Discovery Workflow + +1. Define desired outcome +- Set one measurable outcome to improve. +- Establish baseline and target horizon. + +2. Build Opportunity Solution Tree (OST) +- Outcome -> opportunities -> solution ideas -> experiments +- Keep opportunities grounded in user evidence, not internal opinions. + +3. Map assumptions +- Identify desirability, viability, feasibility, and usability assumptions. +- Score assumptions by risk and certainty. + +Use: +```bash +python3 scripts/assumption_mapper.py assumptions.csv +``` + +4. Validate the problem +- Conduct interviews and behavior analysis. +- Confirm frequency, severity, and willingness to solve. +- Reject weak opportunities early. + +5. Validate the solution +- Prototype before building. +- Run concept, usability, and value tests. +- Measure behavior, not only stated preference. + +6. Plan discovery sprint +- 1-2 week cycle with explicit hypotheses +- Daily evidence reviews +- End with decision: proceed, pivot, or stop + +## Opportunity Solution Tree (Teresa Torres) + +Structure: +- Outcome: metric you want to move +- Opportunities: unmet customer needs/pains +- Solutions: candidate interventions +- Experiments: fastest learning actions + +Quality checks: +- At least 3 distinct opportunities before converging. +- At least 2 experiments per top opportunity. +- Tie every branch to evidence source. + +## Assumption Mapping + +Assumption categories: +- Desirability: users want this +- Viability: business value exists +- Feasibility: team can build/operate it +- Usability: users can successfully use it + +Prioritization rule: +- High risk + low certainty assumptions are tested first. + +## Problem Validation Techniques + +- Problem interviews focused on current behavior +- Journey friction mapping +- Support ticket and sales-call synthesis +- Behavioral analytics triangulation + +Evidence threshold examples: +- Same pain repeated across multiple target users +- Observable workaround behavior +- Measurable cost of current pain + +## Solution Validation Techniques + +- Concept tests (value proposition comprehension) +- Prototype usability tests (task success/time-to-complete) +- Fake door or concierge tests (demand signal) +- Limited beta cohorts (retention/activation signals) + +## Discovery Sprint Planning + +Suggested 10-day structure: +- Day 1-2: Outcome + opportunity framing +- Day 3-4: Assumption mapping + test design +- Day 5-7: Problem and solution tests +- Day 8-9: Evidence synthesis + decision options +- Day 10: Stakeholder decision review + +## Tooling + +### `scripts/assumption_mapper.py` + +CLI utility that: +- reads assumptions from CSV or inline input +- scores risk/certainty priority +- emits prioritized test plan with suggested test types + +See `references/discovery-frameworks.md` for framework details. diff --git a/product-team/product-discovery/references/discovery-frameworks.md b/product-team/product-discovery/references/discovery-frameworks.md new file mode 100644 index 0000000..64a6a08 --- /dev/null +++ b/product-team/product-discovery/references/discovery-frameworks.md @@ -0,0 +1,72 @@ +# Discovery Frameworks + +## Opportunity Solution Tree (OST) + +Purpose: continuously connect product outcomes to validated opportunities and tested solutions. + +Core structure: +- Outcome (metric) +- Opportunity nodes (needs/pains) +- Solution ideas +- Experiments + +OST practice tips: +- Keep tree live; update after each interview or test. +- Separate opportunity evidence from solution proposals. +- Avoid single-branch trees that force one solution. + +## Jobs-to-be-Done (JTBD) + +Use JTBD to understand progress users seek. + +JTBD template: +"When [situation], I want to [motivation], so I can [expected outcome]." + +JTBD interview focus: +- Trigger moments +- Current alternatives and workarounds +- Purchase/adoption anxieties +- Desired progress and success criteria + +## Kano Model + +Classify features by impact on satisfaction: +- Must-be: expected baseline features +- Performance: more is better +- Delighters: unexpected value multipliers +- Indifferent: low impact +- Reverse: can reduce satisfaction for some users + +Use Kano when prioritizing solution concepts after problem validation. + +## Design Sprint Methodology + +Typical phases: +1. Understand +2. Sketch +3. Decide +4. Prototype +5. Test + +Discovery usage: +- Compress learning cycle into one week. +- Best for high-ambiguity opportunities requiring cross-functional alignment. + +## Assumption Prioritization Matrix + +Map assumptions on two axes: +- Risk if wrong (low -> high) +- Certainty (low -> high) + +Priority order: +1. High risk, low certainty (test first) +2. High risk, high certainty (validate quickly) +3. Low risk, low certainty (defer) +4. Low risk, high certainty (document) + +## Discovery Evidence Rules + +- One source is not enough for major decisions. +- Triangulate qualitative and quantitative signals. +- Predefine decision criteria before test execution. +- Archive evidence with date, segment, and method. diff --git a/product-team/product-discovery/scripts/assumption_mapper.py b/product-team/product-discovery/scripts/assumption_mapper.py new file mode 100755 index 0000000..ace7d6f --- /dev/null +++ b/product-team/product-discovery/scripts/assumption_mapper.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +"""Prioritize product assumptions and suggest validation tests.""" + +import argparse +import csv +from dataclasses import dataclass + + +@dataclass +class Assumption: + statement: str + category: str + risk: float + certainty: float + + @property + def priority_score(self) -> float: + # High-risk, low-certainty assumptions should be tested first. + return self.risk * (1.0 - self.certainty) + + +def parse_float(value: str, field: str) -> float: + number = float(value) + if number < 0 or number > 1: + raise ValueError(f"{field} must be in [0, 1]") + return number + + +def suggest_test(category: str) -> str: + category = category.lower().strip() + if category == "desirability": + return "problem interviews or fake-door test" + if category == "viability": + return "pricing/willingness-to-pay test" + if category == "feasibility": + return "technical spike or architecture prototype" + if category == "usability": + return "moderated usability test" + return "smallest possible experiment with clear success criteria" + + +def load_from_csv(path: str) -> list[Assumption]: + assumptions: list[Assumption] = [] + with open(path, "r", encoding="utf-8", newline="") as handle: + reader = csv.DictReader(handle) + required = {"assumption", "category", "risk", "certainty"} + missing = required - set(reader.fieldnames or []) + if missing: + missing_str = ", ".join(sorted(missing)) + raise ValueError(f"Missing required columns: {missing_str}") + + for row in reader: + assumptions.append( + Assumption( + statement=(row.get("assumption") or "").strip(), + category=(row.get("category") or "").strip(), + risk=parse_float(row.get("risk") or "0", "risk"), + certainty=parse_float(row.get("certainty") or "0", "certainty"), + ) + ) + return assumptions + + +def parse_inline(items: list[str]) -> list[Assumption]: + assumptions: list[Assumption] = [] + for item in items: + # format: statement|category|risk|certainty + parts = [part.strip() for part in item.split("|")] + if len(parts) != 4: + raise ValueError("Inline assumption must be: statement|category|risk|certainty") + assumptions.append( + Assumption( + statement=parts[0], + category=parts[1], + risk=parse_float(parts[2], "risk"), + certainty=parse_float(parts[3], "certainty"), + ) + ) + return assumptions + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="Prioritize assumptions and generate test plan.") + parser.add_argument("input", nargs="?", help="CSV file path") + parser.add_argument( + "--assumption", + action="append", + default=[], + help="Inline assumption: statement|category|risk|certainty", + ) + parser.add_argument("--top", type=int, default=10, help="Maximum assumptions to print") + return parser + + +def main() -> int: + parser = build_parser() + args = parser.parse_args() + + assumptions: list[Assumption] = [] + if args.input: + assumptions.extend(load_from_csv(args.input)) + if args.assumption: + assumptions.extend(parse_inline(args.assumption)) + + if not assumptions: + parser.error("Provide a CSV input file or at least one --assumption value.") + + assumptions.sort(key=lambda item: item.priority_score, reverse=True) + + print("prioritized_assumption_test_plan") + print("rank,priority_score,category,risk,certainty,test,assumption") + for rank, item in enumerate(assumptions[: args.top], start=1): + test = suggest_test(item.category) + print( + f"{rank},{item.priority_score:.4f},{item.category},{item.risk:.2f}," + f"{item.certainty:.2f},{test},{item.statement}" + ) + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())