Module 8: Project Define Mercados Strategy

Final project: define Mercado's strategy, from start to finish

Description

This is the close of the complete guide, and of the entire Product Engineering arc that started with product-thinking-for-engineers-guide. In this module's seven previous lessons you built, layer by layer, Mercado's strategy one-pager: the vision (lesson 2), where it plays and by what margin it wins (lesson 3), how it wins and how much time that ground has left (lesson 4), how defensible that advantage is (lesson 5), which backlog bets belong to that strategy (lesson 6), and how to present it all together (lesson 7). Every layer, until now, ran its own model separately.

This project does what no previous lesson did: it chains the guide's four executable models —positionFit (module 3), differentiationMap (module 4), moatScore (module 6), and strategicFilter (module 7)— into a single pipeline, where one layer's output feeds the next one's input. winOn and avoid —the two values that define Mercado's strategy— are no longer declared by hand: they're derived from the real differentiation and from what the vision rules out. And the final roadmap doesn't just say which bets belong to the game —you already knew that from lesson 6—, but which of them, additionally, are building something defensible, and which aren't yet.

Your deliverable has three parts, and all three are verified with code: (1) the complete pipeline, with the four models chained together and the strategy derived, not declared; (2) the final roadmap, crossed against the moat audit; and (3) the honest read of what that crossing reveals — the finding that closes the complete guide.

Connection to the module. This project introduces no new model — it reuses, verbatim, positionFit (module 3), differentiationMap (module 4), moatScore (module 6, complete eight-candidate inventory), and riceScore + strategicFilter (module 7, complete six-bet backlog). The only genuinely new thing is the orchestration: the code connecting one model's output to the next one's input, and a single additional annotation (deepensMoat) the team adds —at its own judgment, not inferred by any model— to cross the final roadmap against the moat layer.

An analogy: the architect's final report, with every column traced back to its why

This module's lesson 1 opened with the image of the architect who, on the final day, delivers a single document where every design decision traces back to the technical report that justifies it. This project is that document, finished. It doesn't just say "this column is this thick" (the final roadmap) — it says "this column is this thick because the soil engineer found such-and-such resistance in the ground, and because the budget allowed it without sacrificing the structure" (the complete pipeline, with each layer feeding the next). A blueprint that only showed the result, without the chain of whys, wouldn't be able to defend a single decision to a skeptical client. This project is the complete chain, run start to finish, so no "and why is this?" question goes without an answer traceable back to a verified model.

The reference solution, verified

Part 1 — The complete pipeline, chained in Node

The four models, verbatim from their source modules, plus the orchestration connecting them: the vision (module 2) feeds avoid; the real differentiation (module 4) feeds winOn; the strategic filter (module 7) runs on the derived strategy; and the final result gets crossed against the moat audit (module 6) through deepensMoat, the annotation the team adds on top of the backlog.

// ===== LAYER 1: positionFit -- reused verbatim from module 3 =====
function positionFit(target, product, alternatives) {
  const dims = Object.keys(target.weights).filter((d) => target.weights[d] > 0);
  const weightedScore = (c) => dims.reduce((sum, d) => sum + target.weights[d] * c.scores[d], 0);
  const productScore = weightedScore(product);
  const rivals = alternatives.map((a) => ({ name: a.name, score: weightedScore(a) }));
  const bestRival = rivals.reduce((a, b) => (b.score > a.score ? b : a));
  const byDimension = dims.map((d) => {
    const rivalBest = alternatives.reduce(
      (best, a) => (a.scores[d] > best.score ? { name: a.name, score: a.scores[d] } : best),
      { name: alternatives[0].name, score: -Infinity }
    );
    return { dimension: d, weight: target.weights[d], productScore: product.scores[d], bestRivalScore: rivalBest.score, bestRivalName: rivalBest.name, wins: product.scores[d] > rivalBest.score };
  });
  return { segment: target.name, productWeightedScore: Number(productScore.toFixed(2)), bestRival: bestRival.name, bestRivalWeightedScore: Number(bestRival.score.toFixed(2)), fitsSegment: productScore > bestRival.score, byDimension };
}

