Lazily enrich catalog specs from TDS on first use

Specific gravity, coverage, and ~55% of cure specs aren't in the Columbia feed.
Rather than read 2,400 TDS PDFs up front, enrich a catalog item the first time
it's actually used:

- FetchTdsCureSpecsAsync now also extracts specific gravity from the TDS.
- New EnsureCatalogTdsSpecsAsync fills a catalog item's specific gravity (and
  any missing cure temp/time) from its TDS, then derives theoretical coverage
  (192.3 / (SG x mils)). No-op once specific gravity is known or when there's no
  TDS; persists to the catalog so the work is done once and benefits everyone.
- Hooked into the catalog->inventory paths (CreateIncomingFromCatalog, the
  custom-powder receive enrichment, and ReceivePowderFromCatalog) so a powder's
  full specs land on both the catalog and the new inventory record. DashboardController
  gains the AI lookup service for this.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-17 16:07:23 -04:00
parent 8401bd77e8
commit 059d94d4fe
4 changed files with 88 additions and 10 deletions
@@ -59,9 +59,17 @@ public interface IInventoryAiLookupService
Task<InventoryAiLookupResult> ScanLabelAsync(string base64Image, string mediaType);
/// <summary>
/// Fetches a Technical Data Sheet URL and extracts cure temperature and cure time.
/// Called when the main lookup found a TDS URL but cure specs are still missing.
/// Fetches a Technical Data Sheet URL and extracts cure temperature, cure time, and specific
/// gravity. Called when the main lookup found a TDS URL but specs are still missing.
/// Returns Success=false silently (no UI error) when the TDS is a PDF or unreachable.
/// </summary>
Task<InventoryAiLookupResult> FetchTdsCureSpecsAsync(string tdsUrl, string? colorName);
/// <summary>
/// Lazily fills a powder catalog item's specific gravity (and any missing cure specs) from its
/// TDS the first time it's needed, then derives theoretical coverage. No-op when specific
/// gravity is already known or no TDS URL is present. Persists the enrichment to the catalog so
/// it's done once and benefits every future use. Returns true if anything was filled.
/// </summary>
Task<bool> EnsureCatalogTdsSpecsAsync(PowderCoating.Core.Entities.PowderCatalogItem catalog);
}