Apply TDS cure fallback and SDS/TDS URL filling to AI Lookup button

Previously these enrichments only ran in the label scanner path (ScanLabel).
The AI Lookup button and AiAugmentFromUrl went through separate code that
returned raw LookupAsync / LookupByUrlAsync results with no TDS fallback
and no SDS/TDS URL propagation to the form.

- InventoryController.ApplyTdsCureFallbackAsync: new private helper that
  checks whether cure temp or cure time is still null after the primary
  lookup, and if a TDS URL was returned calls FetchTdsCureSpecsAsync to
  fill the gap. Mutates the result in place so callers just return it.
- AiLookup: calls ApplyTdsCureFallbackAsync after LookupAsync succeeds.
- AiAugmentFromUrl: calls ApplyTdsCureFallbackAsync after LookupByUrlAsync.
- ScanLabel: replaced the inline TDS fallback block with a call to the
  same helper (merges catalog TDS URL into aiResult first so the helper
  sees the best available URL).
- _InventoryColorFamilyScripts.cshtml: added fillDocUrl() helper that fills
  field-sdsurl / field-tdsurl inputs and shows their open-link buttons when
  the AI lookup returns sdsUrl / tdsUrl. These fields existed in the form
  but were never populated by the AI Lookup button.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-03 20:19:43 -04:00
parent 4182286a31
commit 145da7b5c4
2 changed files with 46 additions and 16 deletions
@@ -679,6 +679,7 @@ public class InventoryController : Controller
return Json(new { success = false, errorMessage = "AI Inventory Assist is not enabled for your account. Contact your administrator." });
var result = await _aiLookupService.LookupAsync(manufacturer, colorName, colorCode, partNumber);
if (result.Success) await ApplyTdsCureFallbackAsync(result, colorName);
return Json(result);
}
@@ -701,9 +702,31 @@ public class InventoryController : Controller
return Json(new { success = false, errorMessage = "No product URL provided." });
var result = await _aiLookupService.LookupByUrlAsync(productUrl, colorName);
if (result.Success) await ApplyTdsCureFallbackAsync(result, colorName);
return Json(result);
}
/// <summary>
/// If cure temperature or cure time is still missing after the primary lookup but a TDS URL
/// was returned, fetches that page and asks Claude to extract only the cure schedule.
/// Mutates <paramref name="result"/> in place; silently no-ops on failure so callers
/// can always return the result even if the TDS fetch does not help.
/// </summary>
private async Task ApplyTdsCureFallbackAsync(InventoryAiLookupResult result, string? colorName)
{
if ((result.CureTemperatureF == null || result.CureTimeMinutes == null)
&& !string.IsNullOrEmpty(result.TdsUrl))
{
_logger.LogInformation("Cure specs missing after lookup; trying TDS at {Url}", result.TdsUrl);
var tds = await _aiLookupService.FetchTdsCureSpecsAsync(result.TdsUrl, colorName);
if (tds.Success)
{
if (result.CureTemperatureF == null) result.CureTemperatureF = tds.CureTemperatureF;
if (result.CureTimeMinutes == null) result.CureTimeMinutes = tds.CureTimeMinutes;
}
}
}
/// <summary>
/// Accepts a base64 label photo or a decoded QR URL from the in-browser label scanner,
/// runs it through Claude (vision for photos, URL-fetch for QR), searches the platform
@@ -834,22 +857,14 @@ public class InventoryController : Controller
}
}
// If cure specs are still missing but we have a TDS URL, fetch it and try to extract
// cure temperature and cure time. Most TDS pages are HTML; PDFs fail silently.
var resolvedCureTemp = catalogMatch?.CureTemperatureF ?? aiResult.CureTemperatureF;
var resolvedCureTime = catalogMatch?.CureTimeMinutes ?? aiResult.CureTimeMinutes;
var resolvedTdsUrl = catalogMatch?.TdsUrl ?? aiResult.TdsUrl;
if ((resolvedCureTemp == null || resolvedCureTime == null) && !string.IsNullOrEmpty(resolvedTdsUrl))
{
_logger.LogInformation("Cure specs missing after main lookup; trying TDS at {Url}", resolvedTdsUrl);
var tdsResult = await _aiLookupService.FetchTdsCureSpecsAsync(resolvedTdsUrl, colorName);
if (tdsResult.Success)
{
if (resolvedCureTemp == null) aiResult.CureTemperatureF = tdsResult.CureTemperatureF;
if (resolvedCureTime == null) aiResult.CureTimeMinutes = tdsResult.CureTimeMinutes;
}
}
// If cure specs are still missing but we have a TDS URL, try reading it.
// Prefer the catalog's TDS URL if present; the catalog record is more reliable.
// Temporarily merge the catalog TDS URL into aiResult so ApplyTdsCureFallbackAsync
// sees the best available URL and writes the result back into aiResult.
if (catalogMatch?.CureTemperatureF != null) aiResult.CureTemperatureF = catalogMatch.CureTemperatureF;
if (catalogMatch?.CureTimeMinutes != null) aiResult.CureTimeMinutes = catalogMatch.CureTimeMinutes;
aiResult.TdsUrl ??= catalogMatch?.TdsUrl;
await ApplyTdsCureFallbackAsync(aiResult, colorName);
// Check if this product already exists in the tenant's inventory.
// Match by ManufacturerPartNumber first (most precise); fall back to color name + manufacturer.