// ===== LAYER 2: differentiationMap -- reused verbatim from module 4 =====
function differentiationMap(us, competitors, dimensions) {
  const REAL_DIFF_MARGIN = 2;
  return dimensions.map((dim) => {
    const usScore = us.scores[dim.key];
    const rivals = competitors.map((c) => ({ name: c.name, score: c.scores[dim.key] }));
    const best = rivals.reduce((a, b) => (b.score > a.score ? b : a));
    const gap = usScore - best.score;
    let verdict;
    if (gap >= REAL_DIFF_MARGIN) verdict = 'differentiation';
    else if (gap <= -REAL_DIFF_MARGIN) verdict = 'gap';
    else verdict = 'parity';
    return {
      dimension: dim.key,
      mattersToSegment: dim.matters,
      usScore,
      bestCompetitor: best.name,
      bestCompetitorScore: best.score,
      verdict,
    };
  });
}

// ===== LAYER 3: moatScore -- reused verbatim from module 6 =====
function moatScore(advantage) {
  const { name, type } = advantage;
  let durability;
  let rationale;

  switch (type) {
    case 'networkEffect': {
      const { sides, localDecay } = advantage;
      durability = sides >= 2 ? 8 : 5;
      if (localDecay) durability -= 3;
      rationale = `network effect ${sides}-sided${localDecay ? ', with local decay' : ', no decay'}`;
      break;
    }
    case 'switchingCost': {
      const { depth } = advantage;
      const depthScore = { contractual: 3, habit: 5, dataAndWorkflow: 8 };
      durability = depthScore[depth] ?? 3;
      rationale = `switching cost of depth '${depth}'`;
      break;
    }
    case 'scaleEconomies': {
      const { fixedCostShare } = advantage;
      durability = Math.round(fixedCostShare * 10);
      rationale = `economies of scale with ${Math.round(fixedCostShare * 100)}% fixed cost`;
      break;
    }
    case 'dataMoat': {
      const { feedbackLoop, uniqueToUs } = advantage;
      durability = feedbackLoop ? 7 : 2;
      if (feedbackLoop && uniqueToUs) durability += 2;
      rationale = feedbackLoop
        ? `the data feeds a loop that improves the product${uniqueToUs ? ' and is exclusive' : ''}`
        : 'the data accumulates but doesn\'t feed back into the product';
      break;
    }
    case 'brand': {
      const { pricingPower } = advantage;
      durability = pricingPower ? 6 : 2;
      rationale = pricingPower
        ? 'the brand changes purchase behavior (tolerates price or friction)'
        : 'the brand is recognized but doesn\'t change purchase behavior';
      break;
    }
    case 'feature': {
      const { timeToCopyWeekends } = advantage;
      durability = Math.max(0, Math.min(3, timeToCopyWeekends));
      rationale = `feature copyable in ~${timeToCopyWeekends} weekend(s)`;
      break;
    }
    default: {
      durability = 0;
      rationale = 'unknown advantage type';
    }
  }

  durability = Math.max(0, Math.min(10, durability));
  const verdict = durability >= 7 ? 'moat' : durability >= 4 ? 'weak-moat' : 'not-a-moat';
  return { name, type, durability, verdict, rationale };
}

// ===== LAYER 4: riceScore + strategicFilter -- reused verbatim from module 7 =====
function riceScore({ reach, impact, confidence, effort }) {
  return (reach * impact * confidence) / effort;
}

function strategicFilter(backlog, strategy) {
  return backlog.map((bet) => {
    const reinforces = bet.servesDimensions.filter((d) => strategy.winOn.includes(d));
    const conflicts = bet.servesDimensions.filter((d) => strategy.avoid.includes(d));
    return {
      feature: bet.feature,
      servesDimensions: bet.servesDimensions,
      reinforces,
      conflicts,
      inStrategy: reinforces.length > 0 && conflicts.length === 0,
      riceScore: Number(riceScore(bet.rice).toFixed(2)),
      deepensMoat: bet.deepensMoat,
    };
  });
}

// ===================================================================
// STEP 1 -- THE VISION (module 2), unchanged.
// ===================================================================
const vision = {
  statement: 'The world where anyone discovers on Mercado what they didn\'t know they wanted.',
  includes: ['curatedDiscovery', 'localSellerTrust', 'serendipity'],
  excludes: ['exactSkuSearchEngine', 'lowestPriceRace', 'genericMegastore'],
};

