| Type: | Package |
| Title: | Farmer Producer Organisation Economics and Member Benefit Accounting |
| Version: | 0.1.0 |
| Description: | Tools to determine when collective marketing, aggregation, grading and bulk input procurement through farmer producer organisations (FPOs) and agricultural cooperatives improve member-farmer returns. The package provides channel-level transaction cost accounting (transport, commissions, market fees, time, rejection and payment-delay costs), estimation of scale economies from consolidated transport, tiered bulk input discounts and grading premiums, break-even throughput and procurement-price analysis, and comparison of individual and collective marketing scenarios. Its core is a member-level distributional accounting framework that separates the total surplus generated by an FPO from the benefits actually received by farmers through prices, transaction cost savings, input savings and dividends, and summarises their distribution across members with Gini and concentration indices and farm-size group shares. |
| License: | GPL (≥ 3) |
| Encoding: | UTF-8 |
| Language: | en-GB |
| Depends: | R (≥ 3.5.0) |
| Imports: | graphics |
| Suggests: | knitr, rmarkdown, testthat (≥ 3.0.0) |
| VignetteBuilder: | knitr |
| Config/testthat/edition: | 3 |
| RoxygenNote: | 7.3.1 |
| NeedsCompilation: | no |
| Packaged: | 2026-10-15 04:40:01 UTC; root |
| Author: | Anbukkani Perumal [aut, cre, cph] |
| Maintainer: | Anbukkani Perumal <anbueconomic@gmail.com> |
| Repository: | CRAN |
| Date/Publication: | 2026-10-24 11:00:15 UTC |
fpoValue: Farmer Producer Organisation Economics and Member Benefit Accounting
Description
Tools to assess when collective marketing, aggregation, grading and bulk procurement through a farmer producer organisation (FPO) improve member-farmer returns.
Details
* [transaction_costs()] - per-farmer transaction costs of a marketing channel. * [aggregation_savings()] - scale economies from consolidated transport, bulk input purchase and grading. * [member_benefit()] - member-level distributional accounting that separates total FPO surplus from benefits received by farmers. * [break_even_volume()] - throughput, membership and procurement price needed for the FPO to break even. * [collective_marketing_scenarios()] - comparison of individual and collective marketing channels for the same members.
Quantities may be in any unit (for example quintals or tonnes) and prices and costs in currency per the same unit, provided they are used consistently.
Author(s)
Maintainer: Anbukkani Perumal anbueconomic@gmail.com [copyright holder]
Scale economies from aggregation
Description
Estimates the gross savings an FPO can generate by aggregating members' produce and purchases, from three sources:
- Transport consolidation
Without the FPO, each member carries produce to the market in small vehicles. With the FPO, members carry produce to a nearby collection centre and the FPO moves the pooled volume in large vehicles. Trip-based costs make the per-unit cost of both legs depend on the quantity carried.
- Bulk input procurement
A tiered discount schedule is applied to each member's own input purchase and to the pooled purchase.
- Grading
Selling sorted grades instead of an ungraded lot earns a price premium per unit equal to the grade-weighted price minus the ungraded price, less any grading cost.
Savings are reported in total, per unit and per member. Transport savings are member-specific; the bulk leg is allocated in proportion to quantity, as are grading premiums. These are *potential* savings: whether members receive them depends on the FPO's pricing and dividend policy, which [member_benefit()] evaluates.
Usage
aggregation_savings(
quantity,
distance_market,
distance_centre = 0,
centre_to_market = NULL,
small_capacity = 1,
small_cost_per_km = 0,
small_fixed_trip = 0,
bulk_capacity = 10,
bulk_cost_per_km = 0,
bulk_fixed_trip = 0,
input_quantity = 0,
input_price = 0,
discount_tiers = NULL,
grade_shares = NULL,
grade_prices = NULL,
ungraded_price = NULL,
grading_cost = 0
)
Arguments
quantity |
Quantity marketed by each member. |
distance_market |
One-way distance (km) from each farm to the market. |
distance_centre |
One-way distance (km) from each farm to the collection centre. |
centre_to_market |
One-way distance (km) from the collection centre to the market. |
small_capacity, small_cost_per_km, small_fixed_trip |
Capacity, cost per km and fixed cost per trip of the small vehicles used by members. |
bulk_capacity, bulk_cost_per_km, bulk_fixed_trip |
Capacity, cost per km and fixed cost per trip of the FPO's bulk vehicles. |
input_quantity |
Quantity of inputs bought by each member. |
input_price |
Undiscounted input price per unit. |
discount_tiers |
Optional data frame with columns 'min_quantity' and 'discount' (share off 'input_price') defining a bulk discount schedule. |
grade_shares, grade_prices |
Optional vectors giving the share of produce in each grade and the price of each grade. |
ungraded_price |
Price of an ungraded lot. |
grading_cost |
Cost of grading per unit. |
Value
A list of class 'aggregation_savings' with 'by_source' (total and per-unit savings for transport, inputs and grading), 'by_member' (savings per member) and 'total'.
Examples
q <- c(4, 8, 15, 25, 60)
aggregation_savings(q, distance_market = 30, distance_centre = 3,
centre_to_market = 30, small_capacity = 5,
small_cost_per_km = 20, bulk_capacity = 100,
bulk_cost_per_km = 45,
input_quantity = c(2, 3, 5, 8, 20), input_price = 1300,
discount_tiers = data.frame(min_quantity = c(10, 30),
discount = c(0.03, 0.06)),
grade_shares = c(0.5, 0.3, 0.2),
grade_prices = c(2000, 1600, 1100),
ungraded_price = 1550, grading_cost = 30)
Break-even throughput, membership and procurement price
Description
Finds the volume an FPO must handle to cover its fixed costs (and an optional surplus target) given the per-unit margin between its sale price and the price it pays members:
Q^{*} = \frac{F + T - M_{inputs}}{p^{sale} - p^{m} - h},
where F is fixed cost, T the surplus target and
M_{inputs} the margin from the input business. It also reports the
number of members needed at the average member volume, the margin of
safety at the current volume, and the highest member price the FPO can
pay at the current volume while breaking even, which bounds how much of
the aggregation gain can be passed to farmers through prices.
Usage
break_even_volume(
fixed_cost,
sale_price,
member_price,
handling_cost = 0,
input_margin = 0,
target_surplus = 0,
avg_member_volume = NULL,
current_volume = NULL
)
Arguments
fixed_cost |
FPO fixed operating cost for the period. |
sale_price |
Price the FPO realises per unit. |
member_price |
Price the FPO pays members per unit. |
handling_cost |
FPO handling cost per unit. |
input_margin |
Surplus from the input business for the period. |
target_surplus |
Surplus the FPO aims to retain (for reserves or dividends). |
avg_member_volume |
Optional average volume per member. |
current_volume |
Optional current throughput. |
Details
All arguments are vectorised and recycled, so a grid of scenarios can be evaluated in one call.
Value
A data frame of class 'break_even_volume' with the inputs and 'unit_margin', 'break_even_volume', 'members_needed', 'margin_of_safety' (share by which current volume exceeds break-even) and 'max_member_price' (at the current volume). Break-even volume is 'Inf' when the unit margin is not positive and the costs are not already covered.
Examples
break_even_volume(fixed_cost = 6e5, sale_price = 1650,
member_price = c(1450, 1500, 1550),
handling_cost = 60, input_margin = 1e5,
avg_member_volume = 15, current_volume = 6000)
Compare individual and collective marketing scenarios
Description
Evaluates the same set of members under alternative marketing channels, for example individual sale to a village trader, individual sale in a wholesale market, FPO aggregation, FPO aggregation with grading, and an FPO contract with a processor or retailer. Each scenario is described by one row of 'scenarios':
- scenario
Name.
- member_price
Price received by members.
- member_cost
Members' transaction cost per unit.
- sale_price
Price realised by the FPO; 'NA' for individual channels.
- handling_cost, fixed_cost, dividend_share
Optional FPO parameters (default 0).
Member net return is q_i(p^m - c^m) + D_i. For FPO channels the
FPO surplus and patronage dividends are computed as in
[member_benefit()]. Each scenario is compared with the 'baseline'
scenario: the gain in members' net returns, the FPO's retained surplus,
the total value created relative to the baseline, the member share of
that value, the share of members better off, and the gain received by
the smallest half of suppliers.
Usage
collective_marketing_scenarios(
quantity,
scenarios,
baseline = NULL,
group = NULL
)
Arguments
quantity |
Quantity marketed by each member. |
scenarios |
Data frame of scenarios (see Details). |
baseline |
Name of the baseline scenario; defaults to the first row. |
group |
Optional group of each member for group-wise gains. |
Value
A list of class 'collective_marketing_scenarios' with 'summary' (one row per scenario), 'member_returns' (matrix of member net returns, members by scenarios) and, if 'group' is given, 'group_gain' (gain per member by group and scenario). A 'plot()' method compares member gains and retained surplus across scenarios.
Examples
q <- c(5, 8, 12, 20, 35, 60)
sc <- data.frame(
scenario = c("village trader", "mandi", "FPO pooled", "FPO graded"),
member_price = c(1300, 1450, 1480, 1540),
member_cost = c(10, 140, 40, 55),
sale_price = c(NA, NA, 1650, 1760),
handling_cost = c(0, 0, 60, 90),
fixed_cost = c(0, 0, 8000, 12000),
dividend_share = c(0, 0, 0.5, 0.5))
res <- collective_marketing_scenarios(q, sc,
group = c("marginal", "marginal", "small", "small",
"medium", "large"))
res
plot(res)
Member-level distributional accounting of FPO benefits
Description
Separates the total surplus generated by a farmer producer organisation (FPO) from the benefits actually received by member farmers.
Usage
member_benefit(
quantity,
outside_price,
member_price,
sale_price,
outside_cost = 0,
member_cost = 0,
handling_cost = 0,
fixed_cost = 0,
input_quantity = 0,
input_market_price = 0,
input_member_price = 0,
input_bulk_price = 0,
dividend_share = 0,
dividend_rule = c("patronage", "equity", "equal"),
equity = NULL,
group = NULL,
member_id = NULL
)
Arguments
quantity |
Quantity each member sells through the FPO. |
outside_price |
Price each member would receive outside the FPO. |
member_price |
Price the FPO pays each member. |
sale_price |
Price the FPO realises when selling the pooled produce. |
outside_cost |
Member transaction cost per unit outside the FPO. |
member_cost |
Member transaction cost per unit when delivering to the FPO. |
handling_cost |
FPO handling, grading and transport cost per unit. |
fixed_cost |
FPO fixed operating cost for the period (staff, rent, compliance, depreciation). |
input_quantity |
Quantity of inputs each member buys through the FPO. |
input_market_price |
Price of inputs in the open market. |
input_member_price |
Price the FPO charges members for inputs. |
input_bulk_price |
Price the FPO pays for inputs in bulk. |
dividend_share |
Share of positive FPO surplus paid out as dividends (0 to 1). |
dividend_rule |
How dividends are allocated: '"patronage"' (in proportion to quantity), '"equity"' (in proportion to 'equity') or '"equal"' (equal per member). |
equity |
Share capital held by each member; required for 'dividend_rule = "equity"'. |
group |
Optional farm-size or social group of each member. |
member_id |
Optional member identifiers. |
Details
**FPO account.** The FPO buys members' produce at 'member_price', sells it at 'sale_price', incurs handling costs per unit and fixed operating costs, and may run an input business buying at 'input_bulk_price' and selling to members at 'input_member_price'. Its net surplus is
S = \sum_i q_i (p^{sale} - p^{m}_i - h) + \sum_i x_i
(w^{m} - w^{bulk}) - F.
A share 'dividend_share' of positive surplus is distributed as dividends; the rest is retained.
**Member account.** Relative to the member's outside option (selling
individually at 'outside_price' with transaction cost 'outside_cost' and
buying inputs at 'input_market_price'), member i gains
B_i = q_i (p^m_i - p^{out}_i) + q_i (c^{out}_i - c^m_i) +
x_i (w^{mkt} - w^{m}) + D_i.
**Value created** is \sum_i (B_i - D_i) + S, which equals
\sum_i B_i plus retained surplus. The member share of value
created shows how much of the FPO's economic contribution actually
reaches farmers. Distribution across members is summarised by the Gini
coefficient of benefits (when all benefits are non-negative), the
concentration index of benefits ranked by quantity marketed (positive
values mean benefits are concentrated among larger suppliers), the
benefit share of the smallest half of suppliers compared with their
quantity share, the share of members made worse off, and, if 'group' is
given, benefit shares and benefit per member by group (for example
marginal, small, medium and large farmers).
Per-member arguments accept a scalar or one value per member. Per-unit transaction costs can be taken from [transaction_costs()].
Value
A list of class 'member_benefit' with
- members
Data frame of benefit components per member: 'price_gain', 'cost_saving', 'input_saving', 'dividend' and 'total'.
- fpo
Named vector: output margin, input margin, fixed cost, net surplus, dividends paid and retained surplus.
- value
Named vector: value created, total member benefit, retained surplus, member share of value created, member benefit per unit marketed.
- distribution
List with 'metrics' and 'by_group'.
Examples
q <- c(5, 8, 12, 20, 35, 60)
size <- c("marginal", "marginal", "small", "small", "medium", "large")
mb <- member_benefit(q, outside_price = 1400, member_price = 1480,
sale_price = 1650, outside_cost = 140,
member_cost = 40, handling_cost = 60,
fixed_cost = 8000, input_quantity = q / 5,
input_market_price = 1350, input_member_price = 1300,
input_bulk_price = 1250, dividend_share = 0.5,
group = size)
mb
mb$distribution$by_group
Plot collective marketing scenarios
Description
Stacked bars of the gain in member net returns and the FPO's retained surplus, relative to the baseline, for each scenario.
Usage
## S3 method for class 'collective_marketing_scenarios'
plot(x, ...)
Arguments
x |
A [collective_marketing_scenarios()] object. |
... |
Further arguments passed to [graphics::barplot()]. |
Value
'x', invisibly.
Examples
sc <- data.frame(scenario = c("individual", "FPO"),
member_price = c(1400, 1480), member_cost = c(120, 40),
sale_price = c(NA, 1650), handling_cost = c(0, 60),
fixed_cost = c(0, 5000), dividend_share = c(0, 0.5))
plot(collective_marketing_scenarios(c(5, 10, 20, 40), sc))
Transaction costs of a marketing channel
Description
Computes, for each farmer, the full cost of selling through one marketing channel. Transport is trip-based, so costs per unit fall with the quantity carried per trip:
transport_i = trips_i \times (2\,d_i\,c_{km} + c_{trip}),\quad
trips_i = \lceil q_i / capacity \rceil.
Other components are commission (a share of sale value), market fees and handling charges per unit, the opportunity cost of the farmer's time per trip, the value of produce rejected or unsold, and the interest cost of delayed payment.
Usage
transaction_costs(
quantity,
price,
distance,
vehicle_capacity = 1,
cost_per_km = 0,
fixed_per_trip = 0,
commission = 0,
market_fee = 0,
handling = 0,
time_hours = 0,
wage = 0,
rejection = 0,
payment_delay = 0,
interest_rate = 0.12,
label = NULL
)
Arguments
quantity |
Quantity sold by each farmer. |
price |
Sale price per unit (scalar or one per farmer). |
distance |
One-way distance (km) from farm to point of sale. |
vehicle_capacity |
Quantity carried per trip. |
cost_per_km |
Vehicle cost per km. |
fixed_per_trip |
Fixed cost per trip (loading, tolls, hire minimum). |
commission |
Commission as a share of sale value. |
market_fee |
Market fee or levy per unit. |
handling |
Handling, packing and loading cost per unit. |
time_hours |
Farmer time per trip (travel, waiting, bargaining). |
wage |
Opportunity cost of farmer time per hour. |
rejection |
Share of quantity rejected or sold at zero value. |
payment_delay |
Days between sale and payment. |
interest_rate |
Annual interest rate used to value payment delay. |
label |
Optional channel name stored with the result. |
Details
Call the function once for each channel (for example individual sale in a wholesale market and delivery to an FPO collection centre) and pass the resulting 'per_unit' costs to [member_benefit()] or [collective_marketing_scenarios()].
Value
A data frame of class 'transaction_costs' with one row per farmer: 'quantity', 'trips', 'transport', 'commission', 'fees', 'handling', 'time_cost', 'rejection_cost', 'delay_cost', 'total', 'per_unit' and 'share_of_value'.
Examples
q <- c(5, 12, 30, 80)
mandi <- transaction_costs(q, price = 1500, distance = 35,
vehicle_capacity = 10, cost_per_km = 25,
commission = 0.06, market_fee = 15,
time_hours = 8, wage = 50,
payment_delay = 7, label = "mandi")
centre <- transaction_costs(q, price = 1500, distance = 4,
vehicle_capacity = 10, cost_per_km = 25,
time_hours = 1.5, wage = 50,
payment_delay = 10, label = "FPO centre")
data.frame(quantity = q, mandi = mandi$per_unit,
centre = centre$per_unit)