This commit is contained in:
arys
2026-01-26 21:09:28 +05:00
parent a89566a83f
commit c8088caa1f
@@ -639,53 +639,38 @@ public class MarketingAnalysisService {
request.getBusinessNiche(), request.getRegion()); request.getBusinessNiche(), request.getRegion());
try { try {
// ------------------------------------------------------- // 1. Сбор данных (оставляем как есть, это работает нормально)
// 1. Сбор данных (Research Phase)
// -------------------------------------------------------
// Собираем данные из Google/SerpApi, чтобы у GPT был контекст (реальные сайты, статьи)
Map<String, Object> researchPack = buildResearchPack(request); Map<String, Object> researchPack = buildResearchPack(request);
// Пытаемся найти 3-5 реальных конкурентов через поиск для подстановки в промпт // Получаем список конкурентов для контекста
List<String> competitorCompanies = resolveCompetitorCompaniesForBenchmarks(request, researchPack, 5); List<String> competitorCompanies = resolveCompetitorCompaniesForBenchmarks(request, researchPack, 5);
researchPack.put("competitor_companies", competitorCompanies);
// ------------------------------------------------------- // 2. Формирование Промпта
// 2. Формирование "Идеального примера" (Structure & Benchmarks) // ВАЖНО: Мы больше не передаем заполненный пример (exampleDto).
// ------------------------------------------------------- // Мы передаем только структуру (Schema) и жесткие инструкции.
// Мы создаем Java-объект с "умными" рыбами (бенчмарками), который GPT будет использовать как шаблон.
// Это гарантирует, что структура JSON будет 1-в-1 как ждет фронтенд.
MarketingAnalysisResponseV2 exampleDto = buildAnalysisV2ExampleForPrompt(request, competitorCompanies);
// Сериализуем пример в строку, чтобы вставить в промпт
String structureExampleJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(exampleDto);
// -------------------------------------------------------
// 3. Формирование Промпта (System & User Instructions)
// -------------------------------------------------------
String systemPrompt = buildSystemPromptForKazakhstan(); String systemPrompt = buildSystemPromptForKazakhstan();
String instruction = buildDetailedInstruction(request, structureExampleJson); String instruction = buildDetailedInstruction(request, competitorCompanies);
// Собираем весь контекст для отправки // Собираем контекст (запрос + найденные данные)
String contextJson = objectMapper.writeValueAsString(Map.of( String contextJson = objectMapper.writeValueAsString(Map.of(
"user_request", request, "user_input", request,
"market_evidence_from_search", researchPack "search_data", researchPack, // Данные из Google/SerpApi
"identified_competitors", competitorCompanies
)); ));
// ------------------------------------------------------- // 3. Вызов OpenAI
// 4. Вызов AI с механизмом Retry (на случай битого JSON)
// -------------------------------------------------------
for (int attempt = 1; attempt <= 2; attempt++) { for (int attempt = 1; attempt <= 2; attempt++) {
try { try {
logger.info("Sending request to OpenAI (Attempt {}/2)...", attempt); // Используем модель с большим контекстом (gpt-4o или gpt-4-turbo)
String aiResponse = openAIAnalyticsService.generateWithInstructionWithModel( String aiResponse = openAIAnalyticsService.generateWithInstructionWithModel(
contextJson, contextJson,
instruction, instruction,
"ru", "ru",
textModelName, // GPT-4o или o1-preview "gpt-4o", // Обязательно мощная модель для аналитики
systemPrompt, systemPrompt,
16000, // Max tokens (увеличенный лимит для детального ответа) 16000,
180000L // Timeout (3 минуты) 180000L
); );
if (aiResponse == null || aiResponse.isBlank()) { if (aiResponse == null || aiResponse.isBlank()) {
@@ -693,120 +678,265 @@ public class MarketingAnalysisService {
} }
String cleaned = cleanJsonFromMarkdown(aiResponse); String cleaned = cleanJsonFromMarkdown(aiResponse);
if (cleaned != null) {
cleaned = cleaned.replace("\uFEFF", "").trim();
}
MarketingAnalysisResponseV2 parsed = objectMapper.readValue(cleaned, MarketingAnalysisResponseV2.class); MarketingAnalysisResponseV2 parsed = objectMapper.readValue(cleaned, MarketingAnalysisResponseV2.class);
replacePlaceholderCompanyKeysIfNeeded(parsed, competitorCompanies);
logger.info("<<< Analysis V2 generated successfully."); // Финальная валидация (если вдруг вернул фитнес данные - можно тут добавить проверку)
return parsed; return parsed;
} catch (Exception e) { } catch (Exception e) {
logger.error("JSON Parsing/Generation failed on attempt {}: {}", attempt, e.getMessage()); logger.error("JSON Parsing/Generation failed on attempt {}: {}", attempt, e.getMessage());
if (attempt == 2) throw e; // Если второй раз не вышло пробрасываем ошибку if (attempt == 2) throw e;
Thread.sleep(2000); // Пауза перед повтором Thread.sleep(2000);
} }
} }
throw new IllegalStateException("Failed to generate valid analysis after retries."); throw new IllegalStateException("Failed to generate analysis.");
} catch (Exception e) { } catch (Exception e) {
logger.error("generateAnalysisV2: Fatal error: {}", e.getMessage(), e); logger.error("Fatal error: {}", e.getMessage(), e);
throw new RuntimeException("Не удалось сгенерировать маркетинговый анализ V2: " + e.getMessage(), e); throw new RuntimeException(e);
} }
} }
private String buildSystemPromptForKazakhstan() { private String buildSystemPromptForKazakhstan() {
return KZ_MARKET_CONTEXT + return """
"\nФОРМАТ ОТВЕТА:\n" + РОЛЬ И МИССИЯ:
"- ТОЛЬКО валидный JSON.\n" + Ты Ведущий Партнер (Senior Partner) стратегического консалтинга уровня Big 4 (McKinsey, BCG, PwC), специализирующийся на рынке Центральной Азии и Казахстана.
"- Никакого Markdown форматирования (```json ... ```) вокруг ответа.\n" + Твой клиент требовательный инвестор, который вкладывает свои деньги. Твоя задача дать ему сухую, аналитическую, ПРАВДИВУЮ оценку рынка, а не "воду" или выдумки.
"- Ключи JSON должны быть в snake_case (например, market_overview, monthly_online_index).\n" +
"- Запрещено возвращать null в полях (особенно income_by_age) — делай экстраполяцию если нет точных данных.\n"; ТЫ МЫСЛИШЬ В ВАЛЮТЕ: KZT (Казахстанский Тенге).
=============== 1. БАЗА ЗНАНИЙ О РЫНКЕ КАЗАХСТАНА (КОНТЕКСТ) ===============
Ты обязан использовать эти факты при генерации стратегии:
1. **Экосистема Kaspi.kz это "воздух" для бизнеса:**
* Kaspi Pay/QR стандарт де-факто для приема оплат (даже на базаре).
* Kaspi Магазин главный маркетплейс для электроники, дома, одежды.
* Kaspi Red/Kredit драйвер продаж. Люди покупают в рассрочку даже кроссовки и еду. Если чек > 10 000 KZT, рассрочка обязательна.
2. **Поисковое поведение:**
* Google для "горячего" спроса и B2B услуг ("купить бетон", "юрист Алматы").
* 2GIS ГЛАВНЫЙ поисковик для офлайн-точек (рестораны, СТО, салоны). Отзывы там важнее, чем на сайте.
* Instagram это "второй Google". Люди ищут товары и услуги через поиск в Инсте. Визуал критичен.
* TikTok главный источник дешёвого охвата и виральности, но конверсия в продажу холоднее.
3. **Региональная специфика (География):**
* *Алматы:* Высокая платежеспособность, креативный класс, высокая конкуренция, пробки (важна доставка).
* *Астана:* Госсектор, клерки, холодно (сезонность!), высокий чек, но население меньше Алматы.
* *Шымкент/Юг:* Традиционный уклад, важны семейные связи, "сарафанное радио", торговля, гостеприимство. Высокая чувствительность к цене.
* *Запад (Атырау, Актау):* Нефтяной сектор, высокие зарплаты, но дорогая логистика.
4. **Экономика:**
* Реальная инфляция потребительской корзины: ~10-15%.
* Средняя ЗП: официально ~350-400к, медианная ~250к. Учитывай это при оценке емкости рынка!
* Язык: Двуязычие. Реклама на казахском языке растет в эффективности (особенно на Юге и Западе).
=============== 2. ПРОТОКОЛ ЗАЩИТЫ ОТ ШАБЛОНОВ (ANTI-HALLUCINATION) ===============
СТРОГО ЗАПРЕЩЕНО:
1. Использовать кейс "Фитнес" (1Fit, Sport Zal), если пользователь спрашивает про другую нишу.
2. Писать "+18.5% YoY", если у тебя нет данных. Генерируй реалистичный тренд для КОНКРЕТНОЙ ниши (для хлеба это +2%, для IT +25%).
3. Выдумывать несуществующие законы РК.
4. Оставлять поля `null`. Если данных нет используй метод Ферми (логическую оценку) и пиши "Оценочно ~X".
=============== 3. АНАЛИТИЧЕСКИЙ ФРЕЙМВОРК (КАК ДУМАТЬ) ===============
Перед генерацией JSON, прогони запрос через этот алгоритм:
ШАГ 1: Пойми юнит-экономику ниши.
- Если это "Недвижимость": LTV = 1-2 сделки, CAC = 100-500$+, цикл = месяцы.
- Если это "Доставка пиццы": LTV = высокий (годами), CAC = дешевый, цикл = минуты.
- Если это "Одежда": Важна сезонность, возвраты, визуальный контент.
ШАГ 2: Определи конкурентов.
- Не пиши "Сетевые лидеры". Назови реальные бренды в KZ, если знаешь (Sulpak, Technodom для техники; Magnum для еды). Если не знаешь пиши "Локальные игроки малого бизнеса".
ШАГ 3: Рассчитай метрики.
- ROI 250% возможен не везде. В стройке маржа меньше, в инфобизнесе больше. Адаптируй цифры!
=============== 4. ТЕХНИЧЕСКИЙ ФОРМАТ ОТВЕТА ===============
1. Выдай ТОЛЬКО чистый JSON. Без ```json ... ```.
2. Все ключи в `snake_case`.
3. Поле `income_by_age` должно содержать ЧИСЛА (Integer), а не строки.
4. Структура JSON должна строго соответствовать запрошенному примеру (но с ТВОИМИ данными).
Твоя репутация зависит от точности цифр и глубины инсайтов. Действуй.
""";
} }
private String buildDetailedInstruction(MarketingAnalysisRequest request, String structureExampleJson) { private String buildDetailedInstruction(MarketingAnalysisRequest request, List<String> competitors) {
String regionsList; String niche = request.getBusinessNiche();
if (request.getRegion() != null && !request.getRegion().isEmpty()) { String region = (request.getRegion() != null && !request.getRegion().isEmpty())
regionsList = String.join(", ", request.getRegion()); ? String.join(", ", request.getRegion())
} else { : "Казахстан";
regionsList = "Алматы, Астана, Шымкент, Караганда";
}
// Логика возрастных групп return """
String ageRangesInstruction = ""; Твоя задача провести глубокий маркетинговый анализ для ниши: "%s" в регионе: "%s".
if (request.getTargetAudience() != null && request.getTargetAudience().containsKey("ageRanges")) {
@SuppressWarnings("unchecked") ТЫ ОБЯЗАН ИСПОЛЬЗОВАТЬ ПРЕДОСТАВЛЕННЫЕ ДАННЫЕ ИЗ 'search_data'.
List<String> ranges = (List<String>) request.getTargetAudience().get("ageRanges"); ЕСЛИ ДАННЫХ НЕТ ИСПОЛЬЗУЙ ЛОГИКУ И ОБЩИЕ ЭКОНОМИЧЕСКИЕ ПОКАЗАТЕЛИ КАЗАХСТАНА (Инфляция, ВВП, Сезонность этой ниши).
if (ranges != null && !ranges.isEmpty()) {
ageRangesInstruction = "В полях 'age_structure' и 'income_by_age' используй СТРОГО только эти возрастные группы: " + String.join(", ", ranges) + "."; ЗАПРЕТ:
1. ЗАПРЕЩЕНО использовать примеры из фитнеса, если ниша не фитнес.
2. ЗАПРЕЩЕНО писать "+18.5%% YoY", если это не подтверждено данными. Генерируй реалистичные цифры для ЭТОЙ ниши.
3. Не возвращай Markdown. Только чистый JSON.
СТРУКТУРА ОТВЕТА (строго следуй этому формату JSON, поля должны быть заполнены аналитикой):
{
"report_title": "Стратегический анализ рынка: [Название Ниши]",
"sections": {
"market_overview": {
"title": "Обзор рынка",
"metrics": {
"online_demand_index": "Оцени спрос от 1 до 10 (строка, например 'Высокий (8/10)') на основе популярности ниши.",
"online_potential": "Трендовое описание (например, 'Стабильный рост' или 'Сезонный спад').",
"demand_growth_rate_yoy": "Процент роста за год (реалистичный для %s, например +5%% для хлеба, +30%% для AI).",
"seasonality_coefficient_spring": "Коэффициент (например, 1.2). Учитывай, когда сезон у %s.",
"seasonality_range": "Месяцы высокого сезона (например, 'Май - Август' для кондиционеров)."
},
"monthly_online_index": {
// Заполни Double значениями от 0.5 до 1.5 для каждого месяца (january...december).
// ВАЖНО: Эти цифры должны отражать РЕАЛЬНУЮ сезонность ниши "%s".
},
"geography": {
// Map<String, Integer>. Ключ - город Казахстана, Значение - индекс интереса (0-100).
// Учитывай регион "%s". Если регион вся страна - укажи топ городов.
},
"conclusion": "Аналитический вывод о состоянии рынка (2-3 предложения)."
},
"target_audience": {
"title": "Портрет целевой аудитории",
"metrics": {
"total_audience_size": "Оценка кол-ва потенциальных клиентов (число).",
"leading_segment_share": "Кто основной покупатель? (Пол/Возраст).",
"above_average_income_share": "Доля аудитории с доходом выше среднего (%%)."
},
"income_by_age": {
// Map<String, Integer>. Средний доход в KZT для групп: '18-24', '25-34', '35-44', '45+'.
// Используй реальные данные по зарплатам в Казахстане.
},
"segments": [
// List of objects {name, share}. Сегментируй аудиторию этой ниши (например 'Новички', 'Профи' или 'B2B', 'B2C').
],
"age_structure": {
// Map<String, String>. Распределение по возрастам в процентах.
},
"conclusion": "Вывод о портрете клиента."
},
"competitive_environment": {
"title": "Конкурентный ландшафт",
"metrics": {
"leader_market_share": "Оценка доли лидера рынка.",
"competition_level": "Уровень конкуренции (Низкая/Средняя/Высокая/Красный океан).",
"ad_pressure": "Давление рекламы (CPC, активность).",
"market_leader_name": "Название типа лидеров (например, 'Маркетплейсы' или 'Сетевые игроки')."
},
"competitors": [
// Используй список конкурентов из context: %s.
// Для каждого создай объект:
// {
// "name": "Имя",
// "website": "Сайт (если есть) или заглушка",
// "visibility_score": Число 0-100,
// "key_strengths": ["Сила 1", "Сила 2"],
// "price_segment": "Economy/Middle/Premium"
// }
],
"conclusion": "Вывод о конкуренции."
},
"demand_and_behavior": {
"title": "Спрос и поведение",
"metrics": {
"search_volume_growth": "Рост поисковых запросов.",
"interactions_before_purchase": "Сколько касаний нужно для покупки в нише %s (например, недвижимость - 20, еда - 1).",
"repeat_refusal_probability": "Вероятность отказа."
},
"search_query_types": {
// Типы запросов (ключ) и их доля (значение). Например: 'Информационные', 'Транзакционные'.
},
"decision_factors": {
// Факторы принятия решения (Цена, Качество, Отзывы) и их вес.
},
"reasons_for_refusal": {
// Почему не покупают? (Дорого, Долгая доставка и т.д.).
},
"conclusion": "Вывод о поведении."
},
"customer_journey": {
"title": "Путь клиента",
"funnel_metrics": {
"ad_traffic": "Примерный трафик (строка)",
"site_visitors": "Посетители",
"leads": "Лиды (число)",
"bookings": "Заказы/Записи",
"payments": "Оплаты"
// Рассчитай воронку с реалистичной конверсией для ниши %s.
},
"conversion_rates": {
"lead_to_booking": "%% конверсии",
"booking_to_payment": "%% конверсии",
"lead_to_payment_total": "Общая конверсия"
},
"funnel_bottlenecks": {
"lead_to_booking": "Главная проблема на этом этапе для ниши %s",
"booking_to_payment": "Главная проблема оплаты"
},
"conclusion": "Вывод по воронке."
},
"acquisition_channels": {
"title": "Каналы привлечения",
"metrics": {
"search_traffic_share": "%% поиска",
"average_lead_cost": "Стоимость лида в KZT (CPL). Реалистичная для ниши!",
"growth_potential": "Какой канал растет?"
},
"channel_shares": {
// Доли каналов (Instagram, Google, TikTok, Kaspi).
// ДЛЯ B2B - LinkedIn/Google. ДЛЯ B2C ТОВАРОВ - Kaspi/Instagram.
},
"channel_efficiency": {
// Map<String, {cost, status}>. Оцени эффективность каналов.
},
"conclusion": "Вывод по каналам."
},
"marketing_economics": {
"title": "Экономика маркетинга",
"metrics": {
"customer_acquisition_cost_cac": "CAC в KZT.",
"repeat_sales_conversion": "Retention rate %%.",
"roi": "Ожидаемый ROI %%."
},
"financial_performance": {
// Заполни объекты MonthYearValue ("month": "...", "year": "...")
// marketing_expenses, clients, cac, revenue_from_client, profit_from_client.
// Цифры должны биться с нишей (недвижимость - миллионы, носки - тысячи).
},
"conclusion": "Финансовый вывод."
},
"bottlenecks": {
"title": "Узкие места",
"statistics": {
// Статистика потерь (%%).
},
"details": {
"landing": ["Проблема 1", "Проблема 2"],
"lead_management": ["Проблема 1"],
"retention": ["Проблема 1"]
},
"conclusion": "Вывод по проблемам."
},
"recommendations": {
"title": "Рекомендации",
"core_pillars": [
// Список стратегий {strategy, action}.
// ДАЙ КОНКРЕТНЫЕ СОВЕТЫ ДЛЯ НИШИ %s (Например, 'Запустить Kaspi Ads' для товаров).
],
"conclusion": "Финальный совет."
} }
},
"sources": [
// Список URL из search_data
]
} }
""".formatted(niche, region, niche, niche, region, niche, competitors != null ? competitors.toString() : "[]", niche, niche, niche, niche, niche);
String template = """
Твоя роль: Ты Ведущий Маркетинговый Стратег (Senior Strategy Consultant) по рынку Казахстана.
Задача: Провести глубокий анализ для РЕАЛЬНОГО бизнеса (B2C/B2B). Никакой воды. Только факты, цифры и применимые советы.
ВХОДНЫЕ ДАННЫЕ:
Ниша: {{NICHE}}
Продукт: {{PRODUCT}}
Регионы для анализа: [{{REGIONS}}]
================================================================================
КРИТИЧЕСКИЕ ИНСТРУКЦИИ (KZ STANDARDS & NO NULLS):
0. **ЗАПРЕЩЕНО ВОЗВРАЩАТЬ NULL**:
- Каждая секция (`demand_and_behavior`, `bottlenecks`, `recommendations`) ДОЛЖНА быть заполнена.
- Если данных нет, используй экспертную оценку или бенчмарки по нише.
- НИКАКИХ ПУСТЫХ ПОЛЕЙ.
- ВСЕГДА ИСПОЛЬЗУЙ ПРАВИЛЬНЫЙ JSON, ПО ПЛАНКЕ КОТОРУЮ МЫ СКИНУЛ, И ЧТО БЫ ВСЕ ПРАВАИЛЬНО
1. **ГЕОГРАФИЯ (Market Overview):**
- Ты ОБЯЗАН включить в анализ (`geography`) ТОЛЬКО указанные города: [{{REGIONS}}].
- Если указан "Весь Казахстан" или список пуст бери Топ-5 (Алматы, Астана, Шымкент...).
- Распределение: Алматы обычно лидер (40-50%), Астана (30%), регионы меньше.
2. **КОНКУРЕНТЫ (Competitive Environment):**
- Используй данные из `market_evidence_from_search`. Найди 3-5 РЕАЛЬНЫХ игроков.
- **ВАЖНО:** В поле `name` пиши ТОЛЬКО БРЕНД (например, "Fitnation", а не "ТОО Fitnation Group"). Без кавычек и юр. форм.
- В поле `website` попытайся найти реальный домен или ссылку на Instagram.
- Оценка видимости (`visibility_score`) должна быть разной (например: 95, 82, 60, 45). Не ставь всем 100.
3. **КАНАЛЫ (Acquisition Channels):**
- Приоритет в Казахстане (B2C): **Instagram (Reels/Stories), TikTok, Kaspi (магазин/платежи), 2GIS (карты)**.
- Google Ads дорогой канал, ставь эффективность "Medium".
- `average_lead_cost`: Пиши текстом с валютой, диапазон (напр. "2500 - 4500 KZT").
4. **ЭКОНОМИКА (Marketing Economics):**
- **БЮДЖЕТ:** Рассчитывай модель для малого/среднего бизнеса (SME).
- Бюджет на маркетинг: **500 000 - 3 000 000 KZT** в месяц. НЕ БОЛЬШЕ.
- `cac` (Стоимость привлечения): Адекватная (2000 - 7000 KZT для масс-маркета услуг).
- `roi`: Реалистичный (150% - 300%). Не пиши фантастические 1000% на старте.
5. **ТИПЫ ДАННЫХ (Строго соблюдай типы):**
- `income_by_age`: Значения - ТОЛЬКО ЧИСЛА (Integer). Пример: 350000. (Никаких кавычек, запятых, "KZT").
- `monthly_online_index`: Дробные числа (0.0 - 1.0).
6. **ИСТОЧНИКИ:**
- Собери все ссылки из `evidence` и добавь авторитетные источники (stat.gov.kz, kase.kz, forbes.kz) в массив `sources` в самом конце.
================================================================================
{{AGE_INSTRUCTION}}
ФОРМАТ ОТВЕТА:
Верни СТРОГО валидный JSON, соответствующий структуре ниже.
Замени все значения `EXAMPLE__` и `-9999` на реальные аналитические данные, специфичные для ниши {{NICHE}}.
ЭТАЛОННАЯ СТРУКТУРА JSON (Следуй ей неукоснительно):
{{JSON_STRUCTURE}}
""";
return template
.replace("{{NICHE}}", request.getBusinessNiche())
.replace("{{PRODUCT}}", request.getProduct())
.replace("{{REGIONS}}", regionsList)
.replace("{{AGE_INSTRUCTION}}", ageRangesInstruction)
.replace("{{JSON_STRUCTURE}}", structureExampleJson);
} }
private String buildJsonStructureExample() { private String buildJsonStructureExample() {
return """ return """
@@ -1004,7 +1134,7 @@ public class MarketingAnalysisService {
private List<String> resolveCompetitorCompaniesForBenchmarks(MarketingAnalysisRequest request, private List<String> resolveCompetitorCompaniesForBenchmarks(MarketingAnalysisRequest request,
Map<String, Object> researchPack, Map<String, Object> researchPack,
int limit) { int limit) {
int max = Math.max(3, Math.min(limit, 15)); int max = Math.max(5, Math.min(limit, 15));
LinkedHashSet<String> names = new LinkedHashSet<>(); LinkedHashSet<String> names = new LinkedHashSet<>();
if (researchPack != null) { if (researchPack != null) {
@@ -1308,7 +1438,7 @@ public class MarketingAnalysisService {
MarketingAnalysisResponseV2.MarketOverviewMetrics moMetrics = new MarketingAnalysisResponseV2.MarketOverviewMetrics(); MarketingAnalysisResponseV2.MarketOverviewMetrics moMetrics = new MarketingAnalysisResponseV2.MarketOverviewMetrics();
moMetrics.setOnlineDemandIndex("Высокий (8/10)"); moMetrics.setOnlineDemandIndex("Высокий (8/10)");
moMetrics.setOnlinePotential("Растущий тренд (+15-20% YoY)"); moMetrics.setOnlinePotential("Растущий тренд )");
moMetrics.setDemandGrowthRateYoy("+18.5%"); moMetrics.setDemandGrowthRateYoy("+18.5%");
moMetrics.setSeasonalityCoefficientSpring("1.25 (Высокий сезон)"); moMetrics.setSeasonalityCoefficientSpring("1.25 (Высокий сезон)");
moMetrics.setSeasonalityRange("Сентябрь - Май"); moMetrics.setSeasonalityRange("Сентябрь - Май");