using Microsoft.EntityFrameworkCore;
using PowderCoating.Core.Entities;
namespace PowderCoating.Infrastructure.Services;
public partial class SeedDataService
{
///
/// Seeds 8 demo records and attaches them to the first
/// 8 eligible records, marking those items as AI-analysed.
///
///
///
/// "Eligible" means SurfaceAreaSqFt > 0, not a labor item, and not already
/// linked to a prediction. This ensures the seeder is safe to run even if a partial seed
/// left some items pre-linked.
///
///
/// Each prediction record captures a realistic AI analysis: predicted surface area,
/// estimated minutes, complexity tier, unit price, confidence level, reasoning text, and
/// comma-separated AI tags. Three of the eight items have UserOverrodeEstimate = true
/// to demonstrate the override-tracking feature on the AI Accuracy report.
///
///
/// Items are updated in-place with IsAiItem = true and the FK
/// AiPredictionId pointing to the new prediction. SaveChangesAsync is called
/// per item so any single FK conflict (unlikely in a fresh seed) does not abort the others.
///
/// Idempotency: returns 0 immediately if any AiItemPrediction records already exist for
/// the company.
///
/// The tenant company to seed predictions for.
/// Number of prediction records inserted, or 0 if already seeded.
private async Task SeedAiPredictionsAsync(Company company)
{
var existingCount = await _context.Set()
.IgnoreQueryFilters()
.CountAsync(p => p.CompanyId == company.Id && !p.IsDeleted);
if (existingCount > 0)
return 0;
// Grab the first 8 eligible quote items ordered by id for determinism
var quoteItems = await _context.Set()
.IgnoreQueryFilters()
.Include(qi => qi.Quote)
.Where(qi => qi.CompanyId == company.Id
&& !qi.IsDeleted
&& qi.SurfaceAreaSqFt > 0
&& !qi.IsLaborItem
&& qi.AiPredictionId == null)
.OrderBy(qi => qi.Id)
.Take(8)
.ToListAsync();
if (quoteItems.Count == 0)
return 0;
// Per-slot prediction specs — deterministic, varied across complexity/confidence tiers.
// PredictedSqFt is intentionally close but NOT identical to the actual item SqFt so the
// AI Accuracy report shows realistic prediction deltas.
var specs = new[]
{
// slot 0 — complex automotive, AI nailed it
( sqft: 13.5m, mins: 88, complexity: "Complex", confidence: "High",
price: 125.00m, tags: "automotive,tubular,custom", rounds: 1, overrode: false,
reasoning: "Detected a tubular motorcycle frame with multiple weld joints. High complexity due to intricate geometry and masking requirements around bearing surfaces. Confidence high — similar frames appear frequently in training data." ),
// slot 1 — wheel set, quick read, accepted as-is
( sqft: 11.2m, mins: 42, complexity: "Simple", confidence: "High",
price: 98.00m, tags: "automotive,wheels,aluminum", rounds: 1, overrode: false,
reasoning: "Four aluminum wheels, uniform shape, minimal masking needed. Straightforward batch candidate for the main oven. Estimated surface area based on standard 18\" wheel profile." ),
// slot 2 — bumper job, user bumped sqft slightly
( sqft: 14.8m, mins: 62, complexity: "Moderate", confidence: "Medium",
price: 135.00m, tags: "automotive,bumper,off-road", rounds: 2, overrode: true,
reasoning: "Steel off-road bumper and rock sliders. Moderate complexity — flat stock with mounting tabs. Second image round requested for accurate rock slider dimensions. User adjusted surface area slightly after physical measurement." ),
// slot 3 — large gate, low confidence, user corrected price
( sqft: 34.2m, mins: 195, complexity: "Complex", confidence: "Low",
price: 310.00m, tags: "architectural,gate,ornamental", rounds: 2, overrode: true,
reasoning: "Wrought iron entry gate with decorative scrollwork. Low confidence due to depth ambiguity in photos — scrollwork surface area is difficult to estimate from images alone. Recommend physical measurement before finalising price. User overrode unit price after measuring on-site." ),
// slot 4 — patio furniture, solid read
( sqft: 22.8m, mins: 52, complexity: "Moderate", confidence: "High",
price: 195.00m, tags: "furniture,outdoor,patio", rounds: 1, overrode: false,
reasoning: "Six-piece patio furniture set: four chairs, one table, one side table. Powder-coated tubular steel, standard outdoor finish. Good photo coverage — confidence high. Recommend Textured Beige or Satin Bronze for exterior durability." ),
// slot 5 — handrail, accepted price
( sqft: 39.0m, mins: 118, complexity: "Moderate", confidence: "High",
price: 342.00m, tags: "architectural,handrail,railing", rounds: 1, overrode: false,
reasoning: "40-foot steel handrail system, square tube construction. Consistent profile makes area calculation straightforward. Standard Gloss Black most common finish for this application — confirmed with customer." ),
// slot 6 — brake calipers, small & simple
( sqft: 3.8m, mins: 30, complexity: "Simple", confidence: "High",
price: 65.00m, tags: "automotive,brake,caliper", rounds: 1, overrode: false,
reasoning: "Set of four brake calipers, cast iron with machined mating surfaces. Masking required on piston bores and bleed nipples. Candy Red most requested finish. High confidence — calipers are a common item with well-established pricing." ),
// slot 7 — bicycle frame, two-round conversation
( sqft: 6.1m, mins: 65, complexity: "Moderate", confidence: "Medium",
price: 82.00m, tags: "recreational,bicycle,frame", rounds: 2, overrode: true,
reasoning: "Road bicycle frame, aluminium alloy. Second image round needed to assess cable routing channels and dropout geometry. Moderate complexity due to small-radius bends. User adjusted surface area after AI initially underestimated top-tube length." )
};
var seeded = 0;
for (int i = 0; i < quoteItems.Count && i < specs.Length; i++)
{
var item = quoteItems[i];
var s = specs[i];
var prediction = new AiItemPrediction
{
PredictedSurfaceAreaSqFt = s.sqft,
PredictedMinutes = s.mins,
PredictedComplexity = s.complexity,
PredictedUnitPrice = s.price,
Confidence = s.confidence,
Reasoning = s.reasoning,
AiTags = s.tags,
ConversationRounds = s.rounds,
UserOverrodeEstimate = s.overrode,
CompanyId = company.Id,
CreatedAt = item.CreatedAt
};
await _context.Set().AddAsync(prediction);
await _context.SaveChangesAsync();
seeded++;
// Mark the quote item as AI-analysed and link the prediction
item.IsAiItem = true;
item.AiPredictionId = prediction.Id;
item.AiTags = s.tags;
await _context.SaveChangesAsync();
}
return seeded;
}
}