Maminda KCS learning loop: yield forecast and research Kurima Credit Score
Final-year project, Chinhoyi University of Technology: Machine learning-based credit risk assessment model for smallholder farmers using embedded IoT, satellite imagery and computer vision (Mashonaland West, Zimbabwe).
Live demo: Maminda KCS Research Lab
What it does
- Inputs for each field and season:
- a crop-health score from a close-up leaf photo, produced by the Mashonaland crop classifier
- Sentinel-2 NDVI
- seven soil-probe readings: moisture, temperature, N, P, K, pH and EC
- days after planting
- Yield head: one XGBoost regressor per crop (maize, potato, tomato) forecasts yield in t/ha.
- Repayment head: an XGBoost classifier combines the same inputs with projected cash coverage (forecast yield ร area ร price, minus costs and household needs, plus other cash), the due date and repayment history. It outputs the probability of repaying in full by the due date.
- Score: the probability is mapped to a 300โ850 research KCS. Probability 0.90 maps to 600, and each doubling of the repayment odds adds 50 points. This is a display convention, not a lender-approved policy.
The learning loop (how the model improves as farmers use it)
Photos, NDVI and soil readings are inputs. The model learns only from outcomes: the weighed harvest at season end, and whether each scored loan was repaid by its due date. learning_loop/ contains the code:
outcomes.pyrecords harvests and repayments.- Each repayment is linked to the exact estimate that scored the loan.
- Farmer-estimated harvests are stored but never used as labels.
- Corrections are audited.
learning_loop.pyretrains the models.- Synthetic data as a prior: the synthetic data counts as S real examples. With n real outcomes, the synthetic share of the training weight is S / (S + n).
- Candidate challengers: recalibrate the current model, or retrain at several prior strengths (S = 0 means real data only).
- Fair testing: every candidate is scored by grouped cross-validation over farmers, on outcomes the current release never saw.
- Promotion: the best candidate replaces the champion only if it is better.
- Versioning: every release is versioned and checksummed in
releases/registry.json.
Current release
releases/v0-synthetic-prior is trained on:
- simulated yields for all three crops
- ten public US maize field-season means from the Genomes to Fields 2022 dataset (maize only)
- simulated loan outcomes
It contains no real Mashonaland West outcomes yet. Its credit labels are entirely simulated.
Evidence that the loop works (results/)
Because no real outcomes exist yet, the loop was run against a deliberately different simulator ("field reality": lower smallholder yields, stronger nitrogen and drought limits, harder-hitting disease, side-selling repayment shocks). The test ran for 6 seasons with 140 field-seasons per season, across 3 runs, and was scored on 1,500 unseen farms per crop and 4,500 unseen loans.
| Yield MAE (t/ha) | Synthetic only | Loop after 1 season | Real-only after 1 season | Loop after 6 seasons | Real-only after 6 seasons |
|---|---|---|---|---|---|
| Maize | 1.28 | 0.55 | 0.61 | 0.53 | 0.52 |
| Potato | 4.27 | 4.11 | 4.87 | 3.95 | 3.92 |
| Tomato | 7.04 | 6.43 | 7.46 | 5.99 | 6.12 |
| Repayment model | Synthetic only | Loop after 1 season | Real-only after 1 season | Loop after 6 seasons | Real-only after 6 seasons |
|---|---|---|---|---|---|
| Log loss | 0.553 | 0.549 | 0.617 | 0.543 | 0.552 |
| ROC AUC | 0.752 | 0.760 | 0.672 | 0.761 | 0.748 |
These results show that the method corrects a mis-specified prior, and that the prior helps while real data is scarce. Both sides of the test are simulators. They say nothing about accuracy on real farms.
Use
from huggingface_hub import snapshot_download
import json, sys
root = snapshot_download('awesomeddiey/Maminda-KCS-Learning-Loop')
active = json.load(open(f'{root}/releases/registry.json'))['active']
sys.path.insert(0, f'{root}/releases/{active}')
from model_core import assess
result = assess({'crop': 'maize', 'features': {
'sentinel_ndvi_score': 0.62, 'cv_health_score_0_100': 80, 'soil_moisture_vwc_pct': 24,
'soil_temperature_c': 25, 'soil_n_mg_kg': 90, 'soil_p_mg_kg': 30, 'soil_k_mg_kg': 140,
'soil_ph': 5.9, 'soil_ec_ms_cm': 0.6, 'days_after_planting': 70}},
{'area_ha': 1.2, 'farmgate_price_per_tonne': 320, 'remaining_costs_excluding_loan_financed': 60,
'other_cash_available_by_due': 40, 'household_cash_needed_by_due': 30, 'amount_due': 300,
'days_until_due': 120, 'currency': 'USD', 'prior_loans_count': None,
'prior_late_repayments': None, 'prior_defaults': None},
enable_synthetic_credit=True, root=f'{root}/releases/{active}')
print(result['experimental_yield_t_ha'], result['research_credit_estimate']['kcs_score'])
Requirements: xgboost==3.4.1, numpy, pandas (plus scikit-learn to retrain).
Limits
- Simulated relationships: the effects of NPK, pH, EC and the crop-health score on yield are learned from simulation. Potato and tomato yield heads are simulation-only.
- Uncalibrated sensors: probe NPK, pH and EC methods are not calibrated against laboratory measurements. Equal units do not establish equivalence.
- Synthetic credit labels: repayment probabilities in this release come from simulated loans. They are not established repayment probabilities for real farmers.
- No lending use: no release is validated for lending.
validated_kcs_scoreis always null. - Leaf-level CV score: the crop-health score describes one leaf photo. It does not measure the whole field.
Files
releases/: model releases and the registry. The Space serves the active release.learning_loop/: outcome capture, retraining, and the season replay experiment.prior/: the synthetic prior used for retraining (the G2F rows are not redistributed here).results/: the season replay metrics, champion/challenger decisions and figures.