// ===================================================================
// STEP 2 -- WHERE TO PLAY: positionFit (module 3), verbatim data.
// ===================================================================
const mercado = { name: 'Mercado', scores: { curatedDiscovery: 9, sellerTrust: 8, catalogBreadth: 6, price: 5, deliverySpeed: 5, convenience: 6 } };
const genericMegastore = { name: 'genericMegastore', scores: { curatedDiscovery: 3, sellerTrust: 4, catalogBreadth: 9, price: 8, deliverySpeed: 9, convenience: 8 } };
const localShop = { name: 'localShop', scores: { curatedDiscovery: 6, sellerTrust: 9, catalogBreadth: 2, price: 4, deliverySpeed: 3, convenience: 3 } };
const explorers = { name: 'explorers', weights: { curatedDiscovery: 0.4, sellerTrust: 0.3, catalogBreadth: 0.1, price: 0.1, deliverySpeed: 0.05, convenience: 0.05 } };

const whereToPlay = positionFit(explorers, mercado, [genericMegastore, localShop]);

// ===================================================================
// STEP 3 -- HOW TO WIN: differentiationMap (module 4), verbatim data.
// ===================================================================
const mercadoDiff = { name: 'Mercado', scores: { catalogBreadth: 3, price: 3, deliverySpeed: 4, curatedDiscovery: 5, localSellerTrust: 5, appAnimationPolish: 5 } };
const genericMegastoreDiff = { name: 'genericMegastore', scores: { catalogBreadth: 5, price: 5, deliverySpeed: 4, curatedDiscovery: 2, localSellerTrust: 2, appAnimationPolish: 2 } };
const neighborhoodShop = { name: 'neighborhoodShop', scores: { catalogBreadth: 1, price: 3, deliverySpeed: 2, curatedDiscovery: 3, localSellerTrust: 3, appAnimationPolish: 1 } };
const dimensions = [
  { key: 'catalogBreadth', matters: false },
  { key: 'price', matters: false },
  { key: 'deliverySpeed', matters: true },
  { key: 'curatedDiscovery', matters: true },
  { key: 'localSellerTrust', matters: true },
  { key: 'appAnimationPolish', matters: false },
];

const howToWin = differentiationMap(mercadoDiff, [genericMegastoreDiff, neighborhoodShop], dimensions);

// ===================================================================
// STEP 4 -- MOATS: moatScore (module 6), complete verbatim inventory.
// ===================================================================
const moatCandidates = [
  { name: 'sellerNetwork', type: 'networkEffect', sides: 2, localDecay: false },
  { name: 'purchaseData', type: 'dataMoat', feedbackLoop: true, uniqueToUs: true },
  { name: 'logisticsNetwork', type: 'scaleEconomies', fixedCostShare: 0.8 },
  { name: 'sellerToolsWorkflow', type: 'switchingCost', depth: 'dataAndWorkflow' },
  { name: 'localSellerTrust', type: 'brand', pricingPower: true },
  { name: 'curatedDiscovery', type: 'feature', timeToCopyWeekends: 3 },
  { name: 'fasterCheckout', type: 'feature', timeToCopyWeekends: 1 },
  { name: 'mercadoBrand', type: 'brand', pricingPower: false },
];
const moatAudit = moatCandidates.map(moatScore);
const moatByName = Object.fromEntries(moatAudit.map((m) => [m.name, m]));

// ===================================================================
// STEP 5 -- THE STRATEGY IS DERIVED, NOT DECLARED BY HAND.
// M4/M6 call this pillar 'localSellerTrust'; M3/M7 call it 'sellerTrust'.
// Same concept, two identifiers inherited from different modules --
// the one-pager reconciles them here, explicitly, instead of pretending
// they're different things.
// ===================================================================
const dimensionAlias = { localSellerTrust: 'sellerTrust' };
const winOn = howToWin
  .filter((d) => d.verdict === 'differentiation' && d.mattersToSegment)
  .map((d) => dimensionAlias[d.dimension] ?? d.dimension);

const avoidMap = { lowestPriceRace: 'price' };
const avoid = vision.excludes.map((e) => avoidMap[e]).filter(Boolean);

const mercadoStrategy = { winOn, avoid };

