~/mesha-vegas$⌘
$ cd ../

2026-06-26

A kilo of plantain doesn't cost the same as a plantain

Building a pricing engine for Cameroonian cooking taught me that the real problem is never the arithmetic. It's modelling a market where you buy by the heap, by the bunch and by the piece.

  • #produit
  • #afrique
  • #fcfa
  • #modelisation
  • #donnees
  • #planif
$6 min read37 reads
A kilo of plantain doesn't cost the same as a plantain

In Dr. Stone, Senku rebuilds civilisation from zero. My favourite moment isn't when he makes gunpowder: it's when he first has to establish units of measurement. Without a shared standard, no recipe transmits, no quantity reproduces. Measurement precedes science.

I relived that moment building the pricing engine for Pla'nif, an app for planning Cameroonian meals. I thought I was coding a multiplication. I spent most of the time answering an apparently trivial question: how much does an ingredient cost?

The problem nobody sees coming

A recipe says: "3 ripe plantains."

The Bafoussam market sells: "a heap of plantains for 500 FCFA."

Between the two, there's no obvious conversion. A heap holds between four and seven plantains depending on the seller, the season and your ability to haggle. A plantain weighs between 150 and 400 grams.

Multiply that problem by two hundred ingredients and you understand why meal-planning apps designed in Europe are unusable here. They assume a barcode, a printed net weight and a displayed price per kilo. Three false assumptions in an African market.

The same gap appears everywhere:

  • Leaves sell by the bunch. A bunch of ndolé isn't a unit of mass, it's a unit of hand.
  • Palm oil sells by the litre, is cooked by the spoon, and is counted by weight in nutritional calculations. Three systems for one product.
  • Smoked fish sells by the piece, but the piece varies threefold.

The canonical unit, and why it saves everything

The structuring decision was this: every ingredient has one canonical unit, and everything else is a conversion into it.

Plantain is stored in grams. Always. A recipe asking for "3 plantains" is converted via a grams_per_piece specific to the ingredient. A price recorded as "a heap for 500 FCFA" is converted via an average heap weight.

What that changes: there is only one place where a conversion can be wrong. Before that decision, I had conversions scattered across display code, cost calculation and the shopping list — and of course they diverged.

The general principle is worth keeping beyond cooking: when you handle heterogeneous quantities, convert on the way in, store canonically, convert back for display. Never compute on mixed units. The costliest bug I fixed in this project came from an ingredient stored in litres while the formula treated it as millilitres — a factor of a thousand on the displayed price, in an app whose selling point is precisely the price.

Volume and weight don't convert without density

Second trap, more insidious: how much does "50 cl of oil" weigh?

Not 500 grams. Palm oil has a density of around 0.91 — so 455 grams. The 10% gap looks negligible until it propagates across a whole week of menus and drifts a budget.

Each ingredient therefore carries a density_g_per_ml. Dry ingredients don't need it, liquids do, and powders are a case apart — flour's density depends on how it's packed, which is why serious baking recipes give grams and not cups.

It's a detail. Useful software is made of details like that, accumulated.

Price depends on place, and often you don't have it

A kilo of tomatoes in Douala, in Bafoussam and in Garoua are not the same prices. A budget app that ignores that gives wrong numbers, therefore useless ones.

But you can't require a price reading for every ingredient in every city. Coverage would be laughable at launch, and the app would display "price unavailable" everywhere — the worst possible experience.

The fallback rule I adopted:

  1. Price recorded in the user's city → use it.
  2. Otherwise, national median for that ingredient → use it, flagging the approximation.
  3. Otherwise, the ingredient is marked non-estimable and excluded from the total, without disappearing from the recipe.

The median, not the mean. One aberrant reading — a typo, a tourist-supermarket price — shifts the mean and leaves the median intact. On community-entered data, the median is nearly always the right choice.

The third case matters most for trust: better to display "about 2,300 FCFA (2 ingredients not estimated)" than a wrong total presented as exact. The user who discovers at the market that your estimate was 40% off does not come back.

A meal is not a recipe

The last obstacle wasn't measurement but cultural modelling.

Planning apps assume: one meal = one recipe. A lasagne, a curry, a bowl.

A Cameroonian meal is very often a sauce and an accompaniment. Ndolé with plantain. Okok with cassava sticks. Generating a weekly menu by drawing seven recipes at random produces absurd results — seven sauces with no starch, or three accompaniments in a row.

We had to introduce a dish type, and coherent sauce/accompaniment pairs. The generator no longer draws recipes: it composes meals.

That's the kind of thing no technical specification contains, because it's obvious to anyone who grew up here and invisible to anyone who didn't. A developer coding for a market they don't know will produce an app that works and that nobody uses.

The underlying problem: software assumes a context

This is the point I really want to make.

Every library, every framework, every component we use carries assumptions about the world. A date picker assumes a Gregorian calendar. An address field assumes a street and a postcode. A price formatter assumes two decimals — the CFA franc has none. A phone field assumes a format.

Those assumptions are invisible as long as you build for the context that produced them. They become bugs the moment you leave it.

What I now do systematically on any project aimed at an African market:

  • List the hidden assumptions before choosing a library. Number, address, phone and name formats — many people don't have a "first name, last name" in the Western sense.
  • Go into the field. An hour at Bafoussam market changed Pla'nif's model more than three days of design on paper.
  • Code the "I don't know" case. Missing data is a legitimate state, not an exception to hide.

That last point travels best. Our schemas are full of NOT NULL columns that express a wish rather than a reality. The world is incomplete; models that deny it produce software that lies.

What's left to do

The engine works, and estimates are consistent with real market prices. But data quality remains the real work site: duplicate recipe titles, badly entered units, uneven price coverage across cities.

That's the least glamorous and most reliable lesson of this project: a correct algorithm on mediocre data produces mediocre results. Time spent cleaning data pays more than time spent refining the formula.


Pla'nif is described on its project page. If you're building a product whose core is a model of the field, we can discuss it.