GPA
This commit is contained in:
@@ -355,29 +355,54 @@ router.post('/plans', validateRequest(applicationPlanSchema), async (req, res, n
|
||||
|
||||
const plan = planResult.rows[0];
|
||||
|
||||
// Calculate shared water amount and speed for liquid applications
|
||||
const sectionArea = areaSquareFeet || parseFloat(section.area);
|
||||
const firstProduct = products[0];
|
||||
const isLiquid = firstProduct.applicationType === 'liquid';
|
||||
|
||||
let sharedWaterAmount = 0;
|
||||
let sharedTargetSpeed = 3;
|
||||
|
||||
if (isLiquid) {
|
||||
// Prepare equipment and nozzle objects for water calculation
|
||||
const equipmentForCalc = {
|
||||
categoryName: equipmentData.category_name,
|
||||
tankSizeGallons: equipmentData.tank_size_gallons,
|
||||
sprayWidthFeet: equipmentData.spray_width_feet,
|
||||
capacityLbs: equipmentData.capacity_lbs,
|
||||
spreadWidth: equipmentData.spread_width
|
||||
};
|
||||
|
||||
const nozzleForCalc = nozzleData ? {
|
||||
flowRateGpm: nozzleData.flow_rate_gpm,
|
||||
sprayAngle: nozzleData.spray_angle
|
||||
} : null;
|
||||
|
||||
// Calculate water and speed once for the entire application
|
||||
const waterCalculation = calculateApplication({
|
||||
areaSquareFeet: sectionArea,
|
||||
rateAmount: 1, // Use dummy rate for water calculation
|
||||
rateUnit: 'oz/1000 sq ft',
|
||||
applicationType: 'liquid',
|
||||
equipment: equipmentForCalc,
|
||||
nozzle: nozzleForCalc
|
||||
});
|
||||
|
||||
sharedWaterAmount = waterCalculation.waterAmountGallons || 0;
|
||||
sharedTargetSpeed = waterCalculation.applicationSpeedMph || 3;
|
||||
|
||||
console.log('Shared liquid calculation:', {
|
||||
sectionArea,
|
||||
sharedWaterAmount,
|
||||
sharedTargetSpeed,
|
||||
productsCount: products.length
|
||||
});
|
||||
}
|
||||
|
||||
// Add products to plan with calculations
|
||||
for (const product of products) {
|
||||
const { productId, userProductId, rateAmount, rateUnit, applicationType } = product;
|
||||
|
||||
// Use passed area or get from database
|
||||
const sectionArea = areaSquareFeet || parseFloat(section.area);
|
||||
|
||||
console.log('Calculation inputs:', {
|
||||
areaSquareFeet: sectionArea,
|
||||
rateAmount: parseFloat(rateAmount),
|
||||
rateUnit,
|
||||
applicationType,
|
||||
equipmentData: {
|
||||
category_name: equipmentData.category_name,
|
||||
spray_width_feet: equipmentData.spray_width_feet,
|
||||
tank_size_gallons: equipmentData.tank_size_gallons
|
||||
},
|
||||
nozzleData: nozzleData ? {
|
||||
flow_rate_gpm: nozzleData.flow_rate_gpm,
|
||||
spray_angle: nozzleData.spray_angle
|
||||
} : null
|
||||
});
|
||||
|
||||
// Prepare equipment object for calculations
|
||||
const equipmentForCalc = {
|
||||
categoryName: equipmentData.category_name,
|
||||
@@ -393,7 +418,7 @@ router.post('/plans', validateRequest(applicationPlanSchema), async (req, res, n
|
||||
sprayAngle: nozzleData.spray_angle
|
||||
} : null;
|
||||
|
||||
// Perform advanced calculations using the calculation engine
|
||||
// Calculate product amount
|
||||
const calculations = calculateApplication({
|
||||
areaSquareFeet: sectionArea,
|
||||
rateAmount: parseFloat(rateAmount),
|
||||
@@ -403,7 +428,12 @@ router.post('/plans', validateRequest(applicationPlanSchema), async (req, res, n
|
||||
nozzle: nozzleForCalc
|
||||
});
|
||||
|
||||
console.log('Plan creation calculations:', calculations);
|
||||
console.log('Individual product calculation:', {
|
||||
product: productId || userProductId,
|
||||
rateAmount,
|
||||
rateUnit,
|
||||
calculatedAmount: calculations.productAmountOunces || calculations.productAmountPounds
|
||||
});
|
||||
|
||||
// Extract calculated values based on application type
|
||||
let calculatedProductAmount = 0;
|
||||
@@ -412,7 +442,9 @@ router.post('/plans', validateRequest(applicationPlanSchema), async (req, res, n
|
||||
|
||||
if (calculations.type === 'liquid') {
|
||||
calculatedProductAmount = calculations.productAmountOunces || 0;
|
||||
calculatedWaterAmount = calculations.waterAmountGallons || 0;
|
||||
// Use shared water amount for liquid applications
|
||||
calculatedWaterAmount = sharedWaterAmount;
|
||||
targetSpeed = sharedTargetSpeed;
|
||||
} else if (calculations.type === 'granular') {
|
||||
calculatedProductAmount = calculations.productAmountPounds || 0;
|
||||
calculatedWaterAmount = 0; // No water for granular
|
||||
@@ -565,12 +597,47 @@ router.put('/plans/:id', validateParams(idParamSchema), validateRequest(applicat
|
||||
// Delete existing products
|
||||
await client.query('DELETE FROM application_plan_products WHERE plan_id = $1', [planId]);
|
||||
|
||||
// Calculate shared water amount and speed for liquid applications
|
||||
const sectionArea = areaSquareFeet || parseFloat(section.area);
|
||||
const firstProduct = products[0];
|
||||
const isLiquid = firstProduct.applicationType === 'liquid';
|
||||
|
||||
let sharedWaterAmount = 0;
|
||||
let sharedTargetSpeed = 3;
|
||||
|
||||
if (isLiquid) {
|
||||
// Prepare equipment and nozzle objects for water calculation
|
||||
const equipmentForCalc = {
|
||||
categoryName: equipmentData.category_name,
|
||||
tankSizeGallons: equipmentData.tank_size_gallons,
|
||||
sprayWidthFeet: equipmentData.spray_width_feet,
|
||||
capacityLbs: equipmentData.capacity_lbs,
|
||||
spreadWidth: equipmentData.spread_width
|
||||
};
|
||||
|
||||
const nozzleForCalc = nozzleData ? {
|
||||
flowRateGpm: nozzleData.flow_rate_gpm,
|
||||
sprayAngle: nozzleData.spray_angle
|
||||
} : null;
|
||||
|
||||
// Calculate water and speed once for the entire application
|
||||
const waterCalculation = calculateApplication({
|
||||
areaSquareFeet: sectionArea,
|
||||
rateAmount: 1, // Use dummy rate for water calculation
|
||||
rateUnit: 'oz/1000 sq ft',
|
||||
applicationType: 'liquid',
|
||||
equipment: equipmentForCalc,
|
||||
nozzle: nozzleForCalc
|
||||
});
|
||||
|
||||
sharedWaterAmount = waterCalculation.waterAmountGallons || 0;
|
||||
sharedTargetSpeed = waterCalculation.applicationSpeedMph || 3;
|
||||
}
|
||||
|
||||
// Add updated products with recalculation
|
||||
for (const product of products) {
|
||||
const { productId, userProductId, rateAmount, rateUnit, applicationType } = product;
|
||||
|
||||
const sectionArea = areaSquareFeet || parseFloat(section.area);
|
||||
|
||||
// Prepare equipment object for calculations
|
||||
const equipmentForCalc = {
|
||||
categoryName: equipmentData.category_name,
|
||||
@@ -603,7 +670,9 @@ router.put('/plans/:id', validateParams(idParamSchema), validateRequest(applicat
|
||||
|
||||
if (calculations.type === 'liquid') {
|
||||
calculatedProductAmount = calculations.productAmountOunces || 0;
|
||||
calculatedWaterAmount = calculations.waterAmountGallons || 0;
|
||||
// Use shared water amount for liquid applications
|
||||
calculatedWaterAmount = sharedWaterAmount;
|
||||
targetSpeed = sharedTargetSpeed;
|
||||
} else if (calculations.type === 'granular') {
|
||||
calculatedProductAmount = calculations.productAmountPounds || 0;
|
||||
calculatedWaterAmount = 0;
|
||||
|
||||
@@ -27,53 +27,52 @@ function calculateLiquidApplication(areaSquareFeet, rateAmount, rateUnit, equipm
|
||||
Equipment: ${equipment?.categoryName} (width: ${equipment?.sprayWidthFeet || 'N/A'} ft)
|
||||
Nozzle GPM: ${nozzle?.flowRateGpm || 'N/A'}`);
|
||||
|
||||
// Calculate application speed first based on equipment and nozzle
|
||||
if (equipment && nozzle && nozzle.flowRateGpm && equipment.sprayWidthFeet) {
|
||||
const sprayWidthFeet = equipment.sprayWidthFeet;
|
||||
const nozzleGpm = nozzle.flowRateGpm;
|
||||
// Use a practical application speed for lawn treatments
|
||||
applicationSpeedMph = 3; // Standard walking speed for most lawn applications
|
||||
|
||||
// For liquid applications, we need to determine optimal speed based on target carrier rate
|
||||
// Let's use a reasonable default carrier rate of 20 GPA (gallons per acre)
|
||||
const targetGallonsPerAcre = 20;
|
||||
const targetGallonsPerSqft = targetGallonsPerAcre / 43560;
|
||||
|
||||
// Formula: Speed (MPH) = GPM / (width_ft × target_rate_gal_per_sqft × 88)
|
||||
// Where 88 converts MPH to ft/min
|
||||
applicationSpeedMph = nozzleGpm / (sprayWidthFeet * targetGallonsPerSqft * 88);
|
||||
|
||||
// Limit speed to reasonable range (1-8 MPH for lawn applications)
|
||||
applicationSpeedMph = Math.max(1, Math.min(8, applicationSpeedMph));
|
||||
} else {
|
||||
applicationSpeedMph = 3; // Default speed
|
||||
}
|
||||
|
||||
// Now calculate actual carrier (water) rate using the calculated speed
|
||||
// Calculate actual carrier (water) rate based on equipment specs and application speed
|
||||
// Formula: Rate (gal/sqft) = flow (GPM) / (width (ft) × speed (ft/min))
|
||||
// Where speed (ft/min) = speed (MPH) × 88
|
||||
let carrierRateGalPerSqft = 0;
|
||||
let actualGallonsPerAcre = 0;
|
||||
|
||||
if (equipment && nozzle && nozzle.flowRateGpm && equipment.sprayWidthFeet) {
|
||||
const flowGpm = nozzle.flowRateGpm;
|
||||
const widthFt = equipment.sprayWidthFeet;
|
||||
const speedFtPerMin = applicationSpeedMph * 88; // Convert MPH to ft/min
|
||||
|
||||
// Calculate carrier rate based on actual equipment parameters
|
||||
carrierRateGalPerSqft = flowGpm / (widthFt * speedFtPerMin);
|
||||
waterGallons = carrierRateGalPerSqft * areaSquareFeet;
|
||||
|
||||
console.log('Carrier rate calculation:', {
|
||||
// Calculate the resulting GPA for reference
|
||||
actualGallonsPerAcre = carrierRateGalPerSqft * 43560;
|
||||
|
||||
console.log('Equipment-based carrier rate calculation:', {
|
||||
flowGpm,
|
||||
widthFt,
|
||||
speedMph: applicationSpeedMph,
|
||||
speedFtPerMin,
|
||||
carrierRateGalPerSqft,
|
||||
carrierRateGalPerSqft: carrierRateGalPerSqft.toFixed(6),
|
||||
areaSquareFeet,
|
||||
totalWaterGallons: waterGallons,
|
||||
carrierRateGalPer1000Sqft: carrierRateGalPerSqft * 1000
|
||||
totalWaterGallons: waterGallons.toFixed(2),
|
||||
resultingGPA: actualGallonsPerAcre.toFixed(1),
|
||||
carrierRateGalPer1000Sqft: (carrierRateGalPerSqft * 1000).toFixed(3)
|
||||
});
|
||||
} else {
|
||||
// Fallback to area-based calculation if equipment data is missing
|
||||
// Fallback when equipment/nozzle data is missing
|
||||
console.log('Using fallback water calculation - missing equipment/nozzle data');
|
||||
waterGallons = area1000sqft * 0.164; // Use your example: 0.164 gal/1000sqft
|
||||
// Use a reasonable default rate for lawn applications (25 GPA)
|
||||
const fallbackGPA = 25;
|
||||
const fallbackRatePerSqft = fallbackGPA / 43560;
|
||||
waterGallons = fallbackRatePerSqft * areaSquareFeet;
|
||||
actualGallonsPerAcre = fallbackGPA;
|
||||
|
||||
console.log('Fallback calculation:', {
|
||||
fallbackGPA,
|
||||
areaSquareFeet,
|
||||
totalWaterGallons: waterGallons.toFixed(2)
|
||||
});
|
||||
}
|
||||
|
||||
// Calculate product amount based on rate unit
|
||||
|
||||
@@ -322,7 +322,12 @@ const Applications = () => {
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p>• Product: {application.totalProductAmount.toFixed(2)} {application.totalWaterAmount > 0 ? 'oz' : 'lbs'}</p>
|
||||
{/* Show single product with name */}
|
||||
{application.productDetails && application.productDetails.length === 1 ? (
|
||||
<p>• {application.productDetails[0].name}{application.productDetails[0].brand ? ` (${application.productDetails[0].brand})` : ''}: {application.productDetails[0].calculatedAmount.toFixed(2)} {application.totalWaterAmount > 0 ? 'oz' : 'lbs'}</p>
|
||||
) : (
|
||||
<p>• Product: {application.totalProductAmount.toFixed(2)} {application.totalWaterAmount > 0 ? 'oz' : 'lbs'}</p>
|
||||
)}
|
||||
{application.totalWaterAmount > 0 && (
|
||||
<p>• Water: {application.totalWaterAmount.toFixed(2)} gallons</p>
|
||||
)}
|
||||
@@ -835,18 +840,18 @@ const ApplicationPlanModal = ({
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="number"
|
||||
step="0.1"
|
||||
step="0.01"
|
||||
min="0"
|
||||
value={item.rateAmount || ''}
|
||||
onChange={(e) => {
|
||||
const newProducts = [...planData.selectedProducts];
|
||||
newProducts[index] = {
|
||||
...item,
|
||||
rateAmount: parseFloat(e.target.value) || 0
|
||||
rateAmount: e.target.value === '' ? '' : parseFloat(e.target.value)
|
||||
};
|
||||
setPlanData({ ...planData, selectedProducts: newProducts });
|
||||
}}
|
||||
className="w-20 px-2 py-1 text-sm border rounded"
|
||||
className="w-24 px-2 py-1 text-sm border rounded"
|
||||
placeholder="Rate"
|
||||
/>
|
||||
<select
|
||||
|
||||
Reference in New Issue
Block a user