// ===================================================================
// STEP 6 -- THE STRATEGIC FILTER over the backlog (module 7), with the
// strategy DERIVED from step 5 -- not the hardcoded copy from module 7.
// deepensMoat is a NEW annotation from this project: which moat-audit
// candidate (step 4) this bet would deepen if built well -- a team
// judgment call, not something any model infers on its own. It doesn't
// change servesDimensions or rice inherited from M7.
// ===================================================================
const backlog = [
  { feature: 'fasterCheckout', servesDimensions: ['convenience'], rice: { reach: 8000, impact: 2, confidence: 0.8, effort: 2 }, deepensMoat: 'fasterCheckout' },
  { feature: 'recommendations', servesDimensions: ['curatedDiscovery'], rice: { reach: 5000, impact: 1, confidence: 0.5, effort: 3 }, deepensMoat: 'curatedDiscovery' },
  { feature: 'sellerTools', servesDimensions: ['sellerTrust'], rice: { reach: 1200, impact: 2, confidence: 0.8, effort: 2 }, deepensMoat: 'sellerToolsWorkflow' },
  { feature: 'reviews', servesDimensions: ['sellerTrust'], rice: { reach: 6000, impact: 0.5, confidence: 0.8, effort: 1 }, deepensMoat: 'localSellerTrust' },
  { feature: 'improvedSearch', servesDimensions: ['catalogBreadth'], rice: { reach: 9000, impact: 1, confidence: 0.5, effort: 3 }, deepensMoat: null },
  { feature: 'lowestPriceMatch', servesDimensions: ['price'], rice: { reach: 9500, impact: 3, confidence: 0.8, effort: 3 }, deepensMoat: null },
];

const filtered = strategicFilter(backlog, mercadoStrategy).sort((a, b) => b.riceScore - a.riceScore);

// ===================================================================
// OUTPUT
// ===================================================================
console.log('=== STEP 2: where to play (positionFit) ===\n');
console.log(`segment: ${whereToPlay.segment} | productWeightedScore: ${whereToPlay.productWeightedScore} | bestRival: ${whereToPlay.bestRival} (${whereToPlay.bestRivalWeightedScore}) | fitsSegment: ${whereToPlay.fitsSegment}`);

console.log('\n=== STEP 3: how to win (differentiationMap) ===\n');
const realDiffs = howToWin.filter((d) => d.verdict === 'differentiation' && d.mattersToSegment);
console.log(`Real differentiation: ${realDiffs.map((d) => d.dimension).join(', ')}`);

console.log('\n=== STEP 4: moats (moatScore) over the differentiation pillars ===\n');
console.table(realDiffs.map((d) => moatByName[d.dimension]).map(({ name, durability, verdict }) => ({ name, durability, verdict })));

console.log('\n=== STEP 5: the strategy, derived (not hardcoded) ===\n');
console.log(`mercadoStrategy = ${JSON.stringify(mercadoStrategy)}`);

console.log('\n=== STEP 6: the strategic filter + the crossing with moats, over the complete backlog ===\n');
console.table(filtered.map((b) => ({
  feature: b.feature,
  inStrategy: b.inStrategy,
  riceScore: b.riceScore,
  deepensMoat: b.deepensMoat ?? 'none',
  moatVerdict: b.deepensMoat ? moatByName[b.deepensMoat].verdict : 'n/a',
})));

const roadmap = filtered.filter((b) => b.inStrategy);
const rejected = filtered.filter((b) => !b.inStrategy);

console.log('\n=== Mercado\'s final roadmap, with its moat backing ===\n');
for (const b of roadmap) {
  const moatNote = b.deepensMoat
    ? `deepens '${b.deepensMoat}' (${moatByName[b.deepensMoat].verdict}, durability ${moatByName[b.deepensMoat].durability})`
    : 'no moat candidate in the inventory';
  console.log(`- ${b.feature} (riceScore ${b.riceScore}): ${moatNote}`);
}

console.log(`\nRejected by the filter (regardless of riceScore): ${rejected.map((b) => `${b.feature} (${b.riceScore})`).join(', ')}.`);

What to expect. Running the file with Node produces exactly this output:

=== STEP 2: where to play (positionFit) ===

segment: explorers | productWeightedScore: 7.65 | bestRival: localShop (6) | fitsSegment: true

=== STEP 3: how to win (differentiationMap) ===

Real differentiation: curatedDiscovery, localSellerTrust

=== STEP 4: moats (moatScore) over the differentiation pillars ===

