Fix AI photo quote always returning shop minimum price

Two bugs caused AI estimates to collapse to the shop minimum floor:

1. Coating rate with no guard: when a shop hadn't calibrated their
   coating gun (rate = 0), the prompt injected '~0 sqft/hr' paired
   with 'MUST use shop-specific rates' — Claude returned near-zero
   estimatedMinutes, zeroing labor cost and triggering the floor.
   Fixed to mirror the existing blast-rate guard: rate=0 now sends
   a fallback instruction to use conservative industry-average times.

2. Per-item minutes divided by quantity: both the system prompt and
   user prompt explicitly tell Claude to return estimatedMinutes 'per
   single item', but CalculatePricingPreview() was dividing by qty
   anyway. For qty > 1 this halved (or more) the labor cost, again
   pushing toward the floor. Removed the incorrect divide.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-11 21:10:05 -04:00
parent 2c353f2e7f
commit 42a8c089d5
@@ -435,7 +435,15 @@ Only ask follow-up questions if truly needed — prefer to make reasonable assum
shopSpeedLine = "- Shop blast rate: not calibrated — use conservative industry-average times for this shop tier";
}
var coatingSpeedLine = $"- THIS SHOP'S coating application rate: ~{coatingRate:F0} sqft/hr";
string coatingSpeedLine;
if (coatingRate > 0)
coatingSpeedLine = $"- THIS SHOP'S coating application rate: ~{coatingRate:F0} sqft/hr — use this to derive coating time (surface area ÷ coating rate), NOT generic industry averages";
else
coatingSpeedLine = "- Shop coating rate: not calibrated — use conservative industry-average coating times for this shop tier";
var rateInstruction = (blastRate > 0 || coatingRate > 0)
? "IMPORTANT: For estimatedMinutes, you MUST use this shop's specific rates above where provided, not generic industry speeds."
: "IMPORTANT: For estimatedMinutes, use conservative industry-average times appropriate for a professional powder coating shop.";
return $@"Please analyze the item(s) in the photo(s) for powder coating estimation.
@@ -453,7 +461,7 @@ Company operating costs for your reference:
{shopSpeedLine}
{coatingSpeedLine}
IMPORTANT: For estimatedMinutes, you MUST use this shop's specific blast and coating rates above, not generic industry speeds.
{rateInstruction}
Sandblasting time = surface area of item ÷ shop blast rate (sqft/hr), adjusted for part complexity (harder-to-reach areas take more passes).
Coating time = surface area ÷ shop coating rate, adjusted for masking and complexity.
Include racking/unracking, inspection, and any material-specific prep (preheat handling, chemical stripping) as ACTIVE labor time.
@@ -547,9 +555,9 @@ Respond with the JSON object only.";
_ => 0
};
// Labor cost — AI returns total batch minutes, so divide by quantity to get per-item minutes.
// The unit price × quantity must equal the total batch labor cost.
var rawPerItemMinutes = aiResult.EstimatedMinutes / Math.Max(1m, (decimal)request.Quantity);
// Labor cost — AI returns per-item minutes (both system prompt and user prompt say "per single item").
// Unit price is per item; the caller multiplies by quantity for the line total.
var rawPerItemMinutes = aiResult.EstimatedMinutes;
var minFloorApplied = materialMinMinutes > 0 && rawPerItemMinutes < materialMinMinutes;
var perItemMinutes = minFloorApplied ? materialMinMinutes : rawPerItemMinutes;
var laborHours = perItemMinutes / 60m;