work-license-server 1.0.0 → 1.0.1

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,38 @@ export function calculateOrderQuote({
170
276
  }
171
277
 
172
278
  /**
173
- * Параметры партнёрской программы и региональной франшизы WORK:
174
- * - Фиксированная стоимость франшизы пропорциональна экономическому индексу региона (baseFranchiseRub * economicIndex).
175
- * - Максимальное количество партнёров в регионе пропорционально населению региона (partnersPerMillion * populationMillions, мин. 1).
279
+ * Параметры партнёрской программы и региональной франшизы ODANT-WORK:
280
+ * - Предварительная базовая стоимость франшизы = 5 000 000 ₽ (корректируется пропорционально экономическому индексу региона).
281
+ * - Сразу при резервировании франшизы вносится невозвратный залог = 100 000 ₽.
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: 5_000_000, // Предварительная базовая стоимость франшизы (5 млн ₽ при экономическом индексе 1.0)
287
+ reservationDepositRub: 100_000, // Невозвратный залог сразу за резервирование региональной франшизы (100 тыс. ₽)
288
+ partnersPerMillionPopulation: 1, // Норматив квоты партнёров: 1 франшиза на 1 млн населения региона
181
289
  minDiscountPercent: 10, // Начальная партнёрская скидка (10%)
182
290
  maxDiscountPercent: 50, // Максимальная партнёрская скидка (50%)
183
- maxDiscountAnnualSalesRub: 100_000_000, // Годовой объём продаж (100 млн ₽ = ~20 ПАК), при котором достигается максимальная скидка 50%
184
- hardwareWarrantyYears: 1, // Обязательная годовая гарантия на оборудование ПАК, предоставляемая партнёром
291
+ maxDiscountAnnualSalesRub: 100_000_000, // Годовой объём продаж (100 млн ₽ = ~20 ПАК), при котором достигается скидка 50%
292
+ hardwareWarrantyYears: 1, // Годовая гарантия на оборудование, предоставляемая партнёром
185
293
  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 год) на поставленное оборудование ПАК',
294
+ 'Партнёр самостоятельно закупает аппаратную платформу (AMD Ryzen™ AI Max+ PRO 495 · Radeon™ 8065S · 192 ГБ RAM · SSD 4 ТБ)',
295
+ 'Партнёр устанавливает ODANT-WORK, систему учёта, документооборота, процессов и 4 локальные модели ИИ',
296
+ 'Партнёр самостоятельно привязывает оборудование (HWID + #system/node-key.json) и выдаёт лицензии клиентам',
297
+ 'Партнёр предоставляет клиенту годовую гарантию (1 год) на поставленное оборудование',
190
298
  ]),
191
299
  partnerRevenueStreams: Object.freeze([
192
300
  {
193
301
  id: 'pak-license-margin',
194
- title: 'Продажа ПАК WORK и лицензий (скидка 10% → 50%)',
195
- share: 'Скидка партнёра от 10% до 50% пропорционально годовому объёму продаж + аппаратная наценка при сборке ПАК',
302
+ title: 'Продажа комплексов ODANT-WORK и лицензий (скидка 10% → 50%)',
303
+ share: 'Скидка партнёра от 10% до 50% пропорционально годовому объёму продаж + аппаратная маржа сборки',
196
304
  recommendedAvgRub: 1_500_000,
197
305
  },
198
306
  {
199
307
  id: 'nas-backup-hardware',
200
308
  title: 'Поставка NAS-систем для резервного копирования',
201
309
  share: '100% доход партнёра',
202
- description: 'Поставка и настройка сетевых хранилищ NAS для автоматической архивации и снапшотов баз организаций WORK',
310
+ description: 'Поставка и настройка сетевых хранилищ NAS для автоматической архивации и резервного копирования баз организаций',
203
311
  recommendedAvgRub: 350_000,
204
312
  },
205
313
  {
@@ -219,7 +327,7 @@ export const FRANCHISE_PROGRAM = Object.freeze({
219
327
  {
220
328
  id: 'partner-tech-support',
221
329
  title: 'Техническая поддержка и сервисное сопровождение',
222
- share: 'Доход партнёра по договорам SLA и выездному обслуживанию',
330
+ share: 'Доход партнёра по договорам SLA и гарантийному обслуживанию',
223
331
  description: 'Первая и вторая линия техподдержки предприятий региона, администрирование серверов и регламентное ТО',
224
332
  recommendedAvgRub: 240_000,
225
333
  },
@@ -227,77 +335,192 @@ export const FRANCHISE_PROGRAM = Object.freeze({
227
335
  id: 'agent-training-skills',
228
336
  title: 'Дообучение ИИ-агентов (LoRA) и разработка навыков (skills)',
229
337
  share: '100% проектный доход партнёра',
230
- description: 'Сборка датасетов предприятия, дообучение моделей под отраслевую специфику, создание навыков (skills) и учётных форм',
338
+ description: 'Сборка датасетов предприятия, дообучение моделей под отраслевую специфику, создание навыков (skills) и процессных схем',
231
339
  recommendedAvgRub: 600_000,
232
340
  },
233
341
  ]),
234
342
  });
235
343
 
236
344
  /**
237
- * Справочник регионов РФ с экономическими характеристиками и численностью населения:
238
- * - Стоимость региональной франшизы пропорциональна экономическим характеристикам региона (economicIndex).
239
- * - Квота (максимальное число партнёров в регионе) пропорциональна населению региона (populationMillions).
345
+ * Перечень всех субъектов Российской Федерации для партнёрской программы:
346
+ * численность населения (populationMillions — определяет квоту франшиз), ВРП (grpTrillionRub, оценка)
347
+ * и экономический индекс (economicIndex — множитель базовой стоимости франшизы).
348
+ * Контуры субъектов для интерактивной карты лежат в assets/russia-regions.json (по коду субъекта).
349
+ * Служебный «Типовой регион» (RU-REG, hidden) используется для расчётов и на витрине не показывается.
240
350
  */
351
+ const region = (code, name, district, populationMillions, grpTrillionRub, economicIndex, extra = {}) => Object.freeze({
352
+ code,
353
+ name,
354
+ district,
355
+ populationMillions,
356
+ grpTrillionRub,
357
+ activeEnterprises: Math.round(populationMillions * 22_000 / 1000) * 1000,
358
+ economicIndex,
359
+ ...extra,
360
+ });
361
+
362
+ export const FEDERAL_DISTRICTS = Object.freeze({
363
+ 'ЦФО': 'Центральный',
364
+ 'СЗФО': 'Северо-Западный',
365
+ 'ЮФО': 'Южный',
366
+ 'СКФО': 'Северо-Кавказский',
367
+ 'ПФО': 'Приволжский',
368
+ 'УФО': 'Уральский',
369
+ 'СФО': 'Сибирский',
370
+ 'ДФО': 'Дальневосточный',
371
+ });
372
+
241
373
  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 },
