89 lines
2.8 KiB
JavaScript
89 lines
2.8 KiB
JavaScript
'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
|
||
};
|