diff --git a/src/PowderCoating.Infrastructure/Services/AiCatalogPriceCheckService.cs b/src/PowderCoating.Infrastructure/Services/AiCatalogPriceCheckService.cs
index 28d7d47..dc13479 100644
--- a/src/PowderCoating.Infrastructure/Services/AiCatalogPriceCheckService.cs
+++ b/src/PowderCoating.Infrastructure/Services/AiCatalogPriceCheckService.cs
@@ -238,6 +238,8 @@ public class AiCatalogPriceCheckService : IAiCatalogPriceCheckService
sb.AppendLine("- \"medium\" — reasonable assumptions were possible");
sb.AppendLine("- \"low\" — item is too vague to estimate reliably (e.g., 'Custom Part', 'Job Special')");
sb.AppendLine();
+ sb.AppendLine("The \"category\" field contains the full path, e.g. \"Cerakote > Firearms\" or \"Powder Coat > Wheels\". Use this to determine the coating process — Cerakote items have a very different cost profile than standard powder coat (different equipment, cure times, and market rates). Price accordingly.");
+ sb.AppendLine();
sb.AppendLine("If the item already has an ApproximateArea or EstimatedMinutes, use those instead of guessing.");
sb.AppendLine();
sb.AppendLine("IMPORTANT: Keep responses concise to avoid truncation. Limit assumptions to 20 words max. Limit reasoning to 25 words max.");
diff --git a/src/PowderCoating.Web/Controllers/CatalogItemsController.cs b/src/PowderCoating.Web/Controllers/CatalogItemsController.cs
index 5eb3ee6..f9c7b22 100644
--- a/src/PowderCoating.Web/Controllers/CatalogItemsController.cs
+++ b/src/PowderCoating.Web/Controllers/CatalogItemsController.cs
@@ -938,8 +938,8 @@ namespace PowderCoating.Web.Controllers
r => r.CompanyId == currentUser.CompanyId);
var report = existing.OrderByDescending(r => r.RunAt).FirstOrDefault();
- var activeItems = await _unitOfWork.CatalogItems.FindAsync(ci => ci.IsActive);
- ViewBag.ActiveItemCount = activeItems.Count();
+ var pricedItems = await _unitOfWork.CatalogItems.FindAsync(ci => ci.IsActive && ci.DefaultPrice > 0);
+ ViewBag.ActiveItemCount = pricedItems.Count();
CatalogPriceCheckReportDto? dto = null;
if (report != null)
@@ -990,16 +990,23 @@ namespace PowderCoating.Web.Controllers
try
{
- // Load all active catalog items with categories
+ // Load active catalog items with a real price — skip $0 items (placeholders,
+ // category headers, etc.) since there's no pricing to evaluate.
var items = (await _unitOfWork.CatalogItems.FindAsync(
- ci => ci.IsActive, false, ci => ci.Category)).ToList();
+ ci => ci.IsActive && ci.DefaultPrice > 0, false, ci => ci.Category)).ToList();
if (items.Count == 0)
{
- TempData["Warning"] = "No active catalog items to analyze.";
+ TempData["Warning"] = "No priced catalog items to analyze. Add prices to your catalog items first.";
return RedirectToAction(nameof(AiPriceCheck));
}
+ // Load all categories so we can build full paths (e.g. "Cerakote > Firearms").
+ // The full path gives Claude the coating-type context it needs — an item in
+ // "Firearms" under "Cerakote" costs very differently than one under "Powder Coat".
+ var allCategories = (await _unitOfWork.CatalogCategories.GetAllAsync())
+ .ToDictionary(c => c.Id);
+
// Load company operating costs
var costs = (await _unitOfWork.CompanyOperatingCosts.FindAsync(
c => c.CompanyId == currentUser.CompanyId)).FirstOrDefault();
@@ -1012,7 +1019,7 @@ namespace PowderCoating.Web.Controllers
Id = i.Id,
Name = i.Name,
Description = i.Description,
- CategoryName = i.Category?.Name ?? "Uncategorized",
+ CategoryName = BuildCategoryPath(i.CategoryId, allCategories),
CurrentPrice = i.DefaultPrice,
ApproximateAreaSqFt = i.ApproximateArea,
EstimatedMinutes = i.DefaultEstimatedMinutes,
@@ -1083,6 +1090,25 @@ namespace PowderCoating.Web.Controllers
$"Blaster ${c.SandblasterCostPerHour:F2}/hr | Booth ${c.CoatingBoothCostPerHour:F2}/hr | " +
$"Powder ${c.PowderCostPerSqFt:F2}/sqft | " +
$"{(c.PricingMode == "margin" ? "Margin" : "Markup")} {c.MarkupOrMarginPercent:F1}%";
+
+ ///
+ /// Walks up the category parent chain to produce a full path like "Cerakote > Firearms",
+ /// giving Claude the coating-type context it needs for accurate pricing analysis.
+ ///
+ private static string BuildCategoryPath(int? categoryId, Dictionary all)
+ {
+ if (categoryId == null) return "Uncategorized";
+ var parts = new List();
+ var current = all.GetValueOrDefault(categoryId.Value);
+ while (current != null)
+ {
+ parts.Insert(0, current.Name);
+ current = current.ParentCategoryId.HasValue
+ ? all.GetValueOrDefault(current.ParentCategoryId.Value)
+ : null;
+ }
+ return parts.Count > 0 ? string.Join(" > ", parts) : "Uncategorized";
+ }
}
// Helper class for hierarchical display