Add AI Catalog Price Check feature
Claude reviews every active catalog item against the shop's own operating costs and returns a per-item verdict (below-cost / thin-margin / high / ok) with a suggested price range, cost floor, and assumptions. - New entity: CatalogPriceCheckReport (JSON blob, archived per company) - New service: IAiCatalogPriceCheckService / AiCatalogPriceCheckService batches items 25 at a time to stay within model context limits - Two new controller actions: GET AiPriceCheck (view report) + POST RunAiPriceCheck - AiPriceCheck view: summary cards (counts by verdict), color-coded item cards with Edit Price link, assumptions detail, and loading spinner on submit - AI Price Check button added to catalog Index header - Migration AddCatalogPriceCheckReport applied Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
namespace PowderCoating.Application.DTOs.AI;
|
||||
|
||||
// ── Input ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Lightweight representation of a catalog item sent to Claude for analysis.</summary>
|
||||
public class CatalogItemForPriceCheck
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? Description { get; set; }
|
||||
public string CategoryName { get; set; } = string.Empty;
|
||||
public decimal CurrentPrice { get; set; }
|
||||
public decimal? ApproximateAreaSqFt { get; set; }
|
||||
public int? EstimatedMinutes { get; set; }
|
||||
public bool RequiresSandblasting { get; set; }
|
||||
public bool RequiresMasking { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>Operating cost summary injected into the Claude system prompt.</summary>
|
||||
public class ShopOperatingCostSummary
|
||||
{
|
||||
public decimal LaborRatePerHour { get; set; }
|
||||
public decimal OvenCostPerHour { get; set; }
|
||||
public decimal SandblasterCostPerHour { get; set; }
|
||||
public decimal CoatingBoothCostPerHour { get; set; }
|
||||
public decimal PowderCostPerSqFt { get; set; }
|
||||
public decimal ShopSuppliesRatePercent { get; set; }
|
||||
public decimal MarkupOrMarginPercent { get; set; }
|
||||
public string PricingMode { get; set; } = "markup"; // "markup" or "margin"
|
||||
public decimal ShopMinimumCharge { get; set; }
|
||||
public string? AiContextProfile { get; set; }
|
||||
}
|
||||
|
||||
// ── Per-Item Result ───────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Verdict on a single catalog item's price health.</summary>
|
||||
public class CatalogItemPriceVerdict
|
||||
{
|
||||
public int CatalogItemId { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public decimal CurrentPrice { get; set; }
|
||||
|
||||
/// <summary>Assumptions Claude made about size/complexity to estimate costs.</summary>
|
||||
public string Assumptions { get; set; } = string.Empty;
|
||||
|
||||
public decimal EstimatedSqFtMin { get; set; }
|
||||
public decimal EstimatedSqFtMax { get; set; }
|
||||
public int EstimatedMinutesMin { get; set; }
|
||||
public int EstimatedMinutesMax { get; set; }
|
||||
|
||||
/// <summary>Calculated cost floor using the shop's own rates.</summary>
|
||||
public decimal CostFloor { get; set; }
|
||||
|
||||
/// <summary>ok | low | high | below-cost</summary>
|
||||
public string Verdict { get; set; } = "ok";
|
||||
|
||||
public decimal SuggestedPriceMin { get; set; }
|
||||
public decimal SuggestedPriceMax { get; set; }
|
||||
|
||||
/// <summary>high | medium | low</summary>
|
||||
public string Confidence { get; set; } = "medium";
|
||||
|
||||
public string Reasoning { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
// ── Report ────────────────────────────────────────────────────────────────────
|
||||
|
||||
public class CatalogPriceCheckReportDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public DateTime RunAt { get; set; }
|
||||
public int ItemsChecked { get; set; }
|
||||
public int BelowCostCount { get; set; }
|
||||
public int LowMarginCount { get; set; }
|
||||
public int HighPriceCount { get; set; }
|
||||
public int OkCount { get; set; }
|
||||
public List<CatalogItemPriceVerdict> Results { get; set; } = new();
|
||||
public string OperatingCostsSummary { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using PowderCoating.Application.DTOs.AI;
|
||||
|
||||
namespace PowderCoating.Application.Interfaces;
|
||||
|
||||
public interface IAiCatalogPriceCheckService
|
||||
{
|
||||
/// <summary>
|
||||
/// Analyzes the provided catalog items against the shop's operating costs and returns
|
||||
/// a verdict for each item. Items are batched into groups of 25 to stay within Claude's
|
||||
/// context limits. Returns null results for any item that could not be analyzed.
|
||||
/// </summary>
|
||||
Task<List<CatalogItemPriceVerdict>> AnalyzeAsync(
|
||||
List<CatalogItemForPriceCheck> items,
|
||||
ShopOperatingCostSummary costs,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
Reference in New Issue
Block a user