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; } }