.
This commit is contained in:
@@ -83,33 +83,69 @@ API для генерации маркетингового анализа был
|
||||
|
||||
### 3. `targetAudience` (обязательное)
|
||||
|
||||
**Описание**: Детальное описание целевой аудитории. Это поле заменяет старое поле `client` и должно содержать более подробную информацию.
|
||||
**Описание**: Структурированное описание целевой аудитории в формате JSON. Позволяет выбрать гендер, возрастные диапазоны и типы аудитории. Можно выбрать несколько вариантов в каждой категории.
|
||||
|
||||
**Валидация**:
|
||||
|
||||
- Минимальная длина: 3 символа
|
||||
- Максимальная длина: 300 символов
|
||||
- Разрешены любые символы
|
||||
- Должно быть объектом JSON с полями `genders`, `ageRanges`, `types`
|
||||
- Хотя бы одно поле должно быть заполнено и содержать непустой массив
|
||||
- Каждое поле должно содержать только допустимые значения
|
||||
|
||||
**Структура**:
|
||||
|
||||
```json
|
||||
{
|
||||
"genders": ["Женщины", "Мужчины"],
|
||||
"ageRanges": ["20-40", "25-45"],
|
||||
"types": ["Семьи", "Молодёжь"]
|
||||
}
|
||||
```
|
||||
|
||||
**Допустимые значения**:
|
||||
|
||||
- `genders`: `["Женщины", "Мужчины"]` - можно выбрать один или оба
|
||||
- `ageRanges`: `["20-40", "25-45", "18-25", "40-60", "60+"]` - можно выбрать один или несколько диапазонов
|
||||
- `types`: `["Семьи", "Молодёжь", "Все подряд"]` - можно выбрать один или несколько типов
|
||||
|
||||
**Примеры**:
|
||||
|
||||
```json
|
||||
"targetAudience": "Малый и средний бизнес, владельцы интернет-магазинов в возрасте 30-50 лет"
|
||||
"targetAudience": "Стартапы и технологические компании, нуждающиеся в быстрой разработке MVP"
|
||||
"targetAudience": "Частные лица, желающие создать личный бренд в социальных сетях"
|
||||
"targetAudience": {
|
||||
"genders": ["Женщины"],
|
||||
"ageRanges": ["20-40"],
|
||||
"types": ["Молодёжь"]
|
||||
}
|
||||
```
|
||||
|
||||
**Примечание**: В отличие от старого поля `client`, которое принимало только фиксированные значения, `targetAudience` принимает свободный текст для более гибкого описания.
|
||||
```json
|
||||
"targetAudience": {
|
||||
"genders": ["Женщины", "Мужчины"],
|
||||
"ageRanges": ["25-45", "40-60"],
|
||||
"types": ["Семьи"]
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
"targetAudience": {
|
||||
"genders": ["Мужчины"],
|
||||
"ageRanges": ["18-25"],
|
||||
"types": ["Молодёжь", "Все подряд"]
|
||||
}
|
||||
```
|
||||
|
||||
**Примечание**: Можно выбрать несколько вариантов в каждой категории. Все выбранные значения будут отражены в анализе.
|
||||
|
||||
---
|
||||
|
||||
### 4. `region` (обязательное)
|
||||
|
||||
**Описание**: Регион (город Казахстана), в котором работает бизнес. Это поле заменяет старое поле `location`.
|
||||
**Описание**: Регионы (города Казахстана), в которых работает бизнес. Можно выбрать один или несколько регионов. Это поле заменяет старое поле `location`.
|
||||
|
||||
**Валидация**:
|
||||
|
||||
- Должно быть одним из допустимых городов Казахстана
|
||||
- Должно быть массивом строк
|
||||
- Минимум один регион должен быть выбран
|
||||
- Каждый регион должен быть одним из допустимых городов Казахстана
|
||||
- Проверка выполняется через валидатор `@ValidRegion`
|
||||
|
||||
**Допустимые значения** (точное совпадение):
|
||||
@@ -138,12 +174,21 @@ API для генерации маркетингового анализа был
|
||||
**Примеры**:
|
||||
|
||||
```json
|
||||
"region": "Алматы"
|
||||
"region": "Астана"
|
||||
"region": "Шымкент"
|
||||
"region": ["Алматы"]
|
||||
```
|
||||
|
||||
**Важно**: Значение должно точно совпадать с одним из допустимых городов (регистр важен).
|
||||
```json
|
||||
"region": ["Алматы", "Астана", "Шымкент"]
|
||||
```
|
||||
|
||||
```json
|
||||
"region": ["Астана", "Караганда"]
|
||||
```
|
||||
|
||||
**Важно**:
|
||||
- Значения должны точно совпадать с допустимыми городами (регистр важен)
|
||||
- Можно выбрать все регионы, перечислив их в массиве
|
||||
- Все выбранные регионы будут отражены в анализе
|
||||
|
||||
---
|
||||
|
||||
@@ -244,15 +289,41 @@ API для генерации маркетингового анализа был
|
||||
|
||||
### 9. `analysisType` (обязательное)
|
||||
|
||||
**Описание**: Тип анализа, который необходимо провести. Поле осталось без изменений.
|
||||
**Описание**: Тип(ы) анализа, которые необходимо провести. Можно выбрать один или несколько типов анализа. При выборе нескольких типов будет создан отдельный анализ для каждого типа.
|
||||
|
||||
**Валидация**:
|
||||
|
||||
- Должно быть массивом строк
|
||||
- Минимум один тип анализа должен быть выбран
|
||||
- Каждый тип должен быть одним из допустимых значений
|
||||
- Проверка выполняется через валидатор `@ValidAnalysisType`
|
||||
|
||||
**Допустимые значения**:
|
||||
|
||||
- `"РЫНОК"` - Анализ рынка
|
||||
- `"КОНКУРЕНТЫ"` - Анализ конкурентов
|
||||
- `"ЦА"` - Анализ целевой аудитории
|
||||
- `"КАНАЛЫ"` - Анализ маркетинговых каналов
|
||||
- `"SWOT"` - SWOT-анализ
|
||||
- `"РЫНОК"` - Анализ рынка (размер рынка, динамика роста, сегменты, тренды)
|
||||
- `"КОНКУРЕНТЫ"` - Анализ конкурентов (основные конкуренты, их сильные/слабые стороны, позиционирование)
|
||||
- `"ЦА"` - Анализ целевой аудитории (демография, психография, потребности, поведение)
|
||||
- `"КАНАЛЫ"` - Анализ маркетинговых каналов (эффективность каналов, рекомендации по выбору)
|
||||
- `"SWOT"` - SWOT-анализ (сильные стороны, слабые стороны, возможности, угрозы)
|
||||
|
||||
**Примеры**:
|
||||
|
||||
```json
|
||||
"analysisType": ["РЫНОК"]
|
||||
```
|
||||
|
||||
```json
|
||||
"analysisType": ["РЫНОК", "КОНКУРЕНТЫ", "ЦА"]
|
||||
```
|
||||
|
||||
```json
|
||||
"analysisType": ["РЫНОК", "КОНКУРЕНТЫ", "ЦА", "КАНАЛЫ", "SWOT"]
|
||||
```
|
||||
|
||||
**Важно**:
|
||||
- При выборе нескольких типов анализа API создаст отдельные записи анализа для каждого типа
|
||||
- В ответе будет возвращен массив с информацией о каждом созданном анализе
|
||||
- Каждый анализ будет обрабатываться независимо и иметь свой статус
|
||||
|
||||
---
|
||||
|
||||
@@ -260,17 +331,61 @@ API для генерации маркетингового анализа был
|
||||
|
||||
### POST `/api/marketing/analysis/start`
|
||||
|
||||
**Пример 1: Один тип анализа, один регион**
|
||||
|
||||
```json
|
||||
{
|
||||
"businessNiche": "E-commerce платформы",
|
||||
"product": "Разработка мобильных приложений для интернет-магазинов",
|
||||
"targetAudience": "Малый и средний бизнес, владельцы интернет-магазинов в возрасте 30-50 лет, нуждающиеся в мобильных решениях",
|
||||
"region": "Алматы",
|
||||
"targetAudience": {
|
||||
"genders": ["Женщины"],
|
||||
"ageRanges": ["25-45"],
|
||||
"types": ["Молодёжь"]
|
||||
},
|
||||
"region": ["Алматы"],
|
||||
"goal": "Увеличить количество клиентов на 50% за следующие 6 месяцев и выйти на рынок соседних регионов",
|
||||
"detailLevel": "СТАНДАРТНО",
|
||||
"strongSide": "Опытная команда разработчиков с 10+ летним опытом, быстрая доставка проектов, использование современных технологий",
|
||||
"weakSide": "Ограниченный маркетинговый бюджет, недостаточная узнаваемость бренда в новых регионах",
|
||||
"analysisType": "РЫНОК"
|
||||
"analysisType": ["РЫНОК"]
|
||||
}
|
||||
```
|
||||
|
||||
**Пример 2: Несколько типов анализа, несколько регионов**
|
||||
|
||||
```json
|
||||
{
|
||||
"businessNiche": "E-commerce платформы",
|
||||
"product": "Разработка мобильных приложений для интернет-магазинов",
|
||||
"targetAudience": {
|
||||
"genders": ["Женщины", "Мужчины"],
|
||||
"ageRanges": ["25-45", "40-60"],
|
||||
"types": ["Семьи", "Молодёжь"]
|
||||
},
|
||||
"region": ["Алматы", "Астана", "Шымкент"],
|
||||
"goal": "Увеличить количество клиентов на 50% за следующие 6 месяцев и выйти на рынок соседних регионов",
|
||||
"detailLevel": "СТАНДАРТНО",
|
||||
"strongSide": "Опытная команда разработчиков с 10+ летним опытом, быстрая доставка проектов",
|
||||
"weakSide": "Ограниченный маркетинговый бюджет",
|
||||
"analysisType": ["РЫНОК", "КОНКУРЕНТЫ", "ЦА"]
|
||||
}
|
||||
```
|
||||
|
||||
**Пример 3: Все типы анализа**
|
||||
|
||||
```json
|
||||
{
|
||||
"businessNiche": "E-commerce платформы",
|
||||
"product": "Разработка мобильных приложений для интернет-магазинов",
|
||||
"targetAudience": {
|
||||
"genders": ["Женщины", "Мужчины"],
|
||||
"ageRanges": ["20-40", "25-45"],
|
||||
"types": ["Семьи", "Молодёжь", "Все подряд"]
|
||||
},
|
||||
"region": ["Алматы", "Астана"],
|
||||
"goal": "Увеличить количество клиентов на 50% за следующие 6 месяцев",
|
||||
"detailLevel": "ПОДРОБНО",
|
||||
"analysisType": ["РЫНОК", "КОНКУРЕНТЫ", "ЦА", "КАНАЛЫ", "SWOT"]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -281,16 +396,22 @@ API для генерации маркетингового анализа был
|
||||
### JavaScript/TypeScript
|
||||
|
||||
```typescript
|
||||
interface TargetAudience {
|
||||
genders?: ('Женщины' | 'Мужчины')[];
|
||||
ageRanges?: ('20-40' | '25-45' | '18-25' | '40-60' | '60+')[];
|
||||
types?: ('Семьи' | 'Молодёжь' | 'Все подряд')[];
|
||||
}
|
||||
|
||||
interface MarketingAnalysisRequest {
|
||||
businessNiche: string;
|
||||
product: string;
|
||||
targetAudience: string;
|
||||
region: string;
|
||||
targetAudience: TargetAudience;
|
||||
region: string[];
|
||||
goal: string;
|
||||
detailLevel: 'КРАТКО' | 'СТАНДАРТНО' | 'ПОДРОБНО';
|
||||
strongSide?: string;
|
||||
weakSide?: string;
|
||||
analysisType: 'РЫНОК' | 'КОНКУРЕНТЫ' | 'ЦА' | 'КАНАЛЫ' | 'SWOT';
|
||||
analysisType: ('РЫНОК' | 'КОНКУРЕНТЫ' | 'ЦА' | 'КАНАЛЫ' | 'SWOT')[];
|
||||
}
|
||||
|
||||
// Список допустимых регионов
|
||||
@@ -323,7 +444,7 @@ const DETAIL_LEVELS = ['КРАТКО', 'СТАНДАРТНО', 'ПОДРОБНО
|
||||
// Функция для отправки запроса
|
||||
async function startMarketingAnalysis(
|
||||
data: MarketingAnalysisRequest
|
||||
): Promise<string> {
|
||||
): Promise<MarketingAnalysisResponse | MarketingAnalysisResponse[]> {
|
||||
const response = await fetch(
|
||||
'https://api.konturai.kz/api/marketing/analysis/start',
|
||||
{
|
||||
@@ -342,21 +463,43 @@ async function startMarketingAnalysis(
|
||||
throw new Error(result.error?.message || 'Failed to start analysis');
|
||||
}
|
||||
|
||||
return result.data.analysisId;
|
||||
// Если выбрано несколько типов анализа, result.data будет массивом
|
||||
return result.data;
|
||||
}
|
||||
|
||||
interface MarketingAnalysisResponse {
|
||||
analysisId: string;
|
||||
status: string;
|
||||
estimatedCompletion: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
// Пример использования
|
||||
const analysisId = await startMarketingAnalysis({
|
||||
const result = await startMarketingAnalysis({
|
||||
businessNiche: 'E-commerce платформы',
|
||||
product: 'Разработка мобильных приложений',
|
||||
targetAudience: 'Малый и средний бизнес, владельцы интернет-магазинов',
|
||||
region: 'Алматы',
|
||||
targetAudience: {
|
||||
genders: ['Женщины', 'Мужчины'],
|
||||
ageRanges: ['25-45'],
|
||||
types: ['Молодёжь']
|
||||
},
|
||||
region: ['Алматы', 'Астана'],
|
||||
goal: 'Увеличить количество клиентов на 50% за следующие 6 месяцев',
|
||||
detailLevel: 'СТАНДАРТНО',
|
||||
strongSide: 'Опытная команда, быстрая доставка',
|
||||
weakSide: 'Ограниченный маркетинговый бюджет',
|
||||
analysisType: 'РЫНОК',
|
||||
analysisType: ['РЫНОК', 'КОНКУРЕНТЫ'],
|
||||
});
|
||||
|
||||
// Если выбрано несколько типов анализа, result будет массивом
|
||||
if (Array.isArray(result)) {
|
||||
console.log(`Создано ${result.length} анализов`);
|
||||
result.forEach((analysis, index) => {
|
||||
console.log(`Анализ ${index + 1}: ${analysis.analysisId}`);
|
||||
});
|
||||
} else {
|
||||
console.log('Анализ создан:', result.analysisId);
|
||||
}
|
||||
```
|
||||
|
||||
### React компонент с формой
|
||||
@@ -368,13 +511,17 @@ const MarketingAnalysisForm: React.FC = () => {
|
||||
const [formData, setFormData] = useState<MarketingAnalysisRequest>({
|
||||
businessNiche: '',
|
||||
product: '',
|
||||
targetAudience: '',
|
||||
region: '',
|
||||
targetAudience: {
|
||||
genders: [],
|
||||
ageRanges: [],
|
||||
types: []
|
||||
},
|
||||
region: [],
|
||||
goal: '',
|
||||
detailLevel: 'СТАНДАРТНО',
|
||||
strongSide: '',
|
||||
weakSide: '',
|
||||
analysisType: 'РЫНОК',
|
||||
analysisType: [],
|
||||
});
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
@@ -421,31 +568,132 @@ const MarketingAnalysisForm: React.FC = () => {
|
||||
|
||||
<div>
|
||||
<label>Целевая аудитория *</label>
|
||||
<textarea
|
||||
value={formData.targetAudience}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, targetAudience: e.target.value })
|
||||
}
|
||||
required
|
||||
minLength={3}
|
||||
maxLength={300}
|
||||
/>
|
||||
<div>
|
||||
<div>
|
||||
<label>Гендер:</label>
|
||||
{['Женщины', 'Мужчины'].map((gender) => (
|
||||
<label key={gender}>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={formData.targetAudience.genders?.includes(gender as any)}
|
||||
onChange={(e) => {
|
||||
const genders = formData.targetAudience.genders || [];
|
||||
if (e.target.checked) {
|
||||
setFormData({
|
||||
...formData,
|
||||
targetAudience: {
|
||||
...formData.targetAudience,
|
||||
genders: [...genders, gender as any]
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setFormData({
|
||||
...formData,
|
||||
targetAudience: {
|
||||
...formData.targetAudience,
|
||||
genders: genders.filter((g) => g !== gender)
|
||||
}
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{gender}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<label>Возраст:</label>
|
||||
{['20-40', '25-45', '18-25', '40-60', '60+'].map((age) => (
|
||||
<label key={age}>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={formData.targetAudience.ageRanges?.includes(age as any)}
|
||||
onChange={(e) => {
|
||||
const ageRanges = formData.targetAudience.ageRanges || [];
|
||||
if (e.target.checked) {
|
||||
setFormData({
|
||||
...formData,
|
||||
targetAudience: {
|
||||
...formData.targetAudience,
|
||||
ageRanges: [...ageRanges, age as any]
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setFormData({
|
||||
...formData,
|
||||
targetAudience: {
|
||||
...formData.targetAudience,
|
||||
ageRanges: ageRanges.filter((a) => a !== age)
|
||||
}
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{age}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<label>Тип:</label>
|
||||
{['Семьи', 'Молодёжь', 'Все подряд'].map((type) => (
|
||||
<label key={type}>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={formData.targetAudience.types?.includes(type as any)}
|
||||
onChange={(e) => {
|
||||
const types = formData.targetAudience.types || [];
|
||||
if (e.target.checked) {
|
||||
setFormData({
|
||||
...formData,
|
||||
targetAudience: {
|
||||
...formData.targetAudience,
|
||||
types: [...types, type as any]
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setFormData({
|
||||
...formData,
|
||||
targetAudience: {
|
||||
...formData.targetAudience,
|
||||
types: types.filter((t) => t !== type)
|
||||
}
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{type}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>Регион *</label>
|
||||
<select
|
||||
value={formData.region}
|
||||
onChange={(e) => setFormData({ ...formData, region: e.target.value })}
|
||||
required
|
||||
>
|
||||
<option value=''>Выберите регион</option>
|
||||
<div>
|
||||
{VALID_REGIONS.map((region) => (
|
||||
<option key={region} value={region}>
|
||||
<label key={region}>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={formData.region.includes(region)}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
setFormData({
|
||||
...formData,
|
||||
region: [...formData.region, region]
|
||||
});
|
||||
} else {
|
||||
setFormData({
|
||||
...formData,
|
||||
region: formData.region.filter((r) => r !== region)
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{region}
|
||||
</option>
|
||||
</label>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -508,19 +756,34 @@ const MarketingAnalysisForm: React.FC = () => {
|
||||
|
||||
<div>
|
||||
<label>Тип анализа *</label>
|
||||
<select
|
||||
value={formData.analysisType}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, analysisType: e.target.value as any })
|
||||
}
|
||||
required
|
||||
>
|
||||
<option value='РЫНОК'>Рынок</option>
|
||||
<option value='КОНКУРЕНТЫ'>Конкуренты</option>
|
||||
<option value='ЦА'>Целевая аудитория</option>
|
||||
<option value='КАНАЛЫ'>Каналы</option>
|
||||
<option value='SWOT'>SWOT</option>
|
||||
</select>
|
||||
<div>
|
||||
{['РЫНОК', 'КОНКУРЕНТЫ', 'ЦА', 'КАНАЛЫ', 'SWOT'].map((type) => (
|
||||
<label key={type}>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={formData.analysisType.includes(type as any)}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
setFormData({
|
||||
...formData,
|
||||
analysisType: [...formData.analysisType, type as any]
|
||||
});
|
||||
} else {
|
||||
setFormData({
|
||||
...formData,
|
||||
analysisType: formData.analysisType.filter((t) => t !== type)
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{type === 'РЫНОК' && ' Рынок'}
|
||||
{type === 'КОНКУРЕНТЫ' && ' Конкуренты'}
|
||||
{type === 'ЦА' && ' Целевая аудитория'}
|
||||
{type === 'КАНАЛЫ' && ' Каналы'}
|
||||
{type === 'SWOT' && ' SWOT'}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type='submit'>Сгенерировать анализ бизнеса</button>
|
||||
|
||||
Reference in New Issue
Block a user