374
+ // Центральный федеральный округ
375
+ region('RU-MOW', 'Москва', 'ЦФО', 13.1, 28.5, 2.0, { activeEnterprises: 620_000 }),
376
+ region('RU-MOS', 'Московская область', 'ЦФО', 8.6, 8.4, 1.4, { activeEnterprises: 185_000 }),
377
+ region('RU-BEL', 'Белгородская область', 'ЦФО', 1.5, 1.4, 0.9),
378
+ region('RU-BRY', 'Брянская область', 'ЦФО', 1.15, 0.6, 0.7),
379
+ region('RU-VLA', 'Владимирская область', 'ЦФО', 1.33, 0.75, 0.8),
380
+ region('RU-VOR', 'Воронежская область', 'ЦФО', 2.3, 1.5, 0.9),
381
+ region('RU-IVA', 'Ивановская область', 'ЦФО', 0.91, 0.35, 0.6),
382
+ region('RU-KLU', 'Калужская область', 'ЦФО', 1.07, 0.8, 0.8),
383
+ region('RU-KOS', 'Костромская область', 'ЦФО', 0.57, 0.3, 0.6),
384
+ region('RU-KRS', 'Курская область', 'ЦФО', 1.07, 0.8, 0.8),
385
+ region('RU-LIP', 'Липецкая область', 'ЦФО', 1.12, 1.0, 0.9),
386
+ region('RU-ORL', 'Орловская область', 'ЦФО', 0.7, 0.35, 0.6),
387
+ region('RU-RYA', 'Рязанская область', 'ЦФО', 1.08, 0.65, 0.8, { headquarters: true }),
388
+ region('RU-SMO', 'Смоленская область', 'ЦФО', 0.88, 0.45, 0.7),
389
+ region('RU-TAM', 'Тамбовская область', 'ЦФО', 0.97, 0.5, 0.7),
390
+ region('RU-TVE', 'Тверская область', 'ЦФО', 1.21, 0.7, 0.8),
391
+ region('RU-TUL', 'Тульская область', 'ЦФО', 1.5, 1.1, 0.9),
392
+ region('RU-YAR', 'Ярославская область', 'ЦФО', 1.2, 0.9, 0.9),
393
+
394
+ // Северо-Западный федеральный округ
395
+ region('RU-SPE', 'Санкт-Петербург', 'СЗФО', 5.6, 11.2, 1.6, { activeEnterprises: 245_000 }),
396
+ region('RU-LEN', 'Ленинградская область', 'СЗФО', 2.0, 1.8, 1.0),
397
+ region('RU-KGD', 'Калининградская область', 'СЗФО', 1.0, 0.9, 0.9),
398
+ region('RU-MUR', 'Мурманская область', 'СЗФО', 0.7, 1.2, 1.0),
399
+ region('RU-ARK', 'Архангельская область', 'СЗФО', 0.96, 1.1, 0.9),
400
+ region('RU-NEN', 'Ненецкий автономный округ', 'СЗФО', 0.04, 0.35, 0.6),
401
+ region('RU-VLG', 'Вологодская область', 'СЗФО', 1.1, 1.1, 0.9),
402
+ region('RU-KR', 'Республика Карелия', 'СЗФО', 0.52, 0.45, 0.7),
403
+ region('RU-KO', 'Республика Коми', 'СЗФО', 0.72, 0.9, 0.9),
404
+ region('RU-NGR', 'Новгородская область', 'СЗФО', 0.58, 0.4, 0.7),
405
+ region('RU-PSK', 'Псковская область', 'СЗФО', 0.58, 0.25, 0.6),
406
+
407
+ // Южный федеральный округ
408
+ region('RU-KDA', 'Краснодарский край', 'ЮФО', 5.8, 4.3, 1.3, { activeEnterprises: 120_000 }),
409
+ region('RU-ROS', 'Ростовская область', 'ЮФО', 4.2, 2.6, 1.0),
410
+ region('RU-VGG', 'Волгоградская область', 'ЮФО', 2.5, 1.4, 0.9),
411
+ region('RU-AST', 'Астраханская область', 'ЮФО', 0.95, 0.6, 0.8),
412
+ region('RU-AD', 'Республика Адыгея', 'ЮФО', 0.5, 0.15, 0.6),
413
+ region('RU-KL', 'Республика Калмыкия', 'ЮФО', 0.26, 0.1, 0.5),
414
+ region('RU-CR', 'Республика Крым', 'ЮФО', 1.91, 0.75, 0.8),
415
+ region('RU-SEV', 'Севастополь', 'ЮФО', 0.56, 0.2, 0.8),
416
+ region('RU-DPR', 'Донецкая Народная Республика', 'ЮФО', 2.29, null, 0.6),
417
+ region('RU-LPR', 'Луганская Народная Республика', 'ЮФО', 1.32, null, 0.6),
418
+ region('RU-ZAP', 'Запорожская область', 'ЮФО', 0.49, null, 0.6),
419
+ region('RU-KHE', 'Херсонская область', 'ЮФО', 0.36, null, 0.6),
420
+
421
+ // Северо-Кавказский федеральный округ
422
+ region('RU-STA', 'Ставропольский край', 'СКФО', 2.9, 1.3, 0.9),
423
+ region('RU-DA', 'Республика Дагестан', 'СКФО', 3.2, 1.0, 0.8),
424
+ region('RU-CE', 'Чеченская Республика', 'СКФО', 1.53, 0.35, 0.6),
425
+ region('RU-KB', 'Кабардино-Балкарская Республика', 'СКФО', 0.9, 0.25, 0.6),
426
+ region('RU-SE', 'Республика Северная Осетия — Алания', 'СКФО', 0.68, 0.2, 0.6),
427
+ region('RU-IN', 'Республика Ингушетия', 'СКФО', 0.52, 0.1, 0.5),
428
+ region('RU-KC', 'Карачаево-Черкесская Республика', 'СКФО', 0.47, 0.13, 0.5),
429
+
430
+ // Приволжский федеральный округ
431
+ region('RU-TA', 'Республика Татарстан', 'ПФО', 4.0, 4.6, 1.3, { activeEnterprises: 98_000 }),
432
+ region('RU-BA', 'Республика Башкортостан', 'ПФО', 4.1, 2.4, 1.0),
433
+ region('RU-NIZ', 'Нижегородская область', 'ПФО', 3.1, 2.5, 1.1),
434
+ region('RU-SAM', 'Самарская область', 'ПФО', 3.1, 2.6, 1.1),
435
+ region('RU-PER', 'Пермский край', 'ПФО', 2.5, 2.1, 1.0),
436
+ region('RU-SAR', 'Саратовская область', 'ПФО', 2.4, 1.3, 0.9),
437
+ region('RU-ORE', 'Оренбургская область', 'ПФО', 1.84, 1.6, 0.9),
438
+ region('RU-UD', 'Удмуртская Республика', 'ПФО', 1.5, 1.0, 0.9),
439
+ region('RU-PNZ', 'Пензенская область', 'ПФО', 1.24, 0.6, 0.7),
440
+ region('RU-ULY', 'Ульяновская область', 'ПФО', 1.18, 0.6, 0.7),
441
+ region('RU-CU', 'Чувашская Республика', 'ПФО', 1.17, 0.45, 0.7),
442
+ region('RU-KIR', 'Кировская область', 'ПФО', 1.12, 0.5, 0.7),
443
+ region('RU-MO', 'Республика Мордовия', 'ПФО', 0.76, 0.3, 0.6),
444
+ region('RU-ME', 'Республика Марий Эл', 'ПФО', 0.67, 0.25, 0.6),
445
+
446
+ // Уральский федеральный округ
447
+ region('RU-SVE', 'Свердловская область', 'УФО', 4.2, 4.2, 1.3, { activeEnterprises: 112_000 }),
448
+ region('RU-CHE', 'Челябинская область', 'УФО', 3.4, 2.5, 1.0),
449
+ region('RU-TYU', 'Тюменская область', 'УФО', 1.6, 1.9, 1.1),
450
+ region('RU-KHM', 'ХМАО — Югра', 'УФО', 1.7, 6.9, 1.5),
451
+ region('RU-YAN', 'Ямало-Ненецкий автономный округ', 'УФО', 0.5, 5.4, 1.4),
452
+ region('RU-KGN', 'Курганская область', 'УФО', 0.76, 0.3, 0.6),
453
+
454
+ // Сибирский федеральный округ
455
+ region('RU-NVS', 'Новосибирская область', 'СФО', 2.8, 2.3, 1.1, { activeEnterprises: 82_000 }),
456
+ region('RU-KYA', 'Красноярский край', 'СФО', 2.9, 3.5, 1.2),
457
+ region('RU-KEM', 'Кемеровская область — Кузбасс', 'СФО', 2.6, 2.2, 1.0),
458
+ region('RU-IRK', 'Иркутская область', 'СФО', 2.3, 2.4, 1.1),
459
+ region('RU-ALT', 'Алтайский край', 'СФО', 2.1, 1.1, 0.8),
460
+ region('RU-OMS', 'Омская область', 'СФО', 1.8, 1.1, 0.9),
461
+ region('RU-TOM', 'Томская область', 'СФО', 1.1, 0.9, 0.9),
462
+ region('RU-KK', 'Республика Хакасия', 'СФО', 0.53, 0.35, 0.7),
463
+ region('RU-TY', 'Республика Тыва', 'СФО', 0.34, 0.1, 0.5),
464
+ region('RU-AL', 'Республика Алтай', 'СФО', 0.21, 0.07, 0.5),
465
+
466
+ // Дальневосточный федеральный округ
467
+ region('RU-PRI', 'Приморский край', 'ДФО', 1.8, 1.7, 1.0),
468
+ region('RU-KHA', 'Хабаровский край', 'ДФО', 1.3, 1.3, 1.0),
469
+ region('RU-SA', 'Республика Саха (Якутия)', 'ДФО', 1.0, 2.0, 1.1),
470
+ region('RU-ZAB', 'Забайкальский край', 'ДФО', 0.98, 0.55, 0.7),
471
+ region('RU-BU', 'Республика Бурятия', 'ДФО', 0.97, 0.4, 0.7),
472
+ region('RU-AMU', 'Амурская область', 'ДФО', 0.75, 0.6, 0.8),
473
+ region('RU-SAK', 'Сахалинская область', 'ДФО', 0.46, 1.6, 1.1),
474
+ region('RU-KAM', 'Камчатский край', 'ДФО', 0.29, 0.35, 0.8),
475
+ region('RU-YEV', 'Еврейская автономная область', 'ДФО', 0.15, 0.08, 0.5),
476
+ region('RU-MAG', 'Магаданская область', 'ДФО', 0.14, 0.4, 0.8),
477
+ region('RU-CHU', 'Чукотский автономный округ', 'ДФО', 0.05, 0.15, 0.6),
478
+
479
+ // Служебный типовой регион для расчётов (на витрине не показывается)
480
+ region('RU-REG', 'Типовой регион РФ (до 1 млн чел.)', 'РФ', 1.0, 0.9, 0.5, { hidden: true, activeEnterprises: 25_000 }),
258
481
  ]);
