Savings Methodology
v1.0 · May 2026 · Source data: market-prices.json v1.1.0
Versi Bahasa Malaysia akan datang. Sila rujuk versi Bahasa Inggeris di bawah buat masa ini.
How Mudah Tani calculates the "you saved RM X" figure on your home dashboard.
How savings are calculated
When you log a harvest in the app — for example "5 cucumbers" or "1.2 kg of kangkung" — we estimate what those same vegetables would have cost at a market or supermarket. That estimated cost is your savings.
The formula is:
savings = harvest_in_kg × price_per_kgFor weight-based logs (kg, g), the harvest is already in kg. For piece-based logs (fruits, leaves, bunches, etc.), we multiply your quantity by a per-unit weight for that crop, then by the price per kg.
Worked example
You logged 5 tomatoes from your garden. The app's data says:
- A typical medium tomato weighs about 80 g (variety: generic round)
- Tomato sells at the local market for around MYR 6/kg (range: MYR 4.80 to MYR 7.20)
The math is:
5 fruits × 80 g/fruit = 400 g = 0.4 kg
0.4 kg × MYR 6/kg = MYR 2.40 savedIf you logged the price you actually paid for tomatoes recently (the "price at harvest" field when adding a yield), we use thatinstead of the market estimate — see "Priority ladder" below.
Before your first harvest — the estimate
Until you've logged a real harvest, there's nothing to calculate realized savings from — so instead the app shows an estimate("Estimated RM X when you harvest") so you can see the potential payoff of what you're already growing. Once you log your first harvest, the realized figure above takes over and the estimate disappears (so the two never double-count).
The estimate is driven by your Growing space setting — the small card at the bottom of My Garden (you can also tap the estimate itself to adjust it). For each active plant we project an expected harvest from its growing area and multiply by the same market price used everywhere else:
estimated savings = Σ (expected yield for the plant's area × price per kg)
expected yield = crop's expected kg per 100 m² × the plant's growing areaHow your growing area is counted depends on the space type you picked:
- Pots / containers and balcony (per-pot): each plant contributes its own pot area, so the estimate grows as you add more pots.
- Raised bed and garden plot (shared plot): your one total area is split across your active plants, so adding or removing a plant re-divides the same ground rather than inventing new space.
Worked example
Say you have 5 pots of leafy greens (each pot ≈ 0.1 m²), the container default:
5 pots × 0.1 m²/pot = 0.5 m²
0.5 m² × ~150 kg/100 m² expected = 0.75 kg
0.75 kg × MYR 4/kg = MYR 3.00 estimatedA few honesty notes about the estimate:
- It uses each crop's expected yield (a typical, deliberately conservative figure), not a guarantee — real harvests vary with care, season, and conditions.
- Prices come from the same sources and priority ladder as realized savings (below).
- It counts only your own plants. In a shared family garden, other members' plants do not inflate your personal estimate.
- You can change the assumed area any time from the Growing space card — the estimate recalculates instantly.
Priority ladder — which price we use
For every harvest we calculate savings against, the app picks the price in this order:
- Your own "price at harvest" entry — if you typed in what tomatoes cost at the market that week, this wins. Most accurate, most personal.
- Typical market price from
market-prices.json— our central estimate for that crop. - Legacy single-point market price— same data, kept around for older app installs that haven't refreshed yet.
If none of the above produces a positive number, that harvest doesn't contribute to your savings figure (we won't make up a number).
Where the prices come from
Prices live in src/features/playbooks/data/market-prices.json — a versioned JSON file shipped with the app and seeded into your device's local database on first launch.
For Malaysia (MYR):
- Pasar malam (night market) survey across 3 urban locations (Klang Valley)
- Lotus's / Village Grocer / Cold Storage retail audit — chain supermarket pricing for the same items
- Shopee MY — online retail average for non-perishables (herbs, dried roots, specialty)
DOA wholesale price feeds were considered for high-volatility crops (chili, tomato, leafy greens) but the actual market-prices.json v1.1.0 data set is built from the three sources above. DOA integration is tracked as a separate post-MVP backlog item.
For Indonesia (IDR):
Initial extrapolation from MYR prices using the typical 2,000–2,500× ratio observed across the same crops at Pasar Tradisional Jakarta. This is a transparent limitation: the IDR figures are not first-party sampled yet. They are a working starting point; first-party Indonesia sampling is a tracked post-launch task.
Sourcing was performed in May 2026. Each row in market-prices.json carries its own as_of date so individual rows can be refreshed for volatile crops (chili spikes mid-season) without touching the rest.
Why prices have a range
Real produce prices vary by:
- Season — chili, tomato, and leafy greens swing 30–60% within a single season
- Region — Sabah / Sarawak typically run higher than Klang Valley for non-local crops
- Channel — supermarket prices run 1.5–2× above pasar malam for the same item
To be honest about that variation, every crop in market-prices.json carries three price points per currency:
low— what you might pay at a local pasar malam on a good daytypical— our central estimate (used for the savings figure)high— supermarket or off-season pricing
The savings figure uses typical by default. The range is shown alongside on the Savings Detail screen so you can sanity-check the math.
For the initial v1.1.0 data, the low/high range is ±20% from the typical value — a conservative placeholder pending real per-crop sampling. Some crops have known wider volatility (chili, watermelon) and will get tighter ranges in future updates.
What the defaults assume
For piece-based units (fruits, leaves, bunches, etc.), the app needs a kg-per-unit conversion factor. Those defaults live in crops.json per crop and are deliberately conservative — leaning slightly low. Underclaiming is reputationally safer than overclaiming.
Notable defaults:
- Tomato (fruit): 80 g — medium variety (not cherry, not beefsteak)
- Watermelon (piece): sold by piece, not kg — priced per fruit at pasar malam
- Basil (leaves): assumes individual basil leaves, not whole plant
- Chili (fruit):generic varietal; bird's-eye / cili padi are much smaller
- Bunch crops (kangkung, spring onion, mint): typical pasar malam bunch size
If your harvest doesn't match the default — beefsteak tomatoes are 3–4× heavier than 80 g — the app lets you override the per-unit factor. See "How to correct" below.
A small set of crops are priced per-piecerather than per-kg, because that's how they're commonly sold:
- Watermelon, pumpkin, coconut, jackfruit, papaya, durian
For those, the savings math applies the price directly to the piece count rather than going through a kg conversion.
Limitations
We try to be upfront about what this number doesn't tell you:
- Initial sampling, not continuous. Prices were sampled in May 2026 in Klang Valley. Real-time price updates are not implemented — a planned post-MVP integration with government wholesale APIs (DOA Malaysia) is on the backlog.
- IDR is extrapolated, not first-party sampled. Indonesian prices are derived from the typical MYR-to-IDR ratio observed across the same crops, not directly sourced from Pasar Tradisional. First-party Indonesia sampling is a tracked future improvement.
- Single-point sampling per crop. We did one survey across 3 Klang Valley pasar malam locations — not a representative sample of Malaysia. Regional variation (Sabah / Sarawak / Johor specialty crops) is not captured.
- Variety assumptions baked in. Cherry vs beefsteak tomatoes weigh 4–20× differently. We pick one default per crop. If you grow a different variety, the override surface (below) is the answer.
- No real-time updates. Once shipped with an app version, prices stay until the next app update (or you refresh the in-app data manually).
- The "range" is a placeholder for v1.1.0. We applied ±20% uniformly to seed the range. Real range data comes from re-sampling — tracked.
These limitations don't break the savings claim; they bound how precise it can be. If you want a more accurate figure, log the price at harvest when adding a yield — that bypasses every estimate and uses your real number.
How users can correct
If the default doesn't match your reality, you have two override paths inside the app:
1. Edit a per-crop factor (Savings Detail screen)
On the Savings Detail screen, every per-crop row that uses a piece-based unit shows a pencil icon. Tap it to enter your own kg-per-unit factor.
Example: you grow beefsteak tomatoes that average 250 g, not 80 g. Tap the pencil on the tomato row, enter 250, and the savings figure recalculates instantly — and persists for all your future tomato harvests.
2. Log a harvest with your actual price (Add Yield screen)
When you add a yield, the price at harvest field lets you record what you actually paid for the same crop that week. That value wins over every default estimate.
Both paths feed back into the priority ladder. If you don't override anything, the app uses the conservative defaults documented above.
Questions or corrections?
Email support@mudahtani.my with the crop name, your local price, and the source (which market, when). We re-sample volatile crops based on user reports.
Last updated: 2026-05-31 · Methodology version: 1.1 (added pre-harvest growing-space estimate) · Source data: market-prices.json v1.1.0
