work-license-server 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/catalog.js CHANGED
@@ -1,11 +1,12 @@
1
1
  /**
2
- * Каталог комплектации ПАК WORK, тарифов годовых пользовательских лицензий и SLA поддержки
3
- * для лицензионного сервера и лендинга https://work.odant.org.
2
+ * Каталог комплектации ODANT-WORK, тарифов годовых пользовательских лицензий со скидками от количества,
3
+ * региональной франшизы (базовая стоимость 5 млн ₽, невозвратный залог резервирования 100 тыс. ₽,
4
+ * квота вакантных франшиз по населению) для сервера https://work.odant.org.
4
5
  */
5
6
 
6
7
  export const PAK_PRODUCT = Object.freeze({
7
8
  sku: 'WORK-PAK-495-192-4T',
8
- name: 'Программно-аппаратный комплекс WORK',
9
+ name: 'ODANT-WORK',
9
10
  domain: 'work.odant.org',
10
11
  basePriceRub: 5_000_000,
11
12
  baseSeats: 5,
@@ -13,56 +14,121 @@ export const PAK_PRODUCT = Object.freeze({
13
14
  includedSupportYears: 1,
14
15
  includedSupportTier: 'business',
15
16
  hardware: Object.freeze({
16
- cpu: 'AMD Ryzen 495 8085',
17
- ram: '192 ГБ Unified RAM',
17
+ cpu: 'AMD Ryzen™ AI Max+ PRO 495 (Zen 5, 16 ядер / 32 потока, до 5.2 ГГц, кэш 80 МБ)',
18
+ gpu: 'AMD Radeon™ 8065S Graphics (40 ядер, 3000 МГц, до 61 TFLOPS FP16)',
19
+ aiEngine: 'До 132 TOPS (NPU 55 TOPS + 61 TFLOPS FP16 AI Compute)',
20
+ ram: '192 ГБ 256-bit LPDDR5x-8533 Unified RAM',
18
21
  ramGb: 192,
19
- storage: 'SSD 4 ТБ NVMe PCIe Gen4',
22
+ storage: 'SSD 4 ТБ NVMe PCIe® 4.0 (поддержка RAID0 / RAID1)',
20
23
  ssdTb: 4,
24
+ security: 'AMD PRO Technologies · AMD Memory Guard · AMD Secure Processor',
21
25
  }),
22
26
  software: Object.freeze({
23
- accounting: 'Предустановленная базовая типовая конфигурация (аналог 1С: Предприятие — справочники, план счетов, двойная запись, оборотно-сальдовая ведомость, анализ счёта, первичные документы, импорт баз 1С)',
24
- seatModel: 'По одновременным активным сессиям (5 вечных рабочих мест в базовом комплекте + докупка срочных годовых лицензий)',
27
+ accounting: 'Встроенная система учёта, документооборота, управления процессами и приватный мультимодальный ИИ (справочники, план счетов, двойная запись, оборотно-сальдовая ведомость, анализ счёта, первичные документы и сквозные процессы)',
28
+ seatModel: 'По одновременным активным сессиям (5 вечных рабочих мест в базовом комплекте + докупка срочных годовых лицензий со скидкой от количества)',
25
29
  hardwareBinding: 'Привязка вечной лицензии к Hardware ID материнской платы/CPU AMD Ryzen + криптографическому ключу узла (#system/node-key.json)',
26
30
  }),
27
31
  preinstalledModels: Object.freeze([
28
32
  {
29
- id: 'Qwen3-30B-A3B',
30
- title: 'Qwen 3 30B',
31
- badge: 'LLM · Учёт · Код · Агент',
32
- description: 'Основная языковая модель для живого диалога, проведения хозяйственных операций, анализа баланса, формирования отчётов и разработки.',
33
+ id: 'llm-core',
34
+ type: 'Большая языковая модель (LLM)',
35
+ title: 'Интеллектуальное текстовое и аналитическое ядро',
36
+ badge: 'Диалог · Учёт · Документы · Процессы',
37
+ description: 'Ведение живого диалога, проведение хозяйственных операций, подготовка документов, управление бизнес-процессами, финансовый анализ и генерация программного кода.',
33
38
  ramGb: 18.5,
34
39
  },
35
40
  {
36
- id: 'Qwen3-Voice',
37
- title: 'Голосовая модель (ASR + TTS)',
38
- badge: 'Полнодуплексный голос',
39
- description: 'Локальное распознавание и синтез естественной речи с мгновенным откликом и взаимным перебиванием собеседников.',
41
+ id: 'voice-core',
42
+ type: 'Голосовая модель (распознавание и синтез речи)',
43
+ title: 'Полнодуплексный речевой контур',
44
+ badge: 'Голосовой диалог · ASR · TTS',
45
+ description: 'Локальное распознавание устной речи, стенографирование совещаний и синтез естественного голоса с мгновенным откликом и взаимным перебиванием.',
40
46
  ramGb: 4.2,
41
47
  },
42
48
  {
43
- id: 'Flux-Image',
44
- title: 'Генерация изображений',
45
- badge: 'Дизайн · Карточки · Схемы',
46
- description: 'Локальный синтез иллюстраций, визуализации товаров, презентационных материалов и графики внутри контура компании.',
49
+ id: 'image-core',
50
+ type: 'Модель генерации изображений',
51
+ title: 'Визуальный и графический синтез',
52
+ badge: 'Иллюстрации · Карточки · Схемы',
53
+ description: 'Локальное создание иллюстраций, визуального оформления карточек номенклатуры, презентационных материалов и графики внутри закрытого контура.',
47
54
  ramGb: 12.0,
48
55
  },
49
56
  {
50
- id: 'Qwen3-Embedding',
51
- title: 'Embedding + RAG',
52
- badge: 'Семантический поиск',
53
- description: 'Векторная индексация всех договоров, первичных документов, переписки и регламентов предприятия.',
57
+ id: 'embedding-core',
58
+ type: 'Модель векторных представлений (Embedding)',
59
+ title: 'Семантический поиск и база знаний (RAG)',
60
+ badge: 'Векторный индекс · Поиск по смыслу',
61
+ description: 'Глубокая индексация всех договоров, первичных документов, регламентов, задач и истории переписки предприятия для мгновенного поиска по смыслу.',
54
62
  ramGb: 2.5,
55
63
  },
56
64
  ]),
57
65
  });
58
66
 
67
+ /**
68
+ * Агентная ИИ-платформа ODANT-WORK: ИИ-консультант в каждой форме, мультиагентная система
69
+ * из ролевых субагентов и библиотека навыков (по контрактам $server/$folder/$class/ai/{agents,skills}).
70
+ */
71
+ export const AI_PLATFORM = Object.freeze({
72
+ consultant: Object.freeze({
73
+ title: 'ИИ-консультант в каждой форме',
74
+ features: Object.freeze([
75
+ 'Вкладка «ИИ» в проводнике и кнопка «?» в шапке любой формы — вопрос по открытой форме задаётся автоматически',
76
+ 'Полнодуплексный голосовой диалог: распознавание и синтез речи с взаимным перебиванием',
77
+ 'Действует в рамках роли и прав пользователя (ADMIN · BOSS · USER)',
78
+ 'Инструменты учётного ядра: описание класса, поиск объектов, проведение операций, отчёты',
79
+ 'Каждая консультация сохраняется задачей в личном кабинете и в ленте пользователя',
80
+ 'Работает полностью локально на комплексе — данные не покидают контур предприятия',
81
+ ]),
82
+ }),
83
+ orchestration: Object.freeze([
84
+ { title: 'Декомпозиция и параллельное исполнение', description: 'Основной агент раскладывает задачу на поручения и запускает независимые субагенты параллельно, собирая их итоговые отчёты.' },
85
+ { title: 'Жёсткие границы прав', description: 'Субагент никогда не получает больше инструментов и прав, чем у вызвавшего; аудитор и ревьюер работают строго на чтение — на уровне кода.' },
86
+ { title: 'Изолированные ветки изменений', description: 'Параллельные правки готовятся в изолированных ветках и сливаются в оригинал с проверкой конфликтов и единым откатом.' },
87
+ { title: 'Фоновые продолжимые задачи', description: 'Долгая работа идёт отдельной задачей в фоне: её можно проверить, дополнить сообщением или остановить.' },
88
+ { title: 'Подтверждение человеком', description: 'Значимые изменения агент согласует с ответственным сотрудником в той же задаче — с полным журналом действий.' },
89
+ { title: 'Навыки и самообучение', description: 'Удачно выполненная работа сохраняется как навык подразделения и переиспользуется всеми агентами.' },
90
+ ]),
91
+ agents: Object.freeze([
92
+ { id: 'explore', title: 'Разведчик', description: 'Исследует дерево организации только на чтение и возвращает отчёт с путями' },
93
+ { id: 'web', title: 'Веб-исследователь', description: 'Находит факты в интернете и подтверждает их ссылками' },
94
+ { id: 'general', title: 'Исполнитель', description: 'Самостоятельно решает подзадачу со всеми разрешёнными инструментами' },
95
+ { id: 'analyst', title: 'Аналитик', description: 'Отчёты и сводки по объектам, лентам и документам, таблицы xlsx / csv' },
96
+ { id: 'secretary', title: 'Секретарь', description: 'Входящие, поручения с исполнителями и сроками, контроль исполнения' },
97
+ { id: 'auditor', title: 'Аудитор', description: 'Аудит прав доступа и журнала безопасности — строго только чтение' },
98
+ { id: 'builder', title: 'Конструктор', description: 'Прикладная модель: типы, поля, роли, зоны доступа и контракты' },
99
+ { id: 'developer', title: 'Разработчик', description: 'Код, точечные правки, git, проверки и диагностика' },
100
+ { id: 'reviewer', title: 'Ревьюер', description: 'Ревью изменений классов, методов и контрактов' },
101
+ { id: 'it-admin', title: 'ИТ-администратор', description: 'Сервер и локальная сеть: здоровье, службы, устройства' },
102
+ { id: 'integrator', title: 'Интегратор', description: 'Чтение внешних учётных систем по OData, выгрузка и сверка данных' },
103
+ { id: 'negotiator', title: 'Представитель', description: 'Действует от имени компании на серверах-партнёрах федерации' },
104
+ { id: 'operator', title: 'Оператор ПК', description: 'Команды, экран и браузер в изолированной песочнице' },
105
+ ]),
106
+ skills: Object.freeze([
107
+ 'Согласование документов', 'Протокол совещания', 'Документ по шаблону', 'Импорт таблиц',
108
+ 'Отчёты по расписанию', 'Делегирование поручений', 'Контроль качества данных', 'Онбординг сотрудника',
109
+ 'Почта и календарь', 'Сканирование в систему', 'Проверка резервных копий', 'ИИ-триггеры на события',
110
+ 'Инвентаризация сети', 'Восстановление файлов', 'Офисные документы', 'Создание новых навыков',
111
+ ]),
112
+ skillsCount: 26,
113
+ });
114
+
115
+ /** Разработчик и правообладатель платформы ODANT. */
116
+ export const VENDOR_CONTACTS = Object.freeze({
117
+ company: 'Бизнес Интер Софт',
118
+ description: 'Разработка и продажа программного обеспечения, автоматизация бизнес-процессов',
119
+ address: '390000, г. Рязань, ул. Ленина, 47',
120
+ phones: Object.freeze(['+7 (4912) 77-70-21', '+7 (4912) 77-70-22']),
121
+ site: 'https://www.odant.ru',
122
+ geo: Object.freeze({ lat: 54.62795, lon: 39.74585 }),
123
+ });
124
+
59
125
  /** Тарифы годовой лицензии на 1 дополнительного пользователя (зависят от уровня поддержки SLA). */
60
126
  export const SLA_TIERS = Object.freeze({
61
127
  start: Object.freeze({
62
128
  id: 'start',
63
129
  name: 'Start',
64
130
  badge: 'Базовый SLA',
65
- pricePerUserYearRub: 15_000,
131
+ pricePerUserYearRub: 10_000,
66
132
  ticketsLimitPerSeatYear: 12,
67
133
  reactionMinutes: 480,
68
134
  reactionLabel: 'до 8 рабочих часов',
@@ -73,14 +139,14 @@ export const SLA_TIERS = Object.freeze({
73
139
  'До 12 обращений в техподдержку в год на каждое место',
74
140
  'Время реакции инженера — до 8 рабочих часов',
75
141
  'Поддержка через боты MAX, Telegram, VK',
76
- 'Доступ ко всем обновлениям ядра WORK и типовой конфигурации',
142
+ 'Доступ ко всем обновлениям ядра ODANT-WORK и встроенной системы учёта',
77
143
  ],
78
144
  }),
79
145
  business: Object.freeze({
80
146
  id: 'business',
81
147
  name: 'Business',
82
148
  badge: 'Рекомендуемый',
83
- pricePerUserYearRub: 40_000,
149
+ pricePerUserYearRub: 15_000,
84
150
  ticketsLimitPerSeatYear: 48,
85
151
  reactionMinutes: 120,
86
152
  reactionLabel: 'до 2 часов',
@@ -90,7 +156,7 @@ export const SLA_TIERS = Object.freeze({
90
156
  '1 дополнительное конкурентное рабочее место на 1 год',
91
157
  'До 48 обращений в техподдержку в год на каждое место',
92
158
  'Приоритетное время реакции — до 2 часов',
93
- 'Консультации по настройке учётных схем и отчётов',
159
+ 'Консультации по настройке учётных схем, документооборота и процессов',
94
160
  'Помощь в администрировании предустановленных локальных моделей ИИ',
95
161
  ],
96
162
  }),
@@ -98,7 +164,7 @@ export const SLA_TIERS = Object.freeze({
98
164
  id: 'enterprise',
99
165
  name: 'Enterprise',
100
166
  badge: 'Максимальный 24/7',
101
- pricePerUserYearRub: 100_000,
167
+ pricePerUserYearRub: 20_000,
102
168
  ticketsLimitPerSeatYear: 9999,
103
169
  reactionMinutes: 15,
104
170
  reactionLabel: 'до 15 минут (24/7)',
@@ -109,13 +175,42 @@ export const SLA_TIERS = Object.freeze({
109
175
  'Безлимитное количество обращений в поддержку',
110
176
  'Критическое время реакции — до 15 минут 24/7',
111
177
  'Выделенный инженер и прямая линия сопровождения',
112
- 'Помощь с дообучением моделей ИИ (LoRA) под специфику предприятия',
178
+ 'Помощь с дообучением моделей ИИ под процессы предприятия',
113
179
  ],
114
180
  }),
115
181
  });
116
182
 
117
183
  /**
118
- * Рассчитать стоимость заказа (покупка базового ПАК и/или докупка годовых пользовательских мест).
184
+ * Шкала скидок от количества докупаемых пользовательских лицензий (объёмная скидка для клиентов).
185
+ */
186
+ export const SEAT_VOLUME_DISCOUNTS = Object.freeze([
187
+ { minSeats: 100, discountPercent: 25, label: 'Корпоративный пакет (100+ мест): −25%' },
188
+ { minSeats: 50, discountPercent: 20, label: 'Крупный пакет (50–99 мест): −20%' },
189
+ { minSeats: 25, discountPercent: 15, label: 'Расширенный пакет (25–49 мест): −15%' },
190
+ { minSeats: 10, discountPercent: 10, label: 'Командный пакет (10–24 места): −10%' },
191
+ { minSeats: 5, discountPercent: 5, label: 'Малый пакет (5–9 мест): −5%' },
192
+ { minSeats: 1, discountPercent: 0, label: 'Стандарт (1–4 места): 0%' },
193
+ ]);
194
+
195
+ /**
196
+ * Вычислить процент скидки от количества докупаемых пользователей.
197
+ */
198
+ export function calculateSeatVolumeDiscount(seats = 1) {
199
+ const count = Math.max(0, Math.floor(Number(seats || 0)));
200
+ for (const tier of SEAT_VOLUME_DISCOUNTS) {
201
+ if (count >= tier.minSeats) {
202
+ return {
203
+ seats: count,
204
+ discountPercent: tier.discountPercent,
205
+ label: tier.label,
206
+ };
207
+ }
208
+ }
209
+ return { seats: count, discountPercent: 0, label: 'Без скидки' };
210
+ }
211
+
212
+ /**
213
+ * Рассчитать стоимость заказа (покупка базового комплекса ODANT-WORK и/или докупка годовых лицензий со скидкой от количества).
119
214
  */
120
215
  export function calculateOrderQuote({
121
216
  includeBasePak = false,
@@ -128,8 +223,14 @@ export function calculateOrderQuote({
128
223
  const seats = Math.max(0, Math.floor(Number(addonSeats || 0)));
129
224
  const periodYears = Math.max(1, Math.min(5, Math.floor(Number(years || 1))));
130
225
 
226
+ const volumeDiscount = calculateSeatVolumeDiscount(seats);
227
+ const volumeDiscountPercent = volumeDiscount.discountPercent;
228
+
131
229
  const baseAmountRub = includeBasePak ? PAK_PRODUCT.basePriceRub : 0;
132
- const addonsAmountRub = seats * tierSpec.pricePerUserYearRub * periodYears;
230
+ const addonsGrossAmountRub = seats * tierSpec.pricePerUserYearRub * periodYears;
231
+ const addonsDiscountRub = Math.round(addonsGrossAmountRub * (volumeDiscountPercent / 100));
232
+ const addonsAmountRub = addonsGrossAmountRub - addonsDiscountRub;
233
+ const effectiveUnitPriceRub = seats > 0 ? Math.round(addonsAmountRub / seats) : tierSpec.pricePerUserYearRub * periodYears;
133
234
  const totalAmountRub = baseAmountRub + addonsAmountRub;
134
235
 
135
236
  const items = [];
@@ -142,10 +243,11 @@ export function calculateOrderQuote({
142
243
  });
143
244
  }
144
245
  if (seats > 0) {
246
+ const discSuffix = volumeDiscountPercent > 0 ? `, скидка −${volumeDiscountPercent}% за объём` : '';
145
247
  items.push({
146
- title: `Годовая лицензия WORK на доп. рабочее место (уровень поддержки ${tierSpec.name}, ${periodYears} г.)`,
248
+ title: `Годовая лицензия ODANT-WORK на доп. рабочее место (уровень ${tierSpec.name}, ${periodYears} г.${discSuffix})`,
147
249
  quantity: seats,
148
- unitPriceRub: tierSpec.pricePerUserYearRub * periodYears,
250
+ unitPriceRub: effectiveUnitPriceRub,
149
251
  totalRub: addonsAmountRub,
150
252
  });
151
253
  }
@@ -160,6 +262,10 @@ export function calculateOrderQuote({
160
262
  pricePerUserYearRub: tierSpec.pricePerUserYearRub,
161
263
  addonSeats: seats,
162
264
  years: periodYears,
265
+ volumeDiscountPercent,
266
+ volumeDiscountLabel: volumeDiscount.label,
267
+ addonsGrossAmountRub,
268
+ addonsDiscountRub,
163
269
  addonsAmountRub,
164
270
  totalAmountRub,
165
271
  reactionMinutes: tierSpec.reactionMinutes,
@@ -170,36 +276,40 @@ export function calculateOrderQuote({
170
276
  }
171
277
 
172
278
  /**
173
- * Параметры партнёрской программы и региональной франшизы WORK:
174
- * - Фиксированная стоимость франшизы пропорциональна экономическому индексу региона (baseFranchiseRub * economicIndex).
175
- * - Максимальное количество партнёров в регионе пропорционально населению региона (partnersPerMillion * populationMillions, мин. 1).
279
+ * Параметры партнёрской программы и региональной франшизы ODANT-WORK:
280
+ * - Базовый паушальный взнос = 500 000 ₽ (корректируется пропорционально экономическому индексу региона).
281
+ * - Сразу при резервировании франшизы вносится невозвратный залог = 100 000 ₽, полная оплата паушального взноса — в течение 1 года (12 мес.).
282
+ * - Максимальное количество франшиз в регионе пропорционально населению региона (partnersPerMillion * populationMillions, мин. 1).
176
283
  * - Партнёрская скидка растёт пропорционально годовому объёму продаж от начальных 10% до максимальных 50%.
177
284
  */
178
285
  export const FRANCHISE_PROGRAM = Object.freeze({
179
- baseFranchiseFeeRub: 1_000_000, // Базовый эталон стоимости франшизы (при экономическом индексе региона = 1.0)
180
- partnersPerMillionPopulation: 1, // Норматив квоты партнёров: 1 партнёр на каждый 1 млн населения региона (пропорционально населению)
286
+ baseFranchiseFeeRub: 500_000, // Базовый паушальный взнос (500 тыс. ₽ при экономическом индексе 1.0)
287
+ reservationDepositRub: 100_000, // Невозвратный залог сразу за резервирование региональной франшизы (100 тыс. ₽)
288
+ fullPaymentPeriodMonths: 12, // Срок полной оплаты паушального взноса — в течение 1 года (12 месяцев)
289
+ fullPaymentPeriodYears: 1, // 1 год на полную оплату паушального взноса после резервирования
290
+ partnersPerMillionPopulation: 1, // Норматив квоты партнёров: 1 франшиза на 1 млн населения региона
181
291
  minDiscountPercent: 10, // Начальная партнёрская скидка (10%)
182
292
  maxDiscountPercent: 50, // Максимальная партнёрская скидка (50%)
183
- maxDiscountAnnualSalesRub: 100_000_000, // Годовой объём продаж (100 млн ₽ = ~20 ПАК), при котором достигается максимальная скидка 50%
184
- hardwareWarrantyYears: 1, // Обязательная годовая гарантия на оборудование ПАК, предоставляемая партнёром
293
+ maxDiscountAnnualSalesRub: 100_000_000, // Годовой объём продаж (100 млн ₽ = ~20 ПАК), при котором достигается скидка 50%
294
+ hardwareWarrantyYears: 1, // Годовая гарантия на оборудование, предоставляемая партнёром
185
295
  partnerOperationsModel: Object.freeze([
186
- 'Партнёр самостоятельно закупает аппаратную платформу (AMD Ryzen 495 8085 · 192 ГБ RAM · SSD 4 ТБ) у дистрибьюторов',
187
- 'Партнёр предустанавливает WORK, типовую учётную конфигурацию и локальные модели ИИ (Qwen 3 30B, Голос, Изображения, Embedding)',
188
- 'Партнёр самостоятельно привязывает оборудование (HWID + #system/node-key.json) и выдаёт лицензии клиентам через партнёрский шлюз',
189
- 'Партнёр предоставляет клиенту годовую гарантию (1 год) на поставленное оборудование ПАК',
296
+ 'Партнёр самостоятельно закупает аппаратную платформу (AMD Ryzen™ AI Max+ PRO 495 · Radeon™ 8065S · 192 ГБ RAM · SSD 4 ТБ)',
297
+ 'Партнёр устанавливает ODANT-WORK, систему учёта, документооборота, процессов и 4 локальные модели ИИ',
298
+ 'Партнёр самостоятельно привязывает оборудование (HWID + #system/node-key.json) и выдаёт лицензии клиентам',
299
+ 'Партнёр предоставляет клиенту годовую гарантию (1 год) на поставленное оборудование',
190
300
  ]),
191
301
  partnerRevenueStreams: Object.freeze([
192
302
  {
193
303
  id: 'pak-license-margin',
194
- title: 'Продажа ПАК WORK и лицензий (скидка 10% → 50%)',
195
- share: 'Скидка партнёра от 10% до 50% пропорционально годовому объёму продаж + аппаратная наценка при сборке ПАК',
304
+ title: 'Продажа комплексов ODANT-WORK и лицензий (скидка 10% → 50%)',
305
+ share: 'Скидка партнёра от 10% до 50% пропорционально годовому объёму продаж + аппаратная маржа сборки',
196
306
  recommendedAvgRub: 1_500_000,
197
307
  },
198
308
  {
199
309
  id: 'nas-backup-hardware',
200
310
  title: 'Поставка NAS-систем для резервного копирования',
201
311
  share: '100% доход партнёра',
202
- description: 'Поставка и настройка сетевых хранилищ NAS для автоматической архивации и снапшотов баз организаций WORK',
312
+ description: 'Поставка и настройка сетевых хранилищ NAS для автоматической архивации и резервного копирования баз организаций',
203
313
  recommendedAvgRub: 350_000,
204
314
  },
205
315
  {
@@ -219,7 +329,7 @@ export const FRANCHISE_PROGRAM = Object.freeze({
219
329
  {
220
330
  id: 'partner-tech-support',
221
331
  title: 'Техническая поддержка и сервисное сопровождение',
222
- share: 'Доход партнёра по договорам SLA и выездному обслуживанию',
332
+ share: 'Доход партнёра по договорам SLA и гарантийному обслуживанию',
223
333
  description: 'Первая и вторая линия техподдержки предприятий региона, администрирование серверов и регламентное ТО',
224
334
  recommendedAvgRub: 240_000,
225
335
  },
@@ -227,77 +337,192 @@ export const FRANCHISE_PROGRAM = Object.freeze({
227
337
  id: 'agent-training-skills',
228
338
  title: 'Дообучение ИИ-агентов (LoRA) и разработка навыков (skills)',
229
339
  share: '100% проектный доход партнёра',
230
- description: 'Сборка датасетов предприятия, дообучение моделей под отраслевую специфику, создание навыков (skills) и учётных форм',
340
+ description: 'Сборка датасетов предприятия, дообучение моделей под отраслевую специфику, создание навыков (skills) и процессных схем',
231
341
  recommendedAvgRub: 600_000,
232
342
  },
233
343
  ]),
234
344
  });
235
345
 
236
346
  /**
237
- * Справочник регионов РФ с экономическими характеристиками и численностью населения:
238
- * - Стоимость региональной франшизы пропорциональна экономическим характеристикам региона (economicIndex).
239
- * - Квота (максимальное число партнёров в регионе) пропорциональна населению региона (populationMillions).
347
+ * Перечень всех субъектов Российской Федерации для партнёрской программы:
348
+ * численность населения (populationMillions — определяет квоту франшиз), ВРП (grpTrillionRub, оценка)
349
+ * и экономический индекс (economicIndex — множитель базовой стоимости франшизы).
350
+ * Контуры субъектов для интерактивной карты лежат в assets/russia-regions.json (по коду субъекта).
351
+ * Служебный «Типовой регион» (RU-REG, hidden) используется для расчётов и на витрине не показывается.
240
352
  */
353
+ const region = (code, name, district, populationMillions, grpTrillionRub, economicIndex, extra = {}) => Object.freeze({
354
+ code,
355
+ name,
356
+ district,
357
+ populationMillions,
358
+ grpTrillionRub,
359
+ activeEnterprises: Math.round(populationMillions * 22_000 / 1000) * 1000,
360
+ economicIndex,
361
+ ...extra,
362
+ });
363
+
364
+ export const FEDERAL_DISTRICTS = Object.freeze({
365
+ 'ЦФО': 'Центральный',
366
+ 'СЗФО': 'Северо-Западный',
367
+ 'ЮФО': 'Южный',
368
+ 'СКФО': 'Северо-Кавказский',
369
+ 'ПФО': 'Приволжский',
370
+ 'УФО': 'Уральский',
371
+ 'СФО': 'Сибирский',
372
+ 'ДФО': 'Дальневосточный',
373
+ });
374
+
241
375
  export const REGIONS_CATALOG = Object.freeze([
242
- { code: 'RU-MOW', name: 'Москва', district: 'ЦФО', populationMillions: 13.1, grpTrillionRub: 28.5, activeEnterprises: 620_000, economicIndex: 3.5 },
243
- { code: 'RU-MOS', name: 'Московская область', district: 'ЦФО', populationMillions: 8.6, grpTrillionRub: 8.4, activeEnterprises: 185_000, economicIndex: 2.0 },
244
- { code: 'RU-SPE', name: 'Санкт-Петербург', district: 'СЗФО', populationMillions: 5.6, grpTrillionRub: 11.2, activeEnterprises: 245_000, economicIndex: 2.2 },
245
- { code: 'RU-KDA', name: 'Краснодарский край', district: 'ЮФО', populationMillions: 5.8, grpTrillionRub: 4.3, activeEnterprises: 120_000, economicIndex: 1.4 },
246
- { code: 'RU-SVE', name: 'Свердловская область', district: 'УФО', populationMillions: 4.2, grpTrillionRub: 4.2, activeEnterprises: 112_000, economicIndex: 1.4 },
247
- { code: 'RU-ROS', name: 'Ростовская область', district: 'ЮФО', populationMillions: 4.2, grpTrillionRub: 2.6, activeEnterprises: 76_000, economicIndex: 1.0 },
248
- { code: 'RU-BA', name: 'Республика Башкортостан', district: 'ПФО', populationMillions: 4.1, grpTrillionRub: 2.4, activeEnterprises: 65_000, economicIndex: 1.0 },
249
- { code: 'RU-TA', name: 'Республика Татарстан', district: 'ПФО', populationMillions: 4.0, grpTrillionRub: 4.6, activeEnterprises: 98_000, economicIndex: 1.5 },
250
- { code: 'RU-CHE', name: 'Челябинская область', district: 'УФО', populationMillions: 3.4, grpTrillionRub: 2.5, activeEnterprises: 68_000, economicIndex: 1.0 },
251
- { code: 'RU-SAM', name: 'Самарская область', district: 'ПФО', populationMillions: 3.1, grpTrillionRub: 2.6, activeEnterprises: 74_000, economicIndex: 1.1 },
252
- { code: 'RU-NIZ', name: 'Нижегородская область', district: 'ПФО', populationMillions: 3.1, grpTrillionRub: 2.5, activeEnterprises: 72_000, economicIndex: 1.1 },
253
- { code: 'RU-KYA', name: 'Красноярский край', district: 'СФО', populationMillions: 2.9, grpTrillionRub: 3.5, activeEnterprises: 58_000, economicIndex: 1.2 },
254
- { code: 'RU-NVS', name: 'Новосибирская область', district: 'СФО', populationMillions: 2.8, grpTrillionRub: 2.3, activeEnterprises: 82_000, economicIndex: 1.1 },
255
- { code: 'RU-VOR', name: 'Воронежская область', district: 'ЦФО', populationMillions: 2.3, grpTrillionRub: 1.5, activeEnterprises: 44_000, economicIndex: 0.8 },
256
- { code: 'RU-PRI', name: 'Приморский край', district: 'ДФО', populationMillions: 1.8, grpTrillionRub: 1.7, activeEnterprises: 48_000, economicIndex: 0.9 },
257
- { code: 'RU-REG', name: 'Стандартный регион РФ (до 1 млн чел.)', district: 'РФ', populationMillions: 1.0, grpTrillionRub: 0.9, activeEnterprises: 25_000, economicIndex: 0.5 },
376
+ // Центральный федеральный округ
377
+ region('RU-MOW', 'Москва', 'ЦФО', 13.1, 28.5, 2.0, { activeEnterprises: 620_000 }),
378
+ region('RU-MOS', 'Московская область', 'ЦФО', 8.6, 8.4, 1.4, { activeEnterprises: 185_000 }),
379
+ region('RU-BEL', 'Белгородская область', 'ЦФО', 1.5, 1.4, 0.9),
380
+ region('RU-BRY', 'Брянская область', 'ЦФО', 1.15, 0.6, 0.7),
381
+ region('RU-VLA', 'Владимирская область', 'ЦФО', 1.33, 0.75, 0.8),
382
+ region('RU-VOR', 'Воронежская область', 'ЦФО', 2.3, 1.5, 0.9),
383
+ region('RU-IVA', 'Ивановская область', 'ЦФО', 0.91, 0.35, 0.6),
384
+ region('RU-KLU', 'Калужская область', 'ЦФО', 1.07, 0.8, 0.8),
385
+ region('RU-KOS', 'Костромская область', 'ЦФО', 0.57, 0.3, 0.6),
386
+ region('RU-KRS', 'Курская область', 'ЦФО', 1.07, 0.8, 0.8),
387
+ region('RU-LIP', 'Липецкая область', 'ЦФО', 1.12, 1.0, 0.9),
388
+ region('RU-ORL', 'Орловская область', 'ЦФО', 0.7, 0.35, 0.6),
389
+ region('RU-RYA', 'Рязанская область', 'ЦФО', 1.08, 0.65, 0.8, { headquarters: true }),
390
+ region('RU-SMO', 'Смоленская область', 'ЦФО', 0.88, 0.45, 0.7),
391
+ region('RU-TAM', 'Тамбовская область', 'ЦФО', 0.97, 0.5, 0.7),
392
+ region('RU-TVE', 'Тверская область', 'ЦФО', 1.21, 0.7, 0.8),
393
+ region('RU-TUL', 'Тульская область', 'ЦФО', 1.5, 1.1, 0.9),
394
+ region('RU-YAR', 'Ярославская область', 'ЦФО', 1.2, 0.9, 0.9),
395
+
396
+ // Северо-Западный федеральный округ
397
+ region('RU-SPE', 'Санкт-Петербург', 'СЗФО', 5.6, 11.2, 1.6, { activeEnterprises: 245_000 }),
398
+ region('RU-LEN', 'Ленинградская область', 'СЗФО', 2.0, 1.8, 1.0),
399
+ region('RU-KGD', 'Калининградская область', 'СЗФО', 1.0, 0.9, 0.9),
400
+ region('RU-MUR', 'Мурманская область', 'СЗФО', 0.7, 1.2, 1.0),
401
+ region('RU-ARK', 'Архангельская область', 'СЗФО', 0.96, 1.1, 0.9),
402
+ region('RU-NEN', 'Ненецкий автономный округ', 'СЗФО', 0.04, 0.35, 0.6),
403
+ region('RU-VLG', 'Вологодская область', 'СЗФО', 1.1, 1.1, 0.9),
404
+ region('RU-KR', 'Республика Карелия', 'СЗФО', 0.52, 0.45, 0.7),
405
+ region('RU-KO', 'Республика Коми', 'СЗФО', 0.72, 0.9, 0.9),
406
+ region('RU-NGR', 'Новгородская область', 'СЗФО', 0.58, 0.4, 0.7),
407
+ region('RU-PSK', 'Псковская область', 'СЗФО', 0.58, 0.25, 0.6),
408
+
409
+ // Южный федеральный округ
410
+ region('RU-KDA', 'Краснодарский край', 'ЮФО', 5.8, 4.3, 1.3, { activeEnterprises: 120_000 }),
411
+ region('RU-ROS', 'Ростовская область', 'ЮФО', 4.2, 2.6, 1.0),
412
+ region('RU-VGG', 'Волгоградская область', 'ЮФО', 2.5, 1.4, 0.9),
413
+ region('RU-AST', 'Астраханская область', 'ЮФО', 0.95, 0.6, 0.8),
414
+ region('RU-AD', 'Республика Адыгея', 'ЮФО', 0.5, 0.15, 0.6),
415
+ region('RU-KL', 'Республика Калмыкия', 'ЮФО', 0.26, 0.1, 0.5),
416
+ region('RU-CR', 'Республика Крым', 'ЮФО', 1.91, 0.75, 0.8),
417
+ region('RU-SEV', 'Севастополь', 'ЮФО', 0.56, 0.2, 0.8),
418
+ region('RU-DPR', 'Донецкая Народная Республика', 'ЮФО', 2.29, null, 0.6),
419
+ region('RU-LPR', 'Луганская Народная Республика', 'ЮФО', 1.32, null, 0.6),
420
+ region('RU-ZAP', 'Запорожская область', 'ЮФО', 0.49, null, 0.6),
421
+ region('RU-KHE', 'Херсонская область', 'ЮФО', 0.36, null, 0.6),
422
+
423
+ // Северо-Кавказский федеральный округ
424
+ region('RU-STA', 'Ставропольский край', 'СКФО', 2.9, 1.3, 0.9),
425
+ region('RU-DA', 'Республика Дагестан', 'СКФО', 3.2, 1.0, 0.8),
426
+ region('RU-CE', 'Чеченская Республика', 'СКФО', 1.53, 0.35, 0.6),
427
+ region('RU-KB', 'Кабардино-Балкарская Республика', 'СКФО', 0.9, 0.25, 0.6),
428
+ region('RU-SE', 'Республика Северная Осетия — Алания', 'СКФО', 0.68, 0.2, 0.6),
429
+ region('RU-IN', 'Республика Ингушетия', 'СКФО', 0.52, 0.1, 0.5),
430
+ region('RU-KC', 'Карачаево-Черкесская Республика', 'СКФО', 0.47, 0.13, 0.5),
431
+
432
+ // Приволжский федеральный округ
433
+ region('RU-TA', 'Республика Татарстан', 'ПФО', 4.0, 4.6, 1.3, { activeEnterprises: 98_000 }),
434
+ region('RU-BA', 'Республика Башкортостан', 'ПФО', 4.1, 2.4, 1.0),
435
+ region('RU-NIZ', 'Нижегородская область', 'ПФО', 3.1, 2.5, 1.1),
436
+ region('RU-SAM', 'Самарская область', 'ПФО', 3.1, 2.6, 1.1),
437
+ region('RU-PER', 'Пермский край', 'ПФО', 2.5, 2.1, 1.0),
438
+ region('RU-SAR', 'Саратовская область', 'ПФО', 2.4, 1.3, 0.9),
439
+ region('RU-ORE', 'Оренбургская область', 'ПФО', 1.84, 1.6, 0.9),
440
+ region('RU-UD', 'Удмуртская Республика', 'ПФО', 1.5, 1.0, 0.9),
441
+ region('RU-PNZ', 'Пензенская область', 'ПФО', 1.24, 0.6, 0.7),
442
+ region('RU-ULY', 'Ульяновская область', 'ПФО', 1.18, 0.6, 0.7),
443
+ region('RU-CU', 'Чувашская Республика', 'ПФО', 1.17, 0.45, 0.7),
444
+ region('RU-KIR', 'Кировская область', 'ПФО', 1.12, 0.5, 0.7),
445
+ region('RU-MO', 'Республика Мордовия', 'ПФО', 0.76, 0.3, 0.6),
446
+ region('RU-ME', 'Республика Марий Эл', 'ПФО', 0.67, 0.25, 0.6),
447
+
448
+ // Уральский федеральный округ
449
+ region('RU-SVE', 'Свердловская область', 'УФО', 4.2, 4.2, 1.3, { activeEnterprises: 112_000 }),
450
+ region('RU-CHE', 'Челябинская область', 'УФО', 3.4, 2.5, 1.0),
451
+ region('RU-TYU', 'Тюменская область', 'УФО', 1.6, 1.9, 1.1),
452
+ region('RU-KHM', 'ХМАО — Югра', 'УФО', 1.7, 6.9, 1.5),
453
+ region('RU-YAN', 'Ямало-Ненецкий автономный округ', 'УФО', 0.5, 5.4, 1.4),
454
+ region('RU-KGN', 'Курганская область', 'УФО', 0.76, 0.3, 0.6),
455
+
456
+ // Сибирский федеральный округ
457
+ region('RU-NVS', 'Новосибирская область', 'СФО', 2.8, 2.3, 1.1, { activeEnterprises: 82_000 }),
458
+ region('RU-KYA', 'Красноярский край', 'СФО', 2.9, 3.5, 1.2),
459
+ region('RU-KEM', 'Кемеровская область — Кузбасс', 'СФО', 2.6, 2.2, 1.0),
460
+ region('RU-IRK', 'Иркутская область', 'СФО', 2.3, 2.4, 1.1),
461
+ region('RU-ALT', 'Алтайский край', 'СФО', 2.1, 1.1, 0.8),
462
+ region('RU-OMS', 'Омская область', 'СФО', 1.8, 1.1, 0.9),
463
+ region('RU-TOM', 'Томская область', 'СФО', 1.1, 0.9, 0.9),
464
+ region('RU-KK', 'Республика Хакасия', 'СФО', 0.53, 0.35, 0.7),
465
+ region('RU-TY', 'Республика Тыва', 'СФО', 0.34, 0.1, 0.5),
466
+ region('RU-AL', 'Республика Алтай', 'СФО', 0.21, 0.07, 0.5),
467
+
468
+ // Дальневосточный федеральный округ
469
+ region('RU-PRI', 'Приморский край', 'ДФО', 1.8, 1.7, 1.0),
470
+ region('RU-KHA', 'Хабаровский край', 'ДФО', 1.3, 1.3, 1.0),
471
+ region('RU-SA', 'Республика Саха (Якутия)', 'ДФО', 1.0, 2.0, 1.1),
472
+ region('RU-ZAB', 'Забайкальский край', 'ДФО', 0.98, 0.55, 0.7),
473
+ region('RU-BU', 'Республика Бурятия', 'ДФО', 0.97, 0.4, 0.7),
474
+ region('RU-AMU', 'Амурская область', 'ДФО', 0.75, 0.6, 0.8),
475
+ region('RU-SAK', 'Сахалинская область', 'ДФО', 0.46, 1.6, 1.1),
476
+ region('RU-KAM', 'Камчатский край', 'ДФО', 0.29, 0.35, 0.8),
477
+ region('RU-YEV', 'Еврейская автономная область', 'ДФО', 0.15, 0.08, 0.5),
478
+ region('RU-MAG', 'Магаданская область', 'ДФО', 0.14, 0.4, 0.8),
479
+ region('RU-CHU', 'Чукотский автономный округ', 'ДФО', 0.05, 0.15, 0.6),
480
+
481
+ // Служебный типовой регион для расчётов (на витрине не показывается)
482
+ region('RU-REG', 'Типовой регион РФ (до 1 млн чел.)', 'РФ', 1.0, 0.9, 0.5, { hidden: true, activeEnterprises: 25_000 }),
258
483
  ]);
259
484
 
260
485
  /**
261
- * Программа обучения и сертификации специалистов партнёров WORK.
486
+ * Программа обучения и сертификации специалистов партнёров ODANT-WORK.
262
487
  */
263
488
  export const PARTNER_TRAINING_TRACKS = Object.freeze([
264
489
  {
265
490
  id: 'pak-ai-engineer',
266
- title: 'Сертифицированный инженер ПАК и локального ИИ WORK',
491
+ title: 'Сертифицированный инженер ПАК и локального ИИ ODANT-WORK',
267
492
  code: 'WORK-CERT-ENG',
268
493
  hours: 40,
269
494
  passScore: 80,
270
495
  requiredForFranchise: true,
271
496
  topics: [
272
- 'Архитектура ПАК AMD Ryzen 495 8085 (192 ГБ Unified RAM, SSD 4 ТБ) и профилирование памяти',
273
- 'Администрирование локальных моделей: Qwen 3 30B, Qwen3-Voice (ASR/TTS), Flux-Image, Qwen3-Embedding',
274
- 'Сборка корпоративных датасетов ChatML и дообучение адаптеров LoRA / SFT / GRPO в контуре предприятия',
497
+ 'Архитектура ПАК на AMD Ryzen™ AI Max+ PRO 495 и Radeon™ 8065S (192 ГБ Unified RAM, SSD 4 ТБ) и профилирование памяти',
498
+ 'Администрирование предустановленных типов моделей: большая языковая модель (LLM), голосовая модель (ASR/TTS), генерация изображений и векторный поиск (Embedding)',
499
+ 'Сборка корпоративных датасетов и дообучение моделей в контуре предприятия',
275
500
  'Аппаратная привязка лицензий (HWID + #system/node-key.json) и закрытый контур безопасности',
276
501
  ],
277
502
  },
278
503
  {
279
504
  id: 'accounting-architect',
280
- title: 'Сертифицированный архитектор учётных систем WORK (1С-класс)',
505
+ title: 'Сертифицированный архитектор систем учёта, документооборота и процессов ODANT-WORK',
281
506
  code: 'WORK-CERT-ACC',
282
507
  hours: 48,
283
508
  passScore: 80,
284
509
  requiredForFranchise: true,
285
510
  topics: [
286
- 'Типовая учётная конфигурация (/MODULES/Register): справочники $catalog, виртуальные справочники $virtual, план счетов $account',
511
+ 'Встроенная система учёта (/MODULES/Register): справочники $catalog, виртуальные справочники $virtual, план счетов $account',
287
512
  'Явная двойная запись, проведение операций $operation, сторно и двухэтапные субаналитики',
288
- 'Отчётность ODANT-стандарта: Оборотно-сальдовая ведомость, Анализ счёта, Обороты счёта, Журнал проводок',
289
- 'Миграция исторических баз и остатков из 1С:Предприятие в изолированные БД организаций WORK',
513
+ 'Отчётность корпоративного стандарта: Оборотно-сальдовая ведомость, Анализ счёта, Обороты счёта, Журнал проводок',
514
+ 'Построение сквозного документооборота, управления процессами и перенос исторических данных в изолированные БД организаций',
290
515
  ],
291
516
  },
292
517
  {
293
518
  id: 'platform-developer',
294
- title: 'Сертифицированный разработчик модулей и ИИ-агентов WORK',
519
+ title: 'Сертифицированный разработчик модулей и ИИ-агентов ODANT-WORK',
295
520
  code: 'WORK-CERT-DEV',
296
521
  hours: 36,
297
522
  passScore: 75,
298
523
  requiredForFranchise: false,
299
524
  topics: [
300
- 'Трёхосевое наследование метаданных WORK (Types → Ancestor → Parent → Class)',
525
+ 'Трёхосевое наследование метаданных ODANT-WORK (Types → Ancestor → Parent → Class)',
301
526
  'Разработка представлений work-form на компонентах ODA и серверных методов шлюза',
302
527
  'Создание прикладных навыков (skills), инструментов и триггеров для ИИ-консультанта',
303
528
  'Федерация серверов NODES и подключение периферии через LAN / MCP',
@@ -305,21 +530,21 @@ export const PARTNER_TRAINING_TRACKS = Object.freeze([
305
530
  },
306
531
  {
307
532
  id: 'sales-sla-manager',
308
- title: 'Сертифицированный эксперт по продажам и сопровождению ПАК WORK',
533
+ title: 'Сертифицированный эксперт по продажам и сопровождению ODANT-WORK',
309
534
  code: 'WORK-CERT-SLS',
310
535
  hours: 24,
311
536
  passScore: 75,
312
537
  requiredForFranchise: true,
313
538
  topics: [
314
- 'Экономическое обоснование внедрения ПАК WORK (5 вечных мест + локальный ИИ против облачных подписок)',
315
- 'Лицензионная политика: расчёт конкурентных сессий и подбор тарифов SLA (Start / Business / Enterprise)',
539
+ 'Экономическое обоснование внедрения ODANT-WORK (5 вечных мест + локальный ИИ в собственном контуре)',
540
+ 'Лицензионная политика: расчёт конкурентных сессий со скидками от объёма и подбор тарифов SLA (Start / Business / Enterprise)',
316
541
  'Регламент первой и второй линии поддержки через боты MAX, Telegram и VK',
317
542
  ],
318
543
  },
319
544
  ]);
320
545
 
321
546
  /**
322
- * Рассчитать максимальное число партнёров (квоту) для региона пропорционально численности населения.
547
+ * Рассчитать максимальное число франшиз (квоту партнёров) для региона пропорционально численности населения.
323
548
  */
324
549
  export function calculateRegionPartnerQuota(populationMillions = 1.0) {
325
550
  const pop = Math.max(0.1, Number(populationMillions || 1.0));
@@ -327,8 +552,8 @@ export function calculateRegionPartnerQuota(populationMillions = 1.0) {
327
552
  }
328
553
 
329
554
  /**
330
- * Рассчитать фиксированную стоимость региональной франшизы пропорционально экономическим характеристикам региона
331
- * и квоту партнёров пропорционально населению региона.
555
+ * Рассчитать фиксированную стоимость региональной франшизы пропорционально экономическим характеристикам региона,
556
+ * невозвратный залог резервирования (100 000 ₽) и квоту франшиз пропорционально населению региона.
332
557
  */
333
558
  export function calculateFranchiseFee(regionCodeOrIndex = 'RU-SVE', customPopulationMillions = null) {
334
559
  let region = null;
@@ -347,12 +572,15 @@ export function calculateFranchiseFee(regionCodeOrIndex = 'RU-SVE', customPopula
347
572
  }
348
573
 
349
574
  const franchiseFeeRub = Math.round(FRANCHISE_PROGRAM.baseFranchiseFeeRub * economicIndex);
575
+ const reservationDepositRub = FRANCHISE_PROGRAM.reservationDepositRub;
350
576
  const maxPartnersQuota = calculateRegionPartnerQuota(populationMillions);
351
577
 
352
578
  return {
353
579
  regionCode: region?.code || 'CUSTOM',
354
580
  regionName: region?.name || 'Выбранный регион',
355
581
  district: region?.district || 'РФ',
582
+ hidden: !!region?.hidden,
583
+ headquarters: !!region?.headquarters,
356
584
  populationMillions,
357
585
  maxPartnersQuota,
358
586
  grpTrillionRub: region?.grpTrillionRub ?? null,
@@ -360,6 +588,10 @@ export function calculateFranchiseFee(regionCodeOrIndex = 'RU-SVE', customPopula
360
588
  economicIndex,
361
589
  baseFranchiseFeeRub: FRANCHISE_PROGRAM.baseFranchiseFeeRub,
362
590
  franchiseFeeRub,
591
+ reservationDepositRub,
592
+ remainingAfterDepositRub: Math.max(0, franchiseFeeRub - reservationDepositRub),
593
+ fullPaymentPeriodMonths: FRANCHISE_PROGRAM.fullPaymentPeriodMonths,
594
+ fullPaymentPeriodYears: FRANCHISE_PROGRAM.fullPaymentPeriodYears,
363
595
  };
364
596
  }
365
597
 
@@ -398,11 +630,7 @@ export function calculatePartnerDiscount(annualSalesRub = 0) {
398
630
  }
399
631
 
400
632
  /**
401
- * Полный расчёт экономики партнёра-франчайзи:
402
- * - фиксированный региональный взнос (по экономике региона) и квота партнёров (по населению);
403
- * - скидка партнёра (10% → 50%) от годового объёма продаж;
404
- * - дополнительный 100% доход партнёра: поставка NAS, серверов инференса, платный API к моделям,
405
- * техподдержка, дообучение ИИ-агентов (LoRA) и разработка навыков (skills).
633
+ * Полный расчёт экономики партнёра-франчайзи.
406
634
  */
407
635
  export function calculatePartnerEconomics({
408
636
  regionCode = 'RU-SVE',
@@ -416,7 +644,6 @@ export function calculatePartnerEconomics({
416
644
  const partnerMarginRub = Math.round(retailRub * (discount.discountPercent / 100));
417
645
  const netToVendorRub = retailRub - partnerMarginRub;
418
646
 
419
- // Расчёт дополнительных источников дохода партнёра (100% выручка партнёра)
420
647
  const nasBackupRub = Math.max(0, Number(extraServices.nasBackupRub ?? 350_000));
421
648
  const inferenceHardwareRub = Math.max(0, Number(extraServices.inferenceHardwareRub ?? 0));
422
649
  const modelApiAnnualRub = Math.max(0, Number(extraServices.modelApiAnnualRub ?? 360_000));
@@ -448,4 +675,3 @@ export function calculatePartnerEconomics({
448
675
  operationsModel: FRANCHISE_PROGRAM.partnerOperationsModel,
449
676
  };
450
677
  }
451
-