diff --git a/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java b/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java index 23cda67..0ece076 100644 --- a/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java +++ b/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java @@ -668,46 +668,84 @@ public class MarketingAnalysisService { + " - проценты/строки формата \"76/100\", \"+27%\", \"1.34x\" => string\n" + " - monthly_online_index.* => number (double)\n" + " - funnel_metrics.leads/bookings/payments => integer\n" - + "5) Пиши на русском языке.\n\n" - + "Ниже пример правильной структуры (пример для ориентира; цифры должны быть основаны на evidence):\n" + + "5) Пиши на русском языке.\n" + + "6) НИКОГДА не копируй значения из примера. В примере намеренно стоят плейсхолдеры, " + + "которые начинаются с 'EXAMPLE__' и числа -9999/-9999.0 — в твоем ответе их быть НЕ должно.\n\n" + + "Ниже пример правильной структуры (пример только для формы/полей; все значения должны быть заменены на значения из evidence):\n" + exampleJson; String contextJson = objectMapper.writeValueAsString(Map.of( "request", request, "evidence", researchPack)); - // 3) Call OpenAI (sleep to reduce 429 rate-limit bursts) - try { - Thread.sleep(2000); - } catch (InterruptedException ie) { - Thread.currentThread().interrupt(); + // 3) Call OpenAI (sleep to reduce 429 rate-limit bursts) + guard against + // copying placeholders + String lastCleaned = null; + for (int attempt = 1; attempt <= 2; attempt++) { + try { + Thread.sleep(2000); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + + String attemptInstruction = instruction; + if (attempt > 1) { + attemptInstruction = instruction + + "\n\nКРИТИЧЕСКИ: В ПРЕДЫДУЩЕМ ОТВЕТЕ БЫЛИ ПЛЕЙСХОЛДЕРЫ." + + " Перегенерируй ответ, полностью заменив любые строки, содержащие 'EXAMPLE__'," + + " и любые числа -9999 / -9999.0 на реальные значения." + + " Верни ТОЛЬКО JSON."; + } + + String aiResponse = openAIAnalyticsService.generateWithInstructionWithModel( + contextJson, + attemptInstruction, + "ru", + textModelName, + systemPrompt, + 15000, + 180000L); + + if (aiResponse == null || aiResponse.isBlank()) { + throw new IllegalStateException("OpenAI returned empty response for analysis v2"); + } + + // 4) Clean + validate placeholders + parse + String cleaned = cleanJsonFromMarkdown(aiResponse); + if (cleaned != null) { + cleaned = cleaned.replace("\uFEFF", "").trim(); + } + lastCleaned = cleaned; + + if (containsV2ExamplePlaceholders(cleaned)) { + logger.warn("generateAnalysisV2: LLM output still contains placeholders (attempt {}/2). Retrying.", + attempt); + continue; + } + + return objectMapper.readValue(cleaned, MarketingAnalysisResponseV2.class); } - String aiResponse = openAIAnalyticsService.generateWithInstructionWithModel( - contextJson, - instruction, - "ru", - textModelName, - systemPrompt, - 15000, - 180000L); - - if (aiResponse == null || aiResponse.isBlank()) { - throw new IllegalStateException("OpenAI returned empty response for analysis v2"); - } - - // 4) Clean + parse - String cleaned = cleanJsonFromMarkdown(aiResponse); - if (cleaned != null) { - cleaned = cleaned.replace("\uFEFF", "").trim(); - } - return objectMapper.readValue(cleaned, MarketingAnalysisResponseV2.class); + throw new IllegalStateException( + "OpenAI returned response containing placeholder tokens for analysis v2: " + lastCleaned); } catch (Exception e) { logger.error("generateAnalysisV2: Failed to generate/parse v2 response: {}", e.getMessage(), e); throw new RuntimeException("Не удалось сгенерировать marketing analysis v2", e); } } + private boolean containsV2ExamplePlaceholders(String json) { + if (json == null || json.isBlank()) { + return true; + } + // Explicit placeholder tokens from buildAnalysisV2ExampleForPrompt() + if (json.contains("EXAMPLE__")) { + return true; + } + // Numeric placeholders from buildAnalysisV2ExampleForPrompt() + return json.contains("-9999"); + } + /** * Генерирует анализ v2 и сохраняет его в MongoDB * @@ -797,213 +835,217 @@ public class MarketingAnalysisService { */ private MarketingAnalysisResponseV2 buildAnalysisV2ExampleForPrompt() { MarketingAnalysisResponseV2 response = new MarketingAnalysisResponseV2(); - response.setReportTitle("Маркетинговый анализ для интернет-продвижения"); + response.setReportTitle("EXAMPLE__REPORT_TITLE"); MarketingAnalysisResponseV2.Sections sections = new MarketingAnalysisResponseV2.Sections(); // market_overview MarketingAnalysisResponseV2.MarketOverview marketOverview = new MarketingAnalysisResponseV2.MarketOverview(); - marketOverview.setTitle("Обзор рынка"); + marketOverview.setTitle("EXAMPLE__MARKET_OVERVIEW_TITLE"); MarketingAnalysisResponseV2.MarketOverviewMetrics moMetrics = new MarketingAnalysisResponseV2.MarketOverviewMetrics(); - moMetrics.setOnlineDemandIndex("76/100"); - moMetrics.setOnlinePotential("Высокий"); - moMetrics.setDemandGrowthRateYoy("+27%"); - moMetrics.setSeasonalityCoefficientSpring("1.34x"); - moMetrics.setSeasonalityRange("1.29 - 1.38"); + moMetrics.setOnlineDemandIndex("EXAMPLE__ONLINE_DEMAND_INDEX"); + moMetrics.setOnlinePotential("EXAMPLE__ONLINE_POTENTIAL"); + moMetrics.setDemandGrowthRateYoy("EXAMPLE__DEMAND_GROWTH_RATE_YOY"); + moMetrics.setSeasonalityCoefficientSpring("EXAMPLE__SEASONALITY_COEFFICIENT_SPRING"); + moMetrics.setSeasonalityRange("EXAMPLE__SEASONALITY_RANGE"); marketOverview.setMetrics(moMetrics); MarketingAnalysisResponseV2.MonthlyOnlineIndex monthlyOnlineIndex = new MarketingAnalysisResponseV2.MonthlyOnlineIndex(); - monthlyOnlineIndex.setMarch(1.32); - monthlyOnlineIndex.setApril(1.34); - monthlyOnlineIndex.setMay(1.38); + monthlyOnlineIndex.setMarch(-9999.0); + monthlyOnlineIndex.setApril(-9999.0); + monthlyOnlineIndex.setMay(-9999.0); marketOverview.setMonthlyOnlineIndex(monthlyOnlineIndex); - marketOverview.setGeography("Казахстан (анализ по регионам представлен на тепловой карте)"); - marketOverview.setConclusion( - "Онлайн-спрос показывает устойчивый рост в течение года, весной достигая пика. Интернет-продвижение даст максимальный эффект."); + marketOverview.setGeography("EXAMPLE__GEOGRAPHY"); + marketOverview.setConclusion("EXAMPLE__MARKET_OVERVIEW_CONCLUSION"); sections.setMarketOverview(marketOverview); // target_audience MarketingAnalysisResponseV2.TargetAudience targetAudience = new MarketingAnalysisResponseV2.TargetAudience(); - targetAudience.setTitle("Целевая аудитория"); + targetAudience.setTitle("EXAMPLE__TARGET_AUDIENCE_TITLE"); MarketingAnalysisResponseV2.TargetAudienceMetrics taMetrics = new MarketingAnalysisResponseV2.TargetAudienceMetrics(); - taMetrics.setTotalAudienceSize("1.8 млн человек"); - taMetrics.setLeadingSegmentShare("29%"); - taMetrics.setAboveAverageIncomeShare("35%"); + taMetrics.setTotalAudienceSize("EXAMPLE__TOTAL_AUDIENCE_SIZE"); + taMetrics.setLeadingSegmentShare("EXAMPLE__LEADING_SEGMENT_SHARE"); + taMetrics.setAboveAverageIncomeShare("EXAMPLE__ABOVE_AVERAGE_INCOME_SHARE"); targetAudience.setMetrics(taMetrics); targetAudience.setSegments(List.of( - new MarketingAnalysisResponseV2.AudienceSegment("Женщины, 25–34 года", "29%"), - new MarketingAnalysisResponseV2.AudienceSegment("Мужчины, 25–44 года", "24%"), - new MarketingAnalysisResponseV2.AudienceSegment("Доход выше среднего", "35%"))); + new MarketingAnalysisResponseV2.AudienceSegment("EXAMPLE__AUDIENCE_SEGMENT_1", + "EXAMPLE__AUDIENCE_SEGMENT_SHARE_1"), + new MarketingAnalysisResponseV2.AudienceSegment("EXAMPLE__AUDIENCE_SEGMENT_2", + "EXAMPLE__AUDIENCE_SEGMENT_SHARE_2"), + new MarketingAnalysisResponseV2.AudienceSegment("EXAMPLE__AUDIENCE_SEGMENT_3", + "EXAMPLE__AUDIENCE_SEGMENT_SHARE_3"))); Map ageStructure = new LinkedHashMap<>(); - ageStructure.put("18-24", "10%"); - ageStructure.put("25-34", "29%"); - ageStructure.put("35-44", "Высокая доля (визуально на графике)"); - ageStructure.put("45-54", "15-24%"); - ageStructure.put("55_plus", "Низкая доля"); + ageStructure.put("18-24", "EXAMPLE__AGE_18_24_SHARE"); + ageStructure.put("25-34", "EXAMPLE__AGE_25_34_SHARE"); + ageStructure.put("35-44", "EXAMPLE__AGE_35_44_SHARE"); + ageStructure.put("45-54", "EXAMPLE__AGE_45_54_SHARE"); + ageStructure.put("55_plus", "EXAMPLE__AGE_55_PLUS_SHARE"); targetAudience.setAgeStructure(ageStructure); - targetAudience.setConclusion( - "Основную ценность представляют мужчины и женщины 25–44 лет с доходом выше среднего (до 35% аудитории)."); + targetAudience.setConclusion("EXAMPLE__TARGET_AUDIENCE_CONCLUSION"); sections.setTargetAudience(targetAudience); // competitive_environment MarketingAnalysisResponseV2.CompetitiveEnvironment competitiveEnvironment = new MarketingAnalysisResponseV2.CompetitiveEnvironment(); - competitiveEnvironment.setTitle("Конкурентная среда"); + competitiveEnvironment.setTitle("EXAMPLE__COMPETITIVE_ENVIRONMENT_TITLE"); MarketingAnalysisResponseV2.CompetitiveEnvironmentMetrics ceMetrics = new MarketingAnalysisResponseV2.CompetitiveEnvironmentMetrics(); - ceMetrics.setMarketShareTraffic("16%"); - ceMetrics.setVisibilityRank("#3"); - ceMetrics.setCompetitorShareInContextAds("27%"); - ceMetrics.setBenchmarkAstana("13-25%"); + ceMetrics.setMarketShareTraffic("EXAMPLE__MARKET_SHARE_TRAFFIC"); + ceMetrics.setVisibilityRank("EXAMPLE__VISIBILITY_RANK"); + ceMetrics.setCompetitorShareInContextAds("EXAMPLE__COMPETITOR_SHARE_IN_CONTEXT_ADS"); + ceMetrics.setBenchmarkAstana("EXAMPLE__BENCHMARK_ASTANA"); competitiveEnvironment.setMetrics(ceMetrics); Map digitalVisibilityBenchmarks = new LinkedHashMap<>(); - digitalVisibilityBenchmarks.put("competitor_a", 1050); - digitalVisibilityBenchmarks.put("your_business", 600); - digitalVisibilityBenchmarks.put("lead", 300); - digitalVisibilityBenchmarks.put("payment", 450); - digitalVisibilityBenchmarks.put("others", 680); + digitalVisibilityBenchmarks.put("competitor_a", -9999); + digitalVisibilityBenchmarks.put("your_business", -9999); + digitalVisibilityBenchmarks.put("lead", -9999); + digitalVisibilityBenchmarks.put("payment", -9999); + digitalVisibilityBenchmarks.put("others", -9999); competitiveEnvironment.setDigitalVisibilityBenchmarks(digitalVisibilityBenchmarks); Map> competitorStrengths = new LinkedHashMap<>(); - competitorStrengths.put("your_business", List.of("Цена", "Оффер")); - competitorStrengths.put("competitor_a", List.of("Отзывы", "Рекламный бюджет")); - competitorStrengths.put("competitor_b", List.of("Контент", "Соцсети", "Экспириенс")); - competitorStrengths.put("competitor_c", List.of("Контент", "Сайт")); + competitorStrengths.put("your_business", List.of("EXAMPLE__STRENGTH_1", "EXAMPLE__STRENGTH_2")); + competitorStrengths.put("competitor_a", List.of("EXAMPLE__COMP_A_STRENGTH_1", "EXAMPLE__COMP_A_STRENGTH_2")); + competitorStrengths.put("competitor_b", List.of("EXAMPLE__COMP_B_STRENGTH_1", "EXAMPLE__COMP_B_STRENGTH_2")); + competitorStrengths.put("competitor_c", List.of("EXAMPLE__COMP_C_STRENGTH_1", "EXAMPLE__COMP_C_STRENGTH_2")); competitiveEnvironment.setCompetitorStrengths(competitorStrengths); - competitiveEnvironment.setConclusion( - "Ваш бизнес занимает 3-е место по видимости. Основные преимущества конкурентов — соцдоказательства и контент. Ценовая борьба не является оптимальной."); + competitiveEnvironment.setConclusion("EXAMPLE__COMPETITIVE_ENVIRONMENT_CONCLUSION"); sections.setCompetitiveEnvironment(competitiveEnvironment); // demand_and_behavior MarketingAnalysisResponseV2.DemandAndBehavior demandAndBehavior = new MarketingAnalysisResponseV2.DemandAndBehavior(); - demandAndBehavior.setTitle("Спрос и поведение"); + demandAndBehavior.setTitle("EXAMPLE__DEMAND_AND_BEHAVIOR_TITLE"); MarketingAnalysisResponseV2.DemandAndBehaviorMetrics dabMetrics = new MarketingAnalysisResponseV2.DemandAndBehaviorMetrics(); - dabMetrics.setSearchVolumeGrowth("+62%"); - dabMetrics.setInteractionsBeforePurchase("3-4"); - dabMetrics.setRepeatRefusalProbability("54%"); + dabMetrics.setSearchVolumeGrowth("EXAMPLE__SEARCH_VOLUME_GROWTH"); + dabMetrics.setInteractionsBeforePurchase("EXAMPLE__INTERACTIONS_BEFORE_PURCHASE"); + dabMetrics.setRepeatRefusalProbability("EXAMPLE__REPEAT_REFUSAL_PROBABILITY"); demandAndBehavior.setMetrics(dabMetrics); Map searchQueryTypes = new LinkedHashMap<>(); - searchQueryTypes.put("direct_interest_catalog", "52%"); - searchQueryTypes.put("question_based_articles", "28%"); - searchQueryTypes.put("competitors_reviews", "13%"); - searchQueryTypes.put("problem_solving_hot_promo", "7%"); + searchQueryTypes.put("direct_interest_catalog", "EXAMPLE__QUERY_TYPE_DIRECT"); + searchQueryTypes.put("question_based_articles", "EXAMPLE__QUERY_TYPE_QUESTION"); + searchQueryTypes.put("competitors_reviews", "EXAMPLE__QUERY_TYPE_COMPETITORS"); + searchQueryTypes.put("problem_solving_hot_promo", "EXAMPLE__QUERY_TYPE_PROMO"); demandAndBehavior.setSearchQueryTypes(searchQueryTypes); Map decisionFactors = new LinkedHashMap<>(); - decisionFactors.put("promo_discount", "43%"); - decisionFactors.put("review_recommendation", "36%"); - decisionFactors.put("warranty_quality", "33%"); - decisionFactors.put("convenience_service", "25%"); - decisionFactors.put("availability", "21%"); - decisionFactors.put("price", "19%"); + decisionFactors.put("promo_discount", "EXAMPLE__FACTOR_PROMO"); + decisionFactors.put("review_recommendation", "EXAMPLE__FACTOR_REVIEWS"); + decisionFactors.put("warranty_quality", "EXAMPLE__FACTOR_WARRANTY"); + decisionFactors.put("convenience_service", "EXAMPLE__FACTOR_CONVENIENCE"); + decisionFactors.put("availability", "EXAMPLE__FACTOR_AVAILABILITY"); + decisionFactors.put("price", "EXAMPLE__FACTOR_PRICE"); demandAndBehavior.setDecisionFactors(decisionFactors); Map reasonsForRefusal = new LinkedHashMap<>(); - reasonsForRefusal.put("high_price", "37%"); - reasonsForRefusal.put("no_reviews_social_proof", "34%"); - reasonsForRefusal.put("choice_difficulty_unclear_offer", "29%"); + reasonsForRefusal.put("high_price", "EXAMPLE__REFUSAL_HIGH_PRICE"); + reasonsForRefusal.put("no_reviews_social_proof", "EXAMPLE__REFUSAL_NO_REVIEWS"); + reasonsForRefusal.put("choice_difficulty_unclear_offer", "EXAMPLE__REFUSAL_UNCLEAR_OFFER"); demandAndBehavior.setReasonsForRefusal(reasonsForRefusal); - demandAndBehavior.setConclusion( - "Основной трафик приходит из поиска. Для отклика ключевую роль играют акции и отзывы."); + demandAndBehavior.setConclusion("EXAMPLE__DEMAND_AND_BEHAVIOR_CONCLUSION"); sections.setDemandAndBehavior(demandAndBehavior); // customer_journey MarketingAnalysisResponseV2.CustomerJourney customerJourney = new MarketingAnalysisResponseV2.CustomerJourney(); - customerJourney.setTitle("Путь клиента"); + customerJourney.setTitle("EXAMPLE__CUSTOMER_JOURNEY_TITLE"); MarketingAnalysisResponseV2.FunnelMetrics funnelMetrics = new MarketingAnalysisResponseV2.FunnelMetrics(); - funnelMetrics.setAdTraffic("Реклама"); - funnelMetrics.setSiteVisitors("Сайт / Лендинг"); - funnelMetrics.setLeads(5600); - funnelMetrics.setBookings(1200); - funnelMetrics.setPayments(680); + funnelMetrics.setAdTraffic("EXAMPLE__FUNNEL_AD_TRAFFIC"); + funnelMetrics.setSiteVisitors("EXAMPLE__FUNNEL_SITE_VISITORS"); + funnelMetrics.setLeads(-9999); + funnelMetrics.setBookings(-9999); + funnelMetrics.setPayments(-9999); customerJourney.setFunnelMetrics(funnelMetrics); MarketingAnalysisResponseV2.ConversionRates conversionRates = new MarketingAnalysisResponseV2.ConversionRates(); - conversionRates.setLeadToBooking("21%"); - conversionRates.setBookingToPayment("57%"); - conversionRates.setLeadToPaymentTotal("12%"); + conversionRates.setLeadToBooking("EXAMPLE__LEAD_TO_BOOKING"); + conversionRates.setBookingToPayment("EXAMPLE__BOOKING_TO_PAYMENT"); + conversionRates.setLeadToPaymentTotal("EXAMPLE__LEAD_TO_PAYMENT_TOTAL"); customerJourney.setConversionRates(conversionRates); MarketingAnalysisResponseV2.FunnelBottlenecks funnelBottlenecks = new MarketingAnalysisResponseV2.FunnelBottlenecks(); - funnelBottlenecks.setLeadToBooking("Медленная скорость ответа"); - funnelBottlenecks.setBookingToPayment("Недостаточно касаний / напоминаний"); + funnelBottlenecks.setLeadToBooking("EXAMPLE__BOTTLENECK_LEAD_TO_BOOKING"); + funnelBottlenecks.setBookingToPayment("EXAMPLE__BOTTLENECK_BOOKING_TO_PAYMENT"); customerJourney.setFunnelBottlenecks(funnelBottlenecks); - customerJourney.setConclusion( - "Основные потери происходят на этапе лид -> запись (конверсия 21%) из-за медленных ответов менеджеров."); + customerJourney.setConclusion("EXAMPLE__CUSTOMER_JOURNEY_CONCLUSION"); sections.setCustomerJourney(customerJourney); // acquisition_channels MarketingAnalysisResponseV2.AcquisitionChannels acquisitionChannels = new MarketingAnalysisResponseV2.AcquisitionChannels(); - acquisitionChannels.setTitle("Каналы привлечения"); + acquisitionChannels.setTitle("EXAMPLE__ACQUISITION_CHANNELS_TITLE"); MarketingAnalysisResponseV2.AcquisitionChannelsMetrics acMetrics = new MarketingAnalysisResponseV2.AcquisitionChannelsMetrics(); - acMetrics.setSearchTrafficShare("48%"); - acMetrics.setAverageLeadCost("37 P"); - acMetrics.setGrowthPotential("+21%"); + acMetrics.setSearchTrafficShare("EXAMPLE__SEARCH_TRAFFIC_SHARE"); + acMetrics.setAverageLeadCost("EXAMPLE__AVERAGE_LEAD_COST"); + acMetrics.setGrowthPotential("EXAMPLE__GROWTH_POTENTIAL"); acquisitionChannels.setMetrics(acMetrics); Map channelShares = new LinkedHashMap<>(); - channelShares.put("seo", "48%"); - channelShares.put("yandex_direct", "31%"); - channelShares.put("retargeting", "13%"); - channelShares.put("vkontakte", "6%"); + channelShares.put("seo", "EXAMPLE__SHARE_SEO"); + channelShares.put("yandex_direct", "EXAMPLE__SHARE_YANDEX_DIRECT"); + channelShares.put("retargeting", "EXAMPLE__SHARE_RETARGETING"); + channelShares.put("vkontakte", "EXAMPLE__SHARE_VKONTAKTE"); acquisitionChannels.setChannelShares(channelShares); Map channelEfficiency = new LinkedHashMap<>(); - channelEfficiency.put("seo", new MarketingAnalysisResponseV2.ChannelEfficiency("25 P", "Эффективен")); - channelEfficiency.put("yandex", new MarketingAnalysisResponseV2.ChannelEfficiency("45 P", "Эффективен")); + channelEfficiency.put("seo", + new MarketingAnalysisResponseV2.ChannelEfficiency("EXAMPLE__CPL_SEO", "EXAMPLE__EFF_SEO")); + channelEfficiency.put("yandex", + new MarketingAnalysisResponseV2.ChannelEfficiency("EXAMPLE__CPL_YANDEX", "EXAMPLE__EFF_YANDEX")); channelEfficiency.put("retargeting", - new MarketingAnalysisResponseV2.ChannelEfficiency("40 P", "Удовлетворительно")); - channelEfficiency.put("vkontakte", new MarketingAnalysisResponseV2.ChannelEfficiency("57 P", "Завышено")); - channelEfficiency.put("youtube", new MarketingAnalysisResponseV2.ChannelEfficiency("63 P", "Завышено")); - channelEfficiency.put("instagram", new MarketingAnalysisResponseV2.ChannelEfficiency("77 P", "Завышено")); + new MarketingAnalysisResponseV2.ChannelEfficiency("EXAMPLE__CPL_RETARGETING", + "EXAMPLE__EFF_RETARGETING")); + channelEfficiency.put("vkontakte", + new MarketingAnalysisResponseV2.ChannelEfficiency("EXAMPLE__CPL_VK", "EXAMPLE__EFF_VK")); + channelEfficiency.put("youtube", + new MarketingAnalysisResponseV2.ChannelEfficiency("EXAMPLE__CPL_YOUTUBE", "EXAMPLE__EFF_YOUTUBE")); + channelEfficiency.put("instagram", + new MarketingAnalysisResponseV2.ChannelEfficiency("EXAMPLE__CPL_INSTAGRAM", "EXAMPLE__EFF_INSTAGRAM")); acquisitionChannels.setChannelEfficiency(channelEfficiency); - acquisitionChannels.setConclusion( - "Основной источник лидов — SEO. VK, Youtube и Instagram требуют оптимизации из-за завышенной стоимости лида."); + acquisitionChannels.setConclusion("EXAMPLE__ACQUISITION_CHANNELS_CONCLUSION"); sections.setAcquisitionChannels(acquisitionChannels); // marketing_economics MarketingAnalysisResponseV2.MarketingEconomics marketingEconomics = new MarketingAnalysisResponseV2.MarketingEconomics(); - marketingEconomics.setTitle("Экономика маркетинга"); + marketingEconomics.setTitle("EXAMPLE__MARKETING_ECONOMICS_TITLE"); MarketingAnalysisResponseV2.MarketingEconomicsMetrics meMetrics = new MarketingAnalysisResponseV2.MarketingEconomicsMetrics(); - meMetrics.setCustomerAcquisitionCostCac("2900 P"); - meMetrics.setRepeatSalesConversion("32%"); - meMetrics.setRoi("3.5x"); + meMetrics.setCustomerAcquisitionCostCac("EXAMPLE__CAC"); + meMetrics.setRepeatSalesConversion("EXAMPLE__REPEAT_SALES_CONVERSION"); + meMetrics.setRoi("EXAMPLE__ROI"); marketingEconomics.setMetrics(meMetrics); MarketingAnalysisResponseV2.FinancialPerformance financialPerformance = new MarketingAnalysisResponseV2.FinancialPerformance(); financialPerformance - .setMarketingExpenses(new MarketingAnalysisResponseV2.MonthYearValue("1.97 млн", "23.6 млн")); - financialPerformance.setClients(new MarketingAnalysisResponseV2.MonthYearValue("680", "8160")); - financialPerformance.setCac(new MarketingAnalysisResponseV2.MonthYearValue("2900 P", "2900 P")); + .setMarketingExpenses( + new MarketingAnalysisResponseV2.MonthYearValue("EXAMPLE__MARKETING_EXPENSES_MONTH", + "EXAMPLE__MARKETING_EXPENSES_YEAR")); + financialPerformance.setClients(new MarketingAnalysisResponseV2.MonthYearValue("EXAMPLE__CLIENTS_MONTH", + "EXAMPLE__CLIENTS_YEAR")); financialPerformance - .setRevenueFromClient(new MarketingAnalysisResponseV2.MonthYearValue("10.200 P", "23.040 P")); - financialPerformance.setProfitFromClient(new MarketingAnalysisResponseV2.MonthYearValue("7.300 P", "16.720 P")); + .setCac(new MarketingAnalysisResponseV2.MonthYearValue("EXAMPLE__CAC_MONTH", "EXAMPLE__CAC_YEAR")); + financialPerformance + .setRevenueFromClient(new MarketingAnalysisResponseV2.MonthYearValue("EXAMPLE__REV_PER_CLIENT_MONTH", + "EXAMPLE__REV_PER_CLIENT_YEAR")); + financialPerformance + .setProfitFromClient(new MarketingAnalysisResponseV2.MonthYearValue("EXAMPLE__PROFIT_PER_CLIENT_MONTH", + "EXAMPLE__PROFIT_PER_CLIENT_YEAR")); marketingEconomics.setFinancialPerformance(financialPerformance); - marketingEconomics.setConclusion( - "Экономика digital-маркетинга эффективна: возврат инвестиций 3.5х. Точки роста — повторные продажи и удержание."); + marketingEconomics.setConclusion("EXAMPLE__MARKETING_ECONOMICS_CONCLUSION"); sections.setMarketingEconomics(marketingEconomics); // bottlenecks MarketingAnalysisResponseV2.Bottlenecks bottlenecks = new MarketingAnalysisResponseV2.Bottlenecks(); - bottlenecks.setTitle("Проблемы и узкие места"); + bottlenecks.setTitle("EXAMPLE__BOTTLENECKS_TITLE"); Map statistics = new LinkedHashMap<>(); - statistics.put("landing_page_issues", "51%"); - statistics.put("slow_manager_responses", "60%"); - statistics.put("repeat_interaction_refusal", "36%"); + statistics.put("landing_page_issues", "EXAMPLE__STAT_LANDING_PAGE_ISSUES"); + statistics.put("slow_manager_responses", "EXAMPLE__STAT_SLOW_MANAGER_RESPONSES"); + statistics.put("repeat_interaction_refusal", "EXAMPLE__STAT_REPEAT_INTERACTION_REFUSAL"); bottlenecks.setStatistics(statistics); MarketingAnalysisResponseV2.BottlenecksDetails details = new MarketingAnalysisResponseV2.BottlenecksDetails(); - details.setLanding(List.of("Низкая конверсия (2%)", "Проблемы с формой записи", "Уход посетителей")); + details.setLanding(List.of("EXAMPLE__LANDING_ISSUE_1", "EXAMPLE__LANDING_ISSUE_2", "EXAMPLE__LANDING_ISSUE_3")); details.setLeadManagement( - List.of("Медленные ответы (30-40+ мин)", "Недостаток напоминаний", "Пропущенные лиды")); - details.setRetention(List.of("Недостаток повторных предложений", "Нет прогрева", "Слабая работа с CRM")); + List.of("EXAMPLE__LEAD_MGMT_ISSUE_1", "EXAMPLE__LEAD_MGMT_ISSUE_2", "EXAMPLE__LEAD_MGMT_ISSUE_3")); + details.setRetention( + List.of("EXAMPLE__RETENTION_ISSUE_1", "EXAMPLE__RETENTION_ISSUE_2", "EXAMPLE__RETENTION_ISSUE_3")); bottlenecks.setDetails(details); - bottlenecks.setConclusion( - "Теряются десятки процентов лидов из-за медленных ответов менеджеров и слабой ретаргетинговой работы."); + bottlenecks.setConclusion("EXAMPLE__BOTTLENECKS_CONCLUSION"); sections.setBottlenecks(bottlenecks); // recommendations MarketingAnalysisResponseV2.Recommendations recommendations = new MarketingAnalysisResponseV2.Recommendations(); - recommendations.setTitle("Рекомендации (итоги)"); + recommendations.setTitle("EXAMPLE__RECOMMENDATIONS_TITLE"); recommendations.setCorePillars(List.of( - new MarketingAnalysisResponseV2.CorePillar("Фокус на эффективных каналах", - "Перераспределение бюджета на каналы с устойчивым спросом (Яндекс/SEO)"), - new MarketingAnalysisResponseV2.CorePillar("Увеличение повторных касаний", - "Усиление удерживающих кампаний и серий касаний с клиентами"), - new MarketingAnalysisResponseV2.CorePillar("Приоритет высокоценных аудиторий", - "Смещение акцента на платёжеспособные сегменты и B2B"), - new MarketingAnalysisResponseV2.CorePillar("Рост эффективности трафика", - "Снижение зависимости от дорогих источников и оптимизация каналов"))); - recommendations.setConclusion( - "Рекомендации фокусируют усилия на эффективности интернет-продвижения и служат базой для следующих шагов."); + new MarketingAnalysisResponseV2.CorePillar("EXAMPLE__PILLAR_1_TITLE", "EXAMPLE__PILLAR_1_DESC"), + new MarketingAnalysisResponseV2.CorePillar("EXAMPLE__PILLAR_2_TITLE", "EXAMPLE__PILLAR_2_DESC"), + new MarketingAnalysisResponseV2.CorePillar("EXAMPLE__PILLAR_3_TITLE", "EXAMPLE__PILLAR_3_DESC"), + new MarketingAnalysisResponseV2.CorePillar("EXAMPLE__PILLAR_4_TITLE", "EXAMPLE__PILLAR_4_DESC"))); + recommendations.setConclusion("EXAMPLE__RECOMMENDATIONS_CONCLUSION"); sections.setRecommendations(recommendations); response.setSections(sections);