MCPcopy Create free account
hub / github.com/Thanas-R/Virdis / buildLocalCropPlan

Function buildLocalCropPlan

src/components/CropPlanningSection.tsx:1362–1450  ·  view source on GitHub ↗
({ field, ndviData, soilData, weatherData, suitabilityData }: Omit<CropPlanningSectionProps, "mapToken">)

Source from the content-addressed store, hash-verified

1360 typeof data.summary === "string",
1361 );
1362}
1363
1364function buildLocalCropPlan({ field, ndviData, soilData, weatherData, suitabilityData }: Omit<CropPlanningSectionProps, "mapToken">): CropPlan {
1365 const signals = buildPlanningSignals({ field, ndviData, soilData, weatherData, suitabilityData });
1366 const detectedRegions = detectRegion(signals.locationText);
1367
1368 // Pre-filter profiles: only keep crops that share at least one region tag with the location
1369 const regionFiltered = CROP_PROFILES.filter(profile => {
1370 const profileRegionTags = profile.tags.filter(t =>
1371 ["tropical", "subtropical", "mediterranean", "temperate", "continental", "arid", "highland"].includes(t)
1372 );
1373 // If profile has no region tags, allow it (generic crop)
1374 if (profileRegionTags.length === 0) return true;
1375 return profileRegionTags.some(tag => detectedRegions.includes(tag as RegionTag));
1376 });
1377
1378 const scoredProfiles = regionFiltered.map((profile) => ({
1379 profile,
1380 score: scoreCropProfile(profile, signals, field.crop),
1381 })).sort((left, right) => right.score - left.score);
1382
1383 const zoneCount = clamp(field.area > 5 ? 4 : 3, 3, 4);
1384 let chosen = scoredProfiles.slice(0, zoneCount);
1385
1386 // Ensure current crop is in the plan (if it's region-appropriate)
1387 const currentCropInPlan = chosen.some(c => c.profile.name.toLowerCase() === field.crop.toLowerCase());
1388 if (!currentCropInPlan) {
1389 const currentProfile = scoredProfiles.find(c => c.profile.name.toLowerCase() === field.crop.toLowerCase());
1390 if (currentProfile) {
1391 chosen[chosen.length - 1] = currentProfile;
1392 }
1393 }
1394
1395 // Ensure at least one tree-type crop from the region-filtered list
1396 const hasTree = chosen.some(c => c.profile.tags.includes("tree"));
1397 if (!hasTree) {
1398 const bestTree = scoredProfiles.find(c => c.profile.tags.includes("tree") && !chosen.includes(c));
1399 if (bestTree) {
1400 const replaceIdx = chosen.length - 1;
1401 chosen[replaceIdx] = bestTree;
1402 }
1403 }
1404
1405 const weights = normalizeAreaPercents(chosen.map((item) => item.score));
1406 const positions = ZONE_POSITION_PRESETS[chosen.length] || ZONE_POSITION_PRESETS[4] || ZONE_POSITION_PRESETS[3];
1407
1408 const zones: CropZone[] = chosen.map(({ profile, score }, index) => ({
1409 id: `zone-${index + 1}`,
1410 name: `${String.fromCharCode(65 + index)} Zone`,
1411 crop: profile.name,
1412 color: profile.color,
1413 area_pct: weights[index],
1414 reason: getZoneReason(profile, signals, index),
1415 spacing_m: profile.spacing_m,
1416 water_needs: profile.waterNeeds,
1417 season: profile.season,
1418 yield_estimate: formatYield(profile, clamp(score, 0.45, 1.25), signals.healthScore),
1419 position: positions[index] || { x: 0.5, y: 0.5 },

Callers 1

CropPlanningSectionFunction · 0.85

Calls 11

buildPlanningSignalsFunction · 0.85
detectRegionFunction · 0.85
scoreCropProfileFunction · 0.85
clampFunction · 0.85
normalizeAreaPercentsFunction · 0.85
getZoneReasonFunction · 0.85
formatYieldFunction · 0.85
chooseIntercroppingFunction · 0.85
chooseRotationPlanFunction · 0.85
roundToFunction · 0.85
getWaterNeedIndexFunction · 0.85

Tested by

no test coverage detected