From 145da7b5c4525332fad2deee1048174083209f7c Mon Sep 17 00:00:00 2001 From: Scott Pouliot Date: Sun, 3 May 2026 20:19:43 -0400 Subject: [PATCH] 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 --- .../Controllers/InventoryController.cs | 47 ++++++++++++------- .../_InventoryColorFamilyScripts.cshtml | 15 ++++++ 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/src/PowderCoating.Web/Controllers/InventoryController.cs b/src/PowderCoating.Web/Controllers/InventoryController.cs index 0cadef0..34305e0 100644 --- a/src/PowderCoating.Web/Controllers/InventoryController.cs +++ b/src/PowderCoating.Web/Controllers/InventoryController.cs @@ -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); } + /// + /// 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 in place; silently no-ops on failure so callers + /// can always return the result even if the TDS fetch does not help. + /// + 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; + } + } + } + /// /// 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. diff --git a/src/PowderCoating.Web/Views/Inventory/_InventoryColorFamilyScripts.cshtml b/src/PowderCoating.Web/Views/Inventory/_InventoryColorFamilyScripts.cshtml index 7b968d4..4dba788 100644 --- a/src/PowderCoating.Web/Views/Inventory/_InventoryColorFamilyScripts.cshtml +++ b/src/PowderCoating.Web/Views/Inventory/_InventoryColorFamilyScripts.cshtml @@ -450,6 +450,21 @@ aiFilledImage = true; } + // SDS / TDS document URLs — fill inputs and show open-link buttons + const fillDocUrl = (fieldId, linkId, url, label) => { + if (!url) return; + const el = document.getElementById(fieldId); + const link = document.getElementById(linkId); + if (el && (forceRefill || !el.value.trim())) { + el.value = url; + filled.push(label); + if (!aiFilledFields.includes(fieldId)) aiFilledFields.push(fieldId); + } + if (link) { link.href = url; link.classList.remove('d-none'); } + }; + fillDocUrl('field-sdsurl', 'field-sdsurl-link', data.sdsUrl, 'SDS'); + fillDocUrl('field-tdsurl', 'field-tdsurl-link', data.tdsUrl, 'TDS'); + // Build a persistent "needs more info" tip if key identity fields are still unknown const missingHints = []; if (!data.manufacturer && !document.getElementById('field-manufacturer')?.value?.trim())