using IntelligenceApi.Models; namespace IntelligenceApi.Engines; /// /// Derives audience recommendations from raw census data for a ZCTA. /// /// This logic was previously embedded in the Gateway's DemographicsController /// as BuildMarketAnalysis(). It belongs here — IntelligenceApi owns all /// recommendation and analysis logic; the Gateway is a thin proxy. /// /// Registered as a singleton: stateless, no IO. /// public sealed class DemographicsAnalyzer { public DemographicAnalysisResponse Analyze(DemographicAnalysisRequest request) { var c = request.Census; var zcta = request.Zcta; // ── Age skew ────────────────────────────────────────────────────────── var youngPct = c.Pct18to24 + c.Pct25to34; var maturePct = c.Pct55to64 + c.Pct65plus; string ageSkew; if (youngPct > maturePct + 10) ageSkew = "young"; else if (maturePct > youngPct + 10) ageSkew = "mature"; else ageSkew = "balanced"; // ── Recommended age chips ───────────────────────────────────────────── // Include brackets with meaningful population share. var ageRanges = new List(); if (c.Pct18to24 >= 10) ageRanges.Add("AGE_18_24"); if (c.Pct25to34 >= 12) ageRanges.Add("AGE_25_34"); if (c.Pct35to44 >= 12) ageRanges.Add("AGE_35_44"); if (c.Pct45to54 >= 12) ageRanges.Add("AGE_45_54"); if (c.Pct55to64 >= 10) ageRanges.Add("AGE_55_64"); if (c.Pct65plus >= 12) ageRanges.Add("AGE_65_UP"); // Fallback: if no bracket clears the threshold, default to prime brackets if (ageRanges.Count == 0) { ageRanges.Add("AGE_25_34"); ageRanges.Add("AGE_35_44"); } // ── Recommended income chips ────────────────────────────────────────── var incomes = c.MedianIncome switch { > 100_000 => new List { "TOP_10", "TOP_11_20" }, > 75_000 => new List { "TOP_11_20", "TOP_21_30" }, > 50_000 => new List { "TOP_21_30", "TOP_31_40" }, _ => new List { "TOP_41_50", "LOWER_50" } }; // ── Human-readable insights ─────────────────────────────────────────── var insights = new List(); if (c.TotalPopulation > 0) insights.Add($"{c.TotalPopulation:N0} people"); if (c.MedianIncome > 0) insights.Add($"Median income ${c.MedianIncome:N0}"); if (c.PctBachelorPlus > 0) insights.Add($"{c.PctBachelorPlus}% college-educated"); if (c.PctOwnerOccupied > 55) insights.Add($"{c.PctOwnerOccupied}% homeowners"); else if (c.PctRenterOccupied > 55) insights.Add($"{c.PctRenterOccupied}% renters"); if (c.PctFamilyHouseholds > 60) insights.Add($"{c.PctFamilyHouseholds}% families"); else if (c.PctLivingAlone > 35) insights.Add($"{c.PctLivingAlone}% single-person households"); insights.Add(ageSkew switch { "young" => "Skews younger (18–34)", "mature" => "Skews older (55+)", _ => "Balanced age distribution" }); return new DemographicAnalysisResponse { Ok = true, Zcta = zcta, Census = c, Recommendations = new AudienceRecommendations { AgeRanges = ageRanges, Incomes = incomes, AgeSkew = ageSkew, MarketScope = "local" // single ZIP is always local scope }, Insights = insights }; } }