┌─────────┬────────────────────┬────────────┬──────────────┐
│ (index) │        name        │ durability │   verdict    │
├─────────┼────────────────────┼────────────┼──────────────┤
│    0    │ 'curatedDiscovery' │     3      │ 'not-a-moat' │
│    1    │ 'localSellerTrust' │     6      │ 'weak-moat'  │
└─────────┴────────────────────┴────────────┴──────────────┘

=== STEP 5: the strategy, derived (not hardcoded) ===

mercadoStrategy = {"winOn":["curatedDiscovery","sellerTrust"],"avoid":["price"]}

=== STEP 6: the strategic filter + the crossing with moats, over the complete backlog ===

┌─────────┬────────────────────┬────────────┬───────────┬───────────────────────┬──────────────┐
│ (index) │      feature       │ inStrategy │ riceScore │      deepensMoat      │ moatVerdict  │
├─────────┼────────────────────┼────────────┼───────────┼───────────────────────┼──────────────┤
│    0    │ 'lowestPriceMatch' │   false    │   7600    │        'none'         │    'n/a'     │
│    1    │  'fasterCheckout'  │   false    │   6400    │   'fasterCheckout'    │ 'not-a-moat' │
│    2    │     'reviews'      │    true    │   2400    │  'localSellerTrust'   │ 'weak-moat'  │
│    3    │  'improvedSearch'  │   false    │   1500    │        'none'         │    'n/a'     │
│    4    │   'sellerTools'    │    true    │    960    │ 'sellerToolsWorkflow' │    'moat'    │
│    5    │ 'recommendations'  │    true    │  833.33   │  'curatedDiscovery'   │ 'not-a-moat' │
└─────────┴────────────────────┴────────────┴───────────┴───────────────────────┴──────────────┘

=== Mercado's final roadmap, with its moat backing ===

- reviews (riceScore 2400): deepens 'localSellerTrust' (weak-moat, durability 6)
- sellerTools (riceScore 960): deepens 'sellerToolsWorkflow' (moat, durability 8)
- recommendations (riceScore 833.33): deepens 'curatedDiscovery' (not-a-moat, durability 3)

Rejected by the filter (regardless of riceScore): lowestPriceMatch (7600), fasterCheckout (6400), improvedSearch (1500).

Part 2 — The honest read of the result

The roadmap is identical to module 7's — and that's proof the pipeline works. reviews > sellerTools > recommendations, with lowestPriceMatch, fasterCheckout, and improvedSearch rejected. If step 5 truly derives winOn and avoid from the earlier layers, it has to reproduce the same strategy module 7 verified independently and hardcoded. The exact match isn't a coincidence — it's validation that this guide's four layers are genuinely consistent with each other.

But the crossing with moats reveals something no earlier module, alone, could show you. The final roadmap's three bets aren't interchangeable, even though all three have inStrategy: true:

  • sellerTools deepens sellerToolsWorkflow, a real moat (verdict: 'moat', durability: 8). It's the strongest bet of the three: it belongs to the strategy and builds something that already crossed the defensibility threshold.
  • reviews deepens localSellerTrust, a weak moat (verdict: 'weak-moat', durability: 6). It belongs to the strategy and moves toward a moat, but doesn't get there yet.
  • recommendations deepens curatedDiscovery, and the crossing confirms what lesson 5 already flagged: verdict: 'not-a-moat', durability: 3. This bet does belong to the strategy —it reinforces exactly the dimension where Mercado wins its segment— and at the same time is not yet building any structural defense.

No earlier module could produce this three-level reading. Module 7, alone, would have told you "all three belong, build them" and stopped there — correct information, but incomplete. Module 6, alone, would have told you "curatedDiscovery isn't a moat" without telling you whether the bet that deepens it is still worth building anyway. Only the complete pipeline, running all four layers over the same backlog, produces the question with the precision a real engineering team needs: build all three — all three belong to the game — but treat recommendations with different urgency than the other two, because the differentiation it deepens is still, today, an algorithm copyable in a few weeks. The path was already flagged back in module 6: connecting curatedDiscovery to the same data loop already proven to work in purchaseData (feedbackLoop: true, uniqueToUs: true, durability: 9) would move that differentiation from 'feature' to 'dataMoat' — the engineering bet this roadmap doesn't yet include, and that the team should add to next quarter's conversation, not as an alternative to recommendations, but as its necessary complement.

And the three rejected bets confirm their exclusion from two independent angles, not just one. fasterCheckout doesn't just fail strategicFilter (it reinforces no winOn dimension) — its own moat candidate, evaluated with the same criterion as everything else, comes out 'not-a-moat' with durability: 1, the lowest score in the whole inventory. Two independent models, run on the same data point, reach the same conclusion through different paths: this bet neither belongs to the game nor builds anything defensible. That kind of cross-confirmation —two different models, one verdict— is the strongest signal a strategy team can ask for before shelving an idea with good RICE.

Common mistakes

Presenting the final roadmap without the deepensMoat column. What happens: the team delivers reviews > sellerTools > recommendations as if all three bets were equally solid, without mentioning that only one of the three deepens an already-built moat. Why it happens: strategicFilter produces a binary result (inStrategy: true/false) that feels complete on its own — adding a third dimension of analysis (defensibility) feels like complicating a result that already seemed finished. How to spot it: if your final roadmap doesn't distinguish between a bet that reinforces a real moat and one that reinforces a still-copyable differentiation, you're treating three very different risk profiles as interchangeable. How to fix it: always require this project's complete crossing — strategic belonging and moat backing, never just the first.

Interpreting curatedDiscovery: not-a-moat as a reason NOT to build recommendations. What happens: someone reads this project's result and concludes that, since curatedDiscovery isn't a moat, recommendations should be pulled from the roadmap — confusing "not yet defensible" with "not worth building." Why it happens: a 'not-a-moat' verdict sounds, on a quick read, like an alarm signal inviting the bet's outright removal. How to spot it: if your team is considering removing recommendations from the roadmap because of this result, they misread the conclusion — recommendations still belongs to the strategy (inStrategy: true, confirmed by strategicFilter); what the result adds is extra urgency, not a veto. How to fix it: remember Part 2's exact distinction — belonging and defensibility are different questions; a not-a-moat is a call to reinforce, not to abandon.

Closing the project without connecting the finding to a concrete engineering action. What happens: the team presents the complete pipeline table, acknowledges curatedDiscovery isn't a moat, and stops there — without adding to the backlog the specific bet that would close that gap (connecting curation to the purchaseData loop). Why it happens: the pipeline delivers a complete, verified diagnosis, and it feels like the work is done — the last step, translating the diagnosis into a new engineering bet, is missing. How to spot it: if nobody on your team can name, after running this project, a specific next-quarter bet that would move curatedDiscovery toward dataMoat, the result stayed pure diagnosis. How to fix it: use this project's Part 2 as the starting point of the next backlog conversation — not the end of this one.

Exercises

Exercise 1 — Add a seventh bet that closes the gap. An engineer proposes discoveryDataLoop —connecting curatedDiscovery's algorithm to the same purchase-data loop as purchaseData—, with servesDimensions: ['curatedDiscovery'], rice: { reach: 4000, impact: 2, confidence: 0.6, effort: 4 }, and deepensMoat: 'purchaseData'. Calculate its riceScore, determine whether it would pass strategicFilter, and explain why its deepensMoat is different from recommendations's.

See solution

riceScore = (4000 × 2 × 0.6) / 4 = 4800 / 4 = 1200. With reinforces: ['curatedDiscovery'] (not empty, it's in winOn) and conflicts: [], inStrategy would be true — it would enter the roadmap above sellerTools (960) but below reviews (2400). Its deepensMoat is 'purchaseData', not 'curatedDiscovery', because this bet isn't more curation — it's the infrastructure connection that turns the existing curation into part of the data loop purchaseData already proved works (verdict: 'moat', durability: 9). If built, this pipeline's next run should reflect curatedDiscovery with a type: 'dataMoat' instead of 'feature' — the architecture change Part 2 of this project identified as pending.

Exercise 2 — Simulate what would happen if sellerToolsWorkflow eroded. If Mercado migrated its seller tools to a shallower integration (depth: 'habit' instead of 'dataAndWorkflow', as in lesson 5's Exercise 2), would sellerTools's moatVerdict change in this project's final table? Should it stay on the roadmap anyway?

See solution

Yes it would change: with depthScore['habit'] = 5, durability would drop from 8 to 5, and verdict would drop from 'moat' to 'weak-moat'. sellerTools would stay on the roadmap regardless —strategicFilter doesn't depend on moatScore's result, it still only evaluates servesDimensions against winOn/avoid, so inStrategy would stay true—, but it would go from being "the strongest of the three bets" to sharing the same fragility level as reviews. This exercise shows that this project's crossing isn't static: an architecture decision made months after this analysis can completely change how urgent each roadmap bet is, without changing at all whether that bet belongs to the strategy.

Exercise 3 — Present the guide's complete close to Mercado's founding team. Write, in a paragraph, how you'd present this project's result —the complete pipeline, the roadmap with its moat backing, and the recommendation about recommendations— as the close of this guide's eight complete modules.

See solution

A sample answer: "Eight modules later, here's what we know with certainty, not intuition: we win the segment we chose, with a measurable margin. We win for two concrete reasons, not a general sense of quality. That ground is open today, with an expiration date in three years if we don't defend it. We already have four real moats built, through four independent mechanisms. And when we cross all of that against our quarter's backlog, three bets belong to the game we decided to play — but not all three build the same thing: sellerTools deepens a moat that already exists, reviews builds toward one that's almost there, and recommendations, though absolutely right to build, isn't yet shielding our central differentiation against a quick copy. Our recommendation isn't to pull recommendations from the roadmap — it's to add, alongside it, the engineering bet that connects our curation to the same data loop we already know works. That's the difference between executing the right strategy well, and just executing well, without asking whether the ground under our feet is still ours a year from now."

Summary and next step

In this project you chained, for the first time in the entire guide, product-strategy-for-engineers-guide's four executable models into a single pipeline: positionFit confirmed Mercado wins its segment (fitsSegment: true, margin 1.65); differentiationMap isolated the real differentiation (curatedDiscovery, localSellerTrust); moatScore audited those two pillars and found one is a weak moat and the other still isn't a moat; and strategicFilter, with a strategy derived from the two earlier layers —not declared by hand—, reproduced exactly module 7's roadmap (reviews > sellerTools > recommendations). The final crossing between the roadmap and the moat audit revealed the finding that closes the entire guide: all three bets belong to the strategy, but only one is building, today, something already defensible — and Mercado's central differentiation is still waiting for the engineering bet that would turn it into a moat.

With this you close the complete guide: product-strategy-for-engineers-guide, its eight modules, from vision to executed roadmap.

The complete Product Engineering arc. You started this ecosystem in product-thinking-for-engineers-guide, learning to prioritize a backlog with RICE, size opportunities, and cut down to the right MVP — the tactical "how the game is played." This guide gave you "what game we're playing": the vision, the segment, the differentiation, the moats, and the filter that decides which of the tactical backlog serves the chosen direction. The sibling guides complete the rest of the cycle: product-discovery-and-prototyping-guide validates with real users the assumptions declared here as strategy; product-metrics-and-experimentation-guide measures, with statistical rigor, whether what you built actually worked; and shipping-and-iterating-products-guide closes the cycle with launch and iteration. With this guide's seven pieces —and product-thinking-for-engineers-guide's before it—, you now know how to decide what to build and why, with traceable evidence at every step.

Where you go next: Fullstack, to build. Everything this guide and its sibling guide taught you answers one question: which bets deserve to become code. The next question —how that gets built, technically, with real engineering judgment— belongs to the Fullstack ecosystem. If you've never built a production interface or backend, start with web-fundamentals-html-css-guide; if you already code and want the modern mental model of product construction, react-fundamentals-guide and nextjs-app-router-guide are the natural entry point. Any of the three bets that survived this project's filter —reviews, sellerTools, recommendations— is, from here, a real case of "we already know what to build and why; now let's build it well."

Resources

  • Roger Martin, "Decoding the Strategy Choice Cascade" — rogermartin.medium.com/decoding-the-strategy-choice-cascade-475d40555eb1. Playing to Win's complete cascade, now that you ran your own executable version of it start to finish for Mercado. In English.
  • Melissa Perri, Escaping the Build Traporeilly.com/library/view/escaping-the-build/9781491973767. The complete diagnosis of building with no strategy, solved here with a verifiable pipeline start to finish, not just an isolated filter. In English.
  • Marty Cagan (Silicon Valley Product Group), "Product Strategy" — svpg.com/product-strategy-overview. Cagan closes the whole guide's argument: product strategy only matters the day it decides, with evidence, what gets built and what doesn't — exactly what this project produced. In English.
  • Hamilton Helmer, 7 Powers7powers.com. The moat vocabulary that, crossed against the strategic filter in this project, revealed the most important gap in Mercado's final roadmap. In English.