Cash Flow and Risk Modelling
To support planning and future additionality analysis, TT-OS can model projected cash flows for regenerative cacao agroforestry. These scenarios compare business-as-usual planting with regenerative agroforestry and carbon-supported pathways.
Key Assumptions
- Project scale: 100 acres, approximately 40 hectares, phased over several years.
- Cacao yield ranges are treated as variable and must be replaced with real site data.
- Carbon revenue assumptions are planning placeholders until feasibility and validation are completed.
- Discount rates, payback periods and IRR outputs should be stress-tested.
Simplified Scenario Structure
| Scenario | Description | Planning Use |
|---|---|---|
| Business-as-Usual | Lower-diversity cacao system with simpler establishment and fewer monitoring costs. | Baseline comparison. |
| Regenerative Agroforestry | Multi-strata cacao system with companion crops, soil regeneration and TT-OS monitoring. | Project scenario without carbon revenue. |
| Regenerative + Carbon Revenue | Regenerative system with potential carbon revenue included as a support pathway. | Additionality and investment comparison draft. |
Python Cash Flow Model
import numpy as np
import pandas as pd
from typing import List
class ElegguaCashFlowModel:
def __init__(self, years: int = 20, discount_rate: float = 0.10):
self.years = years
self.discount_rate = discount_rate
self.results = {}
def calculate_npv(self, cash_flows: List[float]) -> float:
total = 0.0
for year, cash_flow in enumerate(cash_flows):
total += cash_flow / ((1 + self.discount_rate) ** year)
return total
def calculate_irr(self, cash_flows: List[float]) -> float:
low, high = -0.99, 2.0
for _ in range(100):
mid = (low + high) / 2
npv = sum(cf / ((1 + mid) ** year) for year, cf in enumerate(cash_flows))
if npv > 0:
low = mid
else:
high = mid
return low * 100
def run_scenarios(self) -> pd.DataFrame:
scenarios = {
"BAU_Conventional": {
"description": "Business-as-usual low-diversity cacao",
"initial_cost": 800,
"revenue": [0] * 3 + [1400] * 7 + [1600] * 10,
"costs": [1000] * 3 + [700] * 17
},
"Regenerative_No_Carbon": {
"description": "Regenerative agroforestry without carbon revenue",
"initial_cost": 2000,
"revenue": [0] * 4 + [2200] * 6 + [2800] * 10,
"costs": [2200] * 4 + [1000] * 6 + [1100] * 10
},
"Regenerative_With_Carbon": {
"description": "Regenerative agroforestry with carbon revenue placeholder",
"initial_cost": 2000,
"revenue": [0] * 4 + [2600] * 6 + [3200] * 10,
"costs": [2200] * 4 + [1000] * 6 + [1100] * 10
}
}
summary = []
for name, params in scenarios.items():
cash_flows = [-params["initial_cost"]]
cash_flows += [rev - cost for rev, cost in zip(params["revenue"], params["costs"])]
cumulative = np.cumsum(cash_flows)
payback = next((i for i, cf in enumerate(cumulative) if cf > 0), "N/A")
summary.append({
"Scenario": name.replace("_", " "),
"Description": params["description"],
"NPV": round(self.calculate_npv(cash_flows), 0),
"IRR_percent": round(self.calculate_irr(cash_flows), 2),
"Payback_Year": payback
})
return pd.DataFrame(summary)
if __name__ == "__main__":
model = ElegguaCashFlowModel(years=20, discount_rate=0.10)
print(model.run_scenarios().to_string(index=False))
Monte Carlo Simulation
Monte Carlo methods run thousands of scenarios by sampling uncertain variables such as yields, prices, costs and sequestration values. This helps quantify risk rather than pretending there is only one future.
import numpy as np
class ElegguaMonteCarloModel:
def __init__(self, years=20, discount_rate=0.10, n_simulations=5000):
self.years = years
self.discount_rate = discount_rate
self.n_simulations = n_simulations
self.rng = np.random.default_rng(42)
def triangular(self, base, variation):
low = base * (1 - variation)
high = base * (1 + variation)
return self.rng.triangular(low, base, high)
def npv(self, cash_flows):
return sum(cf / ((1 + self.discount_rate) ** year)
for year, cf in enumerate(cash_flows))
def run(self):
npvs = []
for _ in range(self.n_simulations):
initial_cost = self.triangular(2000, 0.25)
revenue_base = self.triangular(2800, 0.30)
annual_cost = self.triangular(1100, 0.20)
cash_flows = [-initial_cost]
for year in range(1, self.years + 1):
maturity_factor = 0.6 if year < 5 else 1.0
revenue = revenue_base * maturity_factor
cash_flows.append(revenue - annual_cost)
npvs.append(self.npv(cash_flows))
values = np.array(npvs)
return {
"npv_mean": float(values.mean()),
"npv_median": float(np.median(values)),
"npv_p10": float(np.percentile(values, 10)),
"npv_p90": float(np.percentile(values, 90)),
"probability_positive_npv": float((values > 0).mean() * 100)
}
Sensitivity Analysis
Sensitivity analysis identifies which variables most influence the outcome. Typical drivers include carbon price, cacao yield, establishment cost, sequestration assumptions and operating costs.
Bayesian Approaches
Bayesian modelling can update assumptions as new soil carbon, biomass or field data arrives. This is useful where early-stage agroforestry data is limited and uncertainty must be represented honestly.
- Prior distributions can be informed by regional studies and early TT-OS field data.
- Posterior updates can occur as new soil or biomass observations arrive.
- Hierarchical models can represent plot-level variation.
- Credible intervals can communicate uncertainty more clearly than single-point estimates.
Open Bayesian Hierarchical Modelling with PyMC
Open Carbon Sequestration Metrics
Public use caution
Public material is evidence-bounded and Founder-gated. It is not medical, legal, financial or investment advice.