|
@@ -63,36 +63,60 @@ if (!verifyCsrfToken($_POST['csrf_token'] ?? '')) {
|
|
|
exit;
|
|
exit;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-$recordId = (int)trim($_POST['rid'] ?? '');
|
|
|
|
|
-$randId = trim($_POST['rand'] ?? '');
|
|
|
|
|
-$section = trim($_POST['section'] ?? '');
|
|
|
|
|
|
|
+$recordId = (int)trim($_POST['rid'] ?? '');
|
|
|
|
|
+$randId = trim($_POST['rand'] ?? '');
|
|
|
|
|
+$section = trim($_POST['section'] ?? '');
|
|
|
|
|
+$recordType = trim($_POST['record_type'] ?? 'soil'); // soil | plant
|
|
|
|
|
|
|
|
-$validSections = ['overview', 'ai_interpretation', 'foliar', 'microbial'];
|
|
|
|
|
-if (!$recordId || $randId === '' || !in_array($section, $validSections, true)) {
|
|
|
|
|
|
|
+$validSoilSections = ['overview', 'ai_interpretation', 'foliar', 'microbial'];
|
|
|
|
|
+$validPlantSections = ['general', 'ai_interpretation', 'recommended', 'foliar'];
|
|
|
|
|
+$validSections = $recordType === 'plant' ? $validPlantSections : $validSoilSections;
|
|
|
|
|
+
|
|
|
|
|
+if (!$recordId || $randId === '' || !in_array($section, $validSections, true)
|
|
|
|
|
+ || !in_array($recordType, ['soil', 'plant'], true)) {
|
|
|
http_response_code(400);
|
|
http_response_code(400);
|
|
|
echo json_encode(['success' => false, 'error' => 'Invalid parameters']);
|
|
echo json_encode(['success' => false, 'error' => 'Invalid parameters']);
|
|
|
exit;
|
|
exit;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// ── Load soil record + spec ───────────────────────────────────────────────────
|
|
|
|
|
|
|
+// ── Load record + spec ────────────────────────────────────────────────────────
|
|
|
try {
|
|
try {
|
|
|
$pdo = getDBConnection();
|
|
$pdo = getDBConnection();
|
|
|
|
|
|
|
|
- $stmt = $pdo->prepare('SELECT * FROM soil_records WHERE id = ? AND rand = ?');
|
|
|
|
|
- $stmt->execute([$recordId, $randId]);
|
|
|
|
|
- $row = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
|
-
|
|
|
|
|
- if (!$row) {
|
|
|
|
|
- http_response_code(404);
|
|
|
|
|
- echo json_encode(['success' => false, 'error' => 'Record not found']);
|
|
|
|
|
- exit;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- $spec = [];
|
|
|
|
|
- if (!empty($row['soil_type'])) {
|
|
|
|
|
- $stmtSpec = $pdo->prepare('SELECT * FROM soil_specifications WHERE soil_type = ? LIMIT 1');
|
|
|
|
|
- $stmtSpec->execute([$row['soil_type']]);
|
|
|
|
|
- $spec = $stmtSpec->fetch(PDO::FETCH_ASSOC) ?: [];
|
|
|
|
|
|
|
+ if ($recordType === 'plant') {
|
|
|
|
|
+ $stmt = $pdo->prepare('SELECT * FROM plant_records WHERE id = ? AND rand = ?');
|
|
|
|
|
+ $stmt->execute([$recordId, $randId]);
|
|
|
|
|
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
|
+
|
|
|
|
|
+ if (!$row) {
|
|
|
|
|
+ http_response_code(404);
|
|
|
|
|
+ echo json_encode(['success' => false, 'error' => 'Record not found']);
|
|
|
|
|
+ exit;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $spec = [];
|
|
|
|
|
+ if (!empty($row['crop_type'])) {
|
|
|
|
|
+ $stmtSpec = $pdo->prepare('SELECT * FROM plant_specifications WHERE plant_type = ? LIMIT 1');
|
|
|
|
|
+ $stmtSpec->execute([$row['crop_type']]);
|
|
|
|
|
+ $spec = $stmtSpec->fetch(PDO::FETCH_ASSOC) ?: [];
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $stmt = $pdo->prepare('SELECT * FROM soil_records WHERE id = ? AND rand = ?');
|
|
|
|
|
+ $stmt->execute([$recordId, $randId]);
|
|
|
|
|
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
|
+
|
|
|
|
|
+ if (!$row) {
|
|
|
|
|
+ http_response_code(404);
|
|
|
|
|
+ echo json_encode(['success' => false, 'error' => 'Record not found']);
|
|
|
|
|
+ exit;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $spec = [];
|
|
|
|
|
+ if (!empty($row['soil_type'])) {
|
|
|
|
|
+ $stmtSpec = $pdo->prepare('SELECT * FROM soil_specifications WHERE soil_type = ? LIMIT 1');
|
|
|
|
|
+ $stmtSpec->execute([$row['soil_type']]);
|
|
|
|
|
+ $spec = $stmtSpec->fetch(PDO::FETCH_ASSOC) ?: [];
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
} catch (PDOException $e) {
|
|
} catch (PDOException $e) {
|
|
@@ -324,80 +348,265 @@ $soilData .= "Deficient: " . (empty($deficiencies) ? 'None detected' : implode('
|
|
|
$soilData .= "In Excess: " . (empty($excesses) ? 'None detected' : implode(', ', $excesses)) . "\n";
|
|
$soilData .= "In Excess: " . (empty($excesses) ? 'None detected' : implode(', ', $excesses)) . "\n";
|
|
|
$soilData .= "=====================================\n";
|
|
$soilData .= "=====================================\n";
|
|
|
|
|
|
|
|
-// ── RAG: retrieve relevant passages from knowledge_chunks ─────────────────────
|
|
|
|
|
-$ragChunks = retrieveRelevantChunks($pdo, $soilData, $section, RAG_TOP_K);
|
|
|
|
|
-$knowledgeContext = '';
|
|
|
|
|
-
|
|
|
|
|
-if (!empty($ragChunks)) {
|
|
|
|
|
- $knowledgeContext = "\n\n===================================================\n"
|
|
|
|
|
- . "RELEVANT PASSAGES FROM SOIL SCIENCE LITERATURE\n"
|
|
|
|
|
- . "(William A. Albrecht and other authorities)\n"
|
|
|
|
|
- . "===================================================\n";
|
|
|
|
|
- foreach ($ragChunks as $i => $chunk) {
|
|
|
|
|
- $knowledgeContext .= sprintf(
|
|
|
|
|
- "\n[%d] \"%s\" — %s (p.%d)\n%s\n",
|
|
|
|
|
- $i + 1,
|
|
|
|
|
- $chunk['source'],
|
|
|
|
|
- $chunk['author'],
|
|
|
|
|
- $chunk['page'],
|
|
|
|
|
- $chunk['chunk_text']
|
|
|
|
|
- );
|
|
|
|
|
|
|
+// ── System prompt per record type ────────────────────────────────────────────
|
|
|
|
|
+$systemPrompts = [
|
|
|
|
|
+ 'soil' =>
|
|
|
|
|
+ "You are a certified agronomist and soil scientist specialising in mineral soil balancing, "
|
|
|
|
|
+ . "trained in the Albrecht method of base saturation and cation exchange chemistry. "
|
|
|
|
|
+ . "You have deep knowledge of soil chemistry, soil biology, and the relationship between "
|
|
|
|
|
+ . "soil mineral balance and crop productivity, livestock health, and human nutrition. "
|
|
|
|
|
+ . "You understand Australian soil types — Vertosols, Chromosols, Sodosols, Tenosols, "
|
|
|
|
|
+ . "Dermosols and others — and how climate and rainfall influence nutrient behaviour. "
|
|
|
|
|
+ . "Always ground your recommendations in the measured data. "
|
|
|
|
|
+ . "Write in a professional but accessible tone suitable for a farmer-facing report. "
|
|
|
|
|
+ . "When knowledge passages from the Albrecht literature are provided, "
|
|
|
|
|
+ . "prefer them over your general training — they are the authoritative reference.",
|
|
|
|
|
+
|
|
|
|
|
+ 'plant' =>
|
|
|
|
|
+ "You are a certified agronomist specialising in plant tissue analysis and crop nutrition. "
|
|
|
|
|
+ . "You have deep knowledge of plant physiology, nutrient uptake mechanisms, and the "
|
|
|
|
|
+ . "relationship between tissue nutrient levels and crop yield, quality, and disease resistance. "
|
|
|
|
|
+ . "You are familiar with the CSIRO Plant Analysis Handbook, Hill Laboratories guidelines, "
|
|
|
|
|
+ . "and PIRSA soil and plant analysis standards used in Australia. "
|
|
|
|
|
+ . "You understand how soil mineral imbalances, pH, and antagonisms (e.g. Ca/Mg, Zn/P, "
|
|
|
|
|
+ . "K/Mg) translate into plant deficiency or excess symptoms. "
|
|
|
|
|
+ . "Always base your interpretation on the measured tissue values and specified ranges. "
|
|
|
|
|
+ . "Write in a professional but accessible tone suitable for a farmer-facing report.",
|
|
|
|
|
+
|
|
|
|
|
+ 'water' =>
|
|
|
|
|
+ "You are a certified irrigation agronomist and water quality specialist. "
|
|
|
|
|
+ . "You have deep knowledge of water chemistry, salinity, sodicity, and the effects of "
|
|
|
|
|
+ . "irrigation water quality on soil structure, nutrient availability, and crop health. "
|
|
|
|
|
+ . "You are familiar with Australian irrigation guidelines (ANZECC/ARMCANZ), SAR and "
|
|
|
|
|
+ . "EC thresholds for different soil types and crops, and bicarbonate/carbonate effects "
|
|
|
|
|
+ . "on calcium and magnesium availability. "
|
|
|
|
|
+ . "Always base your interpretation on the measured water analysis values. "
|
|
|
|
|
+ . "Write in a professional but accessible tone suitable for a farmer-facing report.",
|
|
|
|
|
+
|
|
|
|
|
+ 'animal' =>
|
|
|
|
|
+ "You are a certified animal nutritionist and ruminant dietitian with expertise in "
|
|
|
|
|
+ . "livestock dietary mineral balance for Australian conditions. "
|
|
|
|
|
+ . "You have deep knowledge of macro and trace mineral requirements for cattle, sheep, "
|
|
|
|
|
+ . "and other livestock — including the relationship between pasture/feed mineral levels "
|
|
|
|
|
+ . "and animal health, reproduction, and production outcomes. "
|
|
|
|
|
+ . "You understand antagonisms such as Cu/Mo/S, Se/S, Zn/Fe, and the role of "
|
|
|
|
|
+ . "Albrecht-balanced soils in producing nutritionally complete feed. "
|
|
|
|
|
+ . "Always base your interpretation on the measured dietary analysis values. "
|
|
|
|
|
+ . "Write in a professional but accessible tone suitable for a farmer or veterinarian.",
|
|
|
|
|
+
|
|
|
|
|
+ 'compost' =>
|
|
|
|
|
+ "You are a certified agronomist and composting specialist with expertise in organic "
|
|
|
|
|
+ . "matter management, compost maturity assessment, and the use of compost to restore "
|
|
|
|
|
+ . "soil biology and mineral balance. "
|
|
|
|
|
+ . "You have deep knowledge of C:N ratios, microbial succession, humus formation, "
|
|
|
|
|
+ . "and how compost inputs interact with existing soil chemistry. "
|
|
|
|
|
+ "You are familiar with the Elaine Ingham and Arden Anderson's compost guidelines, "
|
|
|
|
|
+ . "You understand Australian composting standards and the role of biologically active "
|
|
|
|
|
+ . "compost in supporting the Albrecht soil balancing philosophy. "
|
|
|
|
|
+ . "Always base your interpretation on the measured compost analysis values. "
|
|
|
|
|
+ . "Write in a professional but accessible tone suitable for a farmer-facing report.",
|
|
|
|
|
+];
|
|
|
|
|
+
|
|
|
|
|
+$system = $systemPrompts[$recordType] ?? $systemPrompts['soil'];
|
|
|
|
|
+
|
|
|
|
|
+// ── Build analysis data block + prompts depending on record type ──────────────
|
|
|
|
|
+
|
|
|
|
|
+if ($recordType === 'plant') {
|
|
|
|
|
+
|
|
|
|
|
+ // ── Plant data summary ────────────────────────────────────────────────────
|
|
|
|
|
+ $p = $row;
|
|
|
|
|
+ $ps = $spec;
|
|
|
|
|
+
|
|
|
|
|
+ // Spec column map (matches plant_specifications DB schema)
|
|
|
|
|
+ $pSpecCols = [
|
|
|
|
|
+ 'n' => ['n_min', 'n_max'],
|
|
|
|
|
+ 'p' => ['P_Min', 'P_Max'],
|
|
|
|
|
+ 'k' => ['K_Min', 'K_Max'],
|
|
|
|
|
+ 's' => ['S_Min', 'S_Max'],
|
|
|
|
|
+ 'mg' => ['Mg_Min', 'Mg_Max'],
|
|
|
|
|
+ 'ca' => ['Ca_Min', 'Ca_Max'],
|
|
|
|
|
+ 'na' => ['Na_Min', 'Na_Max'],
|
|
|
|
|
+ 'fe' => ['Fe_Min', 'Fe_Max'],
|
|
|
|
|
+ 'mn' => ['Mn_Min', 'Mn_Max'],
|
|
|
|
|
+ 'zn' => ['Zn_Min', 'Zn_Max'],
|
|
|
|
|
+ 'cu' => ['Cu_Min', 'Cu_Max'],
|
|
|
|
|
+ 'b' => ['B_Min', 'B_Max'],
|
|
|
|
|
+ 'm' => ['M_Min', 'M_Max'],
|
|
|
|
|
+ 'co' => ['Co_min', 'Co_max'],
|
|
|
|
|
+ 'se' => ['se_min', 'se_max'],
|
|
|
|
|
+ 'cl' => ['cl_min', 'cl_max'],
|
|
|
|
|
+ 'c' => ['c_min', 'c_max'],
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ $plantElements = [
|
|
|
|
|
+ ['n', 'Nitrogen', '%'],
|
|
|
|
|
+ ['p', 'Phosphorus', '%'],
|
|
|
|
|
+ ['k', 'Potassium', '%'],
|
|
|
|
|
+ ['s', 'Sulfur', '%'],
|
|
|
|
|
+ ['mg', 'Magnesium', '%'],
|
|
|
|
|
+ ['ca', 'Calcium', '%'],
|
|
|
|
|
+ ['na', 'Sodium', '%'],
|
|
|
|
|
+ ['fe', 'Iron', 'ppm'],
|
|
|
|
|
+ ['mn', 'Manganese', 'ppm'],
|
|
|
|
|
+ ['zn', 'Zinc', 'ppm'],
|
|
|
|
|
+ ['cu', 'Copper', 'ppm'],
|
|
|
|
|
+ ['b', 'Boron', 'ppm'],
|
|
|
|
|
+ ['m', 'Molybdenum','ppm'],
|
|
|
|
|
+ ['co', 'Cobalt', 'ppm'],
|
|
|
|
|
+ ['se', 'Selenium', 'ppm'],
|
|
|
|
|
+ ['cl', 'Chloride', 'ppm'],
|
|
|
|
|
+ ['c', 'Carbon', '%'],
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ $plantDeficiencies = [];
|
|
|
|
|
+ $plantExcesses = [];
|
|
|
|
|
+ $elementLines = '';
|
|
|
|
|
+
|
|
|
|
|
+ foreach ($plantElements as [$el, $name, $unit]) {
|
|
|
|
|
+ $val = (float)($p[$el] ?? 0);
|
|
|
|
|
+ [$minCol, $maxCol] = $pSpecCols[$el];
|
|
|
|
|
+ $min = (float)($ps[$minCol] ?? 0);
|
|
|
|
|
+ $max = (float)($ps[$maxCol] ?? 0);
|
|
|
|
|
+ $range = ($min > 0 || $max > 0)
|
|
|
|
|
+ ? number_format($min, 3) . '–' . number_format($max, 3)
|
|
|
|
|
+ : 'N/A';
|
|
|
|
|
+ $found = $val > 0 ? number_format($val, 3) : 'N/A';
|
|
|
|
|
+ $status = '';
|
|
|
|
|
+ if ($val > 0 && ($min > 0 || $max > 0)) {
|
|
|
|
|
+ if ($min > 0 && $val < $min) { $status = '[DEFICIENT]'; $plantDeficiencies[] = $name; }
|
|
|
|
|
+ elseif ($max > 0 && $val > $max) { $status = '[EXCESS]'; $plantExcesses[] = $name; }
|
|
|
|
|
+ else { $status = '[IDEAL]'; }
|
|
|
|
|
+ }
|
|
|
|
|
+ $elementLines .= sprintf("%-20s %s found: %-8s range: %-15s %s\n",
|
|
|
|
|
+ "$name ($unit):", '', $found, $range, $status);
|
|
|
}
|
|
}
|
|
|
-}
|
|
|
|
|
|
|
|
|
|
-// ── Section-specific prompts ──────────────────────────────────────────────────
|
|
|
|
|
-$system = "You are a certified agronomist specialising in soil fertility, trained in the "
|
|
|
|
|
- . "Albrecht method of mineral soil balancing. You have deep knowledge of soil chemistry, "
|
|
|
|
|
- . "plant nutrition, and the relationship between soil mineral balance and crop and livestock health. "
|
|
|
|
|
- . "Always ground your recommendations in the measured data provided. "
|
|
|
|
|
- . "For Australian conditions, reference typical soil types and climate where relevant. "
|
|
|
|
|
- . "Write in a professional but accessible tone suitable for a farmer-facing report. "
|
|
|
|
|
- . "When the knowledge passages conflict with your training, prefer the passages — "
|
|
|
|
|
- . "they are from authoritative soil science texts.";
|
|
|
|
|
-
|
|
|
|
|
-$ctx = $soilData . $knowledgeContext;
|
|
|
|
|
-
|
|
|
|
|
-$prompts = [
|
|
|
|
|
- 'overview' =>
|
|
|
|
|
- "{$system}\n\n{$ctx}\n\n"
|
|
|
|
|
- . "TASK: Write an executive overview of these soil test results (3–4 paragraphs). "
|
|
|
|
|
- . "Cover: (1) overall soil health and fertility level, "
|
|
|
|
|
- . "(2) the most significant deficiencies or imbalances and their likely effect on crop performance, "
|
|
|
|
|
- . "(3) any positive attributes. "
|
|
|
|
|
- . "Use the Albrecht philosophy as a framework. Do not recommend specific product names.",
|
|
|
|
|
-
|
|
|
|
|
- 'ai_interpretation' =>
|
|
|
|
|
- "{$system}\n\n{$ctx}\n\n"
|
|
|
|
|
- . "TASK: Write a detailed technical interpretation structured with these headings:\n"
|
|
|
|
|
- . "1. SOIL REACTION (pH, EC, Paramagnetic)\n"
|
|
|
|
|
- . "2. ORGANIC MATTER & BIOLOGY (C, N, C:N ratio)\n"
|
|
|
|
|
- . "3. CATION EXCHANGE CAPACITY & BASE SATURATIONS\n"
|
|
|
|
|
- . "4. MAJOR ELEMENTS (Ca, Mg, K, Na, P — ppm and saturation %)\n"
|
|
|
|
|
- . "5. TRACE ELEMENTS (S, B, Mn, Cu, Zn, Fe, Al, Si, Co, Mo, Se)\n"
|
|
|
|
|
- . "6. ELEMENTAL RATIOS & INTERACTIONS (Ca:Mg, C:N, K:Mg antagonisms)\n"
|
|
|
|
|
- . "7. OVERALL SOIL BALANCE ASSESSMENT\n"
|
|
|
|
|
- . "For each element marked [DEFICIENT] or [EXCESS], explain agronomic significance "
|
|
|
|
|
- . "and interactions with other elements. Reference the Albrecht literature where relevant.",
|
|
|
|
|
-
|
|
|
|
|
- 'foliar' =>
|
|
|
|
|
- "{$system}\n\n{$ctx}\n\n"
|
|
|
|
|
- . "TASK: Design a foliar nutrition program to address the deficiencies shown. "
|
|
|
|
|
- . "Format as a numbered list or table: "
|
|
|
|
|
- . "Growth Stage | Product Type (generic) | Active Element | Rate (L or kg/ha) | Timing. "
|
|
|
|
|
- . "Prioritise elements marked [DEFICIENT]. "
|
|
|
|
|
- . "Note antagonisms (e.g. Ca/Mg competition, Zn/P, K/Mg lockout). "
|
|
|
|
|
- . "Add a note on carrier water pH and adjuvant recommendations.",
|
|
|
|
|
-
|
|
|
|
|
- 'microbial' =>
|
|
|
|
|
- "{$system}\n\n{$ctx}\n\n"
|
|
|
|
|
- . "TASK: Design a biological/microbial soil improvement program. "
|
|
|
|
|
- . "Structure your response:\n"
|
|
|
|
|
- . "1. CURRENT BIOLOGY ASSESSMENT (based on OM%, C:N ratio, pH)\n"
|
|
|
|
|
- . "2. RECOMMENDED INOCULANTS (mycorrhizae, rhizobia, EM, compost tea etc.)\n"
|
|
|
|
|
- . "3. CARBON FEEDING STRATEGY (humates, fish hydrolysate, molasses, cover crops)\n"
|
|
|
|
|
- . "4. TIMING & INTEGRATION with the mineral balancing program\n"
|
|
|
|
|
- . "Reference Albrecht's work on the relationship between mineral balance and soil biology.",
|
|
|
|
|
-];
|
|
|
|
|
|
|
+ $plantData = "=====================================\n"
|
|
|
|
|
+ . "PLANT TISSUE ANALYSIS — COMPLETE\n"
|
|
|
|
|
+ . "=====================================\n"
|
|
|
|
|
+ . "Client: {$p['client_name']}\n"
|
|
|
|
|
+ . "Crop Type: {$p['crop_type']}\n"
|
|
|
|
|
+ . "Sample ID: {$p['sample_id']}\n"
|
|
|
|
|
+ . "Site ID: {$p['site_id']}\n"
|
|
|
|
|
+ . "Lab No: {$p['lab_no']}\n"
|
|
|
|
|
+ . "Date Sampled: {$p['date_sampled']}\n\n"
|
|
|
|
|
+ . "--- ELEMENT RESULTS ---\n"
|
|
|
|
|
+ . $elementLines . "\n"
|
|
|
|
|
+ . "Deficient: " . (empty($plantDeficiencies) ? 'None detected' : implode(', ', $plantDeficiencies)) . "\n"
|
|
|
|
|
+ . "In Excess: " . (empty($plantExcesses) ? 'None detected' : implode(', ', $plantExcesses)) . "\n"
|
|
|
|
|
+ . "=====================================\n";
|
|
|
|
|
+
|
|
|
|
|
+ $ragChunks = retrieveRelevantChunks($pdo, $plantData, $section, RAG_TOP_K);
|
|
|
|
|
+ $knowledgeContext = '';
|
|
|
|
|
+ if (!empty($ragChunks)) {
|
|
|
|
|
+ $knowledgeContext = "\n\n===================================================\n"
|
|
|
|
|
+ . "RELEVANT PASSAGES FROM SOIL & PLANT SCIENCE LITERATURE\n"
|
|
|
|
|
+ . "===================================================\n";
|
|
|
|
|
+ foreach ($ragChunks as $i => $chunk) {
|
|
|
|
|
+ $knowledgeContext .= sprintf("\n[%d] \"%s\" — %s (p.%d)\n%s\n",
|
|
|
|
|
+ $i + 1, $chunk['source'], $chunk['author'], $chunk['page'], $chunk['chunk_text']);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $ctx = $plantData . $knowledgeContext;
|
|
|
|
|
+
|
|
|
|
|
+ $prompts = [
|
|
|
|
|
+ 'general' =>
|
|
|
|
|
+ "{$system}\n\n{$ctx}\n\n"
|
|
|
|
|
+ . "TASK: Write a concise general comment on these plant tissue analysis results (2–3 paragraphs). "
|
|
|
|
|
+ . "Cover: (1) the overall nutritional status of the crop, "
|
|
|
|
|
+ . "(2) the most significant deficiencies or excesses and their likely effect on crop yield and quality, "
|
|
|
|
|
+ . "(3) any elements in good balance. Do not recommend specific product names.",
|
|
|
|
|
+
|
|
|
|
|
+ 'ai_interpretation' =>
|
|
|
|
|
+ "{$system}\n\n{$ctx}\n\n"
|
|
|
|
|
+ . "TASK: Write a detailed technical interpretation with these headings:\n"
|
|
|
|
|
+ . "1. MAJOR ELEMENTS (N, P, K, S, Mg, Ca, Na)\n"
|
|
|
|
|
+ . "2. TRACE ELEMENTS (Fe, Mn, Zn, Cu, B)\n"
|
|
|
|
|
+ . "3. OTHER ELEMENTS (Mo, Co, Se, Cl, C)\n"
|
|
|
|
|
+ . "4. NUTRIENT INTERACTIONS & ANTAGONISMS\n"
|
|
|
|
|
+ . "5. OVERALL NUTRITIONAL ASSESSMENT\n"
|
|
|
|
|
+ . "For each element marked [DEFICIENT] or [EXCESS], explain the agronomic significance, "
|
|
|
|
|
+ . "likely causes, and interactions with other nutrients.",
|
|
|
|
|
+
|
|
|
|
|
+ 'recommended' =>
|
|
|
|
|
+ "{$system}\n\n{$ctx}\n\n"
|
|
|
|
|
+ . "TASK: Design a recommended remedial soil and fertiliser program to correct the deficiencies shown. "
|
|
|
|
|
+ . "Format as a numbered list or table: "
|
|
|
|
|
+ . "Element | Current Status | Recommended Action | Product Type (generic) | Rate | Timing. "
|
|
|
|
|
+ . "Prioritise elements marked [DEFICIENT]. "
|
|
|
|
|
+ . "Note any nutrient antagonisms that may be limiting uptake.",
|
|
|
|
|
+
|
|
|
|
|
+ 'foliar' =>
|
|
|
|
|
+ "{$system}\n\n{$ctx}\n\n"
|
|
|
|
|
+ . "TASK: Design a foliar spray program to rapidly correct deficiencies shown in the tissue test. "
|
|
|
|
|
+ . "Format as a numbered list or table: "
|
|
|
|
|
+ . "Growth Stage | Product Type (generic) | Active Element | Rate (L or kg/ha) | Timing. "
|
|
|
|
|
+ . "Prioritise elements marked [DEFICIENT]. "
|
|
|
|
|
+ . "Note carrier water pH requirements and any tank-mix incompatibilities.",
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+} else {
|
|
|
|
|
+
|
|
|
|
|
+ // ── Soil: RAG + prompts ───────────────────────────────────────────────────
|
|
|
|
|
+ $ragChunks = retrieveRelevantChunks($pdo, $soilData, $section, RAG_TOP_K);
|
|
|
|
|
+ $knowledgeContext = '';
|
|
|
|
|
+ if (!empty($ragChunks)) {
|
|
|
|
|
+ $knowledgeContext = "\n\n===================================================\n"
|
|
|
|
|
+ . "RELEVANT PASSAGES FROM SOIL SCIENCE LITERATURE\n"
|
|
|
|
|
+ . "(William A. Albrecht and other authorities)\n"
|
|
|
|
|
+ . "===================================================\n";
|
|
|
|
|
+ foreach ($ragChunks as $i => $chunk) {
|
|
|
|
|
+ $knowledgeContext .= sprintf("\n[%d] \"%s\" — %s (p.%d)\n%s\n",
|
|
|
|
|
+ $i + 1, $chunk['source'], $chunk['author'], $chunk['page'], $chunk['chunk_text']);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $ctx = $soilData . $knowledgeContext;
|
|
|
|
|
+
|
|
|
|
|
+ $prompts = [
|
|
|
|
|
+ 'overview' =>
|
|
|
|
|
+ "{$system}\n\n{$ctx}\n\n"
|
|
|
|
|
+ . "TASK: Write an executive overview of these soil test results (3–4 paragraphs). "
|
|
|
|
|
+ . "Cover: (1) overall soil health and fertility level, "
|
|
|
|
|
+ . "(2) the most significant deficiencies or imbalances and their likely effect on crop performance, "
|
|
|
|
|
+ . "(3) any positive attributes. "
|
|
|
|
|
+ . "Use the Albrecht philosophy as a framework. Do not recommend specific product names.",
|
|
|
|
|
+
|
|
|
|
|
+ 'ai_interpretation' =>
|
|
|
|
|
+ "{$system}\n\n{$ctx}\n\n"
|
|
|
|
|
+ . "TASK: Write a detailed technical interpretation structured with these headings:\n"
|
|
|
|
|
+ . "1. SOIL REACTION (pH, EC, Paramagnetic)\n"
|
|
|
|
|
+ . "2. ORGANIC MATTER & BIOLOGY (C, N, C:N ratio)\n"
|
|
|
|
|
+ . "3. CATION EXCHANGE CAPACITY & BASE SATURATIONS\n"
|
|
|
|
|
+ . "4. MAJOR ELEMENTS (Ca, Mg, K, Na, P — ppm and saturation %)\n"
|
|
|
|
|
+ . "5. TRACE ELEMENTS (S, B, Mn, Cu, Zn, Fe, Al, Si, Co, Mo, Se)\n"
|
|
|
|
|
+ . "6. ELEMENTAL RATIOS & INTERACTIONS (Ca:Mg, C:N, K:Mg antagonisms)\n"
|
|
|
|
|
+ . "7. OVERALL SOIL BALANCE ASSESSMENT\n"
|
|
|
|
|
+ . "For each element marked [DEFICIENT] or [EXCESS], explain agronomic significance "
|
|
|
|
|
+ . "and interactions with other elements. Reference the Albrecht literature where relevant.",
|
|
|
|
|
+
|
|
|
|
|
+ 'foliar' =>
|
|
|
|
|
+ "{$system}\n\n{$ctx}\n\n"
|
|
|
|
|
+ . "TASK: Design a foliar nutrition program to address the deficiencies shown. "
|
|
|
|
|
+ . "Format as a numbered list or table: "
|
|
|
|
|
+ . "Growth Stage | Product Type (generic) | Active Element | Rate (L or kg/ha) | Timing. "
|
|
|
|
|
+ . "Prioritise elements marked [DEFICIENT]. "
|
|
|
|
|
+ . "Note antagonisms (e.g. Ca/Mg competition, Zn/P, K/Mg lockout). "
|
|
|
|
|
+ . "Add a note on carrier water pH and adjuvant recommendations.",
|
|
|
|
|
+
|
|
|
|
|
+ 'microbial' =>
|
|
|
|
|
+ "{$system}\n\n{$ctx}\n\n"
|
|
|
|
|
+ . "TASK: Design a biological/microbial soil improvement program. "
|
|
|
|
|
+ . "Structure your response:\n"
|
|
|
|
|
+ . "1. CURRENT BIOLOGY ASSESSMENT (based on OM%, C:N ratio, pH)\n"
|
|
|
|
|
+ . "2. RECOMMENDED INOCULANTS (mycorrhizae, rhizobia, EM, compost tea etc.)\n"
|
|
|
|
|
+ . "3. CARBON FEEDING STRATEGY (humates, fish hydrolysate, molasses, cover crops)\n"
|
|
|
|
|
+ . "4. TIMING & INTEGRATION with the mineral balancing program\n"
|
|
|
|
|
+ . "Reference Albrecht's work on the relationship between mineral balance and soil biology.",
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
// ── Call Ollama ───────────────────────────────────────────────────────────────
|
|
// ── Call Ollama ───────────────────────────────────────────────────────────────
|
|
|
$payload = json_encode([
|
|
$payload = json_encode([
|