Fix AI quote blast rate: single formula path, correct client preview
Root cause: company-settings-lookups.js had its own baseByCfm/multiplier tables that were completely different from ShopCapabilityCalculator.cs, so the UI showed an inflated rate (e.g. 82 sqft/hr) while the AI prompt received the server-computed rate (e.g. 9 sqft/hr). - Add CompanySettingsController.DeriveBlastRate endpoint — thin GET that calls ShopCapabilityCalculator directly; now the single formula path - Delete all client-side formula code (baseByCfm, multiplier tables, deriveBlastRate) — ~30 lines removed - Modal live preview calls /CompanySettings/DeriveBlastRate with 250ms debounce instead of computing locally - Blast setup table uses setup.derivedRate from GetBlastSetups (already server-computed) instead of recalculating client-side - QuotesController.AiAnalyzeItem: when no blast setup is explicitly selected, fall back to the company's default blast setup so the configured rate is always used Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1726,6 +1726,26 @@ public class CompanySettingsController : Controller
|
||||
|
||||
#region Blast Setups
|
||||
|
||||
/// <summary>
|
||||
/// Single authoritative blast-rate calculation endpoint. Takes equipment parameters and
|
||||
/// returns the sqft/hr rate using the same ShopCapabilityCalculator formula the AI uses.
|
||||
/// The modal live preview calls this instead of duplicating the formula in JavaScript.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
public IActionResult DeriveBlastRate(decimal cfm, int nozzle, int setupType, int substrate, decimal? rateOverride)
|
||||
{
|
||||
var setup = new CompanyBlastSetup
|
||||
{
|
||||
CompressorCfm = cfm,
|
||||
BlastNozzleSize = nozzle,
|
||||
SetupType = (BlastSetupType)setupType,
|
||||
PrimarySubstrate = (BlastSubstrateType)substrate,
|
||||
BlastRateSqFtPerHourOverride = rateOverride > 0 ? rateOverride : null
|
||||
};
|
||||
var rate = ShopCapabilityCalculator.GetBlastRateSqFtPerHour(setup);
|
||||
return Json(new { rate });
|
||||
}
|
||||
|
||||
/// <summary>Returns all active blast setups for the current company with their derived rates.</summary>
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetBlastSetups()
|
||||
|
||||
@@ -3435,13 +3435,21 @@ public class QuotesController : Controller
|
||||
// Build company AI context: profile text + recent accepted predictions as few-shot examples
|
||||
var aiContext = await BuildCompanyAiContextAsync(companyId, costs);
|
||||
|
||||
// Load the specific blast setup when the user picked one before analyzing
|
||||
// Load the specific blast setup when the user picked one before analyzing.
|
||||
// If none was explicitly chosen, fall back to the company's default blast setup so
|
||||
// named-setup rates (e.g. a blast cabinet configured at 82 sqft/hr) are always
|
||||
// used instead of the coarser company-level operating cost fallback.
|
||||
CompanyBlastSetup? selectedBlastSetup = null;
|
||||
if (request.BlastSetupId.HasValue)
|
||||
{
|
||||
var setups = await _unitOfWork.BlastSetups.FindAsync(b => b.Id == request.BlastSetupId.Value && b.IsActive && b.CompanyId == companyId);
|
||||
selectedBlastSetup = setups.FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
var defaultSetups = await _unitOfWork.BlastSetups.FindAsync(b => b.IsDefault && b.IsActive && b.CompanyId == companyId);
|
||||
selectedBlastSetup = defaultSetups.FirstOrDefault();
|
||||
}
|
||||
|
||||
var result = await _aiService.AnalyzeItemAsync(request, photos, costs, avgPowderCost, aiContext, selectedBlastSetup);
|
||||
await _usageLogger.LogAsync(companyId, user?.Id ?? "", AppConstants.AiFeatures.PhotoQuote, result.Success, photos.Sum(p => p.Data.Length));
|
||||
|
||||
Reference in New Issue
Block a user