259
482
 
260
483
  /**
261
- * Программа обучения и сертификации специалистов партнёров WORK.
484
+ * Программа обучения и сертификации специалистов партнёров ODANT-WORK.
262
485
  */
263
486
  export const PARTNER_TRAINING_TRACKS = Object.freeze([
264
487
  {
265
488
  id: 'pak-ai-engineer',
266
- title: 'Сертифицированный инженер ПАК и локального ИИ WORK',
489
+ title: 'Сертифицированный инженер ПАК и локального ИИ ODANT-WORK',
267
490
  code: 'WORK-CERT-ENG',
268
491
  hours: 40,
269
492
  passScore: 80,
270
493
  requiredForFranchise: true,
271
494
  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 в контуре предприятия',
495
+ 'Архитектура ПАК на AMD Ryzen™ AI Max+ PRO 495 и Radeon™ 8065S (192 ГБ Unified RAM, SSD 4 ТБ) и профилирование памяти',
496
+ 'Администрирование предустановленных типов моделей: большая языковая модель (LLM), голосовая модель (ASR/TTS), генерация изображений и векторный поиск (Embedding)',
497
+ 'Сборка корпоративных датасетов и дообучение моделей в контуре предприятия',
275
498
  'Аппаратная привязка лицензий (HWID + #system/node-key.json) и закрытый контур безопасности',
276
499
  ],
277
500
  },
278
501
  {
279
502
  id: 'accounting-architect',
280
- title: 'Сертифицированный архитектор учётных систем WORK (1С-класс)',
503
+ title: 'Сертифицированный архитектор систем учёта, документооборота и процессов ODANT-WORK',
281
504
  code: 'WORK-CERT-ACC',
282
505
  hours: 48,
283
506
  passScore: 80,
284
507
  requiredForFranchise: true,
285
508
  topics: [
286
- 'Типовая учётная конфигурация (/MODULES/Register): справочники $catalog, виртуальные справочники $virtual, план счетов $account',
509
+ 'Встроенная система учёта (/MODULES/Register): справочники $catalog, виртуальные справочники $virtual, план счетов $account',
287
510
  'Явная двойная запись, проведение операций $operation, сторно и двухэтапные субаналитики',
288
- 'Отчётность ODANT-стандарта: Оборотно-сальдовая ведомость, Анализ счёта, Обороты счёта, Журнал проводок',
289
- 'Миграция исторических баз и остатков из 1С:Предприятие в изолированные БД организаций WORK',
511
+ 'Отчётность корпоративного стандарта: Оборотно-сальдовая ведомость, Анализ счёта, Обороты счёта, Журнал проводок',
512
+ 'Построение сквозного документооборота, управления процессами и перенос исторических данных в изолированные БД организаций',
290
513
  ],
291
514
  },
292
515
  {
293
516
  id: 'platform-developer',
294
- title: 'Сертифицированный разработчик модулей и ИИ-агентов WORK',
517
+ title: 'Сертифицированный разработчик модулей и ИИ-агентов ODANT-WORK',
295
518
  code: 'WORK-CERT-DEV',
296
519
  hours: 36,
297
520
  passScore: 75,
298
521
  requiredForFranchise: false,
299
522
  topics: [
300
- 'Трёхосевое наследование метаданных WORK (Types → Ancestor → Parent → Class)',
523
+ 'Трёхосевое наследование метаданных ODANT-WORK (Types → Ancestor → Parent → Class)',
301
524
  'Разработка представлений work-form на компонентах ODA и серверных методов шлюза',
302
525
  'Создание прикладных навыков (skills), инструментов и триггеров для ИИ-консультанта',
303
526
  'Федерация серверов NODES и подключение периферии через LAN / MCP',
@@ -305,21 +528,21 @@ export const PARTNER_TRAINING_TRACKS = Object.freeze([
305
528
  },
306
529
  {
307
530
  id: 'sales-sla-manager',
308
- title: 'Сертифицированный эксперт по продажам и сопровождению ПАК WORK',
531
+ title: 'Сертифицированный эксперт по продажам и сопровождению ODANT-WORK',
309
532
  code: 'WORK-CERT-SLS',
310
533
  hours: 24,
311
534
  passScore: 75,
312
535
  requiredForFranchise: true,
313
536
  topics: [
314
- 'Экономическое обоснование внедрения ПАК WORK (5 вечных мест + локальный ИИ против облачных подписок)',
315
- 'Лицензионная политика: расчёт конкурентных сессий и подбор тарифов SLA (Start / Business / Enterprise)',
537
+ 'Экономическое обоснование внедрения ODANT-WORK (5 вечных мест + локальный ИИ в собственном контуре)',
538
+ 'Лицензионная политика: расчёт конкурентных сессий со скидками от объёма и подбор тарифов SLA (Start / Business / Enterprise)',
316
539
  'Регламент первой и второй линии поддержки через боты MAX, Telegram и VK',
317
540
  ],
318
541
  },
319
542
  ]);
320
543
 
321
544
  /**
322
- * Рассчитать максимальное число партнёров (квоту) для региона пропорционально численности населения.
545
+ * Рассчитать максимальное число франшиз (квоту партнёров) для региона пропорционально численности населения.
323
546
  */
324
547
  export function calculateRegionPartnerQuota(populationMillions = 1.0) {
325
548
  const pop = Math.max(0.1, Number(populationMillions || 1.0));
@@ -327,8 +550,8 @@ export function calculateRegionPartnerQuota(populationMillions = 1.0) {
327
550
  }
328
551
 
329
552
  /**
330
- * Рассчитать фиксированную стоимость региональной франшизы пропорционально экономическим характеристикам региона
331
- * и квоту партнёров пропорционально населению региона.
553
+ * Рассчитать фиксированную стоимость региональной франшизы пропорционально экономическим характеристикам региона,
554
+ * невозвратный залог резервирования (100 000 ₽) и квоту франшиз пропорционально населению региона.
332
555
  */
333
556
  export function calculateFranchiseFee(regionCodeOrIndex = 'RU-SVE', customPopulationMillions = null) {
334
557
  let region = null;
@@ -347,12 +570,15 @@ export function calculateFranchiseFee(regionCodeOrIndex = 'RU-SVE', customPopula
347
570
  }
348
571
 
349
572
  const franchiseFeeRub = Math.round(FRANCHISE_PROGRAM.baseFranchiseFeeRub * economicIndex);
573
+ const reservationDepositRub = FRANCHISE_PROGRAM.reservationDepositRub;
350
574
  const maxPartnersQuota = calculateRegionPartnerQuota(populationMillions);
351
575
 
352
576
  return {
353
577
  regionCode: region?.code || 'CUSTOM',
354
578
  regionName: region?.name || 'Выбранный регион',
355
579
  district: region?.district || 'РФ',
580
+ hidden: !!region?.hidden,
581
+ headquarters: !!region?.headquarters,
356
582
  populationMillions,
357
583
  maxPartnersQuota,
358
584
  grpTrillionRub: region?.grpTrillionRub ?? null,
@@ -360,6 +586,8 @@ export function calculateFranchiseFee(regionCodeOrIndex = 'RU-SVE', customPopula
360
586
  economicIndex,
361
587
  baseFranchiseFeeRub: FRANCHISE_PROGRAM.baseFranchiseFeeRub,
362
588
  franchiseFeeRub,
589
+ reservationDepositRub,
590
+ remainingAfterDepositRub: Math.max(0, franchiseFeeRub - reservationDepositRub),
363
591
  };
364
592
  }
365
593
 
@@ -398,11 +626,7 @@ export function calculatePartnerDiscount(annualSalesRub = 0) {
398
626
  }
399
627
 
400
628
  /**
401
- * Полный расчёт экономики партнёра-франчайзи:
402
- * - фиксированный региональный взнос (по экономике региона) и квота партнёров (по населению);
403
- * - скидка партнёра (10% → 50%) от годового объёма продаж;
404
- * - дополнительный 100% доход партнёра: поставка NAS, серверов инференса, платный API к моделям,
405
- * техподдержка, дообучение ИИ-агентов (LoRA) и разработка навыков (skills).
629
+ * Полный расчёт экономики партнёра-франчайзи.
406
630
  */
407
631
  export function calculatePartnerEconomics({
408
632
  regionCode = 'RU-SVE',
@@ -416,7 +640,6 @@ export function calculatePartnerEconomics({
416
640
  const partnerMarginRub = Math.round(retailRub * (discount.discountPercent / 100));
417
641
  const netToVendorRub = retailRub - partnerMarginRub;
418
642
 
419
- // Расчёт дополнительных источников дохода партнёра (100% выручка партнёра)
420
643
  const nasBackupRub = Math.max(0, Number(extraServices.nasBackupRub ?? 350_000));
421
644
  const inferenceHardwareRub = Math.max(0, Number(extraServices.inferenceHardwareRub ?? 0));
422
645
  const modelApiAnnualRub = Math.max(0, Number(extraServices.modelApiAnnualRub ?? 360_000));
@@ -448,4 +671,3 @@ export function calculatePartnerEconomics({
448
671
  operationsModel: FRANCHISE_PROGRAM.partnerOperationsModel,
449
672
  };
450
673
  }
451
-