soil_calculations.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. <?php
  2. /**
  3. * lib/soil_calculations.php
  4. *
  5. * Functions for soil analysis calculations and display
  6. */
  7. require_once __DIR__.'/../config/database.php';
  8. /**
  9. * Calculate and display soil program for a specific element
  10. *
  11. * @param string $symbol Element symbol (e.g., 'Ca', 'Mg', 'K')
  12. * @param string $element Database column name for the element
  13. * @param string $min Min value column (empty string uses element column)
  14. * @param string $max Max value column (empty string uses element column)
  15. * @param string $nutrient Full nutrient name
  16. * @param string $type Measurement type (e.g., 'kg', 'ppm', '%')
  17. * @param int $record_id Soil record ID
  18. * @param float $rand_id Random ID for verification
  19. * @return string HTML output for the program row
  20. */
  21. function soilProgramCalcs($symbol, $element, $min, $max, $nutrient, $type, $record_id, $rand_id) {
  22. try {
  23. $pdo = getDBConnection();
  24. // Determine which table to use for min/max values
  25. if (empty($min)) {
  26. $element_min = $element;
  27. $dbtable = "soil_specifications.";
  28. } else {
  29. $element_min = $min;
  30. $dbtable = "soil_records.";
  31. }
  32. if (empty($max)) {
  33. $element_max = $element;
  34. $dbtable = "soil_specifications.";
  35. } else {
  36. $element_max = $max;
  37. $dbtable = "soil_records.";
  38. }
  39. // Prepare and execute query
  40. $stmt = $pdo->prepare("
  41. SELECT soil_records.{$element},
  42. {$dbtable}{$element_max} AS soilMax,
  43. {$dbtable}{$element_min} AS soilMin
  44. FROM soil_records
  45. INNER JOIN soil_specifications ON soil_records.soil_type = soil_specifications.soil_type
  46. WHERE soil_records.id = ? AND soil_records.rand = ?
  47. ");
  48. $stmt->execute([$record_id, $rand_id]);
  49. $row = $stmt->fetch(PDO::FETCH_ASSOC);
  50. if (!$row) {
  51. return "<div class='row'>
  52. <div class='col-1 border-bottom border-left'>1</div>
  53. <div class='col border-bottom border-left'>{$nutrient}</div>
  54. <div class='col border-bottom border-left'>@</div>
  55. <div class='col border-bottom border-left'>N/A</div>
  56. <div class='col border-bottom border-left'>kg/Ha</div>
  57. </div>";
  58. }
  59. $value = (float) $row[$element];
  60. $max_val = (float) $row['soilMax'];
  61. $measurement = empty($type) ? "" : $type;
  62. $value_p = floatval($value);
  63. // Calculate recommended amount (uses max instead of median like soilAnalysisReportCalcs)
  64. $recommended = $max_val - $value_p;
  65. // Convert acres to hectares (kg/Ac to kg/ha)
  66. $acHa = 2.4710559990832394739; // Acres to Hectares
  67. $value_converted = ($recommended * $acHa);
  68. // Show 0 if value is negative, otherwise round to 2 decimal places
  69. if ($value_converted < 0) {
  70. $value_converted = 0;
  71. } else {
  72. $value_converted = round($value_converted, 2);
  73. }
  74. $result = $value_converted . " " . $measurement;
  75. // Return HTML table row
  76. return "<div class='row'>
  77. <div class='col-1 border-bottom border-left'>1</div>
  78. <div class='col border-bottom border-left'>{$nutrient}</div>
  79. <div class='col border-bottom border-left'>@</div>
  80. <div class='col border-bottom border-left'>{$result}</div>
  81. <div class='col border-bottom border-left'>kg/Ha</div>
  82. </div>";
  83. } catch (PDOException $e) {
  84. error_log("Database error in soilProgramCalcs: " . $e->getMessage());
  85. }
  86. }
  87. /**
  88. * Calculate and display soil analysis report for a specific element
  89. *
  90. * @param string $symbol Element symbol (e.g., 'Ca', 'Mg', 'K')
  91. * @param string $element Database column name for the element
  92. * @param string $min Min value column (empty string uses element column)
  93. * @param string $max Max value column (empty string uses element column)
  94. * @param string $nutrient Full nutrient name
  95. * @param string $type Measurement type (e.g., 'kg', 'ppm', '%')
  96. * @param string $class CSS class for styling
  97. * @param int $record_id Soil record ID
  98. * @param float $rand_id Random ID for verification
  99. * @return string HTML output for the analysis row
  100. */
  101. function soilAnalysisReportCalcs($symbol, $element, $min, $max, $nutrient, $type, $class, $record_id, $rand_id) {
  102. try {
  103. $pdo = getDBConnection();
  104. // Determine which table to use for min/max values
  105. if (empty($min)) {
  106. $element_min = $element;
  107. $dbtable_min = "soil_specifications.";
  108. } else {
  109. $element_min = $min;
  110. $dbtable_min = "soil_records.";
  111. }
  112. if (empty($max)) {
  113. $element_max = $element;
  114. $dbtable_max = "soil_specifications.";
  115. } else {
  116. $element_max = $max;
  117. $dbtable_max = "soil_records.";
  118. }
  119. // Prepare and execute query
  120. $stmt = $pdo->prepare("
  121. SELECT soil_records.{$element},
  122. {$dbtable_min}{$element_min} AS soilMin,
  123. {$dbtable_max}{$element_max} AS soilMax
  124. FROM soil_records
  125. INNER JOIN soil_specifications ON soil_records.soil_type = soil_specifications.soil_type
  126. WHERE soil_records.id = ? AND soil_records.rand = ?
  127. ");
  128. $stmt->execute([$record_id, $rand_id]);
  129. $row = $stmt->fetch(PDO::FETCH_ASSOC);
  130. if (!$row) {
  131. return "<div class='{$class}'>{$nutrient}: N/A</div>";
  132. }
  133. $value = (float) $row[$element];
  134. $min_val = (float) $row['soilMin'];
  135. $max_val = (float) $row['soilMax'];
  136. $measurement = empty($type) ? "" : $type;
  137. $value_p = floatval($value);
  138. // Calculate recommended amount (median between min and max)
  139. $recommended = ($min_val + $max_val) / 2;
  140. // Convert acres to hectares (kg/Ac to kg/ha)
  141. $acHa = 2.4710559990832394739; // Acres to Hectares
  142. $value_converted = ($recommended - $value_p) * $acHa;
  143. // Show 0 if value is negative, otherwise round to 2 decimal places
  144. if ($value_converted < 0) {
  145. $value_converted = 0;
  146. } else {
  147. $value_converted = round($value_converted, 2);
  148. }
  149. $result = $value_converted . " " . $measurement;
  150. // Return HTML div
  151. return "<div class='{$class}'>{$nutrient}:<br>{$result}</div>";
  152. } catch (PDOException $e) {
  153. error_log("Database error in soilAnalysisReportCalcs: " . $e->getMessage());
  154. return "<div class='{$class}'>{$nutrient}: Error</div>";
  155. }
  156. }
  157. /**
  158. * Renders a <tr> for a single nutrient with three progress-bar columns.
  159. *
  160. * Parameters ($p keys):
  161. * element string Column name in soil_records
  162. * sbl string Chemical symbol (may contain HTML, e.g. "NO<sub>3</sub>-N")
  163. * nutrient string Display name (may contain HTML)
  164. * min string Column in soil_records, numeric literal, or '' for no bar
  165. * max string Column in soil_records, 'soil_type' (special), numeric literal, or ''
  166. * type string Unit label (ppm, %, mS/cm …)
  167. * text string Value cell alignment: c|r|l
  168. * rec_text string Recommended cell alignment: c|r|l
  169. * recV string Recommended display: 'n'=none, 'ph'='6.4', 'max'=max only, else=min–max
  170. * decimal int Decimal places for value
  171. * graph string CSS class for progress-bar colour
  172. */
  173. function soilRow(array $row, array $spec, array $p): void
  174. {
  175. $element = $p['element'] ?? '';
  176. $sbl = $p['sbl'] ?? '';
  177. $nutrient = $p['nutrient'] ?? '';
  178. $minParam = $p['min'] ?? '';
  179. $maxParam = $p['max'] ?? '';
  180. $type = $p['type'] ?? '';
  181. $text = $p['text'] ?? 'c';
  182. $recText = $p['rec_text'] ?? 'c';
  183. $recV = $p['recV'] ?? '';
  184. $decimal = (int)($p['decimal'] ?? 2);
  185. $graph = $p['graph'] ?? '';
  186. $label = ($sbl !== '') ? $sbl . ' - ' . $nutrient : $nutrient;
  187. $rawVal = $row[$element] ?? null;
  188. $value = ($rawVal !== null && $rawVal !== '') ? (float)$rawVal : 0.0;
  189. $valueFmt = number_format($value, $decimal, '.', '') . ($type !== '' ? ' ' . $type : '');
  190. // Resolve min
  191. $min = 0.0;
  192. if ($minParam !== '') {
  193. if (is_numeric($minParam)) {
  194. $min = (float)$minParam;
  195. } elseif (isset($row[$minParam]) && $row[$minParam] !== '') {
  196. $min = (float)$row[$minParam];
  197. } elseif (isset($spec[$minParam]) && $spec[$minParam] !== '') {
  198. $min = (float)$spec[$minParam];
  199. }
  200. } elseif (isset($spec[$element]) && $spec[$element] !== '') {
  201. $min = (float)$spec[$element] / 2; // fallback: half the spec average
  202. }
  203. // Resolve max + recommended display label
  204. $max = 0.0;
  205. $maxLabel = '';
  206. if ($maxParam === 'soil_type') {
  207. $st = strtolower($row['soil_type'] ?? '');
  208. $maxLabel = match($st) {
  209. 'light' => 'Light Soil',
  210. 'medium' => 'Medium Soil',
  211. 'heavy' => 'Heavy Soil',
  212. default => htmlspecialchars($row['soil_type'] ?? '', ENT_QUOTES, 'UTF-8'),
  213. };
  214. } elseif ($maxParam !== '') {
  215. if (is_numeric($maxParam)) {
  216. $max = (float)$maxParam;
  217. } elseif (isset($row[$maxParam]) && $row[$maxParam] !== '') {
  218. $max = (float)$row[$maxParam];
  219. } elseif (isset($spec[$maxParam]) && $spec[$maxParam] !== '') {
  220. $max = (float)$spec[$maxParam];
  221. }
  222. } elseif (isset($spec[$element]) && $spec[$element] !== '') {
  223. $max = (float)$spec[$element] * 2; // fallback: double the spec average
  224. }
  225. // Recommended cell text
  226. $measurement = ($type !== '') ? ' ' . $type : '';
  227. if ($maxParam === 'soil_type') {
  228. $recommended = $maxLabel;
  229. } elseif ($recV === 'n') {
  230. $recommended = '';
  231. } elseif ($recV === 'ph') {
  232. $recommended = '6.4';
  233. } elseif ($recV === 'max') {
  234. $recommended = number_format($max, $decimal, '.', '') . $measurement;
  235. } else {
  236. $recommended = number_format($min, $decimal, '.', '') . ' - ' . number_format($max, $decimal, '.', '') . $measurement;
  237. }
  238. $alignVal = match($text) { 'r' => 'text-right', 'l' => 'text-left', default => 'text-center' };
  239. $alignRec = match($recText) { 'r' => 'text-right', 'l' => 'text-left', default => 'text-center' };
  240. // Bar calculations (replicates original int-cast logic)
  241. $c_min = $min - ($max - $min);
  242. $c_max = $max + ($max - $min);
  243. $hasValue = ($rawVal !== null && $rawVal !== '' && $rawVal !== '0');
  244. // First bar (deficit zone: c_min → min)
  245. if (!$hasValue || (int)($c_min - $min) == 0) {
  246. $fb = 0;
  247. } else {
  248. $fb = (int)($c_min - $value) / (int)($c_min - $min) * 100;
  249. }
  250. $fb = !$hasValue ? 0 : ($fb > 100 ? 100 : ($fb < 0 ? 2 : $fb));
  251. // Second bar (ideal zone: min → max)
  252. if (!$hasValue || (int)($min - $max) == 0) {
  253. $sb = 0;
  254. } else {
  255. $sb = (int)($min - $value) / (int)($min - $max) * 100;
  256. }
  257. $sbp = ($fb < 100) ? 0 : ($sb < 0 ? 0 : ($sb > 101 ? 100 : $sb));
  258. // Third bar (excess zone: max → c_max)
  259. if (!$hasValue || (int)($max - $c_max) == 0) {
  260. $tb = 0;
  261. } else {
  262. $tb = (int)($max - $value) / (int)($max - $c_max) * 100;
  263. }
  264. $tbp = ($sb < 100) ? 0 : ($tb < 0 ? 0 : ($tb > 101 ? 100 : $tb));
  265. echo "<tr class='sub-chart'>\n";
  266. echo " <td class='text-left border-left text-capitalize pl-2'>{$label}</td>\n";
  267. echo " <td class='{$alignRec} border-left px-3'>{$recommended}</td>\n";
  268. echo " <td class='{$alignVal} border-left nutrient-balance px-3'>{$valueFmt}</td>\n";
  269. echo " <td class='text-center border-left graph-border'><div class='progress'><div class='progress-bar {$graph}' style='width:{$fb}%'></div></div></td>\n";
  270. echo " <td class='text-center border-left graph-border'><div class='progress'><div class='progress-bar {$graph}' style='width:{$sbp}%'></div></div></td>\n";
  271. echo " <td class='text-center border-left border-right graph-border'><div class='progress'><div class='progress-bar {$graph}' style='width:{$tbp}%'></div></div></td>\n";
  272. echo "</tr>\n";
  273. }
  274. /**
  275. * Renders a <tr> for a calculated ratio (element ÷ elementTwo).
  276. *
  277. * Parameters ($p keys):
  278. * element string Numerator column in soil_records
  279. * elementTwo string Denominator column in soil_records
  280. * sbl string Chemical symbol
  281. * nutrient string Display name
  282. * rec string Column in soil_specifications for the recommended ratio
  283. * type string Unit suffix (e.g. ':1')
  284. * rec_text string Recommended alignment: c|r|l
  285. * decimal int Decimal places
  286. * graph string CSS class for progress-bar colour
  287. */
  288. function soilRatio(array $row, array $spec, array $p): void
  289. {
  290. $element = $p['element'] ?? '';
  291. $element2 = $p['elementTwo'] ?? '';
  292. $sbl = $p['sbl'] ?? '';
  293. $nutrient = $p['nutrient'] ?? '';
  294. $rec = $p['rec'] ?? '';
  295. $type = $p['type'] ?? '';
  296. $recText = $p['rec_text'] ?? 'c';
  297. $decimal = (int)($p['decimal'] ?? 1);
  298. $graph = $p['graph'] ?? '';
  299. $label = ($sbl !== '') ? $sbl . ' - ' . $nutrient : $nutrient;
  300. $val1 = isset($row[$element]) && $row[$element] !== '' ? (float)$row[$element] : 0.0;
  301. $val2 = isset($row[$element2]) && $row[$element2] !== '' ? (float)$row[$element2] : 0.0;
  302. $ratio = ($val2 != 0) ? $val1 / $val2 : 0.0;
  303. $valueFmt = number_format($ratio, $decimal, '.', '') . ($type !== '' ? ' ' . $type : '');
  304. $recommended = (isset($spec[$rec]) && $spec[$rec] !== '') ? htmlspecialchars($spec[$rec], ENT_QUOTES, 'UTF-8') : '';
  305. $alignRec = match($recText) { 'r' => 'text-right', 'l' => 'text-left', default => 'text-center' };
  306. echo "<tr class='sub-chart'>\n";
  307. echo " <td class='text-left border-left text-capitalize pl-2'>{$label}</td>\n";
  308. echo " <td class='{$alignRec} border-left px-3'>{$recommended}</td>\n";
  309. echo " <td class='text-center border-left nutrient-balance px-3'>{$valueFmt}</td>\n";
  310. echo " <td class='text-center border-left graph-border'><div class='progress'><div class='progress-bar {$graph}' style='width:0%'></div></div></td>\n";
  311. echo " <td class='text-center border-left graph-border'><div class='progress'><div class='progress-bar {$graph}' style='width:0%'></div></div></td>\n";
  312. echo " <td class='text-center border-left border-right graph-border'><div class='progress'><div class='progress-bar {$graph}' style='width:0%'></div></div></td>\n";
  313. echo "</tr>\n";
  314. }
  315. ?>