Phase 1 foundation: Kenya migration via REGION toggle
Browse filesAdds Kenya as a sibling region to the existing Tamil Nadu / India
pipeline behind a MARKET_INTEL_REGION env toggle (default: kenya).
- config.py loads markets_kenya.json / commodities_kenya.json /
farmers_kenya.json when REGION=kenya, preserving all India behavior
under REGION=india
- 10 KAMIS wholesale markets (Kibuye, Wakulima Nairobi, Kongowea,
Eldoret, Nakuru Wakulima, Kitengela, Kangemi, Karatina, Kathonzweni,
Kaanwa) with coordinates and commodity routing
- 4 commodities: Dry Maize (KAMIS product_id 1), Beans (30), Red Irish
Potato (57), Green Grams (10). Maize seasonal indices derived from
real 2021-2025 KAMIS cache; others from domain research
- 100 Kenyan farmer personas (mixed Swahili/Kikuyu/Luo/Luhya/Kalenjin
names), deterministic seed=42
- src/ingestion/kamis.py: live scrape + demo snapshot path, ports the
benchmark reference parser (expired-cert handling, 3s polite delay,
retry backoff). Plugs into pipeline._step_ingest as a sibling to
fetch_mandi_prices behind REGION dispatch
- src/rag/knowledge_base_kenya.py: 24 chunks across 6 categories
(crop calendars, market institutions, post-harvest, transport,
storage, shocks) with 20 distinct sources (FEWS NET, WFP, KALRO,
CIMMYT, Purdue PICS, KACE, etc.). provider.py switches KB by REGION
- India-specific tests (DPI Aadhaar/KCC module, Tamil-name assertions,
15-mandi/8-35-quintal range assertions) gated behind
@skipif(REGION != india) so Kenya pytest passes clean
- 5 new KAMIS ingestion tests: demo-mode smoke, commodity coverage,
ISO-date URL builder, PRODUCT_IDS pin for the 4 Kenya crops, HTML
fixture parser
E2E acceptance:
- MARKET_INTEL_REGION=india pytest tests/ -> 33 passed
- MARKET_INTEL_REGION=kenya pytest tests/ -> 15 passed, 18 India-only skipped
- MARKET_INTEL_REGION=kenya MARKET_INTEL_DEMO_MODE=1 pipeline.run() ->
7 steps ok, 308 KAMIS records, 100 Kenyan farmer recommendations in 7.2s
Phase 1.4 (Swahili translation + agent region-awareness), 1.5 (frontend
localization), 1.6 (region-parameterized fixtures), and 1.7 (Docker
default + CI matrix) still to come.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- commodities_kenya.json +94 -0
- config.py +42 -9
- data/demo_snapshots/kamis/kamis_demo_snapshot.csv +133 -0
- farmers_kenya.json +1302 -0
- markets_kenya.json +132 -0
- src/ingestion/kamis.py +578 -0
- src/pipeline.py +14 -0
- src/rag/knowledge_base_kenya.py +339 -0
- src/rag/provider.py +6 -1
- tests/fixtures/kamis_nakuru_dry_maize_sample.html +566 -0
- tests/test_dpi.py +13 -0
- tests/test_kamis_ingestion.py +174 -0
- tests/test_smoke.py +10 -0
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"id": "MZE-DRY",
|
| 4 |
+
"name": "Dry Maize",
|
| 5 |
+
"agmarknet_name": "Dry maize",
|
| 6 |
+
"kamis_product_id": 1,
|
| 7 |
+
"unit": "100kg",
|
| 8 |
+
"category": "cereal",
|
| 9 |
+
"perishability": "low",
|
| 10 |
+
"msp_2025_rs": null,
|
| 11 |
+
"base_price_rs": 8500,
|
| 12 |
+
"storage_rate_per_day": 3.0,
|
| 13 |
+
"spoilage_pct_per_day": 0.0012,
|
| 14 |
+
"harvest_windows": [
|
| 15 |
+
{"season": "long_rains", "months": [8, 9, 10]},
|
| 16 |
+
{"season": "short_rains", "months": [2, 3]}
|
| 17 |
+
],
|
| 18 |
+
"seasonal_indices": {
|
| 19 |
+
"1": 1.10, "2": 1.13, "3": 1.03, "4": 1.02, "5": 0.88, "6": 0.94,
|
| 20 |
+
"7": 0.99, "8": 0.94, "9": 0.88, "10": 0.92, "11": 1.16, "12": 1.11
|
| 21 |
+
},
|
| 22 |
+
"post_harvest_loss": {"harvesting": 3.0, "threshing": 2.5, "transport": 1.5, "storage_per_month": 2.5},
|
| 23 |
+
"aliases": ["Dry maize", "Maize (Dry)", "White maize", "Mahindi"]
|
| 24 |
+
},
|
| 25 |
+
{
|
| 26 |
+
"id": "BEN-YGN",
|
| 27 |
+
"name": "Beans (Yellow-Green)",
|
| 28 |
+
"agmarknet_name": "Beans",
|
| 29 |
+
"kamis_product_id": 30,
|
| 30 |
+
"unit": "100kg",
|
| 31 |
+
"category": "pulse",
|
| 32 |
+
"perishability": "low",
|
| 33 |
+
"msp_2025_rs": null,
|
| 34 |
+
"base_price_rs": 13500,
|
| 35 |
+
"storage_rate_per_day": 3.5,
|
| 36 |
+
"spoilage_pct_per_day": 0.0010,
|
| 37 |
+
"harvest_windows": [
|
| 38 |
+
{"season": "long_rains", "months": [7, 8]},
|
| 39 |
+
{"season": "short_rains", "months": [1, 2]}
|
| 40 |
+
],
|
| 41 |
+
"seasonal_indices": {
|
| 42 |
+
"1": 0.90, "2": 0.88, "3": 0.95, "4": 1.02, "5": 1.08, "6": 1.12,
|
| 43 |
+
"7": 0.92, "8": 0.88, "9": 0.95, "10": 1.05, "11": 1.10, "12": 1.15
|
| 44 |
+
},
|
| 45 |
+
"post_harvest_loss": {"harvesting": 2.5, "threshing": 2.0, "transport": 1.0, "storage_per_month": 2.0},
|
| 46 |
+
"aliases": ["Beans (yellow)", "Beans (green)", "Yellow beans", "Maharagwe"]
|
| 47 |
+
},
|
| 48 |
+
{
|
| 49 |
+
"id": "POT-RIR",
|
| 50 |
+
"name": "Red Irish Potato",
|
| 51 |
+
"agmarknet_name": "Potato",
|
| 52 |
+
"kamis_product_id": 57,
|
| 53 |
+
"unit": "100kg",
|
| 54 |
+
"category": "vegetable",
|
| 55 |
+
"perishability": "medium",
|
| 56 |
+
"msp_2025_rs": null,
|
| 57 |
+
"base_price_rs": 4500,
|
| 58 |
+
"storage_rate_per_day": 4.0,
|
| 59 |
+
"spoilage_pct_per_day": 0.0060,
|
| 60 |
+
"harvest_windows": [
|
| 61 |
+
{"season": "long_rains", "months": [7, 8, 9]},
|
| 62 |
+
{"season": "short_rains", "months": [1, 2]}
|
| 63 |
+
],
|
| 64 |
+
"seasonal_indices": {
|
| 65 |
+
"1": 0.88, "2": 0.85, "3": 0.95, "4": 1.05, "5": 1.15, "6": 1.18,
|
| 66 |
+
"7": 0.95, "8": 0.85, "9": 0.90, "10": 1.00, "11": 1.10, "12": 1.14
|
| 67 |
+
},
|
| 68 |
+
"post_harvest_loss": {"harvesting": 3.5, "threshing": 0.0, "transport": 2.5, "storage_per_month": 6.0},
|
| 69 |
+
"aliases": ["Irish potato", "Potato", "Viazi"]
|
| 70 |
+
},
|
| 71 |
+
{
|
| 72 |
+
"id": "GRG-KEN",
|
| 73 |
+
"name": "Green Grams",
|
| 74 |
+
"agmarknet_name": "Green grams",
|
| 75 |
+
"kamis_product_id": 10,
|
| 76 |
+
"unit": "100kg",
|
| 77 |
+
"category": "pulse",
|
| 78 |
+
"perishability": "low",
|
| 79 |
+
"msp_2025_rs": null,
|
| 80 |
+
"base_price_rs": 16000,
|
| 81 |
+
"storage_rate_per_day": 3.5,
|
| 82 |
+
"spoilage_pct_per_day": 0.0008,
|
| 83 |
+
"harvest_windows": [
|
| 84 |
+
{"season": "long_rains", "months": [7, 8]},
|
| 85 |
+
{"season": "short_rains", "months": [12, 1]}
|
| 86 |
+
],
|
| 87 |
+
"seasonal_indices": {
|
| 88 |
+
"1": 0.88, "2": 0.92, "3": 1.02, "4": 1.08, "5": 1.12, "6": 1.15,
|
| 89 |
+
"7": 0.92, "8": 0.88, "9": 0.95, "10": 1.05, "11": 1.10, "12": 0.90
|
| 90 |
+
},
|
| 91 |
+
"post_harvest_loss": {"harvesting": 2.5, "threshing": 1.5, "transport": 1.0, "storage_per_month": 1.5},
|
| 92 |
+
"aliases": ["Green gram", "Ndengu", "Mung bean"]
|
| 93 |
+
}
|
| 94 |
+
]
|
|
@@ -27,10 +27,18 @@ FEATURED_FARMERS, ...).
|
|
| 27 |
from __future__ import annotations
|
| 28 |
|
| 29 |
import json
|
|
|
|
| 30 |
from dataclasses import dataclass
|
| 31 |
from pathlib import Path
|
| 32 |
|
| 33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
# ── Project root / JSON loader ──────────────────────────────────────────
|
| 35 |
|
| 36 |
_ROOT = Path(__file__).resolve().parent
|
|
@@ -41,11 +49,24 @@ def _load_json(name: str) -> list[dict]:
|
|
| 41 |
return json.load(fh)
|
| 42 |
|
| 43 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
# ── Commodities ──────────────────────────────────────────────────────────
|
| 45 |
# The raw dict shape is preserved so downstream code that indexes with
|
| 46 |
# c["id"], c["name"], c["agmarknet_name"], c["category"], etc. keeps working.
|
| 47 |
|
| 48 |
-
COMMODITIES: list[dict] = _load_json(
|
| 49 |
COMMODITY_MAP: dict[str, dict] = {c["id"]: c for c in COMMODITIES}
|
| 50 |
CATEGORIES = sorted({c["category"] for c in COMMODITIES})
|
| 51 |
|
|
@@ -97,7 +118,7 @@ class Mandi:
|
|
| 97 |
reporting_quality: str # "good", "moderate", "poor"
|
| 98 |
|
| 99 |
|
| 100 |
-
_MARKETS_RAW = _load_json(
|
| 101 |
|
| 102 |
MANDIS: list[Mandi] = [
|
| 103 |
Mandi(
|
|
@@ -118,6 +139,10 @@ MANDIS: list[Mandi] = [
|
|
| 118 |
|
| 119 |
MANDI_MAP: dict[str, Mandi] = {m.mandi_id: m for m in MANDIS}
|
| 120 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
|
| 122 |
# ── API Endpoints ────────────────────────────────────────────────────────
|
| 123 |
|
|
@@ -154,12 +179,16 @@ class FarmerPersona:
|
|
| 154 |
featured: bool = False # Precomputed live in the weekly run for the demo UI
|
| 155 |
|
| 156 |
|
| 157 |
-
# Curated, hand-written personas loaded from
|
| 158 |
-
#
|
| 159 |
-
#
|
| 160 |
-
#
|
| 161 |
-
|
|
|
|
| 162 |
|
|
|
|
|
|
|
|
|
|
| 163 |
_CURATED_FARMERS: list[FarmerPersona] = [
|
| 164 |
FarmerPersona(
|
| 165 |
farmer_id=f["farmer_id"],
|
|
@@ -171,9 +200,9 @@ _CURATED_FARMERS: list[FarmerPersona] = [
|
|
| 171 |
quantity_quintals=float(f["quantity_quintals"]),
|
| 172 |
has_storage=bool(f["has_storage"]),
|
| 173 |
notes=f.get("notes", ""),
|
| 174 |
-
featured=
|
| 175 |
)
|
| 176 |
-
for f in _FARMERS_RAW
|
| 177 |
]
|
| 178 |
|
| 179 |
|
|
@@ -259,3 +288,7 @@ SAMPLE_FARMERS: list[FarmerPersona] = (
|
|
| 259 |
# Featured subset: only these farmers get precomputed RECOMMEND calls in the
|
| 260 |
# weekly pipeline. The rest are available for on-demand computation.
|
| 261 |
FEATURED_FARMERS: list[FarmerPersona] = [f for f in SAMPLE_FARMERS if f.featured]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
from __future__ import annotations
|
| 28 |
|
| 29 |
import json
|
| 30 |
+
import os
|
| 31 |
from dataclasses import dataclass
|
| 32 |
from pathlib import Path
|
| 33 |
|
| 34 |
|
| 35 |
+
# ── Region toggle ───────────────────────────────────────────────────────
|
| 36 |
+
# Default Kenya per product decision (LastMileBench MI primary panel).
|
| 37 |
+
# Set MARKET_INTEL_REGION=india to load the original Tamil Nadu data.
|
| 38 |
+
|
| 39 |
+
REGION = os.getenv("MARKET_INTEL_REGION", "kenya")
|
| 40 |
+
|
| 41 |
+
|
| 42 |
# ── Project root / JSON loader ──────────────────────────────────────────
|
| 43 |
|
| 44 |
_ROOT = Path(__file__).resolve().parent
|
|
|
|
| 49 |
return json.load(fh)
|
| 50 |
|
| 51 |
|
| 52 |
+
# Per-region filenames. Kenya uses *_kenya.json; India (and anything else)
|
| 53 |
+
# uses the original bare filenames so the existing Tamil Nadu pipeline is
|
| 54 |
+
# byte-for-byte unchanged when REGION != "kenya".
|
| 55 |
+
if REGION == "kenya":
|
| 56 |
+
_MARKETS_FILE = "markets_kenya.json"
|
| 57 |
+
_COMMODITIES_FILE = "commodities_kenya.json"
|
| 58 |
+
_FARMERS_FILE = "farmers_kenya.json"
|
| 59 |
+
else:
|
| 60 |
+
_MARKETS_FILE = "markets.json"
|
| 61 |
+
_COMMODITIES_FILE = "commodities.json"
|
| 62 |
+
_FARMERS_FILE = "farmers.json"
|
| 63 |
+
|
| 64 |
+
|
| 65 |
# ── Commodities ──────────────────────────────────────────────────────────
|
| 66 |
# The raw dict shape is preserved so downstream code that indexes with
|
| 67 |
# c["id"], c["name"], c["agmarknet_name"], c["category"], etc. keeps working.
|
| 68 |
|
| 69 |
+
COMMODITIES: list[dict] = _load_json(_COMMODITIES_FILE)
|
| 70 |
COMMODITY_MAP: dict[str, dict] = {c["id"]: c for c in COMMODITIES}
|
| 71 |
CATEGORIES = sorted({c["category"] for c in COMMODITIES})
|
| 72 |
|
|
|
|
| 118 |
reporting_quality: str # "good", "moderate", "poor"
|
| 119 |
|
| 120 |
|
| 121 |
+
_MARKETS_RAW = _load_json(_MARKETS_FILE)
|
| 122 |
|
| 123 |
MANDIS: list[Mandi] = [
|
| 124 |
Mandi(
|
|
|
|
| 139 |
|
| 140 |
MANDI_MAP: dict[str, Mandi] = {m.mandi_id: m for m in MANDIS}
|
| 141 |
|
| 142 |
+
# Region-neutral alias: downstream code that isn't India-specific should
|
| 143 |
+
# prefer MARKETS over MANDIS (the Indian term).
|
| 144 |
+
MARKETS: list[Mandi] = MANDIS
|
| 145 |
+
|
| 146 |
|
| 147 |
# ── API Endpoints ────────────────────────────────────────────────────────
|
| 148 |
|
|
|
|
| 179 |
featured: bool = False # Precomputed live in the weekly run for the demo UI
|
| 180 |
|
| 181 |
|
| 182 |
+
# Curated, hand-written personas loaded from the region's farmers JSON.
|
| 183 |
+
# India: 3 curated personas drive the demo UI; the other 97 are procedurally
|
| 184 |
+
# generated to reach TARGET_FARMER_COUNT. Kenya: the JSON already contains
|
| 185 |
+
# all 100 personas, so every entry is loaded and procedural generation
|
| 186 |
+
# contributes zero additional farmers.
|
| 187 |
+
_FARMERS_RAW = _load_json(_FARMERS_FILE)
|
| 188 |
|
| 189 |
+
# Only the first 3 personas are marked featured=True (precomputed
|
| 190 |
+
# RECOMMEND in the weekly pipeline). For India this covers the full
|
| 191 |
+
# curated set; for Kenya it caps precomputed runs at 3 demo cards.
|
| 192 |
_CURATED_FARMERS: list[FarmerPersona] = [
|
| 193 |
FarmerPersona(
|
| 194 |
farmer_id=f["farmer_id"],
|
|
|
|
| 200 |
quantity_quintals=float(f["quantity_quintals"]),
|
| 201 |
has_storage=bool(f["has_storage"]),
|
| 202 |
notes=f.get("notes", ""),
|
| 203 |
+
featured=(i < 3),
|
| 204 |
)
|
| 205 |
+
for i, f in enumerate(_FARMERS_RAW)
|
| 206 |
]
|
| 207 |
|
| 208 |
|
|
|
|
| 288 |
# Featured subset: only these farmers get precomputed RECOMMEND calls in the
|
| 289 |
# weekly pipeline. The rest are available for on-demand computation.
|
| 290 |
FEATURED_FARMERS: list[FarmerPersona] = [f for f in SAMPLE_FARMERS if f.featured]
|
| 291 |
+
|
| 292 |
+
# Region-neutral alias: downstream code that isn't India-specific should
|
| 293 |
+
# prefer FARMERS over SAMPLE_FARMERS.
|
| 294 |
+
FARMERS: list[FarmerPersona] = SAMPLE_FARMERS
|
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
State,District,Market,Commodity,Variety,Min Price,Max Price,Modal Price,Arrivals (Tonnes),Price Date
|
| 2 |
+
Nairobi,Nairobi,Kawangware,Dry maize,,5500.0,6500.0,6500.0,,17/04/2026
|
| 3 |
+
Nairobi,Nairobi,Kawangware,Beans (yellow-green),,16500.0,19500.0,19500.0,,17/04/2026
|
| 4 |
+
Nairobi,Nairobi,Kawangware,Red irish potato,,4400.0,5200.0,5200.0,,17/04/2026
|
| 5 |
+
Nairobi,Nairobi,Kawangware,Green grams,,19250.0,22750.0,22750.0,,17/04/2026
|
| 6 |
+
Nairobi,Nairobi,Kawangware,Dry maize,,5500.0,6500.0,6500.0,,16/04/2026
|
| 7 |
+
Nairobi,Nairobi,Kawangware,Beans (yellow-green),,16500.0,19500.0,19500.0,,16/04/2026
|
| 8 |
+
Nairobi,Nairobi,Kawangware,Red irish potato,,4400.0,5200.0,5200.0,,16/04/2026
|
| 9 |
+
Nairobi,Nairobi,Kawangware,Green grams,,19250.0,22750.0,22750.0,,16/04/2026
|
| 10 |
+
Nairobi,Nairobi,Kawangware,Dry maize,,5500.0,6500.0,6500.0,,15/04/2026
|
| 11 |
+
Nairobi,Nairobi,Kawangware,Beans (yellow-green),,16500.0,19500.0,19500.0,,15/04/2026
|
| 12 |
+
Nairobi,Nairobi,Kawangware,Red irish potato,,4400.0,5200.0,5200.0,,15/04/2026
|
| 13 |
+
Nairobi,Nairobi,Kawangware,Green grams,,19250.0,22750.0,22750.0,,15/04/2026
|
| 14 |
+
Nairobi,Nairobi,Kawangware,Dry maize,,5500.0,6500.0,6500.0,,14/04/2026
|
| 15 |
+
Nairobi,Nairobi,Kawangware,Beans (yellow-green),,16500.0,19500.0,19500.0,,14/04/2026
|
| 16 |
+
Nairobi,Nairobi,Kawangware,Red irish potato,,4400.0,5200.0,5200.0,,14/04/2026
|
| 17 |
+
Nairobi,Nairobi,Kawangware,Green grams,,19250.0,22750.0,22750.0,,14/04/2026
|
| 18 |
+
Nairobi,Nairobi,Kawangware,Dry maize,,5500.0,6500.0,6500.0,,13/04/2026
|
| 19 |
+
Nairobi,Nairobi,Kawangware,Beans (yellow-green),,16500.0,19500.0,19500.0,,13/04/2026
|
| 20 |
+
Nairobi,Nairobi,Kawangware,Red irish potato,,4400.0,5200.0,5200.0,,13/04/2026
|
| 21 |
+
Nairobi,Nairobi,Kawangware,Green grams,,19250.0,22750.0,22750.0,,13/04/2026
|
| 22 |
+
Nairobi,Nairobi,Kawangware,Dry maize,,5500.0,6500.0,6500.0,,10/04/2026
|
| 23 |
+
Nairobi,Nairobi,Kawangware,Beans (yellow-green),,16500.0,19500.0,19500.0,,10/04/2026
|
| 24 |
+
Nairobi,Nairobi,Kawangware,Red irish potato,,4400.0,5200.0,5200.0,,10/04/2026
|
| 25 |
+
Nairobi,Nairobi,Kawangware,Green grams,,19250.0,22750.0,22750.0,,10/04/2026
|
| 26 |
+
Nairobi,Nairobi,Kawangware,Dry maize,,5500.0,6500.0,6500.0,,09/04/2026
|
| 27 |
+
Nairobi,Nairobi,Kawangware,Beans (yellow-green),,16500.0,19500.0,19500.0,,09/04/2026
|
| 28 |
+
Nairobi,Nairobi,Kawangware,Red irish potato,,4400.0,5200.0,5200.0,,09/04/2026
|
| 29 |
+
Nairobi,Nairobi,Kawangware,Green grams,,19250.0,22750.0,22750.0,,09/04/2026
|
| 30 |
+
Nairobi,Nairobi,Kawangware,Dry maize,,5500.0,6500.0,6500.0,,07/04/2026
|
| 31 |
+
Nairobi,Nairobi,Kawangware,Beans (yellow-green),,16500.0,19500.0,19500.0,,07/04/2026
|
| 32 |
+
Nairobi,Nairobi,Kawangware,Red irish potato,,4400.0,5200.0,5200.0,,07/04/2026
|
| 33 |
+
Nairobi,Nairobi,Kawangware,Green grams,,19250.0,22750.0,22750.0,,07/04/2026
|
| 34 |
+
Nairobi,Nairobi,Kawangware,Dry maize,,5500.0,6500.0,6500.0,,02/04/2026
|
| 35 |
+
Nairobi,Nairobi,Kawangware,Beans (yellow-green),,16500.0,19500.0,19500.0,,02/04/2026
|
| 36 |
+
Nairobi,Nairobi,Kawangware,Red irish potato,,4400.0,5200.0,5200.0,,02/04/2026
|
| 37 |
+
Nairobi,Nairobi,Kawangware,Green grams,,19250.0,22750.0,22750.0,,02/04/2026
|
| 38 |
+
Nairobi,Nairobi,Kawangware,Dry maize,,5500.0,6500.0,6500.0,,01/04/2026
|
| 39 |
+
Nairobi,Nairobi,Kawangware,Beans (yellow-green),,16500.0,19500.0,19500.0,,01/04/2026
|
| 40 |
+
Nairobi,Nairobi,Kawangware,Red irish potato,,4400.0,5200.0,5200.0,,01/04/2026
|
| 41 |
+
Nairobi,Nairobi,Kawangware,Green grams,,19250.0,22750.0,22750.0,,01/04/2026
|
| 42 |
+
Nairobi,Nairobi,Kawangware,Dry maize,,5500.0,6500.0,6500.0,,31/03/2026
|
| 43 |
+
Nairobi,Nairobi,Kawangware,Beans (yellow-green),,16500.0,19500.0,19500.0,,31/03/2026
|
| 44 |
+
Nairobi,Nairobi,Kawangware,Red irish potato,,4400.0,5200.0,5200.0,,31/03/2026
|
| 45 |
+
Nairobi,Nairobi,Kawangware,Green grams,,19250.0,22750.0,22750.0,,31/03/2026
|
| 46 |
+
Nairobi,Nairobi,Kawangware,Dry maize,,5500.0,6500.0,6500.0,,30/03/2026
|
| 47 |
+
Nairobi,Nairobi,Kawangware,Beans (yellow-green),,16500.0,19500.0,19500.0,,30/03/2026
|
| 48 |
+
Nairobi,Nairobi,Kawangware,Red irish potato,,4400.0,5200.0,5200.0,,30/03/2026
|
| 49 |
+
Nairobi,Nairobi,Kawangware,Green grams,,19250.0,22750.0,22750.0,,30/03/2026
|
| 50 |
+
Nairobi,Nairobi,Kawangware,Dry maize,,5500.0,6500.0,6500.0,,27/03/2026
|
| 51 |
+
Nairobi,Nairobi,Kawangware,Beans (yellow-green),,16500.0,19500.0,19500.0,,27/03/2026
|
| 52 |
+
Nairobi,Nairobi,Kawangware,Red irish potato,,4400.0,5200.0,5200.0,,27/03/2026
|
| 53 |
+
Nairobi,Nairobi,Kawangware,Green grams,,19250.0,22750.0,22750.0,,27/03/2026
|
| 54 |
+
Nairobi,Nairobi,Kawangware,Dry maize,,5500.0,6500.0,6500.0,,26/03/2026
|
| 55 |
+
Nairobi,Nairobi,Kawangware,Beans (yellow-green),,16500.0,19500.0,19500.0,,26/03/2026
|
| 56 |
+
Nairobi,Nairobi,Kawangware,Red irish potato,,4400.0,5200.0,5200.0,,26/03/2026
|
| 57 |
+
Nairobi,Nairobi,Kawangware,Green grams,,19250.0,22750.0,22750.0,,26/03/2026
|
| 58 |
+
Nairobi,Nairobi,Kawangware,Dry maize,,5500.0,6500.0,6500.0,,26/03/2026
|
| 59 |
+
Nairobi,Nairobi,Kawangware,Beans (yellow-green),,16500.0,19500.0,19500.0,,26/03/2026
|
| 60 |
+
Nairobi,Nairobi,Kawangware,Red irish potato,,4400.0,5200.0,5200.0,,26/03/2026
|
| 61 |
+
Nairobi,Nairobi,Kawangware,Green grams,,19250.0,22750.0,22750.0,,26/03/2026
|
| 62 |
+
Nairobi,Nairobi,Kawangware,Dry maize,,5500.0,6500.0,6500.0,,25/03/2026
|
| 63 |
+
Nairobi,Nairobi,Kawangware,Beans (yellow-green),,16500.0,19500.0,19500.0,,25/03/2026
|
| 64 |
+
Nairobi,Nairobi,Kawangware,Red irish potato,,4400.0,5200.0,5200.0,,25/03/2026
|
| 65 |
+
Nairobi,Nairobi,Kawangware,Green grams,,19250.0,22750.0,22750.0,,25/03/2026
|
| 66 |
+
Nairobi,Nairobi,Kawangware,Dry maize,,5500.0,6500.0,6500.0,,24/03/2026
|
| 67 |
+
Nairobi,Nairobi,Kawangware,Beans (yellow-green),,16500.0,19500.0,19500.0,,24/03/2026
|
| 68 |
+
Nairobi,Nairobi,Kawangware,Red irish potato,,4400.0,5200.0,5200.0,,24/03/2026
|
| 69 |
+
Nairobi,Nairobi,Kawangware,Green grams,,19250.0,22750.0,22750.0,,24/03/2026
|
| 70 |
+
Nairobi,Nairobi,Kangemi Market,Dry maize,,5000.0,5000.0,5000.0,,23/03/2026
|
| 71 |
+
Nairobi,Nairobi,Kangemi Market,Beans (yellow-green),,15000.0,15000.0,15000.0,,23/03/2026
|
| 72 |
+
Nairobi,Nairobi,Kangemi Market,Red irish potato,,4000.0,4000.0,4000.0,,23/03/2026
|
| 73 |
+
Nairobi,Nairobi,Kangemi Market,Green grams,,17500.0,17500.0,17500.0,,23/03/2026
|
| 74 |
+
Nairobi,Nairobi,Kawangware,Dry maize,,5500.0,6500.0,6500.0,,23/03/2026
|
| 75 |
+
Nairobi,Nairobi,Kawangware,Beans (yellow-green),,16500.0,19500.0,19500.0,,23/03/2026
|
| 76 |
+
Nairobi,Nairobi,Kawangware,Red irish potato,,4400.0,5200.0,5200.0,,23/03/2026
|
| 77 |
+
Nairobi,Nairobi,Kawangware,Green grams,,19250.0,22750.0,22750.0,,23/03/2026
|
| 78 |
+
Nairobi,Nairobi,Kawangware,Dry maize,,5500.0,6500.0,6500.0,,19/03/2026
|
| 79 |
+
Nairobi,Nairobi,Kawangware,Beans (yellow-green),,16500.0,19500.0,19500.0,,19/03/2026
|
| 80 |
+
Nairobi,Nairobi,Kawangware,Red irish potato,,4400.0,5200.0,5200.0,,19/03/2026
|
| 81 |
+
Nairobi,Nairobi,Kawangware,Green grams,,19250.0,22750.0,22750.0,,19/03/2026
|
| 82 |
+
Nairobi,Nairobi,Kawangware,Dry maize,,5389.0,6500.0,6500.0,,18/03/2026
|
| 83 |
+
Nairobi,Nairobi,Kawangware,Beans (yellow-green),,16167.0,19500.0,19500.0,,18/03/2026
|
| 84 |
+
Nairobi,Nairobi,Kawangware,Red irish potato,,4311.2,5200.0,5200.0,,18/03/2026
|
| 85 |
+
Nairobi,Nairobi,Kawangware,Green grams,,18861.5,22750.0,22750.0,,18/03/2026
|
| 86 |
+
Nairobi,Nairobi,Kangemi Market,Dry maize,,5000.0,5000.0,5000.0,,18/03/2026
|
| 87 |
+
Nairobi,Nairobi,Kangemi Market,Beans (yellow-green),,15000.0,15000.0,15000.0,,18/03/2026
|
| 88 |
+
Nairobi,Nairobi,Kangemi Market,Red irish potato,,4000.0,4000.0,4000.0,,18/03/2026
|
| 89 |
+
Nairobi,Nairobi,Kangemi Market,Green grams,,17500.0,17500.0,17500.0,,18/03/2026
|
| 90 |
+
Nakuru,Nakuru,Molo,Dry maize,,4444.0,4444.0,4444.0,,27/05/2025
|
| 91 |
+
Nakuru,Nakuru,Molo,Beans (yellow-green),,13332.0,13332.0,13332.0,,27/05/2025
|
| 92 |
+
Nakuru,Nakuru,Molo,Red irish potato,,3555.2,3555.2,3555.2,,27/05/2025
|
| 93 |
+
Nakuru,Nakuru,Molo,Green grams,,15554.0,15554.0,15554.0,,27/05/2025
|
| 94 |
+
Nakuru,Nakuru,Molo,Dry maize,,4444.0,4444.0,4444.0,,26/05/2025
|
| 95 |
+
Nakuru,Nakuru,Molo,Beans (yellow-green),,13332.0,13332.0,13332.0,,26/05/2025
|
| 96 |
+
Nakuru,Nakuru,Molo,Red irish potato,,3555.2,3555.2,3555.2,,26/05/2025
|
| 97 |
+
Nakuru,Nakuru,Molo,Green grams,,15554.0,15554.0,15554.0,,26/05/2025
|
| 98 |
+
Nakuru,Nakuru,Molo,Dry maize,,4556.0,4556.0,4556.0,,24/05/2025
|
| 99 |
+
Nakuru,Nakuru,Molo,Beans (yellow-green),,13668.0,13668.0,13668.0,,24/05/2025
|
| 100 |
+
Nakuru,Nakuru,Molo,Red irish potato,,3644.8,3644.8,3644.8,,24/05/2025
|
| 101 |
+
Nakuru,Nakuru,Molo,Green grams,,15946.0,15946.0,15946.0,,24/05/2025
|
| 102 |
+
Nakuru,Nakuru,Molo,Dry maize,,4556.0,4556.0,4556.0,,23/05/2025
|
| 103 |
+
Nakuru,Nakuru,Molo,Beans (yellow-green),,13668.0,13668.0,13668.0,,23/05/2025
|
| 104 |
+
Nakuru,Nakuru,Molo,Red irish potato,,3644.8,3644.8,3644.8,,23/05/2025
|
| 105 |
+
Nakuru,Nakuru,Molo,Green grams,,15946.0,15946.0,15946.0,,23/05/2025
|
| 106 |
+
Nakuru,Nakuru,Molo,Dry maize,,4556.0,4556.0,4556.0,,22/05/2025
|
| 107 |
+
Nakuru,Nakuru,Molo,Beans (yellow-green),,13668.0,13668.0,13668.0,,22/05/2025
|
| 108 |
+
Nakuru,Nakuru,Molo,Red irish potato,,3644.8,3644.8,3644.8,,22/05/2025
|
| 109 |
+
Nakuru,Nakuru,Molo,Green grams,,15946.0,15946.0,15946.0,,22/05/2025
|
| 110 |
+
Nakuru,Nakuru,Molo,Dry maize,,4333.0,4333.0,4333.0,,21/05/2025
|
| 111 |
+
Nakuru,Nakuru,Molo,Beans (yellow-green),,12999.0,12999.0,12999.0,,21/05/2025
|
| 112 |
+
Nakuru,Nakuru,Molo,Red irish potato,,3466.4,3466.4,3466.4,,21/05/2025
|
| 113 |
+
Nakuru,Nakuru,Molo,Green grams,,15165.5,15165.5,15165.5,,21/05/2025
|
| 114 |
+
Nakuru,Nakuru,Molo,Dry maize,,4444.0,4444.0,4444.0,,20/05/2025
|
| 115 |
+
Nakuru,Nakuru,Molo,Beans (yellow-green),,13332.0,13332.0,13332.0,,20/05/2025
|
| 116 |
+
Nakuru,Nakuru,Molo,Red irish potato,,3555.2,3555.2,3555.2,,20/05/2025
|
| 117 |
+
Nakuru,Nakuru,Molo,Green grams,,15554.0,15554.0,15554.0,,20/05/2025
|
| 118 |
+
Nakuru,Nakuru,Molo,Dry maize,,4222.0,4222.0,4222.0,,19/05/2025
|
| 119 |
+
Nakuru,Nakuru,Molo,Beans (yellow-green),,12666.0,12666.0,12666.0,,19/05/2025
|
| 120 |
+
Nakuru,Nakuru,Molo,Red irish potato,,3377.6,3377.6,3377.6,,19/05/2025
|
| 121 |
+
Nakuru,Nakuru,Molo,Green grams,,14777.0,14777.0,14777.0,,19/05/2025
|
| 122 |
+
Nakuru,Nakuru,Molo,Dry maize,,3778.0,3778.0,3778.0,,17/05/2025
|
| 123 |
+
Nakuru,Nakuru,Molo,Beans (yellow-green),,11334.0,11334.0,11334.0,,17/05/2025
|
| 124 |
+
Nakuru,Nakuru,Molo,Red irish potato,,3022.4,3022.4,3022.4,,17/05/2025
|
| 125 |
+
Nakuru,Nakuru,Molo,Green grams,,13223.0,13223.0,13223.0,,17/05/2025
|
| 126 |
+
Nakuru,Nakuru,Molo,Dry maize,,3778.0,3778.0,3778.0,,16/05/2025
|
| 127 |
+
Nakuru,Nakuru,Molo,Beans (yellow-green),,11334.0,11334.0,11334.0,,16/05/2025
|
| 128 |
+
Nakuru,Nakuru,Molo,Red irish potato,,3022.4,3022.4,3022.4,,16/05/2025
|
| 129 |
+
Nakuru,Nakuru,Molo,Green grams,,13223.0,13223.0,13223.0,,16/05/2025
|
| 130 |
+
Nakuru,Nakuru,Molo,Dry maize,,3778.0,3778.0,3778.0,,15/05/2025
|
| 131 |
+
Nakuru,Nakuru,Molo,Beans (yellow-green),,11334.0,11334.0,11334.0,,15/05/2025
|
| 132 |
+
Nakuru,Nakuru,Molo,Red irish potato,,3022.4,3022.4,3022.4,,15/05/2025
|
| 133 |
+
Nakuru,Nakuru,Molo,Green grams,,13223.0,13223.0,13223.0,,15/05/2025
|
|
@@ -0,0 +1,1302 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"farmer_id": "FMR-K0001",
|
| 4 |
+
"name": "Mogaka Okoth",
|
| 5 |
+
"location_name": "Kisumu",
|
| 6 |
+
"latitude": -0.2377,
|
| 7 |
+
"longitude": 34.6848,
|
| 8 |
+
"primary_commodity": "MZE-DRY",
|
| 9 |
+
"quantity_quintals": 9.9,
|
| 10 |
+
"has_storage": false,
|
| 11 |
+
"distance_to_market_km": 4.5,
|
| 12 |
+
"nearest_market_id": "MKT-KIB",
|
| 13 |
+
"notes": "Smallholder dry maize farmer in Kisumu. 4.5 km to Kibuye; no storage; must sell at harvest."
|
| 14 |
+
},
|
| 15 |
+
{
|
| 16 |
+
"farmer_id": "FMR-K0002",
|
| 17 |
+
"name": "Juma Odhiambo",
|
| 18 |
+
"location_name": "Kisumu",
|
| 19 |
+
"latitude": -0.0681,
|
| 20 |
+
"longitude": 34.6118,
|
| 21 |
+
"primary_commodity": "MZE-DRY",
|
| 22 |
+
"quantity_quintals": 12.7,
|
| 23 |
+
"has_storage": true,
|
| 24 |
+
"distance_to_market_km": 18.2,
|
| 25 |
+
"nearest_market_id": "MKT-KIB",
|
| 26 |
+
"notes": "Smallholder dry maize farmer in Kisumu. 18.2 km to Kibuye; has dry storage shed."
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"farmer_id": "FMR-K0003",
|
| 30 |
+
"name": "Owino Muriuki",
|
| 31 |
+
"location_name": "Kisumu",
|
| 32 |
+
"latitude": -0.1193,
|
| 33 |
+
"longitude": 34.7371,
|
| 34 |
+
"primary_commodity": "BEN-YGN",
|
| 35 |
+
"quantity_quintals": 33.3,
|
| 36 |
+
"has_storage": false,
|
| 37 |
+
"distance_to_market_km": 1.2,
|
| 38 |
+
"nearest_market_id": "MKT-KIB",
|
| 39 |
+
"notes": "Smallholder beans farmer in Kisumu. 1.2 km to Kibuye; no storage; must sell at harvest."
|
| 40 |
+
},
|
| 41 |
+
{
|
| 42 |
+
"farmer_id": "FMR-K0004",
|
| 43 |
+
"name": "Jepkorir Kosgei",
|
| 44 |
+
"location_name": "Kisumu",
|
| 45 |
+
"latitude": -0.1618,
|
| 46 |
+
"longitude": 34.6669,
|
| 47 |
+
"primary_commodity": "BEN-YGN",
|
| 48 |
+
"quantity_quintals": 8.6,
|
| 49 |
+
"has_storage": true,
|
| 50 |
+
"distance_to_market_km": 13.9,
|
| 51 |
+
"nearest_market_id": "MKT-KIB",
|
| 52 |
+
"notes": "Smallholder beans farmer in Kisumu. 13.9 km to Kibuye; has dry storage shed."
|
| 53 |
+
},
|
| 54 |
+
{
|
| 55 |
+
"farmer_id": "FMR-K0005",
|
| 56 |
+
"name": "Kaloki Mwikali",
|
| 57 |
+
"location_name": "Kisumu",
|
| 58 |
+
"latitude": -0.0031,
|
| 59 |
+
"longitude": 34.8212,
|
| 60 |
+
"primary_commodity": "GRG-KEN",
|
| 61 |
+
"quantity_quintals": 9.4,
|
| 62 |
+
"has_storage": true,
|
| 63 |
+
"distance_to_market_km": 32.4,
|
| 64 |
+
"nearest_market_id": "MKT-KIB",
|
| 65 |
+
"notes": "Smallholder green grams farmer in Kisumu. 32.4 km to Kibuye; has dry storage shed."
|
| 66 |
+
},
|
| 67 |
+
{
|
| 68 |
+
"farmer_id": "FMR-K0006",
|
| 69 |
+
"name": "Wanjala Kibet",
|
| 70 |
+
"location_name": "Kisumu",
|
| 71 |
+
"latitude": -0.072,
|
| 72 |
+
"longitude": 34.8137,
|
| 73 |
+
"primary_commodity": "MZE-DRY",
|
| 74 |
+
"quantity_quintals": 28.1,
|
| 75 |
+
"has_storage": false,
|
| 76 |
+
"distance_to_market_km": 27.3,
|
| 77 |
+
"nearest_market_id": "MKT-KIB",
|
| 78 |
+
"notes": "Smallholder dry maize farmer in Kisumu. 27.3 km to Kibuye; no storage; must sell at harvest."
|
| 79 |
+
},
|
| 80 |
+
{
|
| 81 |
+
"farmer_id": "FMR-K0007",
|
| 82 |
+
"name": "Okoth Ochieng",
|
| 83 |
+
"location_name": "Kisumu",
|
| 84 |
+
"latitude": -0.1312,
|
| 85 |
+
"longitude": 34.7383,
|
| 86 |
+
"primary_commodity": "BEN-YGN",
|
| 87 |
+
"quantity_quintals": 10.7,
|
| 88 |
+
"has_storage": false,
|
| 89 |
+
"distance_to_market_km": 13.1,
|
| 90 |
+
"nearest_market_id": "MKT-KIB",
|
| 91 |
+
"notes": "Smallholder beans farmer in Kisumu. 13.1 km to Kibuye; no storage; must sell at harvest."
|
| 92 |
+
},
|
| 93 |
+
{
|
| 94 |
+
"farmer_id": "FMR-K0008",
|
| 95 |
+
"name": "Kamau Wekesa",
|
| 96 |
+
"location_name": "Kisumu",
|
| 97 |
+
"latitude": -0.085,
|
| 98 |
+
"longitude": 34.6757,
|
| 99 |
+
"primary_commodity": "BEN-YGN",
|
| 100 |
+
"quantity_quintals": 18.3,
|
| 101 |
+
"has_storage": false,
|
| 102 |
+
"distance_to_market_km": 34.6,
|
| 103 |
+
"nearest_market_id": "MKT-KIB",
|
| 104 |
+
"notes": "Smallholder beans farmer in Kisumu. 34.6 km to Kibuye; no storage; must sell at harvest."
|
| 105 |
+
},
|
| 106 |
+
{
|
| 107 |
+
"farmer_id": "FMR-K0009",
|
| 108 |
+
"name": "Asha Nafula",
|
| 109 |
+
"location_name": "Kisumu",
|
| 110 |
+
"latitude": -0.0398,
|
| 111 |
+
"longitude": 34.8552,
|
| 112 |
+
"primary_commodity": "MZE-DRY",
|
| 113 |
+
"quantity_quintals": 13.0,
|
| 114 |
+
"has_storage": true,
|
| 115 |
+
"distance_to_market_km": 2.1,
|
| 116 |
+
"nearest_market_id": "MKT-KIB",
|
| 117 |
+
"notes": "Smallholder dry maize farmer in Kisumu. 2.1 km to Kibuye; has dry storage shed."
|
| 118 |
+
},
|
| 119 |
+
{
|
| 120 |
+
"farmer_id": "FMR-K0010",
|
| 121 |
+
"name": "Wafula Kinyanjui",
|
| 122 |
+
"location_name": "Kisumu",
|
| 123 |
+
"latitude": -0.1819,
|
| 124 |
+
"longitude": 34.8852,
|
| 125 |
+
"primary_commodity": "GRG-KEN",
|
| 126 |
+
"quantity_quintals": 16.0,
|
| 127 |
+
"has_storage": true,
|
| 128 |
+
"distance_to_market_km": 23.3,
|
| 129 |
+
"nearest_market_id": "MKT-KIB",
|
| 130 |
+
"notes": "Smallholder green grams farmer in Kisumu. 23.3 km to Kibuye; has dry storage shed."
|
| 131 |
+
},
|
| 132 |
+
{
|
| 133 |
+
"farmer_id": "FMR-K0011",
|
| 134 |
+
"name": "Korir Omondi",
|
| 135 |
+
"location_name": "Kisumu",
|
| 136 |
+
"latitude": -0.1657,
|
| 137 |
+
"longitude": 34.6763,
|
| 138 |
+
"primary_commodity": "GRG-KEN",
|
| 139 |
+
"quantity_quintals": 23.9,
|
| 140 |
+
"has_storage": true,
|
| 141 |
+
"distance_to_market_km": 26.4,
|
| 142 |
+
"nearest_market_id": "MKT-KIB",
|
| 143 |
+
"notes": "Smallholder green grams farmer in Kisumu. 26.4 km to Kibuye; has dry storage shed."
|
| 144 |
+
},
|
| 145 |
+
{
|
| 146 |
+
"farmer_id": "FMR-K0012",
|
| 147 |
+
"name": "Mutembei Juma",
|
| 148 |
+
"location_name": "Nairobi",
|
| 149 |
+
"latitude": -1.3335,
|
| 150 |
+
"longitude": 36.9762,
|
| 151 |
+
"primary_commodity": "BEN-YGN",
|
| 152 |
+
"quantity_quintals": 22.8,
|
| 153 |
+
"has_storage": true,
|
| 154 |
+
"distance_to_market_km": 4.1,
|
| 155 |
+
"nearest_market_id": "MKT-NWK",
|
| 156 |
+
"notes": "Smallholder beans farmer in Nairobi. 4.1 km to Nairobi Wakulima; has dry storage shed."
|
| 157 |
+
},
|
| 158 |
+
{
|
| 159 |
+
"farmer_id": "FMR-K0013",
|
| 160 |
+
"name": "Kinuthia Ouma",
|
| 161 |
+
"location_name": "Nairobi",
|
| 162 |
+
"latitude": -1.2539,
|
| 163 |
+
"longitude": 36.9146,
|
| 164 |
+
"primary_commodity": "GRG-KEN",
|
| 165 |
+
"quantity_quintals": 25.9,
|
| 166 |
+
"has_storage": false,
|
| 167 |
+
"distance_to_market_km": 14.1,
|
| 168 |
+
"nearest_market_id": "MKT-NWK",
|
| 169 |
+
"notes": "Smallholder green grams farmer in Nairobi. 14.1 km to Nairobi Wakulima; no storage; must sell at harvest."
|
| 170 |
+
},
|
| 171 |
+
{
|
| 172 |
+
"farmer_id": "FMR-K0014",
|
| 173 |
+
"name": "Kipkoech Mogaka",
|
| 174 |
+
"location_name": "Nairobi",
|
| 175 |
+
"latitude": -1.3667,
|
| 176 |
+
"longitude": 36.843,
|
| 177 |
+
"primary_commodity": "MZE-DRY",
|
| 178 |
+
"quantity_quintals": 28.8,
|
| 179 |
+
"has_storage": false,
|
| 180 |
+
"distance_to_market_km": 4.9,
|
| 181 |
+
"nearest_market_id": "MKT-NWK",
|
| 182 |
+
"notes": "Smallholder dry maize farmer in Nairobi. 4.9 km to Nairobi Wakulima; no storage; must sell at harvest."
|
| 183 |
+
},
|
| 184 |
+
{
|
| 185 |
+
"farmer_id": "FMR-K0015",
|
| 186 |
+
"name": "Wafula Kosgei",
|
| 187 |
+
"location_name": "Nairobi",
|
| 188 |
+
"latitude": -1.4086,
|
| 189 |
+
"longitude": 36.8074,
|
| 190 |
+
"primary_commodity": "GRG-KEN",
|
| 191 |
+
"quantity_quintals": 5.1,
|
| 192 |
+
"has_storage": false,
|
| 193 |
+
"distance_to_market_km": 25.6,
|
| 194 |
+
"nearest_market_id": "MKT-NWK",
|
| 195 |
+
"notes": "Smallholder green grams farmer in Nairobi. 25.6 km to Nairobi Wakulima; no storage; must sell at harvest."
|
| 196 |
+
},
|
| 197 |
+
{
|
| 198 |
+
"farmer_id": "FMR-K0016",
|
| 199 |
+
"name": "Halima Barasa",
|
| 200 |
+
"location_name": "Nairobi",
|
| 201 |
+
"latitude": -1.2898,
|
| 202 |
+
"longitude": 36.7089,
|
| 203 |
+
"primary_commodity": "POT-RIR",
|
| 204 |
+
"quantity_quintals": 34.5,
|
| 205 |
+
"has_storage": true,
|
| 206 |
+
"distance_to_market_km": 18.3,
|
| 207 |
+
"nearest_market_id": "MKT-NWK",
|
| 208 |
+
"notes": "Smallholder Irish potato farmer in Nairobi. 18.3 km to Nairobi Wakulima; has dry storage shed."
|
| 209 |
+
},
|
| 210 |
+
{
|
| 211 |
+
"farmer_id": "FMR-K0017",
|
| 212 |
+
"name": "Muthama Wafula",
|
| 213 |
+
"location_name": "Nairobi",
|
| 214 |
+
"latitude": -1.2803,
|
| 215 |
+
"longitude": 36.9106,
|
| 216 |
+
"primary_commodity": "MZE-DRY",
|
| 217 |
+
"quantity_quintals": 26.0,
|
| 218 |
+
"has_storage": true,
|
| 219 |
+
"distance_to_market_km": 17.6,
|
| 220 |
+
"nearest_market_id": "MKT-NWK",
|
| 221 |
+
"notes": "Smallholder dry maize farmer in Nairobi. 17.6 km to Nairobi Wakulima; has dry storage shed."
|
| 222 |
+
},
|
| 223 |
+
{
|
| 224 |
+
"farmer_id": "FMR-K0018",
|
| 225 |
+
"name": "Musyoka Kiprop",
|
| 226 |
+
"location_name": "Nairobi",
|
| 227 |
+
"latitude": -1.3703,
|
| 228 |
+
"longitude": 36.7493,
|
| 229 |
+
"primary_commodity": "MZE-DRY",
|
| 230 |
+
"quantity_quintals": 8.0,
|
| 231 |
+
"has_storage": true,
|
| 232 |
+
"distance_to_market_km": 17.5,
|
| 233 |
+
"nearest_market_id": "MKT-NWK",
|
| 234 |
+
"notes": "Smallholder dry maize farmer in Nairobi. 17.5 km to Nairobi Wakulima; has dry storage shed."
|
| 235 |
+
},
|
| 236 |
+
{
|
| 237 |
+
"farmer_id": "FMR-K0019",
|
| 238 |
+
"name": "Said Opiyo",
|
| 239 |
+
"location_name": "Nairobi",
|
| 240 |
+
"latitude": -1.4036,
|
| 241 |
+
"longitude": 36.8196,
|
| 242 |
+
"primary_commodity": "BEN-YGN",
|
| 243 |
+
"quantity_quintals": 14.3,
|
| 244 |
+
"has_storage": true,
|
| 245 |
+
"distance_to_market_km": 30.7,
|
| 246 |
+
"nearest_market_id": "MKT-NWK",
|
| 247 |
+
"notes": "Smallholder beans farmer in Nairobi. 30.7 km to Nairobi Wakulima; has dry storage shed."
|
| 248 |
+
},
|
| 249 |
+
{
|
| 250 |
+
"farmer_id": "FMR-K0020",
|
| 251 |
+
"name": "Ouma Muriuki",
|
| 252 |
+
"location_name": "Nairobi",
|
| 253 |
+
"latitude": -1.2155,
|
| 254 |
+
"longitude": 36.884,
|
| 255 |
+
"primary_commodity": "POT-RIR",
|
| 256 |
+
"quantity_quintals": 19.0,
|
| 257 |
+
"has_storage": true,
|
| 258 |
+
"distance_to_market_km": 23.8,
|
| 259 |
+
"nearest_market_id": "MKT-NWK",
|
| 260 |
+
"notes": "Smallholder Irish potato farmer in Nairobi. 23.8 km to Nairobi Wakulima; has dry storage shed."
|
| 261 |
+
},
|
| 262 |
+
{
|
| 263 |
+
"farmer_id": "FMR-K0021",
|
| 264 |
+
"name": "Hassan Abdalla",
|
| 265 |
+
"location_name": "Nairobi",
|
| 266 |
+
"latitude": -1.4058,
|
| 267 |
+
"longitude": 36.7444,
|
| 268 |
+
"primary_commodity": "POT-RIR",
|
| 269 |
+
"quantity_quintals": 5.7,
|
| 270 |
+
"has_storage": false,
|
| 271 |
+
"distance_to_market_km": 19.8,
|
| 272 |
+
"nearest_market_id": "MKT-NWK",
|
| 273 |
+
"notes": "Smallholder Irish potato farmer in Nairobi. 19.8 km to Nairobi Wakulima; no storage; must sell at harvest."
|
| 274 |
+
},
|
| 275 |
+
{
|
| 276 |
+
"farmer_id": "FMR-K0022",
|
| 277 |
+
"name": "Wanjiku Mbugua",
|
| 278 |
+
"location_name": "Nairobi",
|
| 279 |
+
"latitude": -1.2297,
|
| 280 |
+
"longitude": 36.6947,
|
| 281 |
+
"primary_commodity": "MZE-DRY",
|
| 282 |
+
"quantity_quintals": 36.7,
|
| 283 |
+
"has_storage": true,
|
| 284 |
+
"distance_to_market_km": 30.2,
|
| 285 |
+
"nearest_market_id": "MKT-NWK",
|
| 286 |
+
"notes": "Smallholder dry maize farmer in Nairobi. 30.2 km to Nairobi Wakulima; has dry storage shed."
|
| 287 |
+
},
|
| 288 |
+
{
|
| 289 |
+
"farmer_id": "FMR-K0023",
|
| 290 |
+
"name": "Nabwire Nzioka",
|
| 291 |
+
"location_name": "Nairobi",
|
| 292 |
+
"latitude": -1.2414,
|
| 293 |
+
"longitude": 36.7413,
|
| 294 |
+
"primary_commodity": "BEN-YGN",
|
| 295 |
+
"quantity_quintals": 30.3,
|
| 296 |
+
"has_storage": false,
|
| 297 |
+
"distance_to_market_km": 31.0,
|
| 298 |
+
"nearest_market_id": "MKT-NWK",
|
| 299 |
+
"notes": "Smallholder beans farmer in Nairobi. 31.0 km to Nairobi Wakulima; no storage; must sell at harvest."
|
| 300 |
+
},
|
| 301 |
+
{
|
| 302 |
+
"farmer_id": "FMR-K0024",
|
| 303 |
+
"name": "Nafula Mutembei",
|
| 304 |
+
"location_name": "Nairobi",
|
| 305 |
+
"latitude": -1.1999,
|
| 306 |
+
"longitude": 36.7341,
|
| 307 |
+
"primary_commodity": "MZE-DRY",
|
| 308 |
+
"quantity_quintals": 28.1,
|
| 309 |
+
"has_storage": true,
|
| 310 |
+
"distance_to_market_km": 13.0,
|
| 311 |
+
"nearest_market_id": "MKT-NWK",
|
| 312 |
+
"notes": "Smallholder dry maize farmer in Nairobi. 13.0 km to Nairobi Wakulima; has dry storage shed."
|
| 313 |
+
},
|
| 314 |
+
{
|
| 315 |
+
"farmer_id": "FMR-K0025",
|
| 316 |
+
"name": "Muthoni Ochieng",
|
| 317 |
+
"location_name": "Mombasa",
|
| 318 |
+
"latitude": -4.1595,
|
| 319 |
+
"longitude": 39.7561,
|
| 320 |
+
"primary_commodity": "MZE-DRY",
|
| 321 |
+
"quantity_quintals": 13.7,
|
| 322 |
+
"has_storage": true,
|
| 323 |
+
"distance_to_market_km": 7.5,
|
| 324 |
+
"nearest_market_id": "MKT-KON",
|
| 325 |
+
"notes": "Smallholder dry maize farmer in Mombasa. 7.5 km to Kongowea; has dry storage shed."
|
| 326 |
+
},
|
| 327 |
+
{
|
| 328 |
+
"farmer_id": "FMR-K0026",
|
| 329 |
+
"name": "Jepkorir Simiyu",
|
| 330 |
+
"location_name": "Mombasa",
|
| 331 |
+
"latitude": -4.0941,
|
| 332 |
+
"longitude": 39.6125,
|
| 333 |
+
"primary_commodity": "MZE-DRY",
|
| 334 |
+
"quantity_quintals": 20.5,
|
| 335 |
+
"has_storage": false,
|
| 336 |
+
"distance_to_market_km": 30.3,
|
| 337 |
+
"nearest_market_id": "MKT-KON",
|
| 338 |
+
"notes": "Smallholder dry maize farmer in Mombasa. 30.3 km to Kongowea; no storage; must sell at harvest."
|
| 339 |
+
},
|
| 340 |
+
{
|
| 341 |
+
"farmer_id": "FMR-K0027",
|
| 342 |
+
"name": "Muthoni Muriuki",
|
| 343 |
+
"location_name": "Mombasa",
|
| 344 |
+
"latitude": -3.9269,
|
| 345 |
+
"longitude": 39.8283,
|
| 346 |
+
"primary_commodity": "BEN-YGN",
|
| 347 |
+
"quantity_quintals": 10.8,
|
| 348 |
+
"has_storage": true,
|
| 349 |
+
"distance_to_market_km": 17.5,
|
| 350 |
+
"nearest_market_id": "MKT-KON",
|
| 351 |
+
"notes": "Smallholder beans farmer in Mombasa. 17.5 km to Kongowea; has dry storage shed."
|
| 352 |
+
},
|
| 353 |
+
{
|
| 354 |
+
"farmer_id": "FMR-K0028",
|
| 355 |
+
"name": "Kipchumba Ndungu",
|
| 356 |
+
"location_name": "Mombasa",
|
| 357 |
+
"latitude": -4.1283,
|
| 358 |
+
"longitude": 39.5382,
|
| 359 |
+
"primary_commodity": "GRG-KEN",
|
| 360 |
+
"quantity_quintals": 14.3,
|
| 361 |
+
"has_storage": false,
|
| 362 |
+
"distance_to_market_km": 27.7,
|
| 363 |
+
"nearest_market_id": "MKT-KON",
|
| 364 |
+
"notes": "Smallholder green grams farmer in Mombasa. 27.7 km to Kongowea; no storage; must sell at harvest."
|
| 365 |
+
},
|
| 366 |
+
{
|
| 367 |
+
"farmer_id": "FMR-K0029",
|
| 368 |
+
"name": "Jepkorir Kathambi",
|
| 369 |
+
"location_name": "Mombasa",
|
| 370 |
+
"latitude": -4.1313,
|
| 371 |
+
"longitude": 39.6266,
|
| 372 |
+
"primary_commodity": "MZE-DRY",
|
| 373 |
+
"quantity_quintals": 25.3,
|
| 374 |
+
"has_storage": false,
|
| 375 |
+
"distance_to_market_km": 19.4,
|
| 376 |
+
"nearest_market_id": "MKT-KON",
|
| 377 |
+
"notes": "Smallholder dry maize farmer in Mombasa. 19.4 km to Kongowea; no storage; must sell at harvest."
|
| 378 |
+
},
|
| 379 |
+
{
|
| 380 |
+
"farmer_id": "FMR-K0030",
|
| 381 |
+
"name": "Nyokabi Gitau",
|
| 382 |
+
"location_name": "Mombasa",
|
| 383 |
+
"latitude": -4.0024,
|
| 384 |
+
"longitude": 39.6885,
|
| 385 |
+
"primary_commodity": "BEN-YGN",
|
| 386 |
+
"quantity_quintals": 7.0,
|
| 387 |
+
"has_storage": false,
|
| 388 |
+
"distance_to_market_km": 18.3,
|
| 389 |
+
"nearest_market_id": "MKT-KON",
|
| 390 |
+
"notes": "Smallholder beans farmer in Mombasa. 18.3 km to Kongowea; no storage; must sell at harvest."
|
| 391 |
+
},
|
| 392 |
+
{
|
| 393 |
+
"farmer_id": "FMR-K0031",
|
| 394 |
+
"name": "Njenga Kinyanjui",
|
| 395 |
+
"location_name": "Mombasa",
|
| 396 |
+
"latitude": -3.9751,
|
| 397 |
+
"longitude": 39.6082,
|
| 398 |
+
"primary_commodity": "MZE-DRY",
|
| 399 |
+
"quantity_quintals": 38.0,
|
| 400 |
+
"has_storage": false,
|
| 401 |
+
"distance_to_market_km": 20.4,
|
| 402 |
+
"nearest_market_id": "MKT-KON",
|
| 403 |
+
"notes": "Smallholder dry maize farmer in Mombasa. 20.4 km to Kongowea; no storage; must sell at harvest."
|
| 404 |
+
},
|
| 405 |
+
{
|
| 406 |
+
"farmer_id": "FMR-K0032",
|
| 407 |
+
"name": "Wangari Otieno",
|
| 408 |
+
"location_name": "Mombasa",
|
| 409 |
+
"latitude": -4.0519,
|
| 410 |
+
"longitude": 39.7127,
|
| 411 |
+
"primary_commodity": "POT-RIR",
|
| 412 |
+
"quantity_quintals": 37.7,
|
| 413 |
+
"has_storage": false,
|
| 414 |
+
"distance_to_market_km": 7.9,
|
| 415 |
+
"nearest_market_id": "MKT-KON",
|
| 416 |
+
"notes": "Smallholder Irish potato farmer in Mombasa. 7.9 km to Kongowea; no storage; must sell at harvest."
|
| 417 |
+
},
|
| 418 |
+
{
|
| 419 |
+
"farmer_id": "FMR-K0033",
|
| 420 |
+
"name": "Nabwire Mwikali",
|
| 421 |
+
"location_name": "Uasin Gishu",
|
| 422 |
+
"latitude": 0.483,
|
| 423 |
+
"longitude": 35.3213,
|
| 424 |
+
"primary_commodity": "BEN-YGN",
|
| 425 |
+
"quantity_quintals": 21.0,
|
| 426 |
+
"has_storage": false,
|
| 427 |
+
"distance_to_market_km": 32.6,
|
| 428 |
+
"nearest_market_id": "MKT-ELD",
|
| 429 |
+
"notes": "Smallholder beans farmer in Uasin Gishu. 32.6 km to Eldoret Main; no storage; must sell at harvest."
|
| 430 |
+
},
|
| 431 |
+
{
|
| 432 |
+
"farmer_id": "FMR-K0034",
|
| 433 |
+
"name": "Njeri Mwenda",
|
| 434 |
+
"location_name": "Uasin Gishu",
|
| 435 |
+
"latitude": 0.5506,
|
| 436 |
+
"longitude": 35.2887,
|
| 437 |
+
"primary_commodity": "MZE-DRY",
|
| 438 |
+
"quantity_quintals": 7.6,
|
| 439 |
+
"has_storage": true,
|
| 440 |
+
"distance_to_market_km": 8.2,
|
| 441 |
+
"nearest_market_id": "MKT-ELD",
|
| 442 |
+
"notes": "Smallholder dry maize farmer in Uasin Gishu. 8.2 km to Eldoret Main; has dry storage shed."
|
| 443 |
+
},
|
| 444 |
+
{
|
| 445 |
+
"farmer_id": "FMR-K0035",
|
| 446 |
+
"name": "Kaloki Kinyanjui",
|
| 447 |
+
"location_name": "Uasin Gishu",
|
| 448 |
+
"latitude": 0.6281,
|
| 449 |
+
"longitude": 35.2307,
|
| 450 |
+
"primary_commodity": "MZE-DRY",
|
| 451 |
+
"quantity_quintals": 20.3,
|
| 452 |
+
"has_storage": true,
|
| 453 |
+
"distance_to_market_km": 19.5,
|
| 454 |
+
"nearest_market_id": "MKT-ELD",
|
| 455 |
+
"notes": "Smallholder dry maize farmer in Uasin Gishu. 19.5 km to Eldoret Main; has dry storage shed."
|
| 456 |
+
},
|
| 457 |
+
{
|
| 458 |
+
"farmer_id": "FMR-K0036",
|
| 459 |
+
"name": "Salim Mwangi",
|
| 460 |
+
"location_name": "Uasin Gishu",
|
| 461 |
+
"latitude": 0.5647,
|
| 462 |
+
"longitude": 35.2862,
|
| 463 |
+
"primary_commodity": "POT-RIR",
|
| 464 |
+
"quantity_quintals": 8.6,
|
| 465 |
+
"has_storage": true,
|
| 466 |
+
"distance_to_market_km": 30.9,
|
| 467 |
+
"nearest_market_id": "MKT-ELD",
|
| 468 |
+
"notes": "Smallholder Irish potato farmer in Uasin Gishu. 30.9 km to Eldoret Main; has dry storage shed."
|
| 469 |
+
},
|
| 470 |
+
{
|
| 471 |
+
"farmer_id": "FMR-K0037",
|
| 472 |
+
"name": "Githinji Ouma",
|
| 473 |
+
"location_name": "Uasin Gishu",
|
| 474 |
+
"latitude": 0.446,
|
| 475 |
+
"longitude": 35.3012,
|
| 476 |
+
"primary_commodity": "POT-RIR",
|
| 477 |
+
"quantity_quintals": 17.0,
|
| 478 |
+
"has_storage": false,
|
| 479 |
+
"distance_to_market_km": 24.4,
|
| 480 |
+
"nearest_market_id": "MKT-ELD",
|
| 481 |
+
"notes": "Smallholder Irish potato farmer in Uasin Gishu. 24.4 km to Eldoret Main; no storage; must sell at harvest."
|
| 482 |
+
},
|
| 483 |
+
{
|
| 484 |
+
"farmer_id": "FMR-K0038",
|
| 485 |
+
"name": "Halima Kathambi",
|
| 486 |
+
"location_name": "Uasin Gishu",
|
| 487 |
+
"latitude": 0.4396,
|
| 488 |
+
"longitude": 35.3922,
|
| 489 |
+
"primary_commodity": "MZE-DRY",
|
| 490 |
+
"quantity_quintals": 8.2,
|
| 491 |
+
"has_storage": true,
|
| 492 |
+
"distance_to_market_km": 15.4,
|
| 493 |
+
"nearest_market_id": "MKT-ELD",
|
| 494 |
+
"notes": "Smallholder dry maize farmer in Uasin Gishu. 15.4 km to Eldoret Main; has dry storage shed."
|
| 495 |
+
},
|
| 496 |
+
{
|
| 497 |
+
"farmer_id": "FMR-K0039",
|
| 498 |
+
"name": "Wanjiku Chebet",
|
| 499 |
+
"location_name": "Uasin Gishu",
|
| 500 |
+
"latitude": 0.5956,
|
| 501 |
+
"longitude": 35.3109,
|
| 502 |
+
"primary_commodity": "BEN-YGN",
|
| 503 |
+
"quantity_quintals": 10.7,
|
| 504 |
+
"has_storage": false,
|
| 505 |
+
"distance_to_market_km": 16.0,
|
| 506 |
+
"nearest_market_id": "MKT-ELD",
|
| 507 |
+
"notes": "Smallholder beans farmer in Uasin Gishu. 16.0 km to Eldoret Main; no storage; must sell at harvest."
|
| 508 |
+
},
|
| 509 |
+
{
|
| 510 |
+
"farmer_id": "FMR-K0040",
|
| 511 |
+
"name": "Asha Mwangi",
|
| 512 |
+
"location_name": "Uasin Gishu",
|
| 513 |
+
"latitude": 0.3979,
|
| 514 |
+
"longitude": 35.4033,
|
| 515 |
+
"primary_commodity": "POT-RIR",
|
| 516 |
+
"quantity_quintals": 36.6,
|
| 517 |
+
"has_storage": false,
|
| 518 |
+
"distance_to_market_km": 19.6,
|
| 519 |
+
"nearest_market_id": "MKT-ELD",
|
| 520 |
+
"notes": "Smallholder Irish potato farmer in Uasin Gishu. 19.6 km to Eldoret Main; no storage; must sell at harvest."
|
| 521 |
+
},
|
| 522 |
+
{
|
| 523 |
+
"farmer_id": "FMR-K0041",
|
| 524 |
+
"name": "Mutembei Omondi",
|
| 525 |
+
"location_name": "Uasin Gishu",
|
| 526 |
+
"latitude": 0.4932,
|
| 527 |
+
"longitude": 35.1323,
|
| 528 |
+
"primary_commodity": "BEN-YGN",
|
| 529 |
+
"quantity_quintals": 36.5,
|
| 530 |
+
"has_storage": false,
|
| 531 |
+
"distance_to_market_km": 28.1,
|
| 532 |
+
"nearest_market_id": "MKT-ELD",
|
| 533 |
+
"notes": "Smallholder beans farmer in Uasin Gishu. 28.1 km to Eldoret Main; no storage; must sell at harvest."
|
| 534 |
+
},
|
| 535 |
+
{
|
| 536 |
+
"farmer_id": "FMR-K0042",
|
| 537 |
+
"name": "Mwende Nabwire",
|
| 538 |
+
"location_name": "Uasin Gishu",
|
| 539 |
+
"latitude": 0.5689,
|
| 540 |
+
"longitude": 35.3199,
|
| 541 |
+
"primary_commodity": "BEN-YGN",
|
| 542 |
+
"quantity_quintals": 32.3,
|
| 543 |
+
"has_storage": true,
|
| 544 |
+
"distance_to_market_km": 31.1,
|
| 545 |
+
"nearest_market_id": "MKT-ELD",
|
| 546 |
+
"notes": "Smallholder beans farmer in Uasin Gishu. 31.1 km to Eldoret Main; has dry storage shed."
|
| 547 |
+
},
|
| 548 |
+
{
|
| 549 |
+
"farmer_id": "FMR-K0043",
|
| 550 |
+
"name": "Moraa Ouma",
|
| 551 |
+
"location_name": "Uasin Gishu",
|
| 552 |
+
"latitude": 0.642,
|
| 553 |
+
"longitude": 35.1908,
|
| 554 |
+
"primary_commodity": "MZE-DRY",
|
| 555 |
+
"quantity_quintals": 39.2,
|
| 556 |
+
"has_storage": false,
|
| 557 |
+
"distance_to_market_km": 28.6,
|
| 558 |
+
"nearest_market_id": "MKT-ELD",
|
| 559 |
+
"notes": "Smallholder dry maize farmer in Uasin Gishu. 28.6 km to Eldoret Main; no storage; must sell at harvest."
|
| 560 |
+
},
|
| 561 |
+
{
|
| 562 |
+
"farmer_id": "FMR-K0044",
|
| 563 |
+
"name": "Mumbi Barasa",
|
| 564 |
+
"location_name": "Uasin Gishu",
|
| 565 |
+
"latitude": 0.5853,
|
| 566 |
+
"longitude": 35.2195,
|
| 567 |
+
"primary_commodity": "BEN-YGN",
|
| 568 |
+
"quantity_quintals": 33.1,
|
| 569 |
+
"has_storage": false,
|
| 570 |
+
"distance_to_market_km": 30.4,
|
| 571 |
+
"nearest_market_id": "MKT-ELD",
|
| 572 |
+
"notes": "Smallholder beans farmer in Uasin Gishu. 30.4 km to Eldoret Main; no storage; must sell at harvest."
|
| 573 |
+
},
|
| 574 |
+
{
|
| 575 |
+
"farmer_id": "FMR-K0045",
|
| 576 |
+
"name": "Wafula Owino",
|
| 577 |
+
"location_name": "Uasin Gishu",
|
| 578 |
+
"latitude": 0.4791,
|
| 579 |
+
"longitude": 35.1314,
|
| 580 |
+
"primary_commodity": "BEN-YGN",
|
| 581 |
+
"quantity_quintals": 12.8,
|
| 582 |
+
"has_storage": false,
|
| 583 |
+
"distance_to_market_km": 28.8,
|
| 584 |
+
"nearest_market_id": "MKT-ELD",
|
| 585 |
+
"notes": "Smallholder beans farmer in Uasin Gishu. 28.8 km to Eldoret Main; no storage; must sell at harvest."
|
| 586 |
+
},
|
| 587 |
+
{
|
| 588 |
+
"farmer_id": "FMR-K0046",
|
| 589 |
+
"name": "Shikuku Mutua",
|
| 590 |
+
"location_name": "Uasin Gishu",
|
| 591 |
+
"latitude": 0.4312,
|
| 592 |
+
"longitude": 35.3178,
|
| 593 |
+
"primary_commodity": "BEN-YGN",
|
| 594 |
+
"quantity_quintals": 16.5,
|
| 595 |
+
"has_storage": false,
|
| 596 |
+
"distance_to_market_km": 30.4,
|
| 597 |
+
"nearest_market_id": "MKT-ELD",
|
| 598 |
+
"notes": "Smallholder beans farmer in Uasin Gishu. 30.4 km to Eldoret Main; no storage; must sell at harvest."
|
| 599 |
+
},
|
| 600 |
+
{
|
| 601 |
+
"farmer_id": "FMR-K0047",
|
| 602 |
+
"name": "Barasa Korir",
|
| 603 |
+
"location_name": "Nakuru",
|
| 604 |
+
"latitude": -0.2607,
|
| 605 |
+
"longitude": 36.0499,
|
| 606 |
+
"primary_commodity": "POT-RIR",
|
| 607 |
+
"quantity_quintals": 16.6,
|
| 608 |
+
"has_storage": false,
|
| 609 |
+
"distance_to_market_km": 1.9,
|
| 610 |
+
"nearest_market_id": "MKT-NAK",
|
| 611 |
+
"notes": "Smallholder Irish potato farmer in Nakuru. 1.9 km to Nakuru Wakulima; no storage; must sell at harvest."
|
| 612 |
+
},
|
| 613 |
+
{
|
| 614 |
+
"farmer_id": "FMR-K0048",
|
| 615 |
+
"name": "Wekesa Barasa",
|
| 616 |
+
"location_name": "Nakuru",
|
| 617 |
+
"latitude": -0.2789,
|
| 618 |
+
"longitude": 36.2251,
|
| 619 |
+
"primary_commodity": "MZE-DRY",
|
| 620 |
+
"quantity_quintals": 8.8,
|
| 621 |
+
"has_storage": false,
|
| 622 |
+
"distance_to_market_km": 15.8,
|
| 623 |
+
"nearest_market_id": "MKT-NAK",
|
| 624 |
+
"notes": "Smallholder dry maize farmer in Nakuru. 15.8 km to Nakuru Wakulima; no storage; must sell at harvest."
|
| 625 |
+
},
|
| 626 |
+
{
|
| 627 |
+
"farmer_id": "FMR-K0049",
|
| 628 |
+
"name": "Mutua Mohamed",
|
| 629 |
+
"location_name": "Nakuru",
|
| 630 |
+
"latitude": -0.2712,
|
| 631 |
+
"longitude": 36.0834,
|
| 632 |
+
"primary_commodity": "BEN-YGN",
|
| 633 |
+
"quantity_quintals": 36.5,
|
| 634 |
+
"has_storage": true,
|
| 635 |
+
"distance_to_market_km": 7.5,
|
| 636 |
+
"nearest_market_id": "MKT-NAK",
|
| 637 |
+
"notes": "Smallholder beans farmer in Nakuru. 7.5 km to Nakuru Wakulima; has dry storage shed."
|
| 638 |
+
},
|
| 639 |
+
{
|
| 640 |
+
"farmer_id": "FMR-K0050",
|
| 641 |
+
"name": "Chepngetich Kamau",
|
| 642 |
+
"location_name": "Nakuru",
|
| 643 |
+
"latitude": -0.2971,
|
| 644 |
+
"longitude": 36.172,
|
| 645 |
+
"primary_commodity": "POT-RIR",
|
| 646 |
+
"quantity_quintals": 30.2,
|
| 647 |
+
"has_storage": false,
|
| 648 |
+
"distance_to_market_km": 26.2,
|
| 649 |
+
"nearest_market_id": "MKT-NAK",
|
| 650 |
+
"notes": "Smallholder Irish potato farmer in Nakuru. 26.2 km to Nakuru Wakulima; no storage; must sell at harvest."
|
| 651 |
+
},
|
| 652 |
+
{
|
| 653 |
+
"farmer_id": "FMR-K0051",
|
| 654 |
+
"name": "Musyoka Mohamed",
|
| 655 |
+
"location_name": "Nakuru",
|
| 656 |
+
"latitude": -0.4321,
|
| 657 |
+
"longitude": 36.1293,
|
| 658 |
+
"primary_commodity": "BEN-YGN",
|
| 659 |
+
"quantity_quintals": 26.8,
|
| 660 |
+
"has_storage": true,
|
| 661 |
+
"distance_to_market_km": 23.6,
|
| 662 |
+
"nearest_market_id": "MKT-NAK",
|
| 663 |
+
"notes": "Smallholder beans farmer in Nakuru. 23.6 km to Nakuru Wakulima; has dry storage shed."
|
| 664 |
+
},
|
| 665 |
+
{
|
| 666 |
+
"farmer_id": "FMR-K0052",
|
| 667 |
+
"name": "Makokha Kwamboka",
|
| 668 |
+
"location_name": "Nakuru",
|
| 669 |
+
"latitude": -0.3603,
|
| 670 |
+
"longitude": 36.0525,
|
| 671 |
+
"primary_commodity": "BEN-YGN",
|
| 672 |
+
"quantity_quintals": 29.4,
|
| 673 |
+
"has_storage": true,
|
| 674 |
+
"distance_to_market_km": 19.9,
|
| 675 |
+
"nearest_market_id": "MKT-NAK",
|
| 676 |
+
"notes": "Smallholder beans farmer in Nakuru. 19.9 km to Nakuru Wakulima; has dry storage shed."
|
| 677 |
+
},
|
| 678 |
+
{
|
| 679 |
+
"farmer_id": "FMR-K0053",
|
| 680 |
+
"name": "Nzioka Barasa",
|
| 681 |
+
"location_name": "Nakuru",
|
| 682 |
+
"latitude": -0.2684,
|
| 683 |
+
"longitude": 36.0203,
|
| 684 |
+
"primary_commodity": "POT-RIR",
|
| 685 |
+
"quantity_quintals": 34.2,
|
| 686 |
+
"has_storage": true,
|
| 687 |
+
"distance_to_market_km": 11.3,
|
| 688 |
+
"nearest_market_id": "MKT-NAK",
|
| 689 |
+
"notes": "Smallholder Irish potato farmer in Nakuru. 11.3 km to Nakuru Wakulima; has dry storage shed."
|
| 690 |
+
},
|
| 691 |
+
{
|
| 692 |
+
"farmer_id": "FMR-K0054",
|
| 693 |
+
"name": "Mutembei Chepkoech",
|
| 694 |
+
"location_name": "Nakuru",
|
| 695 |
+
"latitude": -0.3136,
|
| 696 |
+
"longitude": 36.0626,
|
| 697 |
+
"primary_commodity": "MZE-DRY",
|
| 698 |
+
"quantity_quintals": 22.9,
|
| 699 |
+
"has_storage": false,
|
| 700 |
+
"distance_to_market_km": 28.0,
|
| 701 |
+
"nearest_market_id": "MKT-NAK",
|
| 702 |
+
"notes": "Smallholder dry maize farmer in Nakuru. 28.0 km to Nakuru Wakulima; no storage; must sell at harvest."
|
| 703 |
+
},
|
| 704 |
+
{
|
| 705 |
+
"farmer_id": "FMR-K0055",
|
| 706 |
+
"name": "Awino Otieno",
|
| 707 |
+
"location_name": "Nakuru",
|
| 708 |
+
"latitude": -0.368,
|
| 709 |
+
"longitude": 36.1292,
|
| 710 |
+
"primary_commodity": "POT-RIR",
|
| 711 |
+
"quantity_quintals": 16.7,
|
| 712 |
+
"has_storage": false,
|
| 713 |
+
"distance_to_market_km": 28.8,
|
| 714 |
+
"nearest_market_id": "MKT-NAK",
|
| 715 |
+
"notes": "Smallholder Irish potato farmer in Nakuru. 28.8 km to Nakuru Wakulima; no storage; must sell at harvest."
|
| 716 |
+
},
|
| 717 |
+
{
|
| 718 |
+
"farmer_id": "FMR-K0056",
|
| 719 |
+
"name": "Shikuku Nafula",
|
| 720 |
+
"location_name": "Nakuru",
|
| 721 |
+
"latitude": -0.2111,
|
| 722 |
+
"longitude": 35.9742,
|
| 723 |
+
"primary_commodity": "MZE-DRY",
|
| 724 |
+
"quantity_quintals": 13.6,
|
| 725 |
+
"has_storage": false,
|
| 726 |
+
"distance_to_market_km": 17.2,
|
| 727 |
+
"nearest_market_id": "MKT-NAK",
|
| 728 |
+
"notes": "Smallholder dry maize farmer in Nakuru. 17.2 km to Nakuru Wakulima; no storage; must sell at harvest."
|
| 729 |
+
},
|
| 730 |
+
{
|
| 731 |
+
"farmer_id": "FMR-K0057",
|
| 732 |
+
"name": "Kamau Mwenda",
|
| 733 |
+
"location_name": "Nakuru",
|
| 734 |
+
"latitude": -0.3288,
|
| 735 |
+
"longitude": 36.1189,
|
| 736 |
+
"primary_commodity": "MZE-DRY",
|
| 737 |
+
"quantity_quintals": 30.1,
|
| 738 |
+
"has_storage": true,
|
| 739 |
+
"distance_to_market_km": 14.1,
|
| 740 |
+
"nearest_market_id": "MKT-NAK",
|
| 741 |
+
"notes": "Smallholder dry maize farmer in Nakuru. 14.1 km to Nakuru Wakulima; has dry storage shed."
|
| 742 |
+
},
|
| 743 |
+
{
|
| 744 |
+
"farmer_id": "FMR-K0058",
|
| 745 |
+
"name": "Adhiambo Kamau",
|
| 746 |
+
"location_name": "Nakuru",
|
| 747 |
+
"latitude": -0.1853,
|
| 748 |
+
"longitude": 36.1881,
|
| 749 |
+
"primary_commodity": "MZE-DRY",
|
| 750 |
+
"quantity_quintals": 32.2,
|
| 751 |
+
"has_storage": false,
|
| 752 |
+
"distance_to_market_km": 8.4,
|
| 753 |
+
"nearest_market_id": "MKT-NAK",
|
| 754 |
+
"notes": "Smallholder dry maize farmer in Nakuru. 8.4 km to Nakuru Wakulima; no storage; must sell at harvest."
|
| 755 |
+
},
|
| 756 |
+
{
|
| 757 |
+
"farmer_id": "FMR-K0059",
|
| 758 |
+
"name": "Hassan Kinoti",
|
| 759 |
+
"location_name": "Nakuru",
|
| 760 |
+
"latitude": -0.438,
|
| 761 |
+
"longitude": 36.0048,
|
| 762 |
+
"primary_commodity": "MZE-DRY",
|
| 763 |
+
"quantity_quintals": 21.0,
|
| 764 |
+
"has_storage": false,
|
| 765 |
+
"distance_to_market_km": 28.2,
|
| 766 |
+
"nearest_market_id": "MKT-NAK",
|
| 767 |
+
"notes": "Smallholder dry maize farmer in Nakuru. 28.2 km to Nakuru Wakulima; no storage; must sell at harvest."
|
| 768 |
+
},
|
| 769 |
+
{
|
| 770 |
+
"farmer_id": "FMR-K0060",
|
| 771 |
+
"name": "Asha Kipchumba",
|
| 772 |
+
"location_name": "Kajiado",
|
| 773 |
+
"latitude": -1.3397,
|
| 774 |
+
"longitude": 37.0761,
|
| 775 |
+
"primary_commodity": "GRG-KEN",
|
| 776 |
+
"quantity_quintals": 33.5,
|
| 777 |
+
"has_storage": true,
|
| 778 |
+
"distance_to_market_km": 31.3,
|
| 779 |
+
"nearest_market_id": "MKT-KTG",
|
| 780 |
+
"notes": "Smallholder green grams farmer in Kajiado. 31.3 km to Kitengela; has dry storage shed."
|
| 781 |
+
},
|
| 782 |
+
{
|
| 783 |
+
"farmer_id": "FMR-K0061",
|
| 784 |
+
"name": "Rehema Abdalla",
|
| 785 |
+
"location_name": "Kajiado",
|
| 786 |
+
"latitude": -1.3555,
|
| 787 |
+
"longitude": 37.0318,
|
| 788 |
+
"primary_commodity": "BEN-YGN",
|
| 789 |
+
"quantity_quintals": 20.8,
|
| 790 |
+
"has_storage": false,
|
| 791 |
+
"distance_to_market_km": 26.6,
|
| 792 |
+
"nearest_market_id": "MKT-KTG",
|
| 793 |
+
"notes": "Smallholder beans farmer in Kajiado. 26.6 km to Kitengela; no storage; must sell at harvest."
|
| 794 |
+
},
|
| 795 |
+
{
|
| 796 |
+
"farmer_id": "FMR-K0062",
|
| 797 |
+
"name": "Barasa Nyaboke",
|
| 798 |
+
"location_name": "Kajiado",
|
| 799 |
+
"latitude": -1.4793,
|
| 800 |
+
"longitude": 36.8805,
|
| 801 |
+
"primary_commodity": "BEN-YGN",
|
| 802 |
+
"quantity_quintals": 7.7,
|
| 803 |
+
"has_storage": true,
|
| 804 |
+
"distance_to_market_km": 10.7,
|
| 805 |
+
"nearest_market_id": "MKT-KTG",
|
| 806 |
+
"notes": "Smallholder beans farmer in Kajiado. 10.7 km to Kitengela; has dry storage shed."
|
| 807 |
+
},
|
| 808 |
+
{
|
| 809 |
+
"farmer_id": "FMR-K0063",
|
| 810 |
+
"name": "Mutua Muriuki",
|
| 811 |
+
"location_name": "Kajiado",
|
| 812 |
+
"latitude": -1.6005,
|
| 813 |
+
"longitude": 36.8539,
|
| 814 |
+
"primary_commodity": "BEN-YGN",
|
| 815 |
+
"quantity_quintals": 29.3,
|
| 816 |
+
"has_storage": true,
|
| 817 |
+
"distance_to_market_km": 25.0,
|
| 818 |
+
"nearest_market_id": "MKT-KTG",
|
| 819 |
+
"notes": "Smallholder beans farmer in Kajiado. 25.0 km to Kitengela; has dry storage shed."
|
| 820 |
+
},
|
| 821 |
+
{
|
| 822 |
+
"farmer_id": "FMR-K0064",
|
| 823 |
+
"name": "Chepkoech Chebet",
|
| 824 |
+
"location_name": "Kajiado",
|
| 825 |
+
"latitude": -1.4619,
|
| 826 |
+
"longitude": 36.9334,
|
| 827 |
+
"primary_commodity": "MZE-DRY",
|
| 828 |
+
"quantity_quintals": 34.2,
|
| 829 |
+
"has_storage": false,
|
| 830 |
+
"distance_to_market_km": 14.2,
|
| 831 |
+
"nearest_market_id": "MKT-KTG",
|
| 832 |
+
"notes": "Smallholder dry maize farmer in Kajiado. 14.2 km to Kitengela; no storage; must sell at harvest."
|
| 833 |
+
},
|
| 834 |
+
{
|
| 835 |
+
"farmer_id": "FMR-K0065",
|
| 836 |
+
"name": "Wambui Jepkorir",
|
| 837 |
+
"location_name": "Kajiado",
|
| 838 |
+
"latitude": -1.4816,
|
| 839 |
+
"longitude": 37.0915,
|
| 840 |
+
"primary_commodity": "BEN-YGN",
|
| 841 |
+
"quantity_quintals": 31.4,
|
| 842 |
+
"has_storage": false,
|
| 843 |
+
"distance_to_market_km": 30.0,
|
| 844 |
+
"nearest_market_id": "MKT-KTG",
|
| 845 |
+
"notes": "Smallholder beans farmer in Kajiado. 30.0 km to Kitengela; no storage; must sell at harvest."
|
| 846 |
+
},
|
| 847 |
+
{
|
| 848 |
+
"farmer_id": "FMR-K0066",
|
| 849 |
+
"name": "Chebet Njiru",
|
| 850 |
+
"location_name": "Kajiado",
|
| 851 |
+
"latitude": -1.4004,
|
| 852 |
+
"longitude": 36.9725,
|
| 853 |
+
"primary_commodity": "GRG-KEN",
|
| 854 |
+
"quantity_quintals": 36.4,
|
| 855 |
+
"has_storage": true,
|
| 856 |
+
"distance_to_market_km": 17.6,
|
| 857 |
+
"nearest_market_id": "MKT-KTG",
|
| 858 |
+
"notes": "Smallholder green grams farmer in Kajiado. 17.6 km to Kitengela; has dry storage shed."
|
| 859 |
+
},
|
| 860 |
+
{
|
| 861 |
+
"farmer_id": "FMR-K0067",
|
| 862 |
+
"name": "Amina Njoroge",
|
| 863 |
+
"location_name": "Kajiado",
|
| 864 |
+
"latitude": -1.508,
|
| 865 |
+
"longitude": 37.0094,
|
| 866 |
+
"primary_commodity": "BEN-YGN",
|
| 867 |
+
"quantity_quintals": 30.3,
|
| 868 |
+
"has_storage": false,
|
| 869 |
+
"distance_to_market_km": 29.6,
|
| 870 |
+
"nearest_market_id": "MKT-KTG",
|
| 871 |
+
"notes": "Smallholder beans farmer in Kajiado. 29.6 km to Kitengela; no storage; must sell at harvest."
|
| 872 |
+
},
|
| 873 |
+
{
|
| 874 |
+
"farmer_id": "FMR-K0068",
|
| 875 |
+
"name": "Moraa Njiru",
|
| 876 |
+
"location_name": "Nairobi",
|
| 877 |
+
"latitude": -1.4099,
|
| 878 |
+
"longitude": 36.7144,
|
| 879 |
+
"primary_commodity": "POT-RIR",
|
| 880 |
+
"quantity_quintals": 28.2,
|
| 881 |
+
"has_storage": true,
|
| 882 |
+
"distance_to_market_km": 3.9,
|
| 883 |
+
"nearest_market_id": "MKT-KGM",
|
| 884 |
+
"notes": "Smallholder Irish potato farmer in Nairobi. 3.9 km to Kangemi Market; has dry storage shed."
|
| 885 |
+
},
|
| 886 |
+
{
|
| 887 |
+
"farmer_id": "FMR-K0069",
|
| 888 |
+
"name": "Kipkoech Simiyu",
|
| 889 |
+
"location_name": "Nairobi",
|
| 890 |
+
"latitude": -1.4029,
|
| 891 |
+
"longitude": 36.7099,
|
| 892 |
+
"primary_commodity": "MZE-DRY",
|
| 893 |
+
"quantity_quintals": 20.9,
|
| 894 |
+
"has_storage": false,
|
| 895 |
+
"distance_to_market_km": 12.5,
|
| 896 |
+
"nearest_market_id": "MKT-KGM",
|
| 897 |
+
"notes": "Smallholder dry maize farmer in Nairobi. 12.5 km to Kangemi Market; no storage; must sell at harvest."
|
| 898 |
+
},
|
| 899 |
+
{
|
| 900 |
+
"farmer_id": "FMR-K0070",
|
| 901 |
+
"name": "Barasa Rehema",
|
| 902 |
+
"location_name": "Nairobi",
|
| 903 |
+
"latitude": -1.3423,
|
| 904 |
+
"longitude": 36.6208,
|
| 905 |
+
"primary_commodity": "MZE-DRY",
|
| 906 |
+
"quantity_quintals": 31.2,
|
| 907 |
+
"has_storage": false,
|
| 908 |
+
"distance_to_market_km": 2.8,
|
| 909 |
+
"nearest_market_id": "MKT-KGM",
|
| 910 |
+
"notes": "Smallholder dry maize farmer in Nairobi. 2.8 km to Kangemi Market; no storage; must sell at harvest."
|
| 911 |
+
},
|
| 912 |
+
{
|
| 913 |
+
"farmer_id": "FMR-K0071",
|
| 914 |
+
"name": "Onyango Kinyanjui",
|
| 915 |
+
"location_name": "Nairobi",
|
| 916 |
+
"latitude": -1.1836,
|
| 917 |
+
"longitude": 36.7917,
|
| 918 |
+
"primary_commodity": "MZE-DRY",
|
| 919 |
+
"quantity_quintals": 38.2,
|
| 920 |
+
"has_storage": true,
|
| 921 |
+
"distance_to_market_km": 7.8,
|
| 922 |
+
"nearest_market_id": "MKT-KGM",
|
| 923 |
+
"notes": "Smallholder dry maize farmer in Nairobi. 7.8 km to Kangemi Market; has dry storage shed."
|
| 924 |
+
},
|
| 925 |
+
{
|
| 926 |
+
"farmer_id": "FMR-K0072",
|
| 927 |
+
"name": "Auma Musyoka",
|
| 928 |
+
"location_name": "Nairobi",
|
| 929 |
+
"latitude": -1.3801,
|
| 930 |
+
"longitude": 36.797,
|
| 931 |
+
"primary_commodity": "POT-RIR",
|
| 932 |
+
"quantity_quintals": 38.2,
|
| 933 |
+
"has_storage": true,
|
| 934 |
+
"distance_to_market_km": 16.8,
|
| 935 |
+
"nearest_market_id": "MKT-KGM",
|
| 936 |
+
"notes": "Smallholder Irish potato farmer in Nairobi. 16.8 km to Kangemi Market; has dry storage shed."
|
| 937 |
+
},
|
| 938 |
+
{
|
| 939 |
+
"farmer_id": "FMR-K0073",
|
| 940 |
+
"name": "Muthama Wekesa",
|
| 941 |
+
"location_name": "Nairobi",
|
| 942 |
+
"latitude": -1.2362,
|
| 943 |
+
"longitude": 36.8853,
|
| 944 |
+
"primary_commodity": "POT-RIR",
|
| 945 |
+
"quantity_quintals": 9.0,
|
| 946 |
+
"has_storage": false,
|
| 947 |
+
"distance_to_market_km": 28.9,
|
| 948 |
+
"nearest_market_id": "MKT-KGM",
|
| 949 |
+
"notes": "Smallholder Irish potato farmer in Nairobi. 28.9 km to Kangemi Market; no storage; must sell at harvest."
|
| 950 |
+
},
|
| 951 |
+
{
|
| 952 |
+
"farmer_id": "FMR-K0074",
|
| 953 |
+
"name": "Githinji Njoroge",
|
| 954 |
+
"location_name": "Nairobi",
|
| 955 |
+
"latitude": -1.1393,
|
| 956 |
+
"longitude": 36.7689,
|
| 957 |
+
"primary_commodity": "BEN-YGN",
|
| 958 |
+
"quantity_quintals": 18.9,
|
| 959 |
+
"has_storage": true,
|
| 960 |
+
"distance_to_market_km": 25.3,
|
| 961 |
+
"nearest_market_id": "MKT-KGM",
|
| 962 |
+
"notes": "Smallholder beans farmer in Nairobi. 25.3 km to Kangemi Market; has dry storage shed."
|
| 963 |
+
},
|
| 964 |
+
{
|
| 965 |
+
"farmer_id": "FMR-K0075",
|
| 966 |
+
"name": "Nyaboke Mueni",
|
| 967 |
+
"location_name": "Nairobi",
|
| 968 |
+
"latitude": -1.3874,
|
| 969 |
+
"longitude": 36.8279,
|
| 970 |
+
"primary_commodity": "POT-RIR",
|
| 971 |
+
"quantity_quintals": 26.0,
|
| 972 |
+
"has_storage": false,
|
| 973 |
+
"distance_to_market_km": 5.1,
|
| 974 |
+
"nearest_market_id": "MKT-KGM",
|
| 975 |
+
"notes": "Smallholder Irish potato farmer in Nairobi. 5.1 km to Kangemi Market; no storage; must sell at harvest."
|
| 976 |
+
},
|
| 977 |
+
{
|
| 978 |
+
"farmer_id": "FMR-K0076",
|
| 979 |
+
"name": "Wangari Korir",
|
| 980 |
+
"location_name": "Nairobi",
|
| 981 |
+
"latitude": -1.2582,
|
| 982 |
+
"longitude": 36.7947,
|
| 983 |
+
"primary_commodity": "MZE-DRY",
|
| 984 |
+
"quantity_quintals": 22.7,
|
| 985 |
+
"has_storage": false,
|
| 986 |
+
"distance_to_market_km": 12.6,
|
| 987 |
+
"nearest_market_id": "MKT-KGM",
|
| 988 |
+
"notes": "Smallholder dry maize farmer in Nairobi. 12.6 km to Kangemi Market; no storage; must sell at harvest."
|
| 989 |
+
},
|
| 990 |
+
{
|
| 991 |
+
"farmer_id": "FMR-K0077",
|
| 992 |
+
"name": "Amina Owino",
|
| 993 |
+
"location_name": "Nyeri",
|
| 994 |
+
"latitude": -0.5032,
|
| 995 |
+
"longitude": 37.092,
|
| 996 |
+
"primary_commodity": "BEN-YGN",
|
| 997 |
+
"quantity_quintals": 29.8,
|
| 998 |
+
"has_storage": false,
|
| 999 |
+
"distance_to_market_km": 15.8,
|
| 1000 |
+
"nearest_market_id": "MKT-KRT",
|
| 1001 |
+
"notes": "Smallholder beans farmer in Nyeri. 15.8 km to Karatina; no storage; must sell at harvest."
|
| 1002 |
+
},
|
| 1003 |
+
{
|
| 1004 |
+
"farmer_id": "FMR-K0078",
|
| 1005 |
+
"name": "Wafula Njiru",
|
| 1006 |
+
"location_name": "Nyeri",
|
| 1007 |
+
"latitude": -0.4009,
|
| 1008 |
+
"longitude": 37.1228,
|
| 1009 |
+
"primary_commodity": "POT-RIR",
|
| 1010 |
+
"quantity_quintals": 25.7,
|
| 1011 |
+
"has_storage": true,
|
| 1012 |
+
"distance_to_market_km": 12.0,
|
| 1013 |
+
"nearest_market_id": "MKT-KRT",
|
| 1014 |
+
"notes": "Smallholder Irish potato farmer in Nyeri. 12.0 km to Karatina; has dry storage shed."
|
| 1015 |
+
},
|
| 1016 |
+
{
|
| 1017 |
+
"farmer_id": "FMR-K0079",
|
| 1018 |
+
"name": "Kariuki Nzioka",
|
| 1019 |
+
"location_name": "Nyeri",
|
| 1020 |
+
"latitude": -0.3688,
|
| 1021 |
+
"longitude": 37.0565,
|
| 1022 |
+
"primary_commodity": "BEN-YGN",
|
| 1023 |
+
"quantity_quintals": 24.9,
|
| 1024 |
+
"has_storage": true,
|
| 1025 |
+
"distance_to_market_km": 23.7,
|
| 1026 |
+
"nearest_market_id": "MKT-KRT",
|
| 1027 |
+
"notes": "Smallholder beans farmer in Nyeri. 23.7 km to Karatina; has dry storage shed."
|
| 1028 |
+
},
|
| 1029 |
+
{
|
| 1030 |
+
"farmer_id": "FMR-K0080",
|
| 1031 |
+
"name": "Fatuma Chepkoech",
|
| 1032 |
+
"location_name": "Nyeri",
|
| 1033 |
+
"latitude": -0.5787,
|
| 1034 |
+
"longitude": 37.0469,
|
| 1035 |
+
"primary_commodity": "BEN-YGN",
|
| 1036 |
+
"quantity_quintals": 16.9,
|
| 1037 |
+
"has_storage": false,
|
| 1038 |
+
"distance_to_market_km": 30.9,
|
| 1039 |
+
"nearest_market_id": "MKT-KRT",
|
| 1040 |
+
"notes": "Smallholder beans farmer in Nyeri. 30.9 km to Karatina; no storage; must sell at harvest."
|
| 1041 |
+
},
|
| 1042 |
+
{
|
| 1043 |
+
"farmer_id": "FMR-K0081",
|
| 1044 |
+
"name": "Barasa Mwangi",
|
| 1045 |
+
"location_name": "Nyeri",
|
| 1046 |
+
"latitude": -0.4783,
|
| 1047 |
+
"longitude": 37.0406,
|
| 1048 |
+
"primary_commodity": "MZE-DRY",
|
| 1049 |
+
"quantity_quintals": 30.2,
|
| 1050 |
+
"has_storage": false,
|
| 1051 |
+
"distance_to_market_km": 17.6,
|
| 1052 |
+
"nearest_market_id": "MKT-KRT",
|
| 1053 |
+
"notes": "Smallholder dry maize farmer in Nyeri. 17.6 km to Karatina; no storage; must sell at harvest."
|
| 1054 |
+
},
|
| 1055 |
+
{
|
| 1056 |
+
"farmer_id": "FMR-K0082",
|
| 1057 |
+
"name": "Kibet Kathambi",
|
| 1058 |
+
"location_name": "Nyeri",
|
| 1059 |
+
"latitude": -0.4988,
|
| 1060 |
+
"longitude": 36.9885,
|
| 1061 |
+
"primary_commodity": "BEN-YGN",
|
| 1062 |
+
"quantity_quintals": 12.8,
|
| 1063 |
+
"has_storage": true,
|
| 1064 |
+
"distance_to_market_km": 24.5,
|
| 1065 |
+
"nearest_market_id": "MKT-KRT",
|
| 1066 |
+
"notes": "Smallholder beans farmer in Nyeri. 24.5 km to Karatina; has dry storage shed."
|
| 1067 |
+
},
|
| 1068 |
+
{
|
| 1069 |
+
"farmer_id": "FMR-K0083",
|
| 1070 |
+
"name": "Mutembei Cherop",
|
| 1071 |
+
"location_name": "Nyeri",
|
| 1072 |
+
"latitude": -0.4913,
|
| 1073 |
+
"longitude": 37.1426,
|
| 1074 |
+
"primary_commodity": "BEN-YGN",
|
| 1075 |
+
"quantity_quintals": 39.9,
|
| 1076 |
+
"has_storage": true,
|
| 1077 |
+
"distance_to_market_km": 19.7,
|
| 1078 |
+
"nearest_market_id": "MKT-KRT",
|
| 1079 |
+
"notes": "Smallholder beans farmer in Nyeri. 19.7 km to Karatina; has dry storage shed."
|
| 1080 |
+
},
|
| 1081 |
+
{
|
| 1082 |
+
"farmer_id": "FMR-K0084",
|
| 1083 |
+
"name": "Korir Muthama",
|
| 1084 |
+
"location_name": "Nyeri",
|
| 1085 |
+
"latitude": -0.5413,
|
| 1086 |
+
"longitude": 37.0525,
|
| 1087 |
+
"primary_commodity": "POT-RIR",
|
| 1088 |
+
"quantity_quintals": 11.7,
|
| 1089 |
+
"has_storage": false,
|
| 1090 |
+
"distance_to_market_km": 5.1,
|
| 1091 |
+
"nearest_market_id": "MKT-KRT",
|
| 1092 |
+
"notes": "Smallholder Irish potato farmer in Nyeri. 5.1 km to Karatina; no storage; must sell at harvest."
|
| 1093 |
+
},
|
| 1094 |
+
{
|
| 1095 |
+
"farmer_id": "FMR-K0085",
|
| 1096 |
+
"name": "Otieno Wanjala",
|
| 1097 |
+
"location_name": "Nyeri",
|
| 1098 |
+
"latitude": -0.5684,
|
| 1099 |
+
"longitude": 37.1286,
|
| 1100 |
+
"primary_commodity": "POT-RIR",
|
| 1101 |
+
"quantity_quintals": 25.6,
|
| 1102 |
+
"has_storage": false,
|
| 1103 |
+
"distance_to_market_km": 26.8,
|
| 1104 |
+
"nearest_market_id": "MKT-KRT",
|
| 1105 |
+
"notes": "Smallholder Irish potato farmer in Nyeri. 26.8 km to Karatina; no storage; must sell at harvest."
|
| 1106 |
+
},
|
| 1107 |
+
{
|
| 1108 |
+
"farmer_id": "FMR-K0086",
|
| 1109 |
+
"name": "Maina Wanjala",
|
| 1110 |
+
"location_name": "Nyeri",
|
| 1111 |
+
"latitude": -0.5444,
|
| 1112 |
+
"longitude": 37.0916,
|
| 1113 |
+
"primary_commodity": "BEN-YGN",
|
| 1114 |
+
"quantity_quintals": 5.5,
|
| 1115 |
+
"has_storage": true,
|
| 1116 |
+
"distance_to_market_km": 19.2,
|
| 1117 |
+
"nearest_market_id": "MKT-KRT",
|
| 1118 |
+
"notes": "Smallholder beans farmer in Nyeri. 19.2 km to Karatina; has dry storage shed."
|
| 1119 |
+
},
|
| 1120 |
+
{
|
| 1121 |
+
"farmer_id": "FMR-K0087",
|
| 1122 |
+
"name": "Muthoni Mwende",
|
| 1123 |
+
"location_name": "Makueni",
|
| 1124 |
+
"latitude": -1.8908,
|
| 1125 |
+
"longitude": 37.5379,
|
| 1126 |
+
"primary_commodity": "BEN-YGN",
|
| 1127 |
+
"quantity_quintals": 8.6,
|
| 1128 |
+
"has_storage": true,
|
| 1129 |
+
"distance_to_market_km": 1.4,
|
| 1130 |
+
"nearest_market_id": "MKT-KTZ",
|
| 1131 |
+
"notes": "Smallholder beans farmer in Makueni. 1.4 km to Kathonzweni; has dry storage shed."
|
| 1132 |
+
},
|
| 1133 |
+
{
|
| 1134 |
+
"farmer_id": "FMR-K0088",
|
| 1135 |
+
"name": "Cherop Khamis",
|
| 1136 |
+
"location_name": "Makueni",
|
| 1137 |
+
"latitude": -1.9978,
|
| 1138 |
+
"longitude": 37.7896,
|
| 1139 |
+
"primary_commodity": "BEN-YGN",
|
| 1140 |
+
"quantity_quintals": 37.9,
|
| 1141 |
+
"has_storage": false,
|
| 1142 |
+
"distance_to_market_km": 17.2,
|
| 1143 |
+
"nearest_market_id": "MKT-KTZ",
|
| 1144 |
+
"notes": "Smallholder beans farmer in Makueni. 17.2 km to Kathonzweni; no storage; must sell at harvest."
|
| 1145 |
+
},
|
| 1146 |
+
{
|
| 1147 |
+
"farmer_id": "FMR-K0089",
|
| 1148 |
+
"name": "Kipchumba Kathambi",
|
| 1149 |
+
"location_name": "Makueni",
|
| 1150 |
+
"latitude": -2.0778,
|
| 1151 |
+
"longitude": 37.6888,
|
| 1152 |
+
"primary_commodity": "MZE-DRY",
|
| 1153 |
+
"quantity_quintals": 10.3,
|
| 1154 |
+
"has_storage": false,
|
| 1155 |
+
"distance_to_market_km": 28.6,
|
| 1156 |
+
"nearest_market_id": "MKT-KTZ",
|
| 1157 |
+
"notes": "Smallholder dry maize farmer in Makueni. 28.6 km to Kathonzweni; no storage; must sell at harvest."
|
| 1158 |
+
},
|
| 1159 |
+
{
|
| 1160 |
+
"farmer_id": "FMR-K0090",
|
| 1161 |
+
"name": "Mwangi Mueni",
|
| 1162 |
+
"location_name": "Makueni",
|
| 1163 |
+
"latitude": -2.0645,
|
| 1164 |
+
"longitude": 37.7293,
|
| 1165 |
+
"primary_commodity": "GRG-KEN",
|
| 1166 |
+
"quantity_quintals": 25.9,
|
| 1167 |
+
"has_storage": false,
|
| 1168 |
+
"distance_to_market_km": 22.0,
|
| 1169 |
+
"nearest_market_id": "MKT-KTZ",
|
| 1170 |
+
"notes": "Smallholder green grams farmer in Makueni. 22.0 km to Kathonzweni; no storage; must sell at harvest."
|
| 1171 |
+
},
|
| 1172 |
+
{
|
| 1173 |
+
"farmer_id": "FMR-K0091",
|
| 1174 |
+
"name": "Nzioka Abdalla",
|
| 1175 |
+
"location_name": "Makueni",
|
| 1176 |
+
"latitude": -1.8274,
|
| 1177 |
+
"longitude": 37.5892,
|
| 1178 |
+
"primary_commodity": "GRG-KEN",
|
| 1179 |
+
"quantity_quintals": 39.7,
|
| 1180 |
+
"has_storage": false,
|
| 1181 |
+
"distance_to_market_km": 11.4,
|
| 1182 |
+
"nearest_market_id": "MKT-KTZ",
|
| 1183 |
+
"notes": "Smallholder green grams farmer in Makueni. 11.4 km to Kathonzweni; no storage; must sell at harvest."
|
| 1184 |
+
},
|
| 1185 |
+
{
|
| 1186 |
+
"farmer_id": "FMR-K0092",
|
| 1187 |
+
"name": "Kwamboka Ochieng",
|
| 1188 |
+
"location_name": "Makueni",
|
| 1189 |
+
"latitude": -1.8157,
|
| 1190 |
+
"longitude": 37.5623,
|
| 1191 |
+
"primary_commodity": "MZE-DRY",
|
| 1192 |
+
"quantity_quintals": 14.3,
|
| 1193 |
+
"has_storage": true,
|
| 1194 |
+
"distance_to_market_km": 3.8,
|
| 1195 |
+
"nearest_market_id": "MKT-KTZ",
|
| 1196 |
+
"notes": "Smallholder dry maize farmer in Makueni. 3.8 km to Kathonzweni; has dry storage shed."
|
| 1197 |
+
},
|
| 1198 |
+
{
|
| 1199 |
+
"farmer_id": "FMR-K0093",
|
| 1200 |
+
"name": "Rehema Mbugua",
|
| 1201 |
+
"location_name": "Makueni",
|
| 1202 |
+
"latitude": -2.053,
|
| 1203 |
+
"longitude": 37.6226,
|
| 1204 |
+
"primary_commodity": "GRG-KEN",
|
| 1205 |
+
"quantity_quintals": 25.8,
|
| 1206 |
+
"has_storage": true,
|
| 1207 |
+
"distance_to_market_km": 10.9,
|
| 1208 |
+
"nearest_market_id": "MKT-KTZ",
|
| 1209 |
+
"notes": "Smallholder green grams farmer in Makueni. 10.9 km to Kathonzweni; has dry storage shed."
|
| 1210 |
+
},
|
| 1211 |
+
{
|
| 1212 |
+
"farmer_id": "FMR-K0094",
|
| 1213 |
+
"name": "Simiyu Mwenda",
|
| 1214 |
+
"location_name": "Tharaka Nithi",
|
| 1215 |
+
"latitude": -0.2936,
|
| 1216 |
+
"longitude": 37.68,
|
| 1217 |
+
"primary_commodity": "BEN-YGN",
|
| 1218 |
+
"quantity_quintals": 32.6,
|
| 1219 |
+
"has_storage": false,
|
| 1220 |
+
"distance_to_market_km": 22.3,
|
| 1221 |
+
"nearest_market_id": "MKT-KAN",
|
| 1222 |
+
"notes": "Smallholder beans farmer in Tharaka Nithi. 22.3 km to Kaanwa; no storage; must sell at harvest."
|
| 1223 |
+
},
|
| 1224 |
+
{
|
| 1225 |
+
"farmer_id": "FMR-K0095",
|
| 1226 |
+
"name": "Owino Ali",
|
| 1227 |
+
"location_name": "Tharaka Nithi",
|
| 1228 |
+
"latitude": -0.2806,
|
| 1229 |
+
"longitude": 37.6774,
|
| 1230 |
+
"primary_commodity": "MZE-DRY",
|
| 1231 |
+
"quantity_quintals": 36.8,
|
| 1232 |
+
"has_storage": true,
|
| 1233 |
+
"distance_to_market_km": 29.1,
|
| 1234 |
+
"nearest_market_id": "MKT-KAN",
|
| 1235 |
+
"notes": "Smallholder dry maize farmer in Tharaka Nithi. 29.1 km to Kaanwa; has dry storage shed."
|
| 1236 |
+
},
|
| 1237 |
+
{
|
| 1238 |
+
"farmer_id": "FMR-K0096",
|
| 1239 |
+
"name": "Awino Kiprop",
|
| 1240 |
+
"location_name": "Tharaka Nithi",
|
| 1241 |
+
"latitude": -0.1365,
|
| 1242 |
+
"longitude": 37.8573,
|
| 1243 |
+
"primary_commodity": "BEN-YGN",
|
| 1244 |
+
"quantity_quintals": 20.4,
|
| 1245 |
+
"has_storage": true,
|
| 1246 |
+
"distance_to_market_km": 16.9,
|
| 1247 |
+
"nearest_market_id": "MKT-KAN",
|
| 1248 |
+
"notes": "Smallholder beans farmer in Tharaka Nithi. 16.9 km to Kaanwa; has dry storage shed."
|
| 1249 |
+
},
|
| 1250 |
+
{
|
| 1251 |
+
"farmer_id": "FMR-K0097",
|
| 1252 |
+
"name": "Kipchumba Muthama",
|
| 1253 |
+
"location_name": "Tharaka Nithi",
|
| 1254 |
+
"latitude": -0.1649,
|
| 1255 |
+
"longitude": 37.7581,
|
| 1256 |
+
"primary_commodity": "MZE-DRY",
|
| 1257 |
+
"quantity_quintals": 25.9,
|
| 1258 |
+
"has_storage": false,
|
| 1259 |
+
"distance_to_market_km": 31.2,
|
| 1260 |
+
"nearest_market_id": "MKT-KAN",
|
| 1261 |
+
"notes": "Smallholder dry maize farmer in Tharaka Nithi. 31.2 km to Kaanwa; no storage; must sell at harvest."
|
| 1262 |
+
},
|
| 1263 |
+
{
|
| 1264 |
+
"farmer_id": "FMR-K0098",
|
| 1265 |
+
"name": "Karimi Ndinda",
|
| 1266 |
+
"location_name": "Tharaka Nithi",
|
| 1267 |
+
"latitude": -0.3072,
|
| 1268 |
+
"longitude": 37.6787,
|
| 1269 |
+
"primary_commodity": "GRG-KEN",
|
| 1270 |
+
"quantity_quintals": 34.3,
|
| 1271 |
+
"has_storage": false,
|
| 1272 |
+
"distance_to_market_km": 20.6,
|
| 1273 |
+
"nearest_market_id": "MKT-KAN",
|
| 1274 |
+
"notes": "Smallholder green grams farmer in Tharaka Nithi. 20.6 km to Kaanwa; no storage; must sell at harvest."
|
| 1275 |
+
},
|
| 1276 |
+
{
|
| 1277 |
+
"farmer_id": "FMR-K0099",
|
| 1278 |
+
"name": "Wafula Maina",
|
| 1279 |
+
"location_name": "Tharaka Nithi",
|
| 1280 |
+
"latitude": -0.0861,
|
| 1281 |
+
"longitude": 37.6626,
|
| 1282 |
+
"primary_commodity": "GRG-KEN",
|
| 1283 |
+
"quantity_quintals": 27.8,
|
| 1284 |
+
"has_storage": true,
|
| 1285 |
+
"distance_to_market_km": 32.1,
|
| 1286 |
+
"nearest_market_id": "MKT-KAN",
|
| 1287 |
+
"notes": "Smallholder green grams farmer in Tharaka Nithi. 32.1 km to Kaanwa; has dry storage shed."
|
| 1288 |
+
},
|
| 1289 |
+
{
|
| 1290 |
+
"farmer_id": "FMR-K0100",
|
| 1291 |
+
"name": "Mutembei Mohamed",
|
| 1292 |
+
"location_name": "Tharaka Nithi",
|
| 1293 |
+
"latitude": -0.1246,
|
| 1294 |
+
"longitude": 37.7575,
|
| 1295 |
+
"primary_commodity": "MZE-DRY",
|
| 1296 |
+
"quantity_quintals": 21.5,
|
| 1297 |
+
"has_storage": true,
|
| 1298 |
+
"distance_to_market_km": 14.9,
|
| 1299 |
+
"nearest_market_id": "MKT-KAN",
|
| 1300 |
+
"notes": "Smallholder dry maize farmer in Tharaka Nithi. 14.9 km to Kaanwa; has dry storage shed."
|
| 1301 |
+
}
|
| 1302 |
+
]
|
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"mandi_id": "MKT-KIB",
|
| 4 |
+
"name": "Kibuye",
|
| 5 |
+
"district": "Kisumu",
|
| 6 |
+
"state": "Kisumu",
|
| 7 |
+
"latitude": -0.0952,
|
| 8 |
+
"longitude": 34.7523,
|
| 9 |
+
"market_type": "wholesale",
|
| 10 |
+
"commodities_traded": ["MZE-DRY", "BEN-YGN", "GRG-KEN"],
|
| 11 |
+
"avg_daily_arrivals_tonnes": 340.0,
|
| 12 |
+
"enam_integrated": false,
|
| 13 |
+
"reporting_quality": "good"
|
| 14 |
+
},
|
| 15 |
+
{
|
| 16 |
+
"mandi_id": "MKT-NWK",
|
| 17 |
+
"name": "Nairobi Wakulima",
|
| 18 |
+
"district": "Nairobi",
|
| 19 |
+
"state": "Nairobi",
|
| 20 |
+
"latitude": -1.2921,
|
| 21 |
+
"longitude": 36.8270,
|
| 22 |
+
"market_type": "terminal",
|
| 23 |
+
"commodities_traded": ["MZE-DRY", "BEN-YGN", "POT-RIR", "GRG-KEN"],
|
| 24 |
+
"avg_daily_arrivals_tonnes": 620.0,
|
| 25 |
+
"enam_integrated": false,
|
| 26 |
+
"reporting_quality": "good"
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"mandi_id": "MKT-KON",
|
| 30 |
+
"name": "Kongowea",
|
| 31 |
+
"district": "Mombasa",
|
| 32 |
+
"state": "Mombasa",
|
| 33 |
+
"latitude": -4.0277,
|
| 34 |
+
"longitude": 39.6876,
|
| 35 |
+
"market_type": "terminal",
|
| 36 |
+
"commodities_traded": ["MZE-DRY", "BEN-YGN", "POT-RIR", "GRG-KEN"],
|
| 37 |
+
"avg_daily_arrivals_tonnes": 480.0,
|
| 38 |
+
"enam_integrated": false,
|
| 39 |
+
"reporting_quality": "good"
|
| 40 |
+
},
|
| 41 |
+
{
|
| 42 |
+
"mandi_id": "MKT-ELD",
|
| 43 |
+
"name": "Eldoret Main",
|
| 44 |
+
"district": "Uasin Gishu",
|
| 45 |
+
"state": "Uasin Gishu",
|
| 46 |
+
"latitude": 0.5143,
|
| 47 |
+
"longitude": 35.2698,
|
| 48 |
+
"market_type": "wholesale",
|
| 49 |
+
"commodities_traded": ["MZE-DRY", "BEN-YGN", "POT-RIR"],
|
| 50 |
+
"avg_daily_arrivals_tonnes": 410.0,
|
| 51 |
+
"enam_integrated": false,
|
| 52 |
+
"reporting_quality": "good"
|
| 53 |
+
},
|
| 54 |
+
{
|
| 55 |
+
"mandi_id": "MKT-NAK",
|
| 56 |
+
"name": "Nakuru Wakulima",
|
| 57 |
+
"district": "Nakuru",
|
| 58 |
+
"state": "Nakuru",
|
| 59 |
+
"latitude": -0.3031,
|
| 60 |
+
"longitude": 36.0800,
|
| 61 |
+
"market_type": "wholesale",
|
| 62 |
+
"commodities_traded": ["MZE-DRY", "BEN-YGN", "POT-RIR"],
|
| 63 |
+
"avg_daily_arrivals_tonnes": 295.0,
|
| 64 |
+
"enam_integrated": false,
|
| 65 |
+
"reporting_quality": "good"
|
| 66 |
+
},
|
| 67 |
+
{
|
| 68 |
+
"mandi_id": "MKT-KTG",
|
| 69 |
+
"name": "Kitengela",
|
| 70 |
+
"district": "Kajiado",
|
| 71 |
+
"state": "Kajiado",
|
| 72 |
+
"latitude": -1.4747,
|
| 73 |
+
"longitude": 36.9587,
|
| 74 |
+
"market_type": "regulated",
|
| 75 |
+
"commodities_traded": ["MZE-DRY", "BEN-YGN", "GRG-KEN"],
|
| 76 |
+
"avg_daily_arrivals_tonnes": 95.0,
|
| 77 |
+
"enam_integrated": false,
|
| 78 |
+
"reporting_quality": "poor"
|
| 79 |
+
},
|
| 80 |
+
{
|
| 81 |
+
"mandi_id": "MKT-KGM",
|
| 82 |
+
"name": "Kangemi Market",
|
| 83 |
+
"district": "Nairobi",
|
| 84 |
+
"state": "Nairobi",
|
| 85 |
+
"latitude": -1.2680,
|
| 86 |
+
"longitude": 36.7462,
|
| 87 |
+
"market_type": "wholesale",
|
| 88 |
+
"commodities_traded": ["MZE-DRY", "BEN-YGN", "POT-RIR"],
|
| 89 |
+
"avg_daily_arrivals_tonnes": 175.0,
|
| 90 |
+
"enam_integrated": false,
|
| 91 |
+
"reporting_quality": "moderate"
|
| 92 |
+
},
|
| 93 |
+
{
|
| 94 |
+
"mandi_id": "MKT-KRT",
|
| 95 |
+
"name": "Karatina",
|
| 96 |
+
"district": "Nyeri",
|
| 97 |
+
"state": "Nyeri",
|
| 98 |
+
"latitude": -0.4833,
|
| 99 |
+
"longitude": 37.1333,
|
| 100 |
+
"market_type": "wholesale",
|
| 101 |
+
"commodities_traded": ["MZE-DRY", "BEN-YGN", "POT-RIR"],
|
| 102 |
+
"avg_daily_arrivals_tonnes": 230.0,
|
| 103 |
+
"enam_integrated": false,
|
| 104 |
+
"reporting_quality": "good"
|
| 105 |
+
},
|
| 106 |
+
{
|
| 107 |
+
"mandi_id": "MKT-KTZ",
|
| 108 |
+
"name": "Kathonzweni",
|
| 109 |
+
"district": "Makueni",
|
| 110 |
+
"state": "Makueni",
|
| 111 |
+
"latitude": -1.9500,
|
| 112 |
+
"longitude": 37.6500,
|
| 113 |
+
"market_type": "regulated",
|
| 114 |
+
"commodities_traded": ["MZE-DRY", "BEN-YGN", "GRG-KEN"],
|
| 115 |
+
"avg_daily_arrivals_tonnes": 85.0,
|
| 116 |
+
"enam_integrated": false,
|
| 117 |
+
"reporting_quality": "moderate"
|
| 118 |
+
},
|
| 119 |
+
{
|
| 120 |
+
"mandi_id": "MKT-KAN",
|
| 121 |
+
"name": "Kaanwa",
|
| 122 |
+
"district": "Tharaka Nithi",
|
| 123 |
+
"state": "Tharaka Nithi",
|
| 124 |
+
"latitude": -0.1650,
|
| 125 |
+
"longitude": 37.7600,
|
| 126 |
+
"market_type": "regulated",
|
| 127 |
+
"commodities_traded": ["MZE-DRY", "BEN-YGN", "GRG-KEN"],
|
| 128 |
+
"avg_daily_arrivals_tonnes": 70.0,
|
| 129 |
+
"enam_integrated": false,
|
| 130 |
+
"reporting_quality": "poor"
|
| 131 |
+
}
|
| 132 |
+
]
|
|
@@ -0,0 +1,578 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
KAMIS (Kenya Agricultural Market Information System) price client.
|
| 3 |
+
|
| 4 |
+
Primary data source for Kenya wholesale/retail prices, run by the Kenya
|
| 5 |
+
Ministry of Agriculture, Livestock and Fisheries. It publishes daily
|
| 6 |
+
market reports across 47 counties with wholesale and retail quotes per
|
| 7 |
+
commodity, variety, and market via the public search portal at
|
| 8 |
+
`https://kamis.kilimo.go.ke/site/market_search`.
|
| 9 |
+
|
| 10 |
+
The portal's HTTPS cert is expired/self-signed as of 2026-04 so we
|
| 11 |
+
disable verification (the data is public).
|
| 12 |
+
|
| 13 |
+
Shape mirrors `src.ingestion.agmarknet.fetch_mandi_prices` so the
|
| 14 |
+
`pipeline.MarketIntelligencePipeline._step_ingest` helpers can plug
|
| 15 |
+
this in as a sibling `PriceSource` once Phase 1.1 wires the region
|
| 16 |
+
dispatch.
|
| 17 |
+
|
| 18 |
+
Implementation ported from
|
| 19 |
+
`lastmile-bench/lastmile_bench/benchmarks/market_intelligence/sources/kamis.py`
|
| 20 |
+
(the benchmark reference) — trimmed to drop pydantic + tenacity so we
|
| 21 |
+
don't take new dependencies on the production app.
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
from __future__ import annotations
|
| 25 |
+
|
| 26 |
+
import asyncio
|
| 27 |
+
import csv
|
| 28 |
+
import logging
|
| 29 |
+
import os
|
| 30 |
+
import re
|
| 31 |
+
import time
|
| 32 |
+
from datetime import date, datetime, timedelta
|
| 33 |
+
from pathlib import Path
|
| 34 |
+
from typing import Any, Iterable, Sequence
|
| 35 |
+
from urllib.parse import urlencode
|
| 36 |
+
|
| 37 |
+
import httpx
|
| 38 |
+
from bs4 import BeautifulSoup
|
| 39 |
+
|
| 40 |
+
from src.ingestion.agmarknet import PriceRecord
|
| 41 |
+
|
| 42 |
+
log = logging.getLogger(__name__)
|
| 43 |
+
|
| 44 |
+
# --- KAMIS portal configuration -----------------------------------------
|
| 45 |
+
|
| 46 |
+
PORTAL_BASE_URL = "https://kamis.kilimo.go.ke/site/market_search"
|
| 47 |
+
|
| 48 |
+
KAMIS_ATTRIBUTION = (
|
| 49 |
+
"Kenya Ministry of Agriculture, Livestock and Fisheries, "
|
| 50 |
+
"Kenya Agricultural Market Information System (KAMIS). "
|
| 51 |
+
"Daily wholesale and retail price reports by market and county, "
|
| 52 |
+
"scraped from https://kamis.kilimo.go.ke under the Kenya Access "
|
| 53 |
+
"to Information Act 2016 transparency provisions."
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
# KAMIS product catalog. Integer IDs come from the portal's
|
| 57 |
+
# `<option value="N">Name</option>` tags on the search page.
|
| 58 |
+
PRODUCT_IDS: dict[str, int] = {
|
| 59 |
+
"Dry maize": 1,
|
| 60 |
+
"Red sorghum": 2,
|
| 61 |
+
"Wheat": 3,
|
| 62 |
+
"Rice": 4,
|
| 63 |
+
"Green grams": 10,
|
| 64 |
+
"Ground nuts": 12,
|
| 65 |
+
"Beans (yellow-green)": 30,
|
| 66 |
+
"Finger millet": 54,
|
| 67 |
+
"White sorghum": 56,
|
| 68 |
+
"Red irish potato": 57,
|
| 69 |
+
"Sweet potatoes": 59,
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
# Aliases so callers can pass a commodity dict whose `name` or `kamis_name`
|
| 73 |
+
# lands on a canonical key in PRODUCT_IDS. Keep this small; Phase 1.1 will
|
| 74 |
+
# move the canonical mapping into `commodities.json::kamis_name`.
|
| 75 |
+
_COMMODITY_ALIASES: dict[str, str] = {
|
| 76 |
+
"maize": "Dry maize",
|
| 77 |
+
"dry maize": "Dry maize",
|
| 78 |
+
"beans": "Beans (yellow-green)",
|
| 79 |
+
"green grams": "Green grams",
|
| 80 |
+
"red irish potato": "Red irish potato",
|
| 81 |
+
"irish potato": "Red irish potato",
|
| 82 |
+
"potato": "Red irish potato",
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
_POLITE_DELAY_SECONDS = 3.0
|
| 86 |
+
_MAX_RETRIES = 5
|
| 87 |
+
_TIMEOUT_SECONDS = 45.0
|
| 88 |
+
_USER_AGENT = (
|
| 89 |
+
"Mozilla/5.0 (compatible; market-intelligence/0.1; "
|
| 90 |
+
"+https://crop-pricing.jeff-levine.com) research scraper"
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
# Module-level timestamp backing the polite-delay throttle. KAMIS has no
|
| 94 |
+
# published rate limit; 3s/request matches the benchmark reference and
|
| 95 |
+
# avoids tripping whatever the portal's operational limits are.
|
| 96 |
+
_last_request_at = 0.0
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
# --- Public entry point --------------------------------------------------
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
async def fetch_kamis_prices(
|
| 103 |
+
markets: Sequence[Any] | None = None,
|
| 104 |
+
commodities: Sequence[dict] | None = None,
|
| 105 |
+
days_back: int = 30,
|
| 106 |
+
) -> dict[str, list[PriceRecord]]:
|
| 107 |
+
"""Fetch daily KAMIS prices and return the Agmarknet PriceRecord shape.
|
| 108 |
+
|
| 109 |
+
Parameters
|
| 110 |
+
----------
|
| 111 |
+
markets
|
| 112 |
+
Iterable of market/mandi objects. Each must expose `mandi_id`,
|
| 113 |
+
`name`, and `district` attributes (the current `config.Mandi`
|
| 114 |
+
dataclass satisfies this; a Phase 1.1 Kenya `Market` dataclass
|
| 115 |
+
will also satisfy this if it keeps the same field names, or a
|
| 116 |
+
plain dict with those keys will work via the adaptor below).
|
| 117 |
+
If None, raises — KAMIS has no implicit region default.
|
| 118 |
+
commodities
|
| 119 |
+
Iterable of commodity dicts (as loaded from commodities.json).
|
| 120 |
+
Each must have an `id` key and at least one of: `kamis_name`,
|
| 121 |
+
`name`, or an entry in `_COMMODITY_ALIASES`.
|
| 122 |
+
days_back
|
| 123 |
+
How many days of history to fetch per (market, commodity).
|
| 124 |
+
|
| 125 |
+
Returns
|
| 126 |
+
-------
|
| 127 |
+
dict[str, list[PriceRecord]]
|
| 128 |
+
Price records keyed by `mandi_id`, each record with `source="kamis"`.
|
| 129 |
+
Currency is KES, unit is "kg" (KAMIS publishes KES per kilogram);
|
| 130 |
+
downstream reconciliation / forecasting treats `modal_price_rs` as
|
| 131 |
+
a numeric local-currency-per-standard-unit quote regardless of
|
| 132 |
+
region, matching how the portable base `PriceSource` protocol
|
| 133 |
+
already documents `currency` as caller-supplied.
|
| 134 |
+
|
| 135 |
+
Demo mode
|
| 136 |
+
---------
|
| 137 |
+
Setting ``MARKET_INTEL_DEMO_MODE`` to a truthy value routes reads
|
| 138 |
+
through a committed CSV snapshot at
|
| 139 |
+
``data/demo_snapshots/kamis/kamis_demo_snapshot.csv`` so tests and
|
| 140 |
+
the weekly pipeline can run deterministically without hitting the
|
| 141 |
+
live portal. Missing snapshot → empty result + a warning (the
|
| 142 |
+
pipeline is designed to tolerate empty ingestion from any single
|
| 143 |
+
source).
|
| 144 |
+
"""
|
| 145 |
+
if markets is None or commodities is None:
|
| 146 |
+
raise ValueError(
|
| 147 |
+
"fetch_kamis_prices requires explicit markets + commodities; "
|
| 148 |
+
"KAMIS has no implicit region default."
|
| 149 |
+
)
|
| 150 |
+
|
| 151 |
+
market_list = list(markets)
|
| 152 |
+
commodity_list = list(commodities)
|
| 153 |
+
|
| 154 |
+
demo_mode = os.environ.get("MARKET_INTEL_DEMO_MODE", "").lower() in (
|
| 155 |
+
"1", "true", "yes"
|
| 156 |
+
)
|
| 157 |
+
if demo_mode:
|
| 158 |
+
log.info(
|
| 159 |
+
"KAMIS: DEMO mode — reading snapshot for %d markets x %d commodities",
|
| 160 |
+
len(market_list), len(commodity_list),
|
| 161 |
+
)
|
| 162 |
+
return _load_demo_snapshot(market_list, commodity_list, days_back)
|
| 163 |
+
|
| 164 |
+
log.info(
|
| 165 |
+
"KAMIS: LIVE mode — fetching from %s for %d markets x %d commodities",
|
| 166 |
+
PORTAL_BASE_URL, len(market_list), len(commodity_list),
|
| 167 |
+
)
|
| 168 |
+
return await _fetch_live(market_list, commodity_list, days_back)
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
# --- Live scrape path ----------------------------------------------------
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
async def _fetch_live(
|
| 175 |
+
markets: list[Any],
|
| 176 |
+
commodities: list[dict],
|
| 177 |
+
days_back: int,
|
| 178 |
+
) -> dict[str, list[PriceRecord]]:
|
| 179 |
+
"""Hit the live portal with a 3s polite delay + retry-with-backoff.
|
| 180 |
+
|
| 181 |
+
Runs the blocking httpx call inside `asyncio.to_thread` so this
|
| 182 |
+
coroutine composes with the pipeline's asyncio.gather without us
|
| 183 |
+
having to rewrite the parser async-native.
|
| 184 |
+
"""
|
| 185 |
+
end = date.today()
|
| 186 |
+
start = end - timedelta(days=days_back)
|
| 187 |
+
results: dict[str, list[PriceRecord]] = {_market_id(m): [] for m in markets}
|
| 188 |
+
|
| 189 |
+
# Suppress urllib3 InsecureRequestWarning once up front (KAMIS cert is expired).
|
| 190 |
+
import urllib3
|
| 191 |
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
| 192 |
+
|
| 193 |
+
for commodity in commodities:
|
| 194 |
+
product_id = _resolve_product_id(commodity)
|
| 195 |
+
if product_id is None:
|
| 196 |
+
log.warning(
|
| 197 |
+
"KAMIS: no PRODUCT_IDS mapping for commodity %s — skipping",
|
| 198 |
+
commodity.get("id") or commodity.get("name"),
|
| 199 |
+
)
|
| 200 |
+
continue
|
| 201 |
+
|
| 202 |
+
for market in markets:
|
| 203 |
+
county = _market_county(market)
|
| 204 |
+
url = build_search_url(
|
| 205 |
+
product_id=product_id,
|
| 206 |
+
county=county,
|
| 207 |
+
start=start,
|
| 208 |
+
end=end,
|
| 209 |
+
)
|
| 210 |
+
try:
|
| 211 |
+
html = await asyncio.to_thread(_fetch_html_with_retry, url)
|
| 212 |
+
except Exception as exc:
|
| 213 |
+
log.warning(
|
| 214 |
+
"KAMIS: fetch failed for %s / %s (%s): %s",
|
| 215 |
+
_market_id(market), commodity.get("id"), url, exc,
|
| 216 |
+
)
|
| 217 |
+
continue
|
| 218 |
+
|
| 219 |
+
raw_records = parse_search_result_html(html)
|
| 220 |
+
filtered = _filter_by_market(raw_records, market)
|
| 221 |
+
for rec in filtered:
|
| 222 |
+
results[_market_id(market)].append(
|
| 223 |
+
_to_price_record(rec, market=market, commodity=commodity)
|
| 224 |
+
)
|
| 225 |
+
|
| 226 |
+
total = sum(len(v) for v in results.values())
|
| 227 |
+
log.info("KAMIS: fetched %d price records across %d markets", total, len(markets))
|
| 228 |
+
return results
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
def _fetch_html_with_retry(url: str) -> str:
|
| 232 |
+
"""Synchronous GET with 3s polite delay + exponential backoff.
|
| 233 |
+
|
| 234 |
+
KAMIS's cert is expired/self-signed → `verify=False`. We don't fall
|
| 235 |
+
back to HTTP because the portal 301-redirects HTTP→HTTPS.
|
| 236 |
+
"""
|
| 237 |
+
last_exc: Exception | None = None
|
| 238 |
+
for attempt in range(_MAX_RETRIES):
|
| 239 |
+
_polite_delay()
|
| 240 |
+
try:
|
| 241 |
+
with httpx.Client(
|
| 242 |
+
follow_redirects=True,
|
| 243 |
+
timeout=_TIMEOUT_SECONDS,
|
| 244 |
+
headers={"User-Agent": _USER_AGENT, "Accept": "text/html"},
|
| 245 |
+
verify=False,
|
| 246 |
+
) as client:
|
| 247 |
+
resp = client.get(url)
|
| 248 |
+
resp.raise_for_status()
|
| 249 |
+
return resp.text
|
| 250 |
+
except (httpx.HTTPError, httpx.TimeoutException) as exc:
|
| 251 |
+
last_exc = exc
|
| 252 |
+
backoff = min(30.0, 2.0 * (2 ** attempt))
|
| 253 |
+
log.debug(
|
| 254 |
+
"KAMIS: fetch attempt %d/%d failed (%s); retrying in %.1fs",
|
| 255 |
+
attempt + 1, _MAX_RETRIES, exc, backoff,
|
| 256 |
+
)
|
| 257 |
+
time.sleep(backoff)
|
| 258 |
+
assert last_exc is not None
|
| 259 |
+
raise last_exc
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
def _polite_delay() -> None:
|
| 263 |
+
"""Block until `_POLITE_DELAY_SECONDS` have elapsed since the last call."""
|
| 264 |
+
global _last_request_at
|
| 265 |
+
now = time.monotonic()
|
| 266 |
+
elapsed = now - _last_request_at
|
| 267 |
+
if elapsed < _POLITE_DELAY_SECONDS:
|
| 268 |
+
time.sleep(_POLITE_DELAY_SECONDS - elapsed)
|
| 269 |
+
_last_request_at = time.monotonic()
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
# --- URL + HTML parsing (ported from the benchmark reference) -----------
|
| 273 |
+
|
| 274 |
+
|
| 275 |
+
def build_search_url(
|
| 276 |
+
*,
|
| 277 |
+
product_id: int,
|
| 278 |
+
county: str | None = None,
|
| 279 |
+
start: date,
|
| 280 |
+
end: date,
|
| 281 |
+
per_page: int = 2000,
|
| 282 |
+
) -> str:
|
| 283 |
+
"""Build a KAMIS market_search URL with ISO-format dates.
|
| 284 |
+
|
| 285 |
+
The localized date form in the UI fails silently. `county` is
|
| 286 |
+
optional; if None, the query spans all counties.
|
| 287 |
+
"""
|
| 288 |
+
params: list[tuple[str, str]] = [
|
| 289 |
+
("product[]", str(product_id)),
|
| 290 |
+
("start", start.isoformat()),
|
| 291 |
+
("end", end.isoformat()),
|
| 292 |
+
("per_page", str(per_page)),
|
| 293 |
+
]
|
| 294 |
+
if county is not None:
|
| 295 |
+
params.insert(0, ("county[]", county))
|
| 296 |
+
return f"{PORTAL_BASE_URL}?{urlencode(params)}"
|
| 297 |
+
|
| 298 |
+
|
| 299 |
+
_DATE_FORMATS = ("%Y-%m-%d", "%d-%b-%Y", "%d/%m/%Y")
|
| 300 |
+
_PRICE_RE = re.compile(r"([0-9][0-9,]*(?:\.[0-9]+)?)")
|
| 301 |
+
|
| 302 |
+
|
| 303 |
+
def _parse_price(raw: str) -> float | None:
|
| 304 |
+
cleaned = (raw or "").strip().replace(",", "")
|
| 305 |
+
if not cleaned or cleaned in {"-", "--"}:
|
| 306 |
+
return None
|
| 307 |
+
m = _PRICE_RE.search(cleaned)
|
| 308 |
+
if not m:
|
| 309 |
+
return None
|
| 310 |
+
try:
|
| 311 |
+
return float(m.group(1))
|
| 312 |
+
except ValueError:
|
| 313 |
+
return None
|
| 314 |
+
|
| 315 |
+
|
| 316 |
+
def _parse_date(raw: str) -> date | None:
|
| 317 |
+
cleaned = re.sub(r"\s+", " ", raw or "").strip()
|
| 318 |
+
for fmt in _DATE_FORMATS:
|
| 319 |
+
try:
|
| 320 |
+
return datetime.strptime(cleaned, fmt).date()
|
| 321 |
+
except ValueError:
|
| 322 |
+
continue
|
| 323 |
+
return None
|
| 324 |
+
|
| 325 |
+
|
| 326 |
+
def parse_search_result_html(html: str) -> list[dict[str, Any]]:
|
| 327 |
+
"""Parse a KAMIS /site/market_search response into raw row dicts.
|
| 328 |
+
|
| 329 |
+
Returns dicts (not a typed record) so the caller can map into the
|
| 330 |
+
app's existing `PriceRecord` dataclass. Each dict has keys:
|
| 331 |
+
market, product, variety, wholesale, retail, volume, county,
|
| 332 |
+
price_date.
|
| 333 |
+
Rows missing both prices or with an unparseable date are dropped.
|
| 334 |
+
"""
|
| 335 |
+
soup = BeautifulSoup(html, "html.parser")
|
| 336 |
+
tbody = soup.find("tbody")
|
| 337 |
+
if tbody is None:
|
| 338 |
+
return []
|
| 339 |
+
out: list[dict[str, Any]] = []
|
| 340 |
+
for tr in tbody.find_all("tr", recursive=False):
|
| 341 |
+
cells = [td.get_text(strip=True) for td in tr.find_all("td")]
|
| 342 |
+
if len(cells) != 10:
|
| 343 |
+
continue
|
| 344 |
+
wholesale = _parse_price(cells[5])
|
| 345 |
+
retail = _parse_price(cells[6])
|
| 346 |
+
price_date = _parse_date(cells[9])
|
| 347 |
+
if price_date is None:
|
| 348 |
+
continue
|
| 349 |
+
if wholesale is None and retail is None:
|
| 350 |
+
continue
|
| 351 |
+
wholesale = wholesale if wholesale is not None else retail
|
| 352 |
+
retail = retail if retail is not None else wholesale
|
| 353 |
+
out.append({
|
| 354 |
+
"market": cells[0],
|
| 355 |
+
"product": cells[1],
|
| 356 |
+
"variety": cells[2],
|
| 357 |
+
"wholesale": wholesale,
|
| 358 |
+
"retail": retail,
|
| 359 |
+
"volume": cells[7],
|
| 360 |
+
"county": cells[8],
|
| 361 |
+
"price_date": price_date,
|
| 362 |
+
})
|
| 363 |
+
return out
|
| 364 |
+
|
| 365 |
+
|
| 366 |
+
# --- Demo-mode snapshot reader ------------------------------------------
|
| 367 |
+
|
| 368 |
+
|
| 369 |
+
def _demo_snapshot_path() -> Path:
|
| 370 |
+
"""Resolve the committed KAMIS snapshot relative to the project root."""
|
| 371 |
+
# src/ingestion/kamis.py → project root is parents[2]
|
| 372 |
+
return (
|
| 373 |
+
Path(__file__).resolve().parents[2]
|
| 374 |
+
/ "data" / "demo_snapshots" / "kamis" / "kamis_demo_snapshot.csv"
|
| 375 |
+
)
|
| 376 |
+
|
| 377 |
+
|
| 378 |
+
def _load_demo_snapshot(
|
| 379 |
+
markets: list[Any],
|
| 380 |
+
commodities: list[dict],
|
| 381 |
+
days_back: int,
|
| 382 |
+
) -> dict[str, list[PriceRecord]]:
|
| 383 |
+
"""Read the committed CSV snapshot and filter to requested markets × commodities.
|
| 384 |
+
|
| 385 |
+
The snapshot has the same 10-column shape as
|
| 386 |
+
`records_to_csv_rows` in the benchmark — this lets us reuse
|
| 387 |
+
lastmile-bench raw_cache exports unchanged.
|
| 388 |
+
"""
|
| 389 |
+
path = _demo_snapshot_path()
|
| 390 |
+
results: dict[str, list[PriceRecord]] = {_market_id(m): [] for m in markets}
|
| 391 |
+
if not path.exists():
|
| 392 |
+
log.warning("KAMIS demo: snapshot missing at %s — returning empty", path)
|
| 393 |
+
return results
|
| 394 |
+
|
| 395 |
+
commodity_lookup = _build_commodity_lookup(commodities)
|
| 396 |
+
if not commodity_lookup:
|
| 397 |
+
log.warning("KAMIS demo: no commodity name mappings — returning empty")
|
| 398 |
+
return results
|
| 399 |
+
|
| 400 |
+
# Build a market lookup keyed by normalized market name so we can
|
| 401 |
+
# match against the "Market" CSV column. Duck-typed — works for
|
| 402 |
+
# `config.Mandi` and for any Phase 1.1 Kenya market object that
|
| 403 |
+
# exposes `name`.
|
| 404 |
+
market_by_name: dict[str, Any] = {
|
| 405 |
+
_norm(getattr(m, "name", "")): m for m in markets
|
| 406 |
+
}
|
| 407 |
+
|
| 408 |
+
with open(path, newline="", encoding="utf-8") as fh:
|
| 409 |
+
reader = csv.DictReader(fh)
|
| 410 |
+
for row in reader:
|
| 411 |
+
market_obj = market_by_name.get(_norm(row.get("Market", "")))
|
| 412 |
+
if market_obj is None:
|
| 413 |
+
continue
|
| 414 |
+
commodity = commodity_lookup.get(_norm(row.get("Commodity", "")))
|
| 415 |
+
if commodity is None:
|
| 416 |
+
continue
|
| 417 |
+
price_date = _parse_date(row.get("Price Date", ""))
|
| 418 |
+
if price_date is None:
|
| 419 |
+
continue
|
| 420 |
+
modal = _safe_float(row.get("Modal Price"))
|
| 421 |
+
if modal is None or modal <= 0:
|
| 422 |
+
continue
|
| 423 |
+
min_price = _safe_float(row.get("Min Price")) or modal
|
| 424 |
+
max_price = _safe_float(row.get("Max Price")) or modal
|
| 425 |
+
arrivals = _safe_float(row.get("Arrivals (Tonnes)")) or 0.0
|
| 426 |
+
results[_market_id(market_obj)].append(PriceRecord(
|
| 427 |
+
mandi_id=_market_id(market_obj),
|
| 428 |
+
commodity_id=commodity["id"],
|
| 429 |
+
date=price_date.strftime("%Y-%m-%d"),
|
| 430 |
+
min_price_rs=min_price,
|
| 431 |
+
max_price_rs=max_price,
|
| 432 |
+
modal_price_rs=modal,
|
| 433 |
+
arrivals_tonnes=arrivals,
|
| 434 |
+
source="kamis",
|
| 435 |
+
freshness_hours=24.0,
|
| 436 |
+
quality_flag="good",
|
| 437 |
+
))
|
| 438 |
+
|
| 439 |
+
total = sum(len(v) for v in results.values())
|
| 440 |
+
log.info("KAMIS demo: loaded %d records across %d markets from %s",
|
| 441 |
+
total, len(markets), path.name)
|
| 442 |
+
return results
|
| 443 |
+
|
| 444 |
+
|
| 445 |
+
# --- Helpers -------------------------------------------------------------
|
| 446 |
+
|
| 447 |
+
|
| 448 |
+
def _resolve_product_id(commodity: dict) -> int | None:
|
| 449 |
+
"""Map a commodity dict to a KAMIS product_id.
|
| 450 |
+
|
| 451 |
+
Tries `commodity["kamis_name"]`, `commodity["name"]`,
|
| 452 |
+
`commodity["id"]`, then the alias table. Returns None if nothing
|
| 453 |
+
matches — the caller logs and skips.
|
| 454 |
+
"""
|
| 455 |
+
for key in ("kamis_name", "name", "id"):
|
| 456 |
+
raw = commodity.get(key)
|
| 457 |
+
if not raw:
|
| 458 |
+
continue
|
| 459 |
+
if raw in PRODUCT_IDS:
|
| 460 |
+
return PRODUCT_IDS[raw]
|
| 461 |
+
canonical = _COMMODITY_ALIASES.get(_norm(raw))
|
| 462 |
+
if canonical and canonical in PRODUCT_IDS:
|
| 463 |
+
return PRODUCT_IDS[canonical]
|
| 464 |
+
return None
|
| 465 |
+
|
| 466 |
+
|
| 467 |
+
def _build_commodity_lookup(commodities: list[dict]) -> dict[str, dict]:
|
| 468 |
+
"""Map normalized commodity names back to the commodity dict.
|
| 469 |
+
|
| 470 |
+
Used by the demo reader to assign `commodity_id` to each CSV row.
|
| 471 |
+
"""
|
| 472 |
+
out: dict[str, dict] = {}
|
| 473 |
+
for c in commodities:
|
| 474 |
+
for key in ("kamis_name", "name", "id"):
|
| 475 |
+
val = c.get(key)
|
| 476 |
+
if val:
|
| 477 |
+
out[_norm(val)] = c
|
| 478 |
+
# also register the aliased canonical name
|
| 479 |
+
for key in ("kamis_name", "name", "id"):
|
| 480 |
+
val = c.get(key)
|
| 481 |
+
canonical = _COMMODITY_ALIASES.get(_norm(val or ""))
|
| 482 |
+
if canonical:
|
| 483 |
+
out[_norm(canonical)] = c
|
| 484 |
+
return out
|
| 485 |
+
|
| 486 |
+
|
| 487 |
+
def _filter_by_market(
|
| 488 |
+
rows: list[dict[str, Any]], market: Any
|
| 489 |
+
) -> list[dict[str, Any]]:
|
| 490 |
+
"""Keep only the rows whose "market" cell matches the passed market.
|
| 491 |
+
|
| 492 |
+
KAMIS county queries return all markets in the county — we filter
|
| 493 |
+
client-side to honor the caller's `markets=` argument.
|
| 494 |
+
"""
|
| 495 |
+
target = _norm(getattr(market, "name", ""))
|
| 496 |
+
if not target:
|
| 497 |
+
return rows
|
| 498 |
+
return [r for r in rows if _norm(r.get("market", "")) == target]
|
| 499 |
+
|
| 500 |
+
|
| 501 |
+
def _to_price_record(
|
| 502 |
+
raw: dict[str, Any], *, market: Any, commodity: dict
|
| 503 |
+
) -> PriceRecord:
|
| 504 |
+
"""Convert a parsed KAMIS row into the app's Agmarknet-shaped PriceRecord.
|
| 505 |
+
|
| 506 |
+
We emit the **retail** price as `modal_price_rs` (KAMIS doesn't
|
| 507 |
+
publish a modal; retail is the consumer-facing quote that a farmer
|
| 508 |
+
would compare a selling offer against), wholesale as `min_price_rs`,
|
| 509 |
+
and retail as `max_price_rs`. `arrivals_tonnes` stays 0 because
|
| 510 |
+
KAMIS's "volume" field is an unconfirmed unit.
|
| 511 |
+
"""
|
| 512 |
+
wholesale = float(raw["wholesale"])
|
| 513 |
+
retail = float(raw["retail"])
|
| 514 |
+
return PriceRecord(
|
| 515 |
+
mandi_id=_market_id(market),
|
| 516 |
+
commodity_id=commodity["id"],
|
| 517 |
+
date=raw["price_date"].strftime("%Y-%m-%d"),
|
| 518 |
+
min_price_rs=wholesale,
|
| 519 |
+
max_price_rs=retail,
|
| 520 |
+
modal_price_rs=retail,
|
| 521 |
+
arrivals_tonnes=0.0,
|
| 522 |
+
source="kamis",
|
| 523 |
+
freshness_hours=24.0,
|
| 524 |
+
quality_flag="good",
|
| 525 |
+
)
|
| 526 |
+
|
| 527 |
+
|
| 528 |
+
def _market_id(market: Any) -> str:
|
| 529 |
+
"""Return the market's ID, tolerating both `mandi_id` and `market_id`."""
|
| 530 |
+
for attr in ("mandi_id", "market_id", "id"):
|
| 531 |
+
val = getattr(market, attr, None)
|
| 532 |
+
if val:
|
| 533 |
+
return str(val)
|
| 534 |
+
if isinstance(market, dict):
|
| 535 |
+
for k in ("mandi_id", "market_id", "id"):
|
| 536 |
+
if market.get(k):
|
| 537 |
+
return str(market[k])
|
| 538 |
+
raise ValueError(f"Market has no mandi_id / market_id / id: {market!r}")
|
| 539 |
+
|
| 540 |
+
|
| 541 |
+
def _market_county(market: Any) -> str | None:
|
| 542 |
+
"""Best-effort extraction of a KAMIS-compatible county string.
|
| 543 |
+
|
| 544 |
+
Tries `county`, then `district`, then `state`. Returns None if
|
| 545 |
+
nothing is set so the URL builder queries all counties.
|
| 546 |
+
"""
|
| 547 |
+
for attr in ("county", "district", "state"):
|
| 548 |
+
val = getattr(market, attr, None)
|
| 549 |
+
if val:
|
| 550 |
+
return str(val)
|
| 551 |
+
if isinstance(market, dict):
|
| 552 |
+
for k in ("county", "district", "state"):
|
| 553 |
+
if market.get(k):
|
| 554 |
+
return str(market[k])
|
| 555 |
+
return None
|
| 556 |
+
|
| 557 |
+
|
| 558 |
+
def _norm(s: str) -> str:
|
| 559 |
+
return re.sub(r"\s+", " ", (s or "").strip().lower())
|
| 560 |
+
|
| 561 |
+
|
| 562 |
+
def _safe_float(v: Any) -> float | None:
|
| 563 |
+
if v is None or v == "":
|
| 564 |
+
return None
|
| 565 |
+
try:
|
| 566 |
+
return float(v)
|
| 567 |
+
except (TypeError, ValueError):
|
| 568 |
+
return None
|
| 569 |
+
|
| 570 |
+
|
| 571 |
+
__all__ = [
|
| 572 |
+
"PORTAL_BASE_URL",
|
| 573 |
+
"KAMIS_ATTRIBUTION",
|
| 574 |
+
"PRODUCT_IDS",
|
| 575 |
+
"fetch_kamis_prices",
|
| 576 |
+
"build_search_url",
|
| 577 |
+
"parse_search_result_html",
|
| 578 |
+
]
|
|
@@ -22,6 +22,7 @@ from config import (
|
|
| 22 |
MANDIS,
|
| 23 |
MANDI_MAP,
|
| 24 |
PIPELINE_STEPS,
|
|
|
|
| 25 |
SAMPLE_FARMERS,
|
| 26 |
FEATURED_FARMERS,
|
| 27 |
SEASONAL_INDICES,
|
|
@@ -29,6 +30,7 @@ from config import (
|
|
| 29 |
)
|
| 30 |
from src.ingestion.agmarknet import fetch_mandi_prices, PriceRecord
|
| 31 |
from src.ingestion.enam_scraper import fetch_enam_prices
|
|
|
|
| 32 |
from src.ingestion.nasa_power import fetch_all_mandis_nasa_power, DailyReading
|
| 33 |
from src.extraction.agent import ExtractionAgent, RuleBasedExtractor
|
| 34 |
from src.reconciliation.agent import ReconciliationAgent, RuleBasedReconciler
|
|
@@ -226,6 +228,15 @@ class MarketIntelligencePipeline:
|
|
| 226 |
|
| 227 |
try:
|
| 228 |
async def _ingest_agmarknet():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
try:
|
| 230 |
self._agmarknet_prices = await fetch_mandi_prices(
|
| 231 |
MANDIS, COMMODITIES, days_back=self.days_back,
|
|
@@ -234,6 +245,9 @@ class MarketIntelligencePipeline:
|
|
| 234 |
errors.append(f"Agmarknet fetch failed: {e}")
|
| 235 |
|
| 236 |
async def _ingest_enam():
|
|
|
|
|
|
|
|
|
|
| 237 |
try:
|
| 238 |
self._enam_prices = await fetch_enam_prices(
|
| 239 |
MANDIS, COMMODITIES, days_back=min(14, self.days_back),
|
|
|
|
| 22 |
MANDIS,
|
| 23 |
MANDI_MAP,
|
| 24 |
PIPELINE_STEPS,
|
| 25 |
+
REGION,
|
| 26 |
SAMPLE_FARMERS,
|
| 27 |
FEATURED_FARMERS,
|
| 28 |
SEASONAL_INDICES,
|
|
|
|
| 30 |
)
|
| 31 |
from src.ingestion.agmarknet import fetch_mandi_prices, PriceRecord
|
| 32 |
from src.ingestion.enam_scraper import fetch_enam_prices
|
| 33 |
+
from src.ingestion.kamis import fetch_kamis_prices
|
| 34 |
from src.ingestion.nasa_power import fetch_all_mandis_nasa_power, DailyReading
|
| 35 |
from src.extraction.agent import ExtractionAgent, RuleBasedExtractor
|
| 36 |
from src.reconciliation.agent import ReconciliationAgent, RuleBasedReconciler
|
|
|
|
| 228 |
|
| 229 |
try:
|
| 230 |
async def _ingest_agmarknet():
|
| 231 |
+
# Under Kenya, the primary wholesale source is KAMIS (no eNAM).
|
| 232 |
+
if REGION == "kenya":
|
| 233 |
+
try:
|
| 234 |
+
self._agmarknet_prices = await fetch_kamis_prices(
|
| 235 |
+
MANDIS, COMMODITIES, days_back=self.days_back,
|
| 236 |
+
)
|
| 237 |
+
except Exception as e:
|
| 238 |
+
errors.append(f"KAMIS fetch failed: {e}")
|
| 239 |
+
return
|
| 240 |
try:
|
| 241 |
self._agmarknet_prices = await fetch_mandi_prices(
|
| 242 |
MANDIS, COMMODITIES, days_back=self.days_back,
|
|
|
|
| 245 |
errors.append(f"Agmarknet fetch failed: {e}")
|
| 246 |
|
| 247 |
async def _ingest_enam():
|
| 248 |
+
if REGION == "kenya":
|
| 249 |
+
self._enam_prices = {m.mandi_id: [] for m in MANDIS}
|
| 250 |
+
return
|
| 251 |
try:
|
| 252 |
self._enam_prices = await fetch_enam_prices(
|
| 253 |
MANDIS, COMMODITIES, days_back=min(14, self.days_back),
|
|
@@ -0,0 +1,339 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Kenya agricultural marketing knowledge base for RAG retrieval.
|
| 3 |
+
|
| 4 |
+
~25 knowledge chunks covering bimodal crop calendars, NCPB/KAMIS/county
|
| 5 |
+
institutions, post-harvest handling (aflatoxin, PICS, metal silos),
|
| 6 |
+
transport (A-roads vs feeder roads, Mombasa corridor), storage
|
| 7 |
+
(NCPB depots, CACs, warehouse receipts), and shocks (2022 Horn of
|
| 8 |
+
Africa drought, 2023-24 El Nino floods, IPC classifications).
|
| 9 |
+
|
| 10 |
+
Used by the recommendation agent when REGION=kenya. Sibling to
|
| 11 |
+
knowledge_base.py (Tamil Nadu / India). Schema matches the India
|
| 12 |
+
module exactly: KnowledgeChunk dataclass with id/title/source/category/text.
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
from __future__ import annotations
|
| 16 |
+
|
| 17 |
+
from dataclasses import dataclass
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
@dataclass
|
| 21 |
+
class KnowledgeChunk:
|
| 22 |
+
id: str
|
| 23 |
+
title: str
|
| 24 |
+
source: str
|
| 25 |
+
category: str
|
| 26 |
+
text: str
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
KNOWLEDGE_CHUNKS: list[KnowledgeChunk] = [
|
| 30 |
+
|
| 31 |
+
# -- Crop Calendars -------------------------------------------------
|
| 32 |
+
|
| 33 |
+
KnowledgeChunk(
|
| 34 |
+
"CC-001", "Maize Long Rains Calendar - Rift Valley and Western Kenya",
|
| 35 |
+
"MoALF Kenya Crop Production Guidelines 2023; FEWS NET Kenya Seasonal Monitor",
|
| 36 |
+
"crop_calendar",
|
| 37 |
+
"The Long Rains are Kenya's primary cropping season and produce roughly 70-80% of the national maize crop. "
|
| 38 |
+
"Land preparation begins Feb-Mar, planting Mar-May as rains establish, vegetative growth Apr-Jun, "
|
| 39 |
+
"and harvest Jul-Sep in most zones. Main producing counties: Trans-Nzoia, Uasin Gishu, Nakuru, Narok, "
|
| 40 |
+
"Bungoma, Kakamega, Nyandarua. Rift Valley high-altitude zones (Trans-Nzoia, Uasin Gishu) harvest "
|
| 41 |
+
"latest (Sep-Nov) because of longer maturation at altitude. Wholesale maize price troughs typically "
|
| 42 |
+
"arrive Aug-Oct when Western and Rift Valley grain floods Nairobi, Kisumu, Eldoret, and Nakuru markets."
|
| 43 |
+
),
|
| 44 |
+
KnowledgeChunk(
|
| 45 |
+
"CC-002", "Maize Short Rains Calendar - Eastern and Coastal Kenya",
|
| 46 |
+
"MoALF Kenya Crop Production Guidelines 2023; KALRO Maize Agronomy Manual",
|
| 47 |
+
"crop_calendar",
|
| 48 |
+
"The Short Rains (Oct-Dec) are the secondary season and dominate Eastern, lower Central, and Coastal "
|
| 49 |
+
"Kenya, where a single unimodal rainy season is unreliable. Planting Oct-Nov, harvest Jan-Feb. "
|
| 50 |
+
"Key counties: Machakos, Makueni, Kitui, Embu, Tharaka-Nithi, Kilifi, Kwale. Short Rains maize is "
|
| 51 |
+
"far more vulnerable to dry spells -- the 2022 consecutive failure of five rainy seasons devastated "
|
| 52 |
+
"this region. Short Rains harvest pushes a secondary price trough in Feb-Mar in Machakos, Mwingi, "
|
| 53 |
+
"and Wote markets, though volumes are a fraction of the Long Rains crop."
|
| 54 |
+
),
|
| 55 |
+
KnowledgeChunk(
|
| 56 |
+
"CC-003", "Maize Moisture and Safe-Storage Thresholds",
|
| 57 |
+
"KALRO Post-Harvest Handbook; FAO Kenya Aflatoxin Mitigation Guide",
|
| 58 |
+
"crop_calendar",
|
| 59 |
+
"Maize at harvest is typically 18-25% moisture and must be dried to 13.5% or below before storage. "
|
| 60 |
+
"Above 13.5% moisture, Aspergillus flavus growth accelerates and aflatoxin contamination risk rises "
|
| 61 |
+
"sharply; above 18% grain may rot within weeks. Sun-drying on raised tarpaulins or drying cribs over "
|
| 62 |
+
"5-10 days is standard for smallholders; avoid drying on bare ground (soil contamination and re-wetting). "
|
| 63 |
+
"NCPB rejects deliveries above 13.5% moisture. Traders discount roughly KES 100-200 per 90kg bag for "
|
| 64 |
+
"each percentage point above 13.5%, and a heavily aflatoxin-positive lot may be unsellable entirely."
|
| 65 |
+
),
|
| 66 |
+
KnowledgeChunk(
|
| 67 |
+
"CC-004", "Bean Crop Calendar - Bimodal Smallholder Staple",
|
| 68 |
+
"KALRO Legumes Research Programme; MoALF Pulses Strategy 2020-2025",
|
| 69 |
+
"crop_calendar",
|
| 70 |
+
"Beans (Rosecoco, Mwitemania, Mwezi Moja, Wairimu) are grown in both rainy seasons on 75-90 day cycles, "
|
| 71 |
+
"shorter than maize, and are frequently intercropped with maize. Long Rains: plant Mar-Apr, harvest "
|
| 72 |
+
"Jun-Jul. Short Rains: plant Oct-Nov, harvest Jan-Feb. Main counties: Nakuru, Narok, Bomet, Kirinyaga, "
|
| 73 |
+
"Meru, Embu, Machakos. Beans are drought-sensitive at flowering (days 35-45); mid-season dry spells "
|
| 74 |
+
"routinely cut yields 30-50%. Post-harvest beans must be dried to <13% moisture; bruchid weevils are "
|
| 75 |
+
"the primary storage pest and destroy improperly-stored lots within 2-3 months."
|
| 76 |
+
),
|
| 77 |
+
KnowledgeChunk(
|
| 78 |
+
"CC-005", "Irish Potato Calendar - Nyandarua, Meru, and Nakuru",
|
| 79 |
+
"National Potato Council of Kenya; KALRO Tigoni",
|
| 80 |
+
"crop_calendar",
|
| 81 |
+
"Irish potato (Shangi, Dutch Robijn, Asante) is grown at 1,800-3,000m altitude on a 3-4 month cycle "
|
| 82 |
+
"tied to the rains. Long Rains planting Mar-Apr, harvest Jun-Aug. Short Rains planting Oct-Nov, "
|
| 83 |
+
"harvest Feb-Mar. Production is concentrated in Nyandarua (~40% of national output), Meru, Elgeyo "
|
| 84 |
+
"Marakwet, Nakuru, Bomet, and Bungoma. Shangi dominates (~80% of market) because it ripens quickly "
|
| 85 |
+
"and handles transport, but it has very short dormancy (2-3 weeks) -- farmers cannot hold crop, "
|
| 86 |
+
"which drives distress selling at harvest and extreme price swings. Nyandarua farm-gate prices have "
|
| 87 |
+
"swung from KES 800 to KES 3,500 per 110kg bag within a single season."
|
| 88 |
+
),
|
| 89 |
+
|
| 90 |
+
# -- Market Institutions --------------------------------------------
|
| 91 |
+
|
| 92 |
+
KnowledgeChunk(
|
| 93 |
+
"MI-001", "NCPB - National Cereals and Produce Board",
|
| 94 |
+
"NCPB Annual Reports; MoALF Strategic Food Reserve Policy",
|
| 95 |
+
"market_institution",
|
| 96 |
+
"NCPB is Kenya's parastatal grain buyer of last resort and operator of the Strategic Food Reserve. "
|
| 97 |
+
"When activated by the Cabinet Secretary, NCPB offers a Guaranteed Minimum Return Price (GMRP) for "
|
| 98 |
+
"maize -- recent GMRP levels have ranged KES 3,000-5,200 per 90kg bag, well above distress-sale "
|
| 99 |
+
"farm-gate levels in surplus seasons. NCPB operates ~110 depots nationwide with concentration in "
|
| 100 |
+
"Rift Valley and Western counties. Payment delays of 30-90 days are common; smallholders need "
|
| 101 |
+
"registration, ID, and land documentation. Intake quality standards: <13.5% moisture, <3% foreign "
|
| 102 |
+
"matter, aflatoxin within codex limits. When GMRP is not activated, NCPB buys at market rates or not at all."
|
| 103 |
+
),
|
| 104 |
+
KnowledgeChunk(
|
| 105 |
+
"MI-002", "KAMIS - Kenya Agricultural Market Information System",
|
| 106 |
+
"KAMIS Portal; MoALF Market Information Unit",
|
| 107 |
+
"market_institution",
|
| 108 |
+
"KAMIS is the MoALF-run market information service (kamis.kilimo.go.ke) that publishes daily, weekly, "
|
| 109 |
+
"and monthly wholesale prices for 40+ commodities across 60+ markets nationwide. Coverage is strongest "
|
| 110 |
+
"for Nairobi (Wakulima, Kangemi, Gikomba), Mombasa (Kongowea), Eldoret, Kisumu (Kibuye), and Nakuru. "
|
| 111 |
+
"Daily prices reflect wholesale trades and typically sit 15-40% above farm-gate. KAMIS is the canonical "
|
| 112 |
+
"source for price reconciliation and for retrospective benchmark panels. Gaps: farm-gate prices are "
|
| 113 |
+
"not systematically collected, and rural market coverage thins outside the main trunk corridors."
|
| 114 |
+
),
|
| 115 |
+
KnowledgeChunk(
|
| 116 |
+
"MI-003", "County Governments Post-2013 Devolution",
|
| 117 |
+
"Constitution of Kenya 2010, Fourth Schedule; Kenya Devolution Working Paper (World Bank 2020)",
|
| 118 |
+
"market_institution",
|
| 119 |
+
"Agriculture is a devolved function under Kenya's 2010 Constitution -- each of the 47 counties runs "
|
| 120 |
+
"its own Department of Agriculture with budgets for extension, input subsidies (often fertilizer and "
|
| 121 |
+
"certified seed), market infrastructure, and cooperative support. Practical consequences for farmers: "
|
| 122 |
+
"input subsidy program eligibility, cooperative registration, and county market fees all vary by county. "
|
| 123 |
+
"Meru and Nyandarua run active potato-sector programs; Kakamega and Bungoma run maize-and-dairy extension; "
|
| 124 |
+
"ASAL counties (Kitui, Makueni, Turkana) focus on drought-tolerant sorghum and millet. National-level "
|
| 125 |
+
"MoALF still sets price-support policy (NCPB, GMRP) and runs KAMIS."
|
| 126 |
+
),
|
| 127 |
+
KnowledgeChunk(
|
| 128 |
+
"MI-004", "SACCOs and Cooperatives for Rural Finance and Aggregation",
|
| 129 |
+
"SASRA Annual Supervision Report; Co-operative Bank of Kenya Agri-SACCO Study",
|
| 130 |
+
"market_institution",
|
| 131 |
+
"SACCOs (Savings and Credit Cooperatives) are the backbone of Kenyan rural finance. Regulated by "
|
| 132 |
+
"SASRA, farmer SACCOs pool member savings, provide input credit (fertilizer, seed on 3-6 month terms), "
|
| 133 |
+
"and aggregate produce for bulk sale. Agricultural SACCOs are strongest in coffee (Mt. Kenya), dairy "
|
| 134 |
+
"(Rift Valley), and tea (Kericho, Nandi) zones; maize and bean cooperatives are weaker but growing. "
|
| 135 |
+
"Typical benefits of SACCO membership: input credit at 12-15% annual (vs. 25%+ from informal lenders), "
|
| 136 |
+
"shared transport to terminal markets, and access to NCPB delivery letters. Dormant and weak-governance "
|
| 137 |
+
"cooperatives are common -- farmers should verify SASRA registration before committing savings."
|
| 138 |
+
),
|
| 139 |
+
|
| 140 |
+
# -- Post-Harvest Handling -----------------------------------------
|
| 141 |
+
|
| 142 |
+
KnowledgeChunk(
|
| 143 |
+
"PH-001", "Aflatoxin Risk in Kenya - Regions and History",
|
| 144 |
+
"Kenya Ministry of Health Aflatoxin Surveillance; Lewis et al. 2005 (2004 Machakos Outbreak)",
|
| 145 |
+
"post_harvest",
|
| 146 |
+
"Aflatoxin contamination is Kenya's most severe post-harvest food-safety risk, caused by Aspergillus "
|
| 147 |
+
"flavus on improperly-dried maize. The 2004 outbreak centered on Makueni and Machakos killed 125+ "
|
| 148 |
+
"people from acute aflatoxicosis -- still among the largest aflatoxin mortality events ever recorded. "
|
| 149 |
+
"High-risk zones: Eastern Kenya lower midlands (Machakos, Makueni, Kitui, Embu, Tharaka-Nithi) where "
|
| 150 |
+
"Short Rains harvests coincide with lingering humidity, and coastal Kenya (Kilifi, Kwale, Taita-Taveta) "
|
| 151 |
+
"year-round. Rift Valley Long Rains crops are lower-risk because harvest falls in the drier Jul-Sep "
|
| 152 |
+
"window. Aflasafe KE01 biocontrol is available through KALRO and reduces field contamination 80-90% "
|
| 153 |
+
"when applied 2-3 weeks before flowering."
|
| 154 |
+
),
|
| 155 |
+
KnowledgeChunk(
|
| 156 |
+
"PH-002", "PICS Bags - Hermetic Triple-Layer Storage",
|
| 157 |
+
"Purdue Improved Crop Storage (PICS) Project; KENAFF Distribution Records",
|
| 158 |
+
"post_harvest",
|
| 159 |
+
"PICS (Purdue Improved Crop Storage) bags are triple-layer hermetic storage sacks -- two polyethylene "
|
| 160 |
+
"liners inside a woven polypropylene outer bag -- that suffocate storage pests (maize weevil, larger "
|
| 161 |
+
"grain borer, bruchid) within 10-14 days of sealing, without any chemicals. A 90kg PICS bag costs "
|
| 162 |
+
"KES 250-400 in Kenyan agrovet shops (2024-2025), reusable 2-3 seasons. Storage loss drops from "
|
| 163 |
+
"20-30% over 6 months (open-weave gunny bags + actellic dusting) to under 2% with PICS. Distribution "
|
| 164 |
+
"in Kenya runs through KENAFF (Kenya National Farmers' Federation), KALRO, and private agrovet chains "
|
| 165 |
+
"(Farmers Choice, Pwani, Amiran). Adoption is highest in Western Kenya and Rift Valley."
|
| 166 |
+
),
|
| 167 |
+
KnowledgeChunk(
|
| 168 |
+
"PH-003", "Metal Silo Program - CIMMYT, KALRO, EGSP",
|
| 169 |
+
"CIMMYT Effective Grain Storage Project (EGSP) 2008-2016; KALRO Post-Harvest Unit",
|
| 170 |
+
"post_harvest",
|
| 171 |
+
"Metal silos (galvanized sheet steel, 180-3,000kg capacity) are the premium on-farm storage option, "
|
| 172 |
+
"disseminated in Kenya through the CIMMYT-led Effective Grain Storage Project with KALRO and the "
|
| 173 |
+
"Ministry of Agriculture (2008-2016). A 900kg silo costs KES 18,000-30,000 through trained artisans "
|
| 174 |
+
"in Nakuru, Bungoma, and Embu. Storage losses drop to near zero over 12+ months. Economics work for "
|
| 175 |
+
"farmers with >10 bags to store who want to capture the Oct-trough to Mar-peak maize price cycle "
|
| 176 |
+
"(15-25% seasonal spread). Adoption remains limited by upfront cost; SACCOs and county agriculture "
|
| 177 |
+
"departments occasionally subsidize silos for smallholder cooperatives."
|
| 178 |
+
),
|
| 179 |
+
KnowledgeChunk(
|
| 180 |
+
"PH-004", "WFP and FAO Post-Harvest Programs (P4P and Successors)",
|
| 181 |
+
"WFP Kenya Country Strategic Plan; FAO Kenya Resilience Programme",
|
| 182 |
+
"post_harvest",
|
| 183 |
+
"WFP's Purchase for Progress (P4P, 2008-2014) demonstrated smallholder aggregation into WFP's Kenya "
|
| 184 |
+
"food-aid supply chain, sourcing maize and pulses from producer organizations at prices above farm-gate "
|
| 185 |
+
"in exchange for quality and volume commitments. P4P successors under WFP Country Strategic Plans "
|
| 186 |
+
"continue smallholder sourcing in Bungoma, Busia, Makueni, Kitui, and Trans-Nzoia. FAO's Kenya "
|
| 187 |
+
"Resilience and Food Systems programmes fund post-harvest training, hermetic storage subsidies, and "
|
| 188 |
+
"aflatoxin testing equipment at county level. Participating farmer groups typically see 20-30% price "
|
| 189 |
+
"uplift on the sold portion plus training in quality grading and moisture testing."
|
| 190 |
+
),
|
| 191 |
+
|
| 192 |
+
# -- Transport ------------------------------------------------------
|
| 193 |
+
|
| 194 |
+
KnowledgeChunk(
|
| 195 |
+
"TR-001", "Kenya Road Network - Classes A, B, C and Corridor Geography",
|
| 196 |
+
"Kenya National Highways Authority (KeNHA); Kenya Rural Roads Authority (KeRRA)",
|
| 197 |
+
"transport",
|
| 198 |
+
"Kenya's classified road network has roughly 161,000 km, of which only ~14,000 km is paved. "
|
| 199 |
+
"Class A roads are international trunk corridors (A1 Malaba-Kisumu-Kisii-Isebania west corridor and "
|
| 200 |
+
"A109 Nairobi-Mombasa, the Northern Corridor). Class B are national trunks linking regions. "
|
| 201 |
+
"Class C are primary feeder roads. Classes D and E are rural feeders, almost entirely unpaved and "
|
| 202 |
+
"often impassable in heavy rain. Almost all maize moves on paved A/B roads once aggregated, but the "
|
| 203 |
+
"first 5-50 km from farm to market centre is on D/E feeders -- this is where weather disruption, "
|
| 204 |
+
"broken bridges, and seasonal impassability hit smallholders hardest."
|
| 205 |
+
),
|
| 206 |
+
KnowledgeChunk(
|
| 207 |
+
"TR-002", "Transport Cost Deltas - Paved vs Unpaved",
|
| 208 |
+
"Kenya Transport Sector Study (World Bank 2022); KENHA Haulage Cost Schedules",
|
| 209 |
+
"transport",
|
| 210 |
+
"Typical truckage costs (2024) for maize and produce: Paved trunk road (A/B), 30-tonne truck: "
|
| 211 |
+
"KES 7-12 per tonne-km. Gravel/unpaved feeder road: KES 15-25 per tonne-km, sometimes higher in wet "
|
| 212 |
+
"season because of reduced load factors and longer transit time. Small-vehicle rates: pickup (1-2t) "
|
| 213 |
+
"KES 25-40 per tonne-km; matatu-sized 7t lorries KES 15-20 per tonne-km. Farmers selling 5-20 bags "
|
| 214 |
+
"typically cannot fill a full truck and pay a premium for shared or piecemeal transport. Concrete "
|
| 215 |
+
"example: Trans-Nzoia to Nairobi (~380 km paved trunk) runs ~KES 180-220 per 90kg bag on a full lorry; "
|
| 216 |
+
"the final 15 km from farm to Kitale collection point can cost another KES 40-80 per bag."
|
| 217 |
+
),
|
| 218 |
+
KnowledgeChunk(
|
| 219 |
+
"TR-003", "Aggregators, Brokers, and Farmer Transport Options",
|
| 220 |
+
"Tegemeo Institute Smallholder Market Access Study 2021",
|
| 221 |
+
"transport",
|
| 222 |
+
"Most Kenyan smallholders sell to itinerant middlemen (brokers, 'madalali') who bring lorries or "
|
| 223 |
+
"pickups to the farm gate or village market. Broker margins are typically 10-25% above farm-gate to "
|
| 224 |
+
"wholesale-market prices, reflecting risk, financing, and transport. Alternatives for farmers: "
|
| 225 |
+
"deliver to a county NCPB depot (requires registration and the depot being open for GMRP intake), "
|
| 226 |
+
"aggregate through a SACCO or cooperative for shared truckage, or use a matatu/boda-boda for very "
|
| 227 |
+
"small volumes to the nearest market centre. The smaller the volume, the weaker the farmer's "
|
| 228 |
+
"bargaining position -- sub-10-bag smallholders routinely accept 20-35% below the KAMIS wholesale "
|
| 229 |
+
"price reported for the nearest town."
|
| 230 |
+
),
|
| 231 |
+
KnowledgeChunk(
|
| 232 |
+
"TR-004", "Mombasa Port Bottlenecks and Seasonal Congestion",
|
| 233 |
+
"Kenya Ports Authority Performance Reports; Shippers Council of Eastern Africa",
|
| 234 |
+
"transport",
|
| 235 |
+
"The Nairobi-Mombasa A109 corridor is Kenya's transport spine. Mombasa Port handles imports for the "
|
| 236 |
+
"entire Northern Corridor (including landlocked Uganda, Rwanda, South Sudan, eastern DRC), which "
|
| 237 |
+
"competes with outbound agricultural freight for the same truck fleet. Peak congestion windows: "
|
| 238 |
+
"Nov-Jan (holiday consumer imports) and Mar-May (fertilizer and agro-input imports before Long Rains) "
|
| 239 |
+
"push per-tonne-km haulage rates up 15-30%. The 2023-24 El Nino floods cut the A109 at multiple "
|
| 240 |
+
"points (including the long-running Mtito Andei washout) and diverted freight for weeks. SGR "
|
| 241 |
+
"(Standard Gauge Railway) handles containerized freight but has limited relevance for bulk "
|
| 242 |
+
"agricultural produce moving on short domestic hops."
|
| 243 |
+
),
|
| 244 |
+
KnowledgeChunk(
|
| 245 |
+
"TR-005", "County Road Quality - Nyandarua Potato Case",
|
| 246 |
+
"Nyandarua County Integrated Development Plan; National Potato Council of Kenya",
|
| 247 |
+
"transport",
|
| 248 |
+
"Road quality varies dramatically by county and is a first-order determinant of farm-gate price. "
|
| 249 |
+
"Nyandarua -- Kenya's largest potato producer -- is notorious for poor feeder roads connecting "
|
| 250 |
+
"Kinangop, Ol Kalou, and Ndaragwa to the tarmac network. In the Mar-May Long Rains, Nyandarua feeder "
|
| 251 |
+
"roads become impassable for days at a time, trapping potato harvests at the farm and forcing distress "
|
| 252 |
+
"sales to brokers who own 4WD trucks at steep discounts. By contrast, Meru and Nakuru potato zones "
|
| 253 |
+
"have better C-class road coverage and farm-gate to wholesale price spreads are 15-20 percentage "
|
| 254 |
+
"points narrower. Farmers and aggregators in rain-affected counties should plan sales windows around "
|
| 255 |
+
"forecast dry spells."
|
| 256 |
+
),
|
| 257 |
+
|
| 258 |
+
# -- Storage --------------------------------------------------------
|
| 259 |
+
|
| 260 |
+
KnowledgeChunk(
|
| 261 |
+
"ST-001", "NCPB Depot Storage - Access, Fees, Capacity",
|
| 262 |
+
"NCPB Operations Manual; Kenya Grain Council Storage Directory",
|
| 263 |
+
"storage",
|
| 264 |
+
"NCPB operates ~110 depots with combined storage capacity around 2.0 million bags (180,000 MT) of "
|
| 265 |
+
"grain. Beyond buyer-of-last-resort procurement, NCPB also rents storage to traders and farmer "
|
| 266 |
+
"groups at KES 8-15 per 90kg bag per month (varies by depot and season). Access for smallholders "
|
| 267 |
+
"is mediated by registration requirements (national ID, KRA PIN, cooperative or group membership). "
|
| 268 |
+
"Major depots: Eldoret, Kitale, Bungoma, Nakuru, Meru, Nairobi Industrial Area. Smallholder "
|
| 269 |
+
"deposits have to meet intake quality standards (<13.5% moisture, clean). Warehouse-receipt-backed "
|
| 270 |
+
"lending against NCPB stock exists but is limited in practice."
|
| 271 |
+
),
|
| 272 |
+
KnowledgeChunk(
|
| 273 |
+
"ST-002", "Community Aggregation Centres (CACs)",
|
| 274 |
+
"AGRA Kenya Smallholder Aggregation Review; MoALF Cooperative Sub-Sector Report",
|
| 275 |
+
"storage",
|
| 276 |
+
"Community Aggregation Centres are smallholder-focused collection and short-term storage facilities, "
|
| 277 |
+
"typically county-government-built or AGRA/NGO-funded structures of 50-500 MT capacity. CACs allow "
|
| 278 |
+
"farmers to pool grain, share drying floors and moisture meters, and negotiate as a unit with traders "
|
| 279 |
+
"or with NCPB. Strongest CAC networks are in Bungoma, Busia, Trans-Nzoia, and Meru. Storage fees are "
|
| 280 |
+
"nominal (KES 5-10 per bag per month) but capacity is limited and governance is uneven -- some CACs "
|
| 281 |
+
"are effectively dormant. CAC membership typically provides the bridge smallholders need to access "
|
| 282 |
+
"the warehouse receipt system or direct NCPB delivery."
|
| 283 |
+
),
|
| 284 |
+
KnowledgeChunk(
|
| 285 |
+
"ST-003", "Warehouse Receipt System - KACE and WRC",
|
| 286 |
+
"Kenya Agricultural Commodity Exchange (KACE); Warehouse Receipt System Council Act 2019",
|
| 287 |
+
"storage",
|
| 288 |
+
"Kenya's Warehouse Receipt System, formalized under the Warehouse Receipt System Council Act 2019, "
|
| 289 |
+
"remains nascent relative to more mature counterparts (e.g., Zambia, Uganda). KACE (Kenya Agricultural "
|
| 290 |
+
"Commodity Exchange) pioneered receipt-based trading and price-discovery bulletin services, and a "
|
| 291 |
+
"small network of licensed warehouses issues negotiable receipts usable as loan collateral. Typical "
|
| 292 |
+
"all-in cost (storage + fumigation + receipt fees): KES 40-80 per 90kg bag per month. Pledge-loan "
|
| 293 |
+
"advance against receipt: 50-70% of market value at 12-15% annual. System is most functional for "
|
| 294 |
+
"maize in Rift Valley warehouses; coverage for pulses, sorghum, and perishables is thin."
|
| 295 |
+
),
|
| 296 |
+
|
| 297 |
+
# -- Shocks ---------------------------------------------------------
|
| 298 |
+
|
| 299 |
+
KnowledgeChunk(
|
| 300 |
+
"SH-001", "2020-2023 Horn of Africa Drought - Five Failed Rainy Seasons",
|
| 301 |
+
"FEWS NET Horn of Africa Special Report 2023; WFP VAM Kenya Drought Impact Assessment",
|
| 302 |
+
"shock",
|
| 303 |
+
"The 2020-2023 Horn of Africa drought was the longest and most severe drought in the region since "
|
| 304 |
+
"at least 1981, defined by five consecutive below-average rainy seasons (2020 Short Rains through "
|
| 305 |
+
"2022 Short Rains). In Kenya the ASAL counties -- Turkana, Marsabit, Mandera, Wajir, Garissa, "
|
| 306 |
+
"Isiolo, Samburu, Tana River, Kitui, Makueni -- bore the brunt, with ~4.4 million people food-"
|
| 307 |
+
"insecure at the 2022 peak. Maize wholesale prices in affected northern and eastern markets "
|
| 308 |
+
"(Garissa, Wajir, Mandera, Kitui, Marsabit) spiked 60-100% above five-year averages. Short Rains "
|
| 309 |
+
"2023 brought partial recovery but the humanitarian caseload remained elevated into 2024. The "
|
| 310 |
+
"drought is the signature shock in the first half of the LastMileBench Kenya benchmark window."
|
| 311 |
+
),
|
| 312 |
+
KnowledgeChunk(
|
| 313 |
+
"SH-002", "2023-24 El Nino Floods - October Onwards",
|
| 314 |
+
"Kenya Red Cross Situation Reports 2023-24; World Weather Attribution El Nino Kenya Analysis",
|
| 315 |
+
"shock",
|
| 316 |
+
"The Short Rains 2023 season (Oct-Dec) delivered an El Nino-driven above-average rainfall that "
|
| 317 |
+
"tipped into widespread flooding from late October 2023 through April 2024. Impacts: Tana River "
|
| 318 |
+
"overflow and dam spillover (Garsen, Tana Delta), landslide-driven road closures in Rift Valley "
|
| 319 |
+
"(Elgeyo Marakwet, West Pokot), inundated maize-harvest areas in lower Eastern Kenya just as Short "
|
| 320 |
+
"Rains crops were being brought in, and multiple cuts to the A109 Nairobi-Mombasa highway. More "
|
| 321 |
+
"than 300 deaths and 500,000 displaced at peak in April-May 2024. Maize transit times from "
|
| 322 |
+
"Trans-Nzoia and Uasin Gishu to Nairobi lengthened by 30-60% for weeks, widening producer-to-"
|
| 323 |
+
"wholesale spreads. This is the second signature shock in the LastMileBench Kenya benchmark window."
|
| 324 |
+
),
|
| 325 |
+
KnowledgeChunk(
|
| 326 |
+
"SH-003", "IPC/FEWS NET Food Security Classifications for Kenya",
|
| 327 |
+
"IPC Global Partners; FEWS NET Kenya Food Security Outlook (https://fews.net/east-africa/kenya)",
|
| 328 |
+
"shock",
|
| 329 |
+
"FEWS NET and the IPC (Integrated Food Security Phase Classification) use a five-phase scale: "
|
| 330 |
+
"1-Minimal, 2-Stressed, 3-Crisis, 4-Emergency, 5-Famine. Kenya's ASAL counties have been chronically "
|
| 331 |
+
"in Phase 2 (Stressed) or Phase 3 (Crisis) since the mid-2000s, with brief escalations to Phase 4 "
|
| 332 |
+
"(Emergency) during acute drought peaks -- including much of 2022 across northern and eastern "
|
| 333 |
+
"counties. Agricultural high-potential zones (Rift Valley, Western, Central) typically sit in Phase 1. "
|
| 334 |
+
"FEWS NET publishes a Kenya Food Security Outlook every four months with updated county-level maps, "
|
| 335 |
+
"the canonical reference for cross-regional food-security context in the benchmark panel."
|
| 336 |
+
),
|
| 337 |
+
]
|
| 338 |
+
|
| 339 |
+
KNOWLEDGE_BASE = KNOWLEDGE_CHUNKS
|
|
@@ -12,7 +12,12 @@ from __future__ import annotations
|
|
| 12 |
import logging
|
| 13 |
from typing import Any
|
| 14 |
|
| 15 |
-
from
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
log = logging.getLogger(__name__)
|
| 18 |
|
|
|
|
| 12 |
import logging
|
| 13 |
from typing import Any
|
| 14 |
|
| 15 |
+
from config import REGION
|
| 16 |
+
|
| 17 |
+
if REGION == "kenya":
|
| 18 |
+
from src.rag.knowledge_base_kenya import KNOWLEDGE_BASE, KnowledgeChunk
|
| 19 |
+
else:
|
| 20 |
+
from src.rag.knowledge_base import KNOWLEDGE_BASE, KnowledgeChunk
|
| 21 |
|
| 22 |
log = logging.getLogger(__name__)
|
| 23 |
|
|
@@ -0,0 +1,566 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
| 6 |
+
<title>KAMIS</title>
|
| 7 |
+
<!-- Global site tag (gtag.js) - Google Analytics -->
|
| 8 |
+
<script async src="https://www.googletagmanager.com/gtag/js?id=G-LVPBH4Z6YM"></script>
|
| 9 |
+
<script>
|
| 10 |
+
window.dataLayer = window.dataLayer || [];
|
| 11 |
+
function gtag(){dataLayer.push(arguments);}
|
| 12 |
+
gtag('js', new Date());
|
| 13 |
+
|
| 14 |
+
gtag('config', 'G-LVPBH4Z6YM');
|
| 15 |
+
</script>
|
| 16 |
+
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Abril+Fatface" />
|
| 17 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" integrity="sha512-iBBXm8fW90+nuLcSKlbmrPcLa0OT92xO1BIsZ+ywDWZCvqsWgccV3gFoRBv0z+8dLJgyAHIhR35VZc2oM/gI1w==" crossorigin="anonymous" />
|
| 18 |
+
<link rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
|
| 19 |
+
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i|Nunito:300,300i,400,400i,600,600i,700,700i|Poppins:300,300i,400,400i,500,500i,600,600i,700,700i" rel="stylesheet">
|
| 20 |
+
<link href="https://kamis.kilimo.go.ke/resources/frontpage-vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
| 21 |
+
<link href="https://kamis.kilimo.go.ke/resources/frontpage-vendor/bootstrap-icons/bootstrap-icons.css" rel="stylesheet">
|
| 22 |
+
<link href="https://kamis.kilimo.go.ke/resources/frontpage-vendor/aos/aos.css" rel="stylesheet">
|
| 23 |
+
<link href="https://kamis.kilimo.go.ke/resources/frontpage-vendor/remixicon/remixicon.css?v=1.123" rel="stylesheet">
|
| 24 |
+
<link href="https://kamis.kilimo.go.ke/resources/frontpage-vendor/swiper/swiper-bundle.min.css" rel="stylesheet">
|
| 25 |
+
<link href="https://kamis.kilimo.go.ke/resources/frontpage-vendor/glightbox/css/glightbox.min.css" rel="stylesheet">
|
| 26 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/general.css?v=1.125" rel="stylesheet">
|
| 27 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/sections.css?v=1.123" rel="stylesheet">
|
| 28 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/breadcrumbs.css?v=1.123" rel="stylesheet">
|
| 29 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/back-to-top.css?v=1.123" rel="stylesheet">
|
| 30 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/header.css?v=1.123" rel="stylesheet">
|
| 31 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/navigation-menu.css?v=1.123" rel="stylesheet">
|
| 32 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/hero-section.css?v=1.123" rel="stylesheet">
|
| 33 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/about.css?v=1.123" rel="stylesheet">
|
| 34 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/values.css?v=1.123" rel="stylesheet">
|
| 35 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/counts.css?v=1.123" rel="stylesheet">
|
| 36 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/features.css?v=1.123" rel="stylesheet">
|
| 37 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/services.css?v=1.123" rel="stylesheet">
|
| 38 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/pricing.css?v=1.123" rel="stylesheet">
|
| 39 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/faq.css?v=1.123" rel="stylesheet">
|
| 40 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/counts.css?v=1.123" rel="stylesheet">
|
| 41 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/features.css?v=1.123" rel="stylesheet">
|
| 42 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/services.css?v=1.123" rel="stylesheet">
|
| 43 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/pricing.css?v=1.123" rel="stylesheet">
|
| 44 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/faq.css?v=1.123" rel="stylesheet">
|
| 45 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/portfolio.css?v=1.123" rel="stylesheet">
|
| 46 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/testimonials.css?v=1.123" rel="stylesheet">
|
| 47 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/team.css?v=1.123" rel="stylesheet">
|
| 48 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/clients.css?v=1.123" rel="stylesheet">
|
| 49 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/blog.css?v=1.123" rel="stylesheet">
|
| 50 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/contact.css?v=1.123" rel="stylesheet">
|
| 51 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/product-item.css?v=1.123" rel="stylesheet">
|
| 52 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/footer.css?v=1.124" rel="stylesheet">
|
| 53 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/price-watch.css?v=1.123" rel="stylesheet">
|
| 54 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/latest-reports.css?v=1.123" rel="stylesheet">
|
| 55 |
+
<link href="https://kamis.kilimo.go.ke/resources/css/frontpage/highchart.css?v=1.123" rel="stylesheet">
|
| 56 |
+
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.24/css/jquery.dataTables.css">
|
| 57 |
+
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
| 58 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Zebra_datepicker/1.9.18/css/bootstrap/zebra_datepicker.css" integrity="sha512-jGnP90W9TO7rAXE8dkl8c91DsyVxOKkMP7+fvYGW90qfp5QfHwinUADn4K+jyhDKdynKbS6DR+1UAiEyFl1dcQ==" crossorigin="anonymous" />
|
| 59 |
+
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"
|
| 60 |
+
integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy"
|
| 61 |
+
crossorigin="anonymous"></script>
|
| 62 |
+
<script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/1.10.24/js/jquery.dataTables.js"></script>
|
| 63 |
+
<script src="https://code.highcharts.com/highcharts.js"></script>
|
| 64 |
+
<script src="https://code.highcharts.com/modules/series-label.js"></script>
|
| 65 |
+
<script src="https://code.highcharts.com/modules/exporting.js"></script>
|
| 66 |
+
<script src="https://code.highcharts.com/modules/export-data.js"></script>
|
| 67 |
+
<script src="https://code.highcharts.com/modules/accessibility.js"></script>
|
| 68 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js"></script>
|
| 69 |
+
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css" rel="stylesheet" />
|
| 70 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/Zebra_datepicker/1.9.18/zebra_datepicker.min.js" integrity="sha512-+jF6u7aOmduPkX9JBkvp1B5XgGqvfc3fPGWJG43Ci18tZ4hR1jgHMrlWrM8ai73KzuMc6hvp+9S2k9ggGKYTiw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
| 71 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Zebra_datepicker/1.9.18/css/bootstrap/zebra_datepicker.css" integrity="sha512-jGnP90W9TO7rAXE8dkl8c91DsyVxOKkMP7+fvYGW90qfp5QfHwinUADn4K+jyhDKdynKbS6DR+1UAiEyFl1dcQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
| 72 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js" integrity="sha512-bZS47S7sPOxkjU/4Bt0zrhEtWx0y0CRkhEp8IckzK+ltifIIE9EMIMTuT/mEzoIMewUINruDBIR/jJnbguonqQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
| 73 |
+
<meta name="description" content="KAMIS" />
|
| 74 |
+
<meta name="keywords" content="market, prices" />
|
| 75 |
+
<meta name="author" content="KAMIS" /> </head>
|
| 76 |
+
<body>
|
| 77 |
+
<header id="header" class="header fixed-top" style="padding: 0.4em;">
|
| 78 |
+
|
| 79 |
+
<div class="container container-xl d-flex align-items-center justify-content-between mt-2">
|
| 80 |
+
<a href="https://kamis.kilimo.go.ke/" class="logo d-flex align-items-center">
|
| 81 |
+
<img src="https://kamis.kilimo.go.ke/resources/frontpage-images/logo.jpg" alt="logo">
|
| 82 |
+
</a>
|
| 83 |
+
<nav id="navbar" class="navbar">
|
| 84 |
+
<ul>
|
| 85 |
+
<!--<li class=""><a class="nav-link scrollto active" href="https://kamis.kilimo.go.ke/">HOME</a></li>-->
|
| 86 |
+
<li class=""><a class="nav-link scrollto" href="https://kamis.kilimo.go.ke/">HOME</a></li>
|
| 87 |
+
<li class=""><a class="nav-link" href="https://kamis.kilimo.go.ke/index.php/site/about">ABOUT</a></li>
|
| 88 |
+
<li class="dropdown"><a><span>MARKETS</span></a>
|
| 89 |
+
<ul>
|
| 90 |
+
<li><a href="https://kamis.kilimo.go.ke/site/market">Commodity Prices</a></li>
|
| 91 |
+
<li><a href="https://kamis.kilimo.go.ke/site/market_search">Price Search</a></li>
|
| 92 |
+
</ul>
|
| 93 |
+
</li>
|
| 94 |
+
<li class="dropdown"><a><span>CROSS-BORDER</span></a>
|
| 95 |
+
<ul>
|
| 96 |
+
<li><a href="https://kamis.kilimo.go.ke/site/border">Trade Volumes</a></li>
|
| 97 |
+
<li><a href="https://kamis.kilimo.go.ke/site/border_search">Volume Search</a></li>
|
| 98 |
+
</ul>
|
| 99 |
+
</li>
|
| 100 |
+
</ul>
|
| 101 |
+
<i class="bi bi-list mobile-nav-toggle"></i>
|
| 102 |
+
</nav>
|
| 103 |
+
</div> </header>
|
| 104 |
+
<section id="body-section" class="mt-3" >
|
| 105 |
+
<div class="container">
|
| 106 |
+
<div class="row">
|
| 107 |
+
<div class="col-lg-12">
|
| 108 |
+
<h1 class="page-header" style="font-size: 1.5rem;">Market
|
| 109 |
+
<small>prices</small>
|
| 110 |
+
</h1>
|
| 111 |
+
<ol class="breadcrumb">
|
| 112 |
+
<li><a href="https://kamis.kilimo.go.ke/">Home</a></li>
|
| 113 |
+
|
|
| 114 |
+
<li class="active">Market Search</li>
|
| 115 |
+
</ol>
|
| 116 |
+
</div>
|
| 117 |
+
</div>
|
| 118 |
+
<div class="row">
|
| 119 |
+
<div class="col-lg-12">
|
| 120 |
+
<div class="panel panel-default">
|
| 121 |
+
<div class="panel-body">
|
| 122 |
+
<div class="card bg-light">
|
| 123 |
+
<div class="card-body">
|
| 124 |
+
<form role="form" action="market_search" method="get">
|
| 125 |
+
<div class="row">
|
| 126 |
+
<div class="col-md-4">
|
| 127 |
+
<div class="form-group">
|
| 128 |
+
<label>County</label>
|
| 129 |
+
<select name="county[]" class="form-control" id="selCounty" multiple="multiple">
|
| 130 |
+
<option value="">--Please select--</option>
|
| 131 |
+
<option value="Nairobi">Nairobi</option>
|
| 132 |
+
<option value="Machakos">Machakos</option>
|
| 133 |
+
<option value="Uasin-gishu">Uasin-gishu</option>
|
| 134 |
+
<option value="Mombasa">Mombasa</option>
|
| 135 |
+
<option value="Embu">Embu</option>
|
| 136 |
+
<option value="Isiolo">Isiolo</option>
|
| 137 |
+
<option value="Kitui">Kitui</option>
|
| 138 |
+
<option value="Nakuru">Nakuru</option>
|
| 139 |
+
<option value="Kakamega">Kakamega</option>
|
| 140 |
+
<option value="Kisumu">Kisumu</option>
|
| 141 |
+
<option value="Busia">Busia</option>
|
| 142 |
+
<option value="Migori">Migori</option>
|
| 143 |
+
<option value="Marsabit">Marsabit</option>
|
| 144 |
+
<option value="Kajiado">Kajiado</option>
|
| 145 |
+
<option value="Taveta">Taveta</option>
|
| 146 |
+
<option value="Kwale">Kwale</option>
|
| 147 |
+
<option value="Trans-nzoia">Trans-nzoia</option>
|
| 148 |
+
<option value="Bomet">Bomet</option>
|
| 149 |
+
<option value="Garissa">Garissa</option>
|
| 150 |
+
<option value="Muranga">Muranga</option>
|
| 151 |
+
<option value="Nyandarua">Nyandarua</option>
|
| 152 |
+
<option value="Wajir">Wajir</option>
|
| 153 |
+
<option value="Meru">Meru</option>
|
| 154 |
+
<option value="Narok">Narok</option>
|
| 155 |
+
<option value="Kericho">Kericho</option>
|
| 156 |
+
<option value="Nyamira">Nyamira</option>
|
| 157 |
+
<option value="Taita-taveta">Taita-taveta</option>
|
| 158 |
+
<option value="Lamu">Lamu</option>
|
| 159 |
+
<option value="Tana-river">Tana-river</option>
|
| 160 |
+
<option value="Kilifi">Kilifi</option>
|
| 161 |
+
<option value="Kiambu">Kiambu</option>
|
| 162 |
+
<option value="Nyeri">Nyeri</option>
|
| 163 |
+
<option value="Elgeyo-marakwet">Elgeyo-marakwet</option>
|
| 164 |
+
<option value="West-pokot">West-pokot</option>
|
| 165 |
+
<option value="Samburu">Samburu</option>
|
| 166 |
+
<option value="Laikipia">Laikipia</option>
|
| 167 |
+
<option value="Baringo">Baringo</option>
|
| 168 |
+
<option value="Mandera">Mandera</option>
|
| 169 |
+
<option value="Makueni">Makueni</option>
|
| 170 |
+
<option value="Tharaka-nithi">Tharaka-nithi</option>
|
| 171 |
+
<option value="Kirinyaga">Kirinyaga</option>
|
| 172 |
+
<option value="Vihiga">Vihiga</option>
|
| 173 |
+
<option value="Siaya">Siaya</option>
|
| 174 |
+
<option value="Nandi">Nandi</option>
|
| 175 |
+
<option value="Homa-bay">Homa-bay</option>
|
| 176 |
+
<option value="Kisii">Kisii</option>
|
| 177 |
+
<option value="Turkana">Turkana</option>
|
| 178 |
+
<option value="Test">Test</option>
|
| 179 |
+
<option value="Bungoma">Bungoma</option>
|
| 180 |
+
</select>
|
| 181 |
+
</div>
|
| 182 |
+
</div>
|
| 183 |
+
<div class="col-md-4">
|
| 184 |
+
<div class="form-group">
|
| 185 |
+
<label>Markets</label>
|
| 186 |
+
<select name="market[]" class="multiple-select form-control" id="selMarket" multiple="multiple">
|
| 187 |
+
<option value="">--Please select--</option>
|
| 188 |
+
</select>
|
| 189 |
+
</div>
|
| 190 |
+
</div>
|
| 191 |
+
<div class="col-md-4">
|
| 192 |
+
<div class="form-group">
|
| 193 |
+
<label>Product</label>
|
| 194 |
+
<select name="product[]" class="multiple-select form-control" id="selProduct" multiple="multiple">
|
| 195 |
+
<option value="">--Please select--</option>
|
| 196 |
+
<option value="1">Dry maize</option>
|
| 197 |
+
<option value="2">Red sorghum</option>
|
| 198 |
+
<option value="3">Wheat</option>
|
| 199 |
+
<option value="4">Rice</option>
|
| 200 |
+
<option value="10">Green grams</option>
|
| 201 |
+
<option value="12">Ground nuts</option>
|
| 202 |
+
<option value="29">Beans red haricot (wairimu)</option>
|
| 203 |
+
<option value="30">Beans (yellow-green)</option>
|
| 204 |
+
<option value="50">Dolichos lablab (njahi)</option>
|
| 205 |
+
<option value="51">Pearl rush millet</option>
|
| 206 |
+
<option value="54">Finger millet</option>
|
| 207 |
+
<option value="56">White sorghum</option>
|
| 208 |
+
<option value="57">Red irish potato</option>
|
| 209 |
+
<option value="58">Cabbages</option>
|
| 210 |
+
<option value="59">Sweet potatoes</option>
|
| 211 |
+
<option value="60">Carrots</option>
|
| 212 |
+
<option value="61">Tomatoes</option>
|
| 213 |
+
<option value="64">Beans rosecoco</option>
|
| 214 |
+
<option value="65">Beans (mwitemania)</option>
|
| 215 |
+
<option value="66">Beans (mwezi moja)</option>
|
| 216 |
+
<option value="67">Beans (canadian wonder)</option>
|
| 217 |
+
<option value="68">Tilapia</option>
|
| 218 |
+
<option value="70">Goat milk (at collection point)</option>
|
| 219 |
+
<option value="72">Eggs</option>
|
| 220 |
+
<option value="73">Meat beef</option>
|
| 221 |
+
<option value="74">Meat mutton</option>
|
| 222 |
+
<option value="75">Meat indiginous chicken</option>
|
| 223 |
+
<option value="76">Meat broiler</option>
|
| 224 |
+
<option value="77">Omena</option>
|
| 225 |
+
<option value="78">Cat fish(mkizi/fume)</option>
|
| 226 |
+
<option value="79">Haplochromis</option>
|
| 227 |
+
<option value="80">Trout</option>
|
| 228 |
+
<option value="81">Common carp</option>
|
| 229 |
+
<option value="82">Protopterus</option>
|
| 230 |
+
<option value="83">Black bass</option>
|
| 231 |
+
<option value="84">Labeo</option>
|
| 232 |
+
<option value="85">Mormyrus</option>
|
| 233 |
+
<option value="86">Eel </option>
|
| 234 |
+
<option value="87">African butter catfish</option>
|
| 235 |
+
<option value="89">Synodontis</option>
|
| 236 |
+
<option value="90">Alestes</option>
|
| 237 |
+
<option value="91">Barbus</option>
|
| 238 |
+
<option value="93">Snappers(tazanda)</option>
|
| 239 |
+
<option value="94">Rabbitfish (tafi/tasi)</option>
|
| 240 |
+
<option value="95">Mixed demersal</option>
|
| 241 |
+
<option value="96">Barracuda(kasumba)</option>
|
| 242 |
+
<option value="97">Tuna</option>
|
| 243 |
+
<option value="98">Mackerel</option>
|
| 244 |
+
<option value="99">Kingfish (nguru)</option>
|
| 245 |
+
<option value="100">Sharks</option>
|
| 246 |
+
<option value="101">Sardines</option>
|
| 247 |
+
<option value="102">Swordfishes</option>
|
| 248 |
+
<option value="103">Lobster(kamba mawe)</option>
|
| 249 |
+
<option value="104">Prawns</option>
|
| 250 |
+
<option value="107">Mud crabs</option>
|
| 251 |
+
<option value="108">Golden (deep-sea) crabs kaa</option>
|
| 252 |
+
<option value="109">Fresh water shrimp</option>
|
| 253 |
+
<option value="110">Octopus (pweza)</option>
|
| 254 |
+
<option value="111">Cuttlefish</option>
|
| 255 |
+
<option value="112">Squid(ngisi)</option>
|
| 256 |
+
<option value="113">Oysters</option>
|
| 257 |
+
<option value="114">Fish oil</option>
|
| 258 |
+
<option value="115">Fish maws</option>
|
| 259 |
+
<option value="117">Nile perch skins</option>
|
| 260 |
+
<option value="121">Black nightshade (managu/ osuga)</option>
|
| 261 |
+
<option value="122">Spider flower (saga)</option>
|
| 262 |
+
<option value="123">Amaranthus (terere)</option>
|
| 263 |
+
<option value="124">Jute plant (murenda)</option>
|
| 264 |
+
<option value="125">Passion fruits</option>
|
| 265 |
+
<option value="127">Oranges</option>
|
| 266 |
+
<option value="128">Tree tomato</option>
|
| 267 |
+
<option value="129">Pepino melon</option>
|
| 268 |
+
<option value="130">Thorn melon</option>
|
| 269 |
+
<option value="131">Yam</option>
|
| 270 |
+
<option value="133">Cow milk(at collection point)</option>
|
| 271 |
+
<option value="134">Camel milk(at collection point)</option>
|
| 272 |
+
<option value="138">Camel milk(processed)</option>
|
| 273 |
+
<option value="139">Goat milk (processed)</option>
|
| 274 |
+
<option value="140">Cattle</option>
|
| 275 |
+
<option value="141">Pork</option>
|
| 276 |
+
<option value="142">Avocado</option>
|
| 277 |
+
<option value="143">Arrow root</option>
|
| 278 |
+
<option value="145">Lemons</option>
|
| 279 |
+
<option value="147">Mangoes</option>
|
| 280 |
+
<option value="148">Limes</option>
|
| 281 |
+
<option value="149">Green maize</option>
|
| 282 |
+
<option value="150">Water melon</option>
|
| 283 |
+
<option value="151">Pineapples</option>
|
| 284 |
+
<option value="152">Pawpaw</option>
|
| 285 |
+
<option value="153">Cow milk(processd)</option>
|
| 286 |
+
<option value="154">Kales/sukuma wiki</option>
|
| 287 |
+
<option value="158">Dry onions</option>
|
| 288 |
+
<option value="159">Spring onions</option>
|
| 289 |
+
<option value="160">Fresh peas</option>
|
| 290 |
+
<option value="161">Spinach</option>
|
| 291 |
+
<option value="162">Cassava fresh</option>
|
| 292 |
+
<option value="163">White irish potatoes</option>
|
| 293 |
+
<option value="164">Cassava chips (dry)</option>
|
| 294 |
+
<option value="165">Chillies</option>
|
| 295 |
+
<option value="166">Lettuce</option>
|
| 296 |
+
<option value="167">Sheep</option>
|
| 297 |
+
<option value="168">Goat</option>
|
| 298 |
+
<option value="169">Donkey</option>
|
| 299 |
+
<option value="170">Pumpkin</option>
|
| 300 |
+
<option value="171">Butternuts</option>
|
| 301 |
+
<option value="172">Capsicums</option>
|
| 302 |
+
<option value="173">Cucumber</option>
|
| 303 |
+
<option value="174">Egg plant (brinjals)</option>
|
| 304 |
+
<option value="175">Cauliflower</option>
|
| 305 |
+
<option value="177">French beans</option>
|
| 306 |
+
<option value="178">Ginger</option>
|
| 307 |
+
<option value="180">Garlic</option>
|
| 308 |
+
<option value="182">Njugu mawe</option>
|
| 309 |
+
<option value="183">Beans rosecoco (nyayo)</option>
|
| 310 |
+
<option value="184">Nile perch</option>
|
| 311 |
+
<option value="186">Camel</option>
|
| 312 |
+
<option value="187">Rabbit</option>
|
| 313 |
+
<option value="188">Pigeon peas</option>
|
| 314 |
+
<option value="189">Cowpeas</option>
|
| 315 |
+
<option value="190">Scavengers (changu/tangu)</option>
|
| 316 |
+
<option value="191">Parrotfishes(pono)</option>
|
| 317 |
+
<option value="192">Groupers</option>
|
| 318 |
+
<option value="193">Grunt(taamamba/kora)</option>
|
| 319 |
+
<option value="194">Mullets(fumi)</option>
|
| 320 |
+
<option value="195">Surgeonfishes</option>
|
| 321 |
+
<option value="196">Threadfin breams</option>
|
| 322 |
+
<option value="197">Goatfishes</option>
|
| 323 |
+
<option value="198">Rayfish</option>
|
| 324 |
+
<option value="199">Needlefishes</option>
|
| 325 |
+
<option value="200">Jacks/trevallies(kolekole)</option>
|
| 326 |
+
<option value="201">Halfbeaks</option>
|
| 327 |
+
<option value="202">Anchovies</option>
|
| 328 |
+
<option value="203">Sailfishes</option>
|
| 329 |
+
<option value="204">Wolf herrings</option>
|
| 330 |
+
<option value="205">Marlins</option>
|
| 331 |
+
<option value="206">Jobfish</option>
|
| 332 |
+
<option value="207">Mixed pelagics</option>
|
| 333 |
+
<option value="208">Camel meat</option>
|
| 334 |
+
<option value="209">Meat chevon</option>
|
| 335 |
+
<option value="210">Rabbit meat </option>
|
| 336 |
+
<option value="211">Pigs</option>
|
| 337 |
+
<option value="212">Honey</option>
|
| 338 |
+
<option value="213">Cattle hide</option>
|
| 339 |
+
<option value="214">Camel hide</option>
|
| 340 |
+
<option value="215">Goat skin</option>
|
| 341 |
+
<option value="216">Sheep skin</option>
|
| 342 |
+
<option value="217">Fertilizer</option>
|
| 343 |
+
<option value="218">Tea</option>
|
| 344 |
+
<option value="219">Coffee</option>
|
| 345 |
+
<option value="220">Wheat bran</option>
|
| 346 |
+
<option value="221">Maize bran</option>
|
| 347 |
+
<option value="222">Sunflower cake</option>
|
| 348 |
+
<option value="223">Cotton seed</option>
|
| 349 |
+
<option value="224">Cotton</option>
|
| 350 |
+
<option value="226">Banana (ripening)</option>
|
| 351 |
+
<option value="227">Chicken</option>
|
| 352 |
+
<option value="228">Macadamia seed</option>
|
| 353 |
+
<option value="229">Cashewnuts (korosho)</option>
|
| 354 |
+
<option value="230">Cowpea leaves (kunde)</option>
|
| 355 |
+
<option value="231">Nderema- vine spinach</option>
|
| 356 |
+
<option value="233">Pumpkin leaves</option>
|
| 357 |
+
<option value="234">Ethiopian kales -kanzira</option>
|
| 358 |
+
<option value="235">Indigenous crotolaria (mito/miro)</option>
|
| 359 |
+
<option value="237">Soybean oil</option>
|
| 360 |
+
<option value="238">Coconut oil</option>
|
| 361 |
+
<option value="239">Sunflower seeds</option>
|
| 362 |
+
<option value="240">Sunflower oil</option>
|
| 363 |
+
<option value="241">Walnut seed</option>
|
| 364 |
+
<option value="242">Coriander (dhania)</option>
|
| 365 |
+
<option value="243">Grapes</option>
|
| 366 |
+
<option value="244">Apples</option>
|
| 367 |
+
<option value="245">Dry peas</option>
|
| 368 |
+
<option value="246">Mixed beans</option>
|
| 369 |
+
<option value="247">Rockcode(tewa)</option>
|
| 370 |
+
<option value="248">Queenfish (pandu)</option>
|
| 371 |
+
<option value="249">Maize flour</option>
|
| 372 |
+
<option value="251">Duck</option>
|
| 373 |
+
<option value="255">Banana (cooking)</option>
|
| 374 |
+
<option value="256">Banana (plantain)</option>
|
| 375 |
+
<option value="257">Courgette</option>
|
| 376 |
+
<option value="258">Broccoli</option>
|
| 377 |
+
<option value="259">Lentils</option>
|
| 378 |
+
<option value="261">Fish scales</option>
|
| 379 |
+
<option value="262">Tangerine (sandara)</option>
|
| 380 |
+
<option value="265">Wheat flour</option>
|
| 381 |
+
<option value="267">Paddy rice</option>
|
| 382 |
+
<option value="269">Beans (yellow)</option>
|
| 383 |
+
<option value="270">Coconut</option>
|
| 384 |
+
<option value="272">Okra (lady's fingers or gumbo)</option>
|
| 385 |
+
<option value="273">Dragon fruit</option>
|
| 386 |
+
</select>
|
| 387 |
+
</div>
|
| 388 |
+
</div>
|
| 389 |
+
</div>
|
| 390 |
+
<div class="row">
|
| 391 |
+
<div class="col-md-4">
|
| 392 |
+
<div class="form-group">
|
| 393 |
+
<label for="inputStart">Start Date</label>
|
| 394 |
+
<input type="text" name="start" class="form-control form-date" id="dateStartSearch" value="2024-01-15">
|
| 395 |
+
</div>
|
| 396 |
+
</div>
|
| 397 |
+
<div class="col-md-4">
|
| 398 |
+
<div class="form-group">
|
| 399 |
+
<label for="inputEnd">End Date</label>
|
| 400 |
+
<input type="text" name="end" class="form-control form-date" id="dateEndSearch" value="2024-01-20">
|
| 401 |
+
</div>
|
| 402 |
+
</div>
|
| 403 |
+
<div class="col-md-4">
|
| 404 |
+
<div class="form-group">
|
| 405 |
+
<label>Entries:</label>
|
| 406 |
+
<select id="selPerPage" name="per_page" class="page-size form-control" onchange='this.form.submit()'>
|
| 407 |
+
<option value="10">10</option>
|
| 408 |
+
<option value="50">50</option>
|
| 409 |
+
<option value="100">100</option>
|
| 410 |
+
<option value="500">500</option>
|
| 411 |
+
<option value="1500">1500</option>
|
| 412 |
+
<option value="3000">3000</option>
|
| 413 |
+
</select>
|
| 414 |
+
</div>
|
| 415 |
+
</div>
|
| 416 |
+
</div>
|
| 417 |
+
<div class="row">
|
| 418 |
+
<div class="col-md-4 offset-md-8">
|
| 419 |
+
<div class="form-group">
|
| 420 |
+
<input type="submit" value="Filter Prices" class="btn btn-primary w-100 mt-3">
|
| 421 |
+
</div>
|
| 422 |
+
</div>
|
| 423 |
+
</div>
|
| 424 |
+
</form>
|
| 425 |
+
</div>
|
| 426 |
+
</div>
|
| 427 |
+
</div>
|
| 428 |
+
</div>
|
| 429 |
+
</div>
|
| 430 |
+
<div class="col-lg-12 mt-3">
|
| 431 |
+
<div class="panel panel-default">
|
| 432 |
+
<div class="panel-body">
|
| 433 |
+
<div class="card bg-light">
|
| 434 |
+
<div class="card-body">
|
| 435 |
+
<div class="row d-flex align-items-center justify-content-between mt-1 mb-3">
|
| 436 |
+
<div class="pull-left col-lg-6">
|
| 437 |
+
<ul class="pagination"><li class="active"><a href="#">1</a></li><li><a href="https://kamis.kilimo.go.ke/site/market_search/5?product%5B0%5D=1&county%5B0%5D=Nakuru&start=2024-01-15&end=2024-01-20&per_page=5" data-ci-pagination-page="2">2</a></li><li><a href="https://kamis.kilimo.go.ke/site/market_search/10?product%5B0%5D=1&county%5B0%5D=Nakuru&start=2024-01-15&end=2024-01-20&per_page=5" data-ci-pagination-page="3">3</a></li><li><a href="https://kamis.kilimo.go.ke/site/market_search/15?product%5B0%5D=1&county%5B0%5D=Nakuru&start=2024-01-15&end=2024-01-20&per_page=5" data-ci-pagination-page="4">4</a></li><li><a href="https://kamis.kilimo.go.ke/site/market_search/5?product%5B0%5D=1&county%5B0%5D=Nakuru&start=2024-01-15&end=2024-01-20&per_page=5" data-ci-pagination-page="2" rel="next">></a><li></ul> </div>
|
| 438 |
+
<div class="pull-right col-lg-2">
|
| 439 |
+
<a href="https://kamis.kilimo.go.ke/site/market_search?product%5B%5D=1&county%5B%5D=Nakuru&start=2024-01-15&end=2024-01-20&per_page=5&export=excel" class="btn btn-default mr-2 mt-2 mb-2 btn-export"><i class="fas fa-file-excel"></i> Export to Excel</a> </div>
|
| 440 |
+
</div>
|
| 441 |
+
<div class="row">
|
| 442 |
+
<div class="col-md-12">
|
| 443 |
+
<div class="table-responsive">
|
| 444 |
+
<table class="table table-bordered table-condensed">
|
| 445 |
+
<thead>
|
| 446 |
+
<tr>
|
| 447 |
+
<th>Market</th><th>Commodity</th><th>Classification</th><th>Grade</th><th>Sex</th><th>Wholesale</th><th>Retail</th><th>Supply Volume</th><th>County</th><th>Date</th></tr>
|
| 448 |
+
</thead>
|
| 449 |
+
<tbody>
|
| 450 |
+
<tr>
|
| 451 |
+
<td>Molo</td><td>Dry Maize</td><td>White Maize</td><td>-</td><td>-</td><td> - </td><td>40.00/Kg</td><td>2100.00</td><td>Nakuru</td><td>2024-01-20</td></tr>
|
| 452 |
+
<tr>
|
| 453 |
+
<td>Molo</td><td>Dry Maize</td><td>White Maize</td><td>-</td><td>-</td><td> - </td><td>3600.00/Kg</td><td>2400.00</td><td>Nakuru</td><td>2024-01-19</td></tr>
|
| 454 |
+
<tr>
|
| 455 |
+
<td>Nakuru Wakulima</td><td>Dry Maize</td><td>Yellow Maize</td><td>-</td><td>-</td><td>70.00/Kg</td><td> - </td><td></td><td>Nakuru</td><td>2024-01-19</td></tr>
|
| 456 |
+
<tr>
|
| 457 |
+
<td>Nakuru Wakulima</td><td>Dry Maize</td><td>White Maize</td><td>-</td><td>-</td><td>46.67/Kg</td><td> - </td><td>4590.00</td><td>Nakuru</td><td>2024-01-19</td></tr>
|
| 458 |
+
<tr>
|
| 459 |
+
<td>Molo</td><td>Dry Maize</td><td>White Maize</td><td>-</td><td>-</td><td> - </td><td>151.11/Kg</td><td>2400.00</td><td>Nakuru</td><td>2024-01-19</td></tr>
|
| 460 |
+
</tbody>
|
| 461 |
+
</table> </div>
|
| 462 |
+
</div>
|
| 463 |
+
</div>
|
| 464 |
+
</div>
|
| 465 |
+
</div>
|
| 466 |
+
</div>
|
| 467 |
+
</div>
|
| 468 |
+
</div>
|
| 469 |
+
<script type="text/javascript">
|
| 470 |
+
$(document).ready(function() {
|
| 471 |
+
$('#selPerPage').select2({placeholder : "Select Page"});
|
| 472 |
+
if(("5")){
|
| 473 |
+
$('#selPerPage').select2('destroy');
|
| 474 |
+
$('#selPerPage').val("5").select2();
|
| 475 |
+
}
|
| 476 |
+
$('#selProduct').select2({
|
| 477 |
+
placeholder : "Select Product",
|
| 478 |
+
});
|
| 479 |
+
if(["1"]){
|
| 480 |
+
$('#selProduct').val(["1"])
|
| 481 |
+
$('#selProduct').trigger('change');
|
| 482 |
+
}
|
| 483 |
+
|
| 484 |
+
$('#selMarket').select2({
|
| 485 |
+
placeholder : "Select Market",
|
| 486 |
+
});
|
| 487 |
+
|
| 488 |
+
$('#selCounty').select2({
|
| 489 |
+
placeholder : "Select County",
|
| 490 |
+
});
|
| 491 |
+
if(["Nakuru"]){
|
| 492 |
+
$('#selCounty').val(["Nakuru"])
|
| 493 |
+
$('#selCounty').trigger('change');
|
| 494 |
+
fetchMarkets();
|
| 495 |
+
}
|
| 496 |
+
|
| 497 |
+
$('#dateStartSearch').Zebra_DatePicker({
|
| 498 |
+
show_icon: false,
|
| 499 |
+
});
|
| 500 |
+
if("2024-01-15"){
|
| 501 |
+
$("#dateStartSearch").val("2024-01-15");
|
| 502 |
+
}
|
| 503 |
+
$('#dateEndSearch').Zebra_DatePicker({
|
| 504 |
+
show_icon: false
|
| 505 |
+
});
|
| 506 |
+
if("2024-01-20"){
|
| 507 |
+
$("#dateEndSearch").val("2024-01-20");
|
| 508 |
+
}
|
| 509 |
+
$('#selCounty').change(function(){
|
| 510 |
+
fetchMarkets();
|
| 511 |
+
});
|
| 512 |
+
});
|
| 513 |
+
function fetchMarkets(){
|
| 514 |
+
let select_value = $('#selCounty').val();
|
| 515 |
+
axios.get("https:\/\/kamis.kilimo.go.ke\/"+'index.php/site/county_markets',{
|
| 516 |
+
params: {
|
| 517 |
+
counties: select_value
|
| 518 |
+
}
|
| 519 |
+
}).then((response) => {
|
| 520 |
+
var items = [];
|
| 521 |
+
$('#selMarket').val(null).trigger('change');
|
| 522 |
+
$('#selMarket').find('option').remove().end();
|
| 523 |
+
console.log(response);
|
| 524 |
+
response.data.forEach((j) => {
|
| 525 |
+
if(null){
|
| 526 |
+
$("#selMarket").append(new Option( j.trade_point_name,j.id, false, false));
|
| 527 |
+
$('#selMarket').val(null);
|
| 528 |
+
$('#selMarket').trigger('change');
|
| 529 |
+
return;
|
| 530 |
+
}
|
| 531 |
+
$("#selMarket").append(new Option( j.trade_point_name,j.id, false, false)).trigger('change');
|
| 532 |
+
});
|
| 533 |
+
});
|
| 534 |
+
}
|
| 535 |
+
// </script>
|
| 536 |
+
</div>
|
| 537 |
+
</section>
|
| 538 |
+
<a href="#" class="back-to-top d-flex align-items-center justify-content-center"><i
|
| 539 |
+
class="bi bi-arrow-up-short"></i></a>
|
| 540 |
+
<script src="https://kamis.kilimo.go.ke/resources/frontpage-vendor/bootstrap/js/bootstrap.bundle.js"></script>
|
| 541 |
+
<script src="https://kamis.kilimo.go.ke/resources/frontpage-vendor/aos/aos.js"></script>
|
| 542 |
+
<script src="https://kamis.kilimo.go.ke/resources/frontpage-vendor/php-email-form/validate.js"></script>
|
| 543 |
+
<script src="https://kamis.kilimo.go.ke/resources/frontpage-vendor/swiper/swiper-bundle.min.js"></script>
|
| 544 |
+
<script src="https://kamis.kilimo.go.ke/resources/frontpage-vendor/purecounter/purecounter.js"></script>
|
| 545 |
+
<script src="https://kamis.kilimo.go.ke/resources/frontpage-vendor/isotope-layout/isotope.pkgd.min.js"></script>
|
| 546 |
+
<script src="https://kamis.kilimo.go.ke/resources/frontpage-vendor/glightbox/js/glightbox.min.js"></script>
|
| 547 |
+
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-beta.1/dist/js/select2.min.js"></script>
|
| 548 |
+
<script src="https://kamis.kilimo.go.ke/resources/js/frontpage/main.js"></script>
|
| 549 |
+
<!--<script src="https://kamis.kilimo.go.ke/resources/js/frontpage/fake_data.js"></script>-->
|
| 550 |
+
<!--<script src="https://kamis.kilimo.go.ke/resources/js/frontpage/agricultural-inputs/table.js"></script>-->
|
| 551 |
+
<!--<script src="https://kamis.kilimo.go.ke/resources/js/frontpage/agricultural-inputs/graph.js"></script>-->
|
| 552 |
+
<!--<script src="https://kamis.kilimo.go.ke/resources/js/frontpage/agricultural-inputs/whats-new.js"></script>-->
|
| 553 |
+
<!--<script src="https://kamis.kilimo.go.ke/resources/js/frontpage/crops/table.js"></script>-->
|
| 554 |
+
<!--<script src="https://kamis.kilimo.go.ke/resources/js/frontpage/crops/graph.js"></script>-->
|
| 555 |
+
<!--<script src="https://kamis.kilimo.go.ke/resources/js/frontpage/crops/whats-new.js"></script>-->
|
| 556 |
+
<!--<script src="https://kamis.kilimo.go.ke/resources/js/frontpage/fisheries/table.js"></script>-->
|
| 557 |
+
<!--<script src="https://kamis.kilimo.go.ke/resources/js/frontpage/fisheries/graph.js"></script>-->
|
| 558 |
+
<!--<script src="https://kamis.kilimo.go.ke/resources/js/frontpage/fisheries/whats-new.js"></script>-->
|
| 559 |
+
<!--<script src="https://kamis.kilimo.go.ke/resources/js/frontpage/livestock/table.js"></script>-->
|
| 560 |
+
<!--<script src="https://kamis.kilimo.go.ke/resources/js/frontpage/livestock/graph.js"></script>-->
|
| 561 |
+
<!--<script src="https://kamis.kilimo.go.ke/resources/js/frontpage/livestock/whats-new.js"></script>-->
|
| 562 |
+
<!--<script src="https://kamis.kilimo.go.ke/resources/js/frontpage/agricultural/table.js"></script>-->
|
| 563 |
+
<!--<script src="https://kamis.kilimo.go.ke/resources/js/frontpage/agricultural/graph.js"></script>-->
|
| 564 |
+
<!--<script src="https://kamis.kilimo.go.ke/resources/js/frontpage/agricultural/whats-new.js"></script>-->
|
| 565 |
+
</body>
|
| 566 |
+
</html>
|
|
@@ -3,10 +3,23 @@
|
|
| 3 |
Covers the Aadhaar + Land Record + KCC services, the deterministic
|
| 4 |
registry, DPIAgent composite, and the KCC-aware credit readiness
|
| 5 |
integration. No DB, no API key, no network.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
"""
|
| 7 |
|
| 8 |
from __future__ import annotations
|
| 9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
# ---------------------------------------------------------------------------
|
| 12 |
# Module imports
|
|
|
|
| 3 |
Covers the Aadhaar + Land Record + KCC services, the deterministic
|
| 4 |
registry, DPIAgent composite, and the KCC-aware credit readiness
|
| 5 |
integration. No DB, no API key, no network.
|
| 6 |
+
|
| 7 |
+
Kenya has no equivalent DPI stack (Aadhaar/KCC are India-specific) so
|
| 8 |
+
this whole module is India-only. Phase 1.6 will introduce parallel
|
| 9 |
+
Kenya DPI tests if/when a Kenya DPI subsystem exists.
|
| 10 |
"""
|
| 11 |
|
| 12 |
from __future__ import annotations
|
| 13 |
|
| 14 |
+
import pytest
|
| 15 |
+
|
| 16 |
+
from config import REGION
|
| 17 |
+
|
| 18 |
+
pytestmark = pytest.mark.skipif(
|
| 19 |
+
REGION != "india",
|
| 20 |
+
reason="DPI subsystem is India-specific (Aadhaar/KCC); no Kenya equivalent yet",
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
|
| 24 |
# ---------------------------------------------------------------------------
|
| 25 |
# Module imports
|
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Smoke tests for the KAMIS ingestion source.
|
| 2 |
+
|
| 3 |
+
Runs entirely in demo mode (no network) against the committed snapshot
|
| 4 |
+
at ``data/demo_snapshots/kamis/kamis_demo_snapshot.csv`` plus a static
|
| 5 |
+
HTML fixture copied from the lastmile-bench benchmark repo.
|
| 6 |
+
|
| 7 |
+
These tests deliberately construct their own lightweight market and
|
| 8 |
+
commodity objects rather than importing ``config.MANDIS`` — the
|
| 9 |
+
Kenya-migration ``config.py`` changes land in a parallel Phase 1.1
|
| 10 |
+
task, and we don't want this test's stability to depend on that
|
| 11 |
+
landing first.
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
from __future__ import annotations
|
| 15 |
+
|
| 16 |
+
import asyncio
|
| 17 |
+
import os
|
| 18 |
+
from dataclasses import dataclass
|
| 19 |
+
from datetime import date, datetime
|
| 20 |
+
from pathlib import Path
|
| 21 |
+
|
| 22 |
+
import pytest
|
| 23 |
+
|
| 24 |
+
from src.ingestion.kamis import (
|
| 25 |
+
PRODUCT_IDS,
|
| 26 |
+
build_search_url,
|
| 27 |
+
fetch_kamis_prices,
|
| 28 |
+
parse_search_result_html,
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
# --- Minimal Kenya-shaped market stub -----------------------------------
|
| 33 |
+
# Kept inline so the test doesn't couple to whatever Phase 1.1 picks for
|
| 34 |
+
# the canonical Kenya market dataclass. The ingestion module only reads
|
| 35 |
+
# `mandi_id`, `name`, and `district` / `county`, so this stub suffices.
|
| 36 |
+
@dataclass
|
| 37 |
+
class _KenyaMarket:
|
| 38 |
+
mandi_id: str
|
| 39 |
+
name: str
|
| 40 |
+
district: str # KAMIS county
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
# Three KAMIS markets that actually appear in the demo snapshot.
|
| 44 |
+
_DEMO_MARKETS = [
|
| 45 |
+
_KenyaMarket(mandi_id="KE-NAI-KAW", name="Kawangware", district="Nairobi"),
|
| 46 |
+
_KenyaMarket(mandi_id="KE-NAI-KAN", name="Kangemi Market", district="Nairobi"),
|
| 47 |
+
_KenyaMarket(mandi_id="KE-NAK-MOL", name="Molo", district="Nakuru"),
|
| 48 |
+
]
|
| 49 |
+
|
| 50 |
+
# The four Kenya priority crops for the market-intelligence pivot.
|
| 51 |
+
_DEMO_COMMODITIES: list[dict] = [
|
| 52 |
+
{"id": "dry_maize", "name": "Dry maize", "kamis_name": "Dry maize"},
|
| 53 |
+
{"id": "beans", "name": "Beans", "kamis_name": "Beans (yellow-green)"},
|
| 54 |
+
{"id": "red_irish_potato", "name": "Red irish potato",
|
| 55 |
+
"kamis_name": "Red irish potato"},
|
| 56 |
+
{"id": "green_grams", "name": "Green grams", "kamis_name": "Green grams"},
|
| 57 |
+
]
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
# --- Tests --------------------------------------------------------------
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def test_demo_mode_returns_valid_price_records(monkeypatch: pytest.MonkeyPatch) -> None:
|
| 64 |
+
"""End-to-end demo-mode smoke test.
|
| 65 |
+
|
| 66 |
+
Drives `fetch_kamis_prices` against the committed snapshot and
|
| 67 |
+
asserts every returned record has the schema the downstream
|
| 68 |
+
reconciliation / forecasting / optimization steps expect.
|
| 69 |
+
"""
|
| 70 |
+
monkeypatch.setenv("MARKET_INTEL_DEMO_MODE", "1")
|
| 71 |
+
|
| 72 |
+
result = asyncio.run(fetch_kamis_prices(
|
| 73 |
+
markets=_DEMO_MARKETS,
|
| 74 |
+
commodities=_DEMO_COMMODITIES,
|
| 75 |
+
days_back=60,
|
| 76 |
+
))
|
| 77 |
+
|
| 78 |
+
# Keys are mandi IDs, at least one market has data.
|
| 79 |
+
assert set(result.keys()) == {m.mandi_id for m in _DEMO_MARKETS}
|
| 80 |
+
all_records = [r for recs in result.values() for r in recs]
|
| 81 |
+
assert len(all_records) > 0, "demo snapshot returned zero records"
|
| 82 |
+
|
| 83 |
+
# Every record has the schema the pipeline downstream depends on.
|
| 84 |
+
valid_commodity_ids = {c["id"] for c in _DEMO_COMMODITIES}
|
| 85 |
+
valid_mandi_ids = {m.mandi_id for m in _DEMO_MARKETS}
|
| 86 |
+
for rec in all_records:
|
| 87 |
+
assert rec.source == "kamis"
|
| 88 |
+
assert rec.mandi_id in valid_mandi_ids, f"unknown mandi_id: {rec.mandi_id}"
|
| 89 |
+
assert rec.commodity_id in valid_commodity_ids, (
|
| 90 |
+
f"unknown commodity_id: {rec.commodity_id}"
|
| 91 |
+
)
|
| 92 |
+
# ISO date string
|
| 93 |
+
parsed = datetime.strptime(rec.date, "%Y-%m-%d").date()
|
| 94 |
+
assert parsed <= date.today() + timedelta_days(1), "date in far future"
|
| 95 |
+
# Positive prices
|
| 96 |
+
assert rec.modal_price_rs > 0, f"non-positive modal: {rec}"
|
| 97 |
+
assert rec.min_price_rs > 0, f"non-positive min: {rec}"
|
| 98 |
+
assert rec.max_price_rs > 0, f"non-positive max: {rec}"
|
| 99 |
+
# Quality flag is one of the documented values
|
| 100 |
+
assert rec.quality_flag in {"good", "stale", "anomalous", "missing"}
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def test_demo_mode_covers_at_least_two_commodities(monkeypatch: pytest.MonkeyPatch) -> None:
|
| 104 |
+
"""Guard against a regression where the snapshot or filter loses variety."""
|
| 105 |
+
monkeypatch.setenv("MARKET_INTEL_DEMO_MODE", "1")
|
| 106 |
+
result = asyncio.run(fetch_kamis_prices(
|
| 107 |
+
markets=_DEMO_MARKETS,
|
| 108 |
+
commodities=_DEMO_COMMODITIES,
|
| 109 |
+
days_back=60,
|
| 110 |
+
))
|
| 111 |
+
observed = {r.commodity_id for recs in result.values() for r in recs}
|
| 112 |
+
assert len(observed) >= 2, (
|
| 113 |
+
f"expected >=2 commodities in demo snapshot, got {observed}"
|
| 114 |
+
)
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def test_build_search_url_uses_iso_dates() -> None:
|
| 118 |
+
"""Hard-guard against the localized-date footgun documented in the
|
| 119 |
+
benchmark reference (localized dates fail silently with "No Data")."""
|
| 120 |
+
url = build_search_url(
|
| 121 |
+
product_id=1,
|
| 122 |
+
county="Nairobi",
|
| 123 |
+
start=date(2026, 1, 1),
|
| 124 |
+
end=date(2026, 4, 1),
|
| 125 |
+
)
|
| 126 |
+
assert "start=2026-01-01" in url
|
| 127 |
+
assert "end=2026-04-01" in url
|
| 128 |
+
assert "product%5B%5D=1" in url # product[]=1 URL-encoded
|
| 129 |
+
assert "county%5B%5D=Nairobi" in url
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
def test_product_ids_covers_four_kenya_priority_crops() -> None:
|
| 133 |
+
"""Pin the four v0.1 Kenya crops — if any of these IDs change upstream
|
| 134 |
+
the test fails fast instead of silently returning zero rows."""
|
| 135 |
+
assert PRODUCT_IDS["Dry maize"] == 1
|
| 136 |
+
assert PRODUCT_IDS["Beans (yellow-green)"] == 30
|
| 137 |
+
assert PRODUCT_IDS["Red irish potato"] == 57
|
| 138 |
+
assert PRODUCT_IDS["Green grams"] == 10
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
# --- HTML parser test (fixture-based) -----------------------------------
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
_FIXTURE_PATH = (
|
| 145 |
+
Path(__file__).parent / "fixtures" / "kamis_nakuru_dry_maize_sample.html"
|
| 146 |
+
)
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
@pytest.mark.skipif(
|
| 150 |
+
not _FIXTURE_PATH.exists(),
|
| 151 |
+
reason="KAMIS HTML fixture not available; copy from lastmile-bench",
|
| 152 |
+
)
|
| 153 |
+
def test_parse_search_result_html_on_nakuru_fixture() -> None:
|
| 154 |
+
"""Parse the committed HTML fixture and assert the core row shape.
|
| 155 |
+
|
| 156 |
+
Using the fixture rather than live fetch keeps the test hermetic.
|
| 157 |
+
"""
|
| 158 |
+
html = _FIXTURE_PATH.read_text(encoding="utf-8")
|
| 159 |
+
rows = parse_search_result_html(html)
|
| 160 |
+
assert len(rows) > 0, "fixture parsed to zero rows — parser regression"
|
| 161 |
+
for row in rows:
|
| 162 |
+
assert row["market"], "empty market cell"
|
| 163 |
+
assert row["product"], "empty product cell"
|
| 164 |
+
assert row["wholesale"] is not None and row["wholesale"] > 0
|
| 165 |
+
assert row["retail"] is not None and row["retail"] > 0
|
| 166 |
+
assert isinstance(row["price_date"], date)
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
# --- tiny helper --------------------------------------------------------
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def timedelta_days(n: int): # tiny local shim so we don't add another import
|
| 173 |
+
from datetime import timedelta
|
| 174 |
+
return timedelta(days=n)
|
|
@@ -18,6 +18,13 @@ from __future__ import annotations
|
|
| 18 |
|
| 19 |
import pytest
|
| 20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
# ---------------------------------------------------------------------------
|
| 23 |
# Module imports
|
|
@@ -58,6 +65,7 @@ def test_imports_optimizer():
|
|
| 58 |
# ---------------------------------------------------------------------------
|
| 59 |
|
| 60 |
|
|
|
|
| 61 |
def test_mandis_and_commodities_loaded():
|
| 62 |
"""15 Tamil Nadu mandis and 10 commodities are registered."""
|
| 63 |
from config import COMMODITIES, MANDIS, MANDI_MAP
|
|
@@ -75,6 +83,7 @@ def test_pilot_registry_has_100_farmers():
|
|
| 75 |
assert len(SAMPLE_FARMERS) == 100
|
| 76 |
|
| 77 |
|
|
|
|
| 78 |
def test_featured_subset_is_three_curated_farmers():
|
| 79 |
"""FEATURED_FARMERS is exactly the 3 hand-written personas."""
|
| 80 |
from config import FEATURED_FARMERS, SAMPLE_FARMERS
|
|
@@ -108,6 +117,7 @@ def test_generated_farmers_use_mandi_commodities():
|
|
| 108 |
)
|
| 109 |
|
| 110 |
|
|
|
|
| 111 |
def test_generated_farmers_have_plausible_quantities():
|
| 112 |
"""Generated quantities stay in the 8-35 quintal pilot range."""
|
| 113 |
from config import SAMPLE_FARMERS
|
|
|
|
| 18 |
|
| 19 |
import pytest
|
| 20 |
|
| 21 |
+
from config import REGION
|
| 22 |
+
|
| 23 |
+
_india_only = pytest.mark.skipif(
|
| 24 |
+
REGION != "india",
|
| 25 |
+
reason="India-specific assertions (15 mandis, Tamil names, quintal range); Kenya parity tests in Phase 1.6",
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
|
| 29 |
# ---------------------------------------------------------------------------
|
| 30 |
# Module imports
|
|
|
|
| 65 |
# ---------------------------------------------------------------------------
|
| 66 |
|
| 67 |
|
| 68 |
+
@_india_only
|
| 69 |
def test_mandis_and_commodities_loaded():
|
| 70 |
"""15 Tamil Nadu mandis and 10 commodities are registered."""
|
| 71 |
from config import COMMODITIES, MANDIS, MANDI_MAP
|
|
|
|
| 83 |
assert len(SAMPLE_FARMERS) == 100
|
| 84 |
|
| 85 |
|
| 86 |
+
@_india_only
|
| 87 |
def test_featured_subset_is_three_curated_farmers():
|
| 88 |
"""FEATURED_FARMERS is exactly the 3 hand-written personas."""
|
| 89 |
from config import FEATURED_FARMERS, SAMPLE_FARMERS
|
|
|
|
| 117 |
)
|
| 118 |
|
| 119 |
|
| 120 |
+
@_india_only
|
| 121 |
def test_generated_farmers_have_plausible_quantities():
|
| 122 |
"""Generated quantities stay in the 8-35 quintal pilot range."""
|
| 123 |
from config import SAMPLE_FARMERS
|