'use strict'; /** * Shared helpers for AppDetail record processing. * Used by both api_pub.js and api_export.js to avoid duplication. */ const { RateUnits } = require('./constants'); const utils = require('./utils'); /** * Compute appRateApplied from raw fields. * Formula: lminApp / (grSpeed_m_s × swath_m) × 10000 * Returns null on zero-division to avoid Infinity. */ function computeAppRateApplied(lminApp, grSpeed, swath) { if (!utils.isNumber(lminApp) || !utils.isNumber(grSpeed) || !utils.isNumber(swath)) return null; if (grSpeed === 0 || swath === 0) return null; return lminApp / (grSpeed * swath) * 10000; } /** * Convert app rate (L/ha or Kg/ha) + speed + swath to per-minute flow value. * For liquid this is L/min; for dry this is material/min. */ function flowRateFromAppRate(appRate, grSpeed, swath) { if (!utils.isNumber(appRate) || !utils.isNumber(grSpeed) || !utils.isNumber(swath)) return null; if (grSpeed <= 0 || swath <= 0) return null; return appRate * grSpeed * swath / 10000 * 60; } function isPositiveNumber(v) { return utils.isNumber(v) && v > 0; } /** * Resolve the rate unit code from session metadata or job fallback. */ function inferRateUnitCode(sessionMeta, job) { if (utils.isNumber(sessionMeta?.appRateUnit)) return sessionMeta.appRateUnit; if (typeof sessionMeta?.appRateUnitStr === 'string' && sessionMeta.appRateUnitStr.trim()) { const code = utils.rateStringToCode(sessionMeta.appRateUnitStr); if (utils.isNumber(code)) return code; } if (utils.isNumber(job?.appRateUnit)) return job.appRateUnit; return null; } /** * Determine whether the session material is liquid (vs dry/granular). * Returns true for liquid, false for dry, based on matType string or rate unit. */ function isLikelyLiquidMaterial(sessionMeta, rateUnitCode) { if (typeof sessionMeta?.matType === 'string') { const matType = sessionMeta.matType.trim().toLowerCase(); if (matType === 'wet') return true; if (matType === 'dry') return false; } return rateUnitCode === RateUnits.OZ_PER_ACRE || rateUnitCode === RateUnits.GAL_PER_ACRE || rateUnitCode === RateUnits.LIT_PER_HA; } /** * Resolve a session target app rate into metric-per-hectare units. * Returns L/ha for liquid or Kg/ha for dry depending on source unit. */ function resolveTargetRatePerHa(sessionMeta, job) { const rawRate = utils.isNumber(sessionMeta?.appRate) ? sessionMeta.appRate : (utils.isNumber(job?.appRate) ? job.appRate : null); if (!utils.isNumber(rawRate)) return null; const unitCode = inferRateUnitCode(sessionMeta, job); if (!utils.isNumber(unitCode)) return rawRate; return utils.toMetricRate(rawRate, unitCode).value; } module.exports = { computeAppRateApplied, flowRateFromAppRate, isPositiveNumber, inferRateUnitCode, isLikelyLiquidMaterial, resolveTargetRatePerHa };