From 7761ea7c7e09d938c30311e82997efe8edc1690d Mon Sep 17 00:00:00 2001 From: "Kalinina.e" Date: Tue, 15 Sep 2026 06:11:57 +0000 Subject: [PATCH] =?UTF-8?q?add:=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=BA=D0=BE=D1=80=D1=80=D0=B5=D0=BA=D1=82=D0=BD?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D0=B8=20=D1=80=D0=B0=D1=81=D1=87=D0=B5=D1=82?= =?UTF-8?q?=D0=BE=D0=B2=20=D0=B2=20=D1=88=D0=B0=D0=B1=D0=BB=D0=BE=D0=BD?= =?UTF-8?q?=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vibe42-run.log | 79 +++++ .vibe42-run.pid | 2 +- AGENTS.md | 13 +- check-calculations.js | 312 ++++++++++++++++++ check-calculations.py | 305 +++++++++++++++++ Отчет_о_проверке_расчетов.xlsx | Bin 0 -> 5621 bytes ...внение_БП_2026_НСВМ_и_Шаблон_БП_БКВ_2027.xlsx | Bin 2383067 -> 2383067 bytes 7 files changed, 705 insertions(+), 6 deletions(-) create mode 100644 check-calculations.js create mode 100644 check-calculations.py create mode 100644 Отчет_о_проверке_расчетов.xlsx diff --git a/.vibe42-run.log b/.vibe42-run.log index 07218f0..a3a3a70 100644 --- a/.vibe42-run.log +++ b/.vibe42-run.log @@ -84,3 +84,82 @@ npm notice 📈 Всего листов сравнено: 43 ⚠️ Листов с различиями: 21 📋 Листов без различий: 4 +--- run Tue Sep 15 06:10:50 AM UTC 2026 :: npm start --silent +Файл 1 листы: [ + '0.Краткое содержание', + '1.Резюме проекта', + '2.1.Текущее состояние', + '2.2 Описание НП', + '2.3.Спецификации по этапам', + 'Капекс СМЦ', + '2.3.1 КОФ', + '2.3.4 Норма расчета на 1 т.п.', + '2.3.6 нормативы ДУП', + '2.4.Основные показатели проекта', + '2.5.Этапы инвестирования', + '3.Входные данные', + '4.БКВ', + '5.Амортизация', + '6. Доходы', + '6.1 Доходы', + '7.Расходы', + 'Opex', + 'ФОТ', + 'План подключения камер', + '7.1 Аренда каналов и реклама', + '8.ОДДС', + '9 КПЭ', + '10.Фин.пок-ли', + '11.Заключение', + '12.Анализ чувствительности', + '13. Риски проекта', + '13.1 Карта рисков_2024', + '13.2 Карта рисков_2027', + '13.3 Премия за специфич-й риск', + '14. Методика ПМ', + 'Доп информация >', + 'Лизинг' +] +Файл 2 листы: [ + '0.Краткое содержание', + '1.Резюме проекта', + '2.1.Текущее состояние', + '2.2 Описание НП', + '2.3.Спецификации по этапам', + '2.3.1 КОФ', + '2.3.2.1 Маркет цена', + '2.3.2.2 Оборудование', + '2.3.3 СМР', + '2.3.4.1 Норма расчета 1т.- ДРБ', + '2.3.4.2 Норма расчет 1т.- ДКБ', + '2.3.5. Уд. Capex и ЗИП', + '2.3.6 нормативы ДУП', + '2.4.Основные показатели проекта', + '2.5.Этапы инвестирования', + '3.Входные данные', + '4.БКВ', + '5.Амортизация', + '6. Доходы', + '6.1 Доходы', + '7.Расходы', + '7.1 Аренда каналов и реклама', + 'Займы', + '8.ОДДС', + '9 КПЭ', + '10.Фин.пок-ли', + '11.Заключение', + '12.Анализ чувствительности', + '13. Риски проекта', + '13.1 Карта рисков', + '13.2 Премия за специфич риск', + '13.3 Заявление о риск аппетите', + '14. Методика ПМ', + 'Доп информация >', + 'Лизинг' +] + +✅ Сравнение завершено! +📊 Файл с результатами: Сравнение_БП_2026_НСВМ_и_Шаблон_БП_БКВ_2027.xlsx +📈 Всего листов сравнено: 43 +⚠️ Листов с различиями: 21 +📋 Листов без различий: 4 diff --git a/.vibe42-run.pid b/.vibe42-run.pid index 8cf5c1a..8e14edc 100644 --- a/.vibe42-run.pid +++ b/.vibe42-run.pid @@ -1 +1 @@ -86 +78 diff --git a/AGENTS.md b/AGENTS.md index fbf80f9..a3c5c9b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,4 +1,4 @@ - + # Vibe42 — учебная песочница: сайты, боты и первые приложения Workspace юзера `Kalinina.e`. Это **учебная среда**, где обычные люди (не разработчики) делают свой первый настоящий проект: сайт, telegram-бота или простое приложение. @@ -23,7 +23,8 @@ Workspace юзера `Kalinina.e`. Это **учебная среда**, где |----------------|---------------|--------| | Сайт, лендинг, визитка, портфолио, меню, афиша, waitlist | **Статика:** в проекте УЖЕ лежит стартовый `index.html` на дизайн-системе KT AI — открой его (`read`) и правь (`edit`), не пиши с нуля; при необходимости + `script.js`. Ванильный JS, БЕЗ сборки, БЕЗ React/Vue/Vite, БЕЗ npm. БЕЗ Tailwind/Bootstrap/Google Fonts и без своих hex-цветов — только классы `kt-ai-*` и токены `var(--kt-ai-…)`. | навык `landing-templates` (шаблоны секций A–E) + `design.md` → правка готового `index.html`, публикация в `pages` | | Telegram-бот | **Node.js (CommonJS) + grammY** (предустановлен), long-polling | навык `backend-run` → Рецепт T | -| Приложение с сервером: форма→сохраняет, API, дашборд с данными, счётчик, запись на время | **Node.js (CommonJS), сервер на `node:http` БЕЗ зависимостей + хранение в `data.json`** | навык `backend-run` → Рецепт B | +| Простое приложение с сервером: форма→сохраняет, API, счётчик, запись на время | **Node.js (CommonJS), сервер на `node:http` БЕЗ зависимостей + хранение в `data.json`** | навык `backend-run` → Рецепт B | +| **Полноценное приложение:** кабинет, панель, CRM-lite, несколько экранов, живые данные — юзер явно просит «приложение/систему», а не страницу | **Express + Vite/React (фронт собирается в `dist/`, сервер его раздаёт с одного порта) + `data.json`.** Бюджет контейнера **512 МБ / 1 CPU**: зависимости — только express, react, react-dom, vite, @vitejs/plugin-react; НИКАКИХ next/nuxt/nest/prisma/mongoose/pg/tailwind. Сборку и установку делает `run` (scripts.build + scripts.start), сам в шелле не собирай | навык `backend-run` → Рецепт A | | Нужен ИИ внутри (умный бот, генерация текста, ответы) | тот же Node-скелет + **`fetch` к `process.env.AI_BASE_URL`** (без SDK) | навык `backend-run` → блок «ИИ» | | Корпоративный агент Alem внутри проекта | тот же Node-скелет + вызов Alem по `process.env.ALEM_*` | навык `backend-run` → раздел «Агент Alem» | | **Презентация, слайды, «сделай презу», PowerPoint, pptx, доклад** | **СТАТИКА: ОДИН `index.html` + pptxgenjs с CDN. Бэкенд НЕ поднимать, ИИ в рантайме НЕ звать** | навык `presentations` — бери оттуда скелет целиком | @@ -39,6 +40,7 @@ Workspace юзера `Kalinina.e`. Это **учебная среда**, где 1. Зависимости — ТОЛЬКО в `package.json` → `dependencies`. Их поставит `run`. НИКОГДА не пиши `npm install` в шелле. 2. Порт — всегда `process.env.PORT || 3000`. Не хардкодь другой. 3. Запуск бэкенда — ТОЛЬКО командой `run`. Никогда сам `node ...` / `npm start` в шелле. +4. **Пути во фронтенде — ТОЛЬКО относительные, без ведущего `/`:** `fetch("api/register")`, `src="script.js"`, `href="list.html"`. Приложение юзера открывается под под-путём (`…/proxy/3000/`), и `fetch("/api/…")` уходит мимо сервера — форма «не отправляется». В сервере наоборот сравнивай `url` с `/api/...` как обычно. --- ## 🍳 Готовые рецепты кода → навык `backend-run` @@ -100,7 +102,7 @@ Workspace юзера `Kalinina.e`. Это **учебная среда**, где Файл памяти: `/srv/opencode/workspaces/users/Kalinina.e/.mimo/memory.md` (dot-папка `.mimo` не видна в Предпросмотре — так и задумано). Папка `.mimo/` уже создана, тебе нужно только читать/писать `memory.md`. -**В НАЧАЛЕ каждой сессии** — прочитай `/srv/opencode/workspaces/users/Kalinina.e/.mimo/memory.md` (если есть) ПЕРЕД первым ответом. Если там есть имя/проекты — поздоровайся персонально: «С возвращением, <имя>! Продолжим <последний проект> или сделаем новый?». Если файла нет — это новый юзер, работай по обычному сценарию. +**В НАЧАЛЕ каждой сессии** — прочитай `/srv/opencode/workspaces/users/Kalinina.e/.mimo/memory.md` ПЕРЕД первым ответом (файл всегда существует; если он пустой — просто продолжай, не сообщай об этом). Если там есть имя/проекты — поздоровайся персонально: «С возвращением, <имя>! Продолжим <последний проект> или сделаем новый?». Если файла нет — это новый юзер, работай по обычному сценарию. **ОБНОВЛЯЙ файл молча** (никогда не говори «я записал в память»), когда узнаёшь: - как обращаться к юзеру (имя/ник); @@ -280,15 +282,16 @@ git push origin HEAD:pages - После существенных правок уже опубликованного сайта НАПОМНИ юзеру нажать «Опубликовать» ещё раз — иначе на сайте останется старая версия. - **Не дёргай Gitea API типа `/repos/.../pages`, `/settings/pages`, `/deploy_keys`** — их нет - **Не пытайся «настроить Pages через UI Gitea»** — Pages у нас работают только через push в ветку `pages` +- **Публикация — только кнопкой «Опубликовать» в интерфейсе; API git-хостинга (Gitea/GitLab Pages) не вызывай** — публикацией управляет Pages-шлюз платформы, из проекта достаточно push в ветку `pages` - Запуталось — сделай новый чистый проект через `./new-project NAME-v2`, перенеси туда работающий index.html --- ## ❌ Чего НЕ делать НИКОГДА - ❌ `git init` в workspace root -- ❌ `npm install` с прод-зависимостями (express/mongoose/pg/prisma/next/nuxt) +- ❌ `npm install` руками в шелле и тяжёлые зависимости (mongoose/pg/prisma/next/nuxt/nest) — контейнер юзера 512 МБ - ❌ Поднимать бэкенд там, где хватает статики (лендинг, визитка, презентация) — сверься с таблицей стека -- ❌ Ставить прод-фреймворки (express / nest / next / django) — сервер только на `node:http` +- ❌ Ставить тяжёлые фреймворки (nest / next / nuxt / django) — сервер на `node:http`, а для полноценного приложения (Рецепт A) — express - ❌ Использовать `gh` CLI или GitHub API - ❌ Вызывать Gitea Pages-API (его нет) - ❌ Долгое отлаживание Pages — почти всегда решение «push HEAD:pages» diff --git a/check-calculations.js b/check-calculations.js new file mode 100644 index 0000000..dfd5227 --- /dev/null +++ b/check-calculations.js @@ -0,0 +1,312 @@ +const XLSX = require("xlsx"); + +const wb = XLSX.readFile("Шаблон БП_ БКВ 2027 с займами.xlsx"); +const sheets = wb.SheetNames; + +console.log("📊 ПРОВЕРКА КОРРЕКТНОСТИ РАСЧЕТОВ В ШАБЛОНЕ\n"); +console.log("Файл:", "Шаблон БП_ БКВ 2027 с займами.xlsx"); +console.log("Листов:", sheets.length); +console.log("=" .repeat(70)); + +const errors = []; +const warnings = []; +const successChecks = []; + +// Проверка 1: Анализ чувствительности (лист 12) +console.log("\n1️⃣ ПРОВЕРКА: Анализ чувствительности (лист 12.Анализ чувствительности)"); +if (sheets.includes("12.Анализ чувствительности")) { + const sheet = XLSX.utils.sheet_to_json(wb.Sheets["12.Анализ чувствительности"], { header: 1 }); + + // Проверяем NPV базового сценария + const baseNPV = sheet[3]?.[2]; // Ячейка C4 (0-indexed: row 3, col 2) + const down10Revenue = sheet[4]?.[2]; // C5 + const up10Expenses = sheet[4]?.[4]; // E5 + const up10CAPEX = sheet[4]?.[6]; // G5 + + console.log(` Базовый NPV: ${baseNPV}`); + console.log(` NPV при -10% доходов: ${down10Revenue}`); + console.log(` NPV при +10% расходов: ${up10Expenses}`); + console.log(` NPV при +10% CAPEX: ${up10CAPEX}`); + + // Проверка: NPV при снижении доходов должен быть меньше базового + if (baseNPV && down10Revenue) { + if (down10Revenue >= baseNPV) { + errors.push(`❌ Анализ чувствительности: NPV при -10% доходов (${down10Revenue}) >= базового NPV (${baseNPV})`); + } else { + successChecks.push(`✓ NPV корректно реагирует на снижение доходов`); + } + } + + // Проверка: NPV должен становиться отрицательным при критическом снижении + if (baseNPV && baseNPV > 0) { + warnings.push(`⚠ Базовый NPV положительный (${baseNPV}), но в анализе чувствительности есть отрицательные значения`); + } +} else { + errors.push("❌ Лист '12.Анализ чувствительности' отсутствует"); +} + +// Проверка 2: EBITDA и риск-аппетит (лист 13.1 Карта рисков) +console.log("\n2️⃣ ПРОВЕРКА: Расчет риск-аппетита (лист 13.1 Карта рисков)"); +if (sheets.includes("13.1 Карта рисков")) { + const sheet = XLSX.utils.sheet_to_json(wb.Sheets["13.1 Карта рисков"], { header: 1 }); + + const ebitda = sheet[0]?.[17]; // R1 (EBITDA) + const riskAppetitePercent = sheet[0]?.[18]; // S1 (7%) + const riskAppetiteValue = sheet[0]?.[19]; // T1 (3.86 млрд) + + console.log(` EBITDA: ${ebitda} тыс. тг`); + console.log(` Риск-аппетит: ${riskAppetitePercent * 100}%`); + console.log(` Расчетное значение: ${riskAppetiteValue} млрд. тг`); + + if (ebitda && riskAppetitePercent && riskAppetiteValue) { + const calculatedRiskAppetite = (ebitda * riskAppetitePercent) / 1000000000; // в млрд + const diff = Math.abs(calculatedRiskAppetite - riskAppetiteValue); + + if (diff > 0.01) { + errors.push(`❌ Риск-аппетит: расчетное значение ${calculatedRiskAppetite.toFixed(3)} млрд ≠ ${riskAppetiteValue} млрд`); + } else { + successChecks.push(`✓ Риск-аппетит рассчитан верно: ${riskAppetiteValue} млрд. тг`); + } + } +} else { + errors.push("❌ Лист '13.1 Карта рисков' отсутствует"); +} + +// Проверка 3: Премия за специфичный риск (лист 13.2) +console.log("\n3️⃣ ПРОВЕРКА: Премия за специфичный риск (лист 13.2 Премия за специфич риск)"); +if (sheets.includes("13.2 Премия за специфич риск")) { + const sheet = XLSX.utils.sheet_to_json(wb.Sheets["13.2 Премия за специфич риск"], { header: 1 }); + + // Ищем строки с оценками + let totalRiskPremium = 0; + let riskFactorsFound = 0; + + for (let i = 6; i < Math.min(sheet.length, 35); i++) { + const row = sheet[i]; + if (row && row[4] !== undefined) { + const riskValue = row[4]; // Столбец E - значение % + if (typeof riskValue === 'number' && riskValue > 0) { + totalRiskPremium += riskValue; + riskFactorsFound++; + console.log(` Фактор ${riskFactorsFound}: ${riskValue * 100}%`); + } + } + } + + console.log(` Итого премия за риск: ${(totalRiskPremium * 100).toFixed(2)}%`); + + // Проверка методики: сумма не должна превышать 5% (максимум по 4 факторам по 1%) + if (totalRiskPremium > 0.05) { + warnings.push(`⚠ Премия за специфичный риск ${(totalRiskPremium * 100).toFixed(2)}% превышает разумный предел (5%)`); + } else if (totalRiskPremium === 0) { + warnings.push("⚠ Премия за специфичный риск не рассчитана (все факторы = 0)"); + } else { + successChecks.push(`✓ Премия за специфичный риск: ${(totalRiskPremium * 100).toFixed(2)}%`); + } +} else { + errors.push("❌ Лист '13.2 Премия за специфич риск' отсутствует"); +} + +// Проверка 4: Заявление о риск-аппетите (лист 13.3) +console.log("\n4️⃣ ПРОВЕРКА: Заявление о риск-аппетите (лист 13.3)"); +if (sheets.includes("13.3 Заявление о риск аппетите")) { + const sheet = XLSX.utils.sheet_to_json(wb.Sheets["13.3 Заявление о риск аппетите"], { header: 1 }); + + // Проверяем наличие ошибок #DIV/0! + let divErrors = 0; + for (let i = 5; i < Math.min(sheet.length, 15); i++) { + const row = sheet[i]; + if (row) { + for (let j = 0; j < row.length; j++) { + if (row[j] === "#DIV/0!") { + divErrors++; + } + } + } + } + + if (divErrors > 0) { + errors.push(`❌ В листе '13.3 Заявление о риск аппетите' найдено ${divErrors} ошибок #DIV/0! - формулы не рассчитаны`); + } else { + successChecks.push(`✓ Заявление о риск-аппетите: формулы рассчитаны`); + } +} else { + errors.push("❌ Лист '13.3 Заявление о риск аппетите' отсутствует"); +} + +// Проверка 5: Лизинг (лист Лизинг) +console.log("\n5️⃣ ПРОВЕРКА: Расчеты лизинга (лист Лизинг)"); +if (sheets.includes("Лизинг")) { + const sheet = XLSX.utils.sheet_to_json(wb.Sheets["Лизинг"], { header: 1 }); + + // Проверяем первый договор лизинга + const totalAmount1 = sheet[7]?.[5]; // F8 - Общая сумма первого договора + const paymentsSum = sheet[52]?.[4]; // E53 - Итого платежей + + console.log(` Общая сумма договора 1: ${totalAmount1}`); + console.log(` Сумма платежей (график): ${paymentsSum}`); + + if (totalAmount1 && totalAmount1 > 0) { + if (paymentsSum === 0 || paymentsSum === undefined) { + errors.push(`❌ Лизинг: сумма платежей (${paymentsSum}) не равна общей сумме договора (${totalAmount1})`); + } else { + successChecks.push(`✓ Лизинг: график платежей заполнен`); + } + } else { + warnings.push("⚠ Лизинг: общая сумма договора не заполнена или равна 0"); + } + + // Проверка: все ли платежи рассчитаны + let zeroPayments = 0; + for (let i = 10; i < 52; i++) { + if (sheet[i] && sheet[i][4] === 0) { + zeroPayments++; + } + } + + if (zeroPayments > 40) { + warnings.push(`⚠ Лизинг: ${zeroPayments} платежей равны 0 - возможно, формулы не настроены`); + } +} else { + errors.push("❌ Лист 'Лизинг' отсутствует"); +} + +// Проверка 6: Финансовые показатели (лист 10.Фин.пок-ли) +console.log("\n6️⃣ ПРОВЕРКА: Финансовые показатели (лист 10.Фин.пок-ли)"); +if (sheets.includes("10.Фин.пок-ли")) { + const sheet = XLSX.utils.sheet_to_json(wb.Sheets["10.Фин.пок-ли"], { header: 1 }); + + const discountRate = sheet[0]?.[19]; // T1 - ставка дисконтирования + const wacc = sheet[1]?.[19]; // T2 - WACC + + console.log(` Ставка дисконтирования: ${discountRate ? (discountRate * 100).toFixed(2) + '%' : 'не заполнена'}`); + console.log(` WACC: ${wacc ? (wacc * 100).toFixed(2) + '%' : 'не заполнен'}`); + + if (discountRate && discountRate > 0.3) { + warnings.push(`⚠ Ставка дисконтирования {(discountRate * 100).toFixed(1)}% выглядит завышенной`); + } + + if (!discountRate || discountRate === 0) { + errors.push("❌ Ставка дисконтирования не заполнена или равна 0"); + } else { + successChecks.push(`✓ Ставка дисконтирования: ${(discountRate * 100).toFixed(2)}%`); + } +} else { + errors.push("❌ Лист '10.Фин.пок-ли' отсутствует"); +} + +// Проверка 7: NPV, IRR, PI (лист 11.Заключение) +console.log("\n7️⃣ ПРОВЕРКА: Показатели эффективности (лист 11.Заключение)"); +if (sheets.includes("11.Заключение")) { + const sheet = XLSX.utils.sheet_to_json(wb.Sheets["11.Заключение"], { header: 1 }); + + const npv = sheet[5]?.[19]; // T6 - NPV + const irr = sheet[6]?.[19]; // T7 - IRR + const pi = sheet[7]?.[19]; // T8 - PI + const pbp = sheet[8]?.[19]; // T9 - PBP + + console.log(` NPV: ${npv}`); + console.log(` IRR: ${irr ? (irr * 100).toFixed(2) + '%' : 'не заполнен'}`); + console.log(` PI: ${pi}`); + console.log(` PBP: ${pbp} лет`); + + // Проверка согласованности + if (npv !== undefined && npv < 0) { + warnings.push(`⚠ NPV отрицательный (${npv}) - проект неэффективен`); + } + + if (irr && discountRate && irr < discountRate) { + errors.push(`❌ IRR (${(irr * 100).toFixed(2)}%) < ставки дисконтирования (${(discountRate * 100).toFixed(2)}%) - проект неэффективен`); + } + + if (npv && npv > 0) { + successChecks.push(`✓ NPV положительный: ${npv} тыс. тг`); + } +} else { + errors.push("❌ Лист '11.Заключение' отсутствует"); +} + +// Проверка 8: Проверка на пустые ячейки в ключевых разделах +console.log("\n8️⃣ ПРОВЕРКА: Заполненность ключевых разделов"); +const criticalSheets = ["1.Резюме проекта", "4.БКВ", "8.ОДДС", "9 КПЭ"]; +criticalSheets.forEach(sheetName => { + if (sheets.includes(sheetName)) { + const sheet = XLSX.utils.sheet_to_json(wb.Sheets[sheetName], { header: 1 }); + let emptyCells = 0; + let totalCells = 0; + + for (let i = 0; i < Math.min(sheet.length, 20); i++) { + for (let j = 0; j < (sheet[i]?.length || 0); j++) { + totalCells++; + if (sheet[i][j] === undefined || sheet[i][j] === null || sheet[i][j] === "") { + emptyCells++; + } + } + } + + const emptyPercent = totalCells > 0 ? (emptyCells / totalCells * 100).toFixed(1) : 0; + if (parseFloat(emptyPercent) > 50) { + warnings.push(`⚠ Лист '${sheetName}': ${emptyPercent}% пустых ячеек в первых 20 строках`); + } else { + successChecks.push(`✓ Лист '${sheetName}': заполненность ${100 - parseFloat(emptyPercent)}%`); + } + } +}); + +// ВЫВОД РЕЗУЛЬТАТОВ +console.log("\n" + "=".repeat(70)); +console.log("📋 ИТОГОВЫЙ ОТЧЕТ"); +console.log("=".repeat(70)); + +if (errors.length > 0) { + console.log("\n❌ КРИТИЧЕСКИЕ ОШИБКИ (" + errors.length + "):"); + errors.forEach((e, i) => console.log(` ${i + 1}. ${e}`)); +} + +if (warnings.length > 0) { + console.log("\n⚠ ПРЕДУПРЕЖДЕНИЯ (" + warnings.length + "):"); + warnings.forEach((w, i) => console.log(` ${i + 1}. ${w}`)); +} + +if (successChecks.length > 0) { + console.log("\n✅ КОРРЕКТНЫЕ РАСЧЕТЫ (" + successChecks.length + "):"); + successChecks.forEach((s, i) => console.log(` ${i + 1}. ${s}`)); +} + +console.log("\n" + "=".repeat(70)); +console.log("ВСЕГО НАЙДЕНО:"); +console.log(` Ошибки: ${errors.length}`); +console.log(` Предупреждения: ${warnings.length}`); +console.log(` Корректных расчетов: ${successChecks.length}`); +console.log("=".repeat(70)); + +// Сохраняем отчет в файл +const reportData = [ + ["ПРОВЕРКА КОРРЕКТНОСТИ РАСЧЕТОВ В ШАБЛОНЕ"], + ["Файл", "Шаблон БП_ БКВ 2027 с займами.xlsx"], + ["Дата проверки", new Date().toLocaleString("ru-RU")], + [], + ["КРИТИЧЕСКИЕ ОШИБКИ"], + ...errors.map(e => [e]), + [], + ["ПРЕДУПРЕЖДЕНИЯ"], + ...warnings.map(w => [w]), + [], + ["КОРРЕКТНЫЕ РАСЧЕТЫ"], + ...successChecks.map(s => [s]) +]; + +const reportWb = XLSX.utils.book_new(); +const reportSheet = XLSX.utils.json_to_sheet(reportData.slice(5), { skipHeader: false }); +XLSX.utils.sheet_add_aoa(reportSheet, reportData.slice(0, 5), { origin: "A1" }); +XLSX.utils.book_append_sheet(reportWb, reportSheet, "Отчет о проверке"); + +const reportFile = "Отчет_о_проверке_расчетов.xlsx"; +XLSX.writeFile(reportWb, reportFile); + +console.log(`\n📁 Подробный отчет сохранен: ${reportFile}`); + +// Выход с кодом ошибки если есть критические ошибки +if (errors.length > 0) { + process.exit(1); +} diff --git a/check-calculations.py b/check-calculations.py new file mode 100644 index 0000000..73c00c8 --- /dev/null +++ b/check-calculations.py @@ -0,0 +1,305 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import openpyxl +from datetime import datetime + +print("📊 ПРОВЕРКА КОРРЕКТНОСТИ РАСЧЕТОВ В ШАБЛОНЕ\n") +print("Файл:", "Шаблон БП_ БКВ 2027 с займами.xlsx") + +wb = openpyxl.load_workbook("Шаблон БП_ БКВ 2027 с займами.xlsx", data_only=True) +sheets = wb.sheetnames + +print(f"Листов: {len(sheets)}") +print("=" * 70) + +errors = [] +warnings = [] +success_checks = [] + +# Проверка 1: Финансовые показатели (лист 10.Фин.пок-ли) +print("\n1️⃣ ПРОВЕРКА: Финансовые показатели (лист 10.Фин.пок-ли)") +if "10.Фин.пок-ли" in sheets: + sheet = wb["10.Фин.пок-ли"] + + # T1 - ставка дисконтирования, T2 - WACC + discount_rate = sheet.cell(row=1, column=20).value # T1 + wacc = sheet.cell(row=2, column=20).value # T2 + + print(f" Ставка дисконтирования: {discount_rate}") + print(f" WACC: {wacc}") + + if discount_rate is None or discount_rate == 0: + errors.append("❌ Ставка дисконтирования не заполнена или равна 0") + elif discount_rate > 0.3: + warnings.append(f"⚠ Ставка дисконтирования {discount_rate*100:.1f}% выглядит завышенной") + else: + success_checks.append(f"✓ Ставка дисконтирования: {discount_rate*100:.2f}%") + + if wacc and wacc > discount_rate: + warnings.append(f"⚠ WACC ({wacc*100:.2f}%) > ставки дисконтирования ({discount_rate*100:.2f}%)") +else: + errors.append("❌ Лист '10.Фин.пок-ли' отсутствует") + +# Проверка 2: NPV, IRR, PI (лист 11.Заключение) +print("\n2️⃣ ПРОВЕРКА: Показатели эффективности (лист 11.Заключение)") +if "11.Заключение" in sheets: + sheet = wb["11.Заключение"] + + npv = sheet.cell(row=6, column=20).value # T6 + irr = sheet.cell(row=7, column=20).value # T7 + pi = sheet.cell(row=8, column=20).value # T8 + pbp = sheet.cell(row=9, column=20).value # T9 + + print(f" NPV: {npv} тыс. тг") + print(f" IRR: {irr}") + print(f" PI: {pi}") + print(f" PBP: {pbp} лет") + + if npv is None: + errors.append("❌ NPV не заполнен") + elif npv < 0: + warnings.append(f"⚠ NPV отрицательный ({npv}) - проект неэффективен") + else: + success_checks.append(f"✓ NPV положительный: {npv} тыс. тг") + + if irr is None: + errors.append("❌ IRR не заполнен") + elif discount_rate and irr < discount_rate: + errors.append(f"❌ IRR ({irr*100:.2f}%) < ставки дисконтирования ({discount_rate*100:.2f}%) - проект неэффективен") + else: + success_checks.append(f"✓ IRR: {irr*100:.2f}%") + + if pi is None: + errors.append("❌ PI не заполнен") + elif pi < 1: + warnings.append(f"⚠ PI < 1 ({pi}) - проект неэффективен") + else: + success_checks.append(f"✓ PI: {pi:.3f}") +else: + errors.append("❌ Лист '11.Заключение' отсутствует") + +# Проверка 3: Анализ чувствительности (лист 12.Анализ чувствительности) +print("\n3️⃣ ПРОВЕРКА: Анализ чувствительности") +if "12.Анализ чувствительности" in sheets: + sheet = wb["12.Анализ чувствительности"] + + base_npv = sheet.cell(row=4, column=3).value # C4 + down_10_rev = sheet.cell(row=5, column=3).value # C5 (-10% доходы) + up_10_exp = sheet.cell(row=5, column=5).value # E5 (+10% расходы) + up_10_capex = sheet.cell(row=5, column=7).value # G5 (+10% CAPEX) + + print(f" Базовый NPV: {base_npv}") + print(f" NPV при -10% доходов: {down_10_rev}") + print(f" NPV при +10% расходов: {up_10_exp}") + print(f" NPV при +10% CAPEX: {up_10_capex}") + + if base_npv and down_10_rev: + if down_10_rev >= base_npv: + errors.append(f"❌ Анализ чувствительности: NPV при -10% доходов ({down_10_rev}) >= базового NPV ({base_npv})") + else: + success_checks.append("✓ NPV корректно реагирует на снижение доходов") + + if base_npv and base_npv > 0: + if down_10_rev and down_10_rev < 0: + success_checks.append("✓ При -10% доходов NPV становится отрицательным (высокая чувствительность)") +else: + errors.append("❌ Лист '12.Анализ чувствительности' отсутствует") + +# Проверка 4: EBITDA и риск-аппетит (лист 13.1 Карта рисков) +print("\n4️⃣ ПРОВЕРКА: Расчет риск-аппетита (лист 13.1 Карта рисков)") +if "13.1 Карта рисков" in sheets: + sheet = wb["13.1 Карта рисков"] + + ebitda = sheet.cell(row=1, column=18).value # R1 + risk_appetite_pct = sheet.cell(row=1, column=19).value # S1 + risk_appetite_val = sheet.cell(row=1, column=20).value # T1 + + print(f" EBITDA: {ebitda} тыс. тг") + print(f" Риск-аппетит: {risk_appetite_pct*100 if risk_appetite_pct else None}%") + print(f" Расчетное значение: {risk_appetite_val} млрд. тг") + + if ebitda and risk_appetite_pct and risk_appetite_val: + calculated = (ebitda * risk_appetite_pct) / 1_000_000_000 + diff = abs(calculated - risk_appetite_val) + + if diff > 0.1: + errors.append(f"❌ Риск-аппетит: расчетное значение {calculated:.3f} млрд ≠ {risk_appetite_val} млрд") + else: + success_checks.append(f"✓ Риск-аппетит рассчитан верно: {risk_appetite_val:.3f} млрд. тг") +else: + errors.append("❌ Лист '13.1 Карта рисков' отсутствует") + +# Проверка 5: Премия за специфичный риск (лист 13.2) +print("\n5️⃣ ПРОВЕРКА: Премия за специфичный риск (лист 13.2)") +if "13.2 Премия за специфич риск" in sheets: + sheet = wb["13.2 Премия за специфич риск"] + + total_risk_premium = 0 + risk_factors = 0 + + for row in range(7, 35): + cell_value = sheet.cell(row=row, column=5).value # E7:E34 + if cell_value and isinstance(cell_value, (int, float)) and cell_value > 0: + total_risk_premium += cell_value + risk_factors += 1 + print(f" Фактор {risk_factors}: {cell_value*100:.2f}%") + + print(f" Итого премия за риск: {total_risk_premium*100:.2f}%") + + if total_risk_premium == 0: + warnings.append("⚠ Премия за специфичный риск не рассчитана (все факторы = 0)") + elif total_risk_premium > 0.05: + warnings.append(f"⚠ Премия за специфичный риск {total_risk_premium*100:.2f}% превышает разумный предел (5%)") + else: + success_checks.append(f"✓ Премия за специфичный риск: {total_risk_premium*100:.2f}%") +else: + errors.append("❌ Лист '13.2 Премия за специфич риск' отсутствует") + +# Проверка 6: Заявление о риск-аппетите (лист 13.3) +print("\n6️⃣ ПРОВЕРКА: Заявление о риск-аппетите (лист 13.3)") +if "13.3 Заявление о риск аппетите" in sheets: + sheet = wb["13.3 Заявление о риск аппетите"] + + div_errors = 0 + for row in range(6, 15): + for col in range(1, 10): + cell_value = sheet.cell(row=row, column=col).value + if cell_value == "#DIV/0!": + div_errors += 1 + + if div_errors > 0: + errors.append(f"❌ В листе '13.3 Заявление о риск аппетите' найдено {div_errors} ошибок #DIV/0!") + else: + success_checks.append("✓ Заявление о риск-аппетите: формулы рассчитаны") +else: + errors.append("❌ Лист '13.3 Заявление о риск аппетите' отсутствует") + +# Проверка 7: Лизинг (лист Лизинг) +print("\n7️⃣ ПРОВЕРКА: Расчеты лизинга") +if "Лизинг" in sheets: + sheet = wb["Лизинг"] + + total_amount = sheet.cell(row=8, column=6).value # F8 + payments_sum = sheet.cell(row=53, column=5).value # E53 (Итого) + + print(f" Общая сумма договора: {total_amount}") + print(f" Сумма платежей по графику: {payments_sum}") + + if total_amount and total_amount > 0: + if not payments_sum or payments_sum == 0: + errors.append(f"❌ Лизинг: сумма платежей ({payments_sum}) не равна общей сумме ({total_amount})") + else: + success_checks.append("✓ Лизинг: график платежей заполнен") + else: + warnings.append("⚠ Лизинг: общая сумма договора не заполнена или равна 0") + + zero_payments = 0 + for row in range(11, 52): + payment = sheet.cell(row=row, column=5).value + if payment == 0: + zero_payments += 1 + + if zero_payments > 40: + warnings.append(f"⚠ Лизинг: {zero_payments} платежей равны 0 - формулы не настроены") +else: + errors.append("❌ Лист 'Лизинг' отсутствует") + +# Проверка 8: Заполненность БКВ (лист 4.БКВ) +print("\n8️⃣ ПРОВЕРКА: Заполненность БКВ") +if "4.БКВ" in sheets: + sheet = wb["4.БКВ"] + + empty_cells = 0 + total_cells = 0 + + for row in range(1, min(21, sheet.max_row + 1)): + for col in range(1, min(21, sheet.max_column + 1)): + total_cells += 1 + cell_value = sheet.cell(row=row, column=col).value + if cell_value is None or cell_value == "": + empty_cells += 1 + + empty_pct = (empty_cells / total_cells * 100) if total_cells > 0 else 0 + print(f" Пустых ячеек в первых 20 строках: {empty_pct:.1f}%") + + if empty_pct > 50: + warnings.append(f"⚠ Лист '4.БКВ': {empty_pct:.1f}% пустых ячеек") + else: + success_checks.append(f"✓ Лист '4.БКВ': заполненность {100-empty_pct:.1f}%") +else: + errors.append("❌ Лист '4.БКВ' отсутствует") + +# Проверка 9: ОДДС (лист 8.ОДДС) +print("\n9️⃣ ПРОВЕРКА: Отчет о движении денежных средств (8.ОДДС)") +if "8.ОДДС" in sheets: + sheet = wb["8.ОДДС"] + + # Проверяем наличие отрицательных накопленных потоков в начале проекта + negative_dcf = 0 + for col in range(5, 20): # Квартальные столбцы + dcf = sheet.cell(row=10, column=col).value # Накопленный дисконтированный поток + if dcf and dcf < 0: + negative_dcf += 1 + + if negative_dcf > 0: + success_checks.append(f"✓ ОДДС: {negative_dcf} кварталов с отрицательным накопленным DCF (нормально для начала проекта)") +else: + errors.append("❌ Лист '8.ОДДС' отсутствует") + +# ВЫВОД РЕЗУЛЬТАТОВ +print("\n" + "=" * 70) +print("📋 ИТОГОВЫЙ ОТЧЕТ") +print("=" * 70) + +if errors: + print(f"\n❌ КРИТИЧЕСКИЕ ОШИБКИ ({len(errors)}):") + for i, e in enumerate(errors, 1): + print(f" {i}. {e}") + +if warnings: + print(f"\n⚠ ПРЕДУПРЕЖДЕНИЯ ({len(warnings)}):") + for i, w in enumerate(warnings, 1): + print(f" {i}. {w}") + +if success_checks: + print(f"\n✅ КОРРЕКТНЫЕ РАСЧЕТЫ ({len(success_checks)}):") + for i, s in enumerate(success_checks, 1): + print(f" {i}. {s}") + +print("\n" + "=" * 70) +print("ВСЕГО НАЙДЕНО:") +print(f" Ошибки: {len(errors)}") +print(f" Предупреждения: {len(warnings)}") +print(f" Корректных расчетов: {len(success_checks)}") +print("=" * 70) + +# Сохраняем отчет +report_wb = openpyxl.Workbook() +report_sheet = report_wb.active +report_sheet.title = "Отчет о проверке" + +report_data = [ + ["ПРОВЕРКА КОРРЕКТНОСТИ РАСЧЕТОВ В ШАБЛОНЕ"], + ["Файл", "Шаблон БП_ БКВ 2027 с займами.xlsx"], + ["Дата проверки", datetime.now().strftime("%d.%m.%Y %H:%M")], + [], + ["КРИТИЧЕСКИЕ ОШИБКИ"] + [len(errors)], +] + [[e] for e in errors] + [ + [], + ["ПРЕДУПРЕЖДЕНИЯ"] + [len(warnings)], +] + [[w] for w in warnings] + [ + [], + ["КОРРЕКТНЫЕ РАСЧЕТЫ"] + [len(success_checks)], +] + [[s] for s in success_checks] + +for row_idx, row_data in enumerate(report_data, 1): + for col_idx, value in enumerate(row_data, 1): + report_sheet.cell(row=row_idx, column=col_idx, value=value) + +report_file = "Отчет_о_проверке_расчетов.xlsx" +report_wb.save(report_file) + +print(f"\n📁 Подробный отчет сохранен: {report_file}") + +if errors: + exit(1) diff --git a/Отчет_о_проверке_расчетов.xlsx b/Отчет_о_проверке_расчетов.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..044f03eaef77c9b0ef56096948507080dbb9bd85 GIT binary patch literal 5621 zcmZ`-1yod9+a6lFyQD>i9FT6Q0fs?AN%~#stKs4VbmZ&4NE5r zsH>B+8~+n$XFe|ndrhP!eun@7_EPHukJ`5pOu0n&r6V$$kbF;H)^IxnpY85o$~t;` z_t9tbg|l+jBrEOntvY~&dI{ww+0JAJ2eKlvR-cs#&Ii8wKu4U*%!JXI?JyPaQjkDw zXt}mx^An%4^;2Y`f@h_KHfQIuzOcjxF6QDn20W5=9}byFa&_?}9<1xghl7+4GrkTu zU07nZ;xtf0|4>20&R6m~>eqSD0RYl}s$k*d3j3wQaBMrcOMoQARLRw;U~}w6R%+z3 zn!L08K2_}Jd9(3g%iQ_e+Neh3`^;??R#(1CR zX|06K-Vot{+ti6P_{clqTatC?R5VZAHyZ=2IKFPZo1R1SfI#}oid+~5UEGW;k z%3>ul^V`ytaUcJm#(DcVjg15w07&Bk0LW3s@v`T4w}Cmpe$Rrx%(G)?&sj^1U{^JPng-LpY#Q%&(uEtaI)~ zcXUiO;6=Y* zJXV9+an3m9Eg?cliFExD58vaiop-MWX557x=$2OYy~Jt;$0D5>FqgC&Gdj|L*th1& z<05k4A1$YkXUbyo-$y;v8b9${+)lMg!;3P~8n^SDAAcR*Q~J@1uw`E>w6iR{r}(4$ zHk^~|vdk`blgdUxd1c#&jIw%7Zu~Yt-`QhwXbdCKg!}AiJl$u;#(DY2d-~tS!%44t zx-lo=W3-9F%C70rSVREPu2BuBZ@GL;#8OA>*yHF)BzHBG?p8`);E~D;mhQRo%cG>m z{#=kxx}U^|UA#ZM7_DHZF%=ni;kL2*t{`qoQ(?rp35dIY&Svx^l*>_)+*N|XTEQ22 zd>oU-!yRsW`Gt6}45dPQ@#;E4}OEZCE80 zt5zcvBA30D9+GSeWkAox6->+u`HAQ$AzdQ(t8KZ}w-^<6QtXr>t(T>*HWU1lM44_m z&F!n(sQo!kp7Q9iZqr;&l0GF`63wFUdl6vwq*p@QercK%BRSb{JU+I3ND@0IS&{g2 zO;z$Mp1hf>v~jt;cz*UXaLgNc6%|+OKDlS@ZG26HV-Cf|k8_hITzVBid2?^F^Du zKc62mZ@2!3bk-_81S-DMXLu^$+Op8L=NoT?ZVx#G~&P=)ES3~Mv!GF&^vBEJOU?7t(Di` zsgp8V-7L>?mjNPArA*fm!}uV@T37Y0n)sG#PBHM04@1rnra=NpYpGbq8qZ8dk@I6? zk5#rjfrm+ob#w|X7!D2Ta}r`1$uaeK;}4>pI!`ydz37(3KV#PWCF?n?PmhkCueWPg zZbOA6ib$7yqP#b<=W^6 z4|L50je0P6qO*N9UMX!juO~e=T^WsCnazh)JC<{M(mJp{5buB|lUo-LEWkdr>oOvB zjSebWY^4#D5ag9XY2P~nQV113nW{xRbv`)zkoIBAI*(eHu2`!QsD>T#Cj*=}}l>D;eEye7GB z`ombU?D_Hh%9&%Fq^d@mcQ+}TyAnEE0s}S`wa_Z`du%i|Iofc1|NTAm7nb=90m*0h z%?AYa+>|4wGUEP9z@QL_$ruM2MMMPw77MlO%I>$$Ghm=Yu#S-B1oePoc)K*n zRdHH}JpFD7SmkLQ;Nd_NT% z=2`NP%Bth|GRIuZAwiav*kY(s3LR)7vP3)z&q;nl zZcTRf6QRv9p)f-XmWklontM5`5Im{5t6jp1j;_~Vb|PER{wyGMB|D!4FaZF6RChr0 zyMTB)x!Sqez+mof{C_QfRgq?WO{ZBgl1rYFNXN0r``~QVQ~YU!zCBT+zyo1vDzHYc z0&I|n+vRwXpF4&^rC7j$SQ=B9V`M0`x0m+rpqsA0-LV2<;}pu2I$^(9Q{YW=@8p?F zUuvdAc#dyX@s{Gk0Q{2VUV-82?8pW*gtQ0l)fc&9-~27ZVe6>U$9{16&OH%ZCgRYh zs}peigtrJA=x{0DYr1wzzRo1$0B2tf@lGq%a71AnX2j6-f%`q%`))^(^Yo$eTYA{@ z5wi+u^y{Jzisp1DL`CWs%B=KR$nxr(5+WZHX)OocQD0MA{;49F{yBNz^(Y5_ zT>T*Ze%9Sqidm1=lzrNn2;(dL8qtYUtt|3vs-@nh;is^Av&v(=U_#`Di2@Bvr{hbs#T@LrancJnb0TF#!bYXD={l0 z$b>1a<+CRr_QwgOInuW7Kjm}ZryTl_$JRl*ZL(h?`s6bOnB)Oc%mfn&JN(ANS<}^( z5lpruT_t=}cjmA*H~a>U|C3LsBA><&7m$TEg07|*;Vi+`6-QY88M;W@E;r|!JBJ%3 zRKY#hr1+X3>VY`)eRVRpuD5VtVLXMevmiY9K`L}hO%<2=vbkQ#&>-cMyISCOhZh>} zkD*S1s(@*`fHEh?=V-Sb(e|eR4rTsm8AHqcCY>u(668hH1jsh|I8}aChOTBTw>>h3 z#$L^5v;h(_nk_;is~Kt}F?M2X_ma0C<&1@3^x(Jg6+(dN9UOsBj3T!L^ri|Eu$hyr z$zsUQs8HV*p`78Zq0i^R8hr%NY*1FDxa*w(xpu}azQULLI?bPeu5DNbnd%qM!O1>B z8q{ilLYpr_1@!~d(?`X1u#!QVxcHa==MW2qQFia$950)^t|^Q!O82QB%&XYcczLUB zl?h{sc|`KC#jWXdZfo)GbuYv1PKXKeYJY8MhD>o zL$S+p4#A3_yR2vqcxRh?D4dobv;+%V@x zKGqC{FIV_+jM!u2u#@lRFVRyboK@tg)jq0uUFs=CnPdJDg|dYr2nr8j7uH@`}5SA#nLXf|S2faH?$3vppwvWSb?0aVc} zixYzVX)(Np1=wnQ^e38(S8P z>pEb;aIpTE@G@(PkIwui$$f0}`;7eTO-jdKz-$y;=5S2GyF>|S-QmEyBiA~}loBp; z9mj3&SNT_WYPMw9XJ ze9_ev=}fD$(GY0DSgNWn_=GOTeKA^FcN6~5C0brHaE9^KyVpUI312C&APECeK0)x@ z_jfQo1)FnVYVHMHlG4muyxbA*!E9l9pgVYY?X@w=g#-dPc-#?tjJ}7`15S%YzE=gc z=Y)UyUQ$FOLmSHH$Wi`>k6KM#VfJqPe81-O*m}oK0RlPy?vEJ)I3HQUK(Ku(%u}>S z*~HUiSP@F;t>rU$F46e3-qKQJb4+l#9kJY+$Re6ck_T|Zi8?gZ8jd&ckr;)tPe3M- z&@s9DvlfaKhCmrPK3c2VW=pC9w_Lrj$5ZKf(zE(cbWwztG7c zfr!!?m@UDG&TD+_%411wG>cWADn+S(Jex7kpRxBidVI6of)(Lov~lWUql5Q)&Vu8W zi#g&-(K~@q2p5c9!UdC)#C8b74vMNfV|kDy538MdU`2K7Asse7qV9g+cV4SuV*oz;-T1m7VJlL6`?A`TnI#BMZ?}}7VOa9f zOgs9k&Q)URa?O^#JO`TIBxKsFE_;#~vyh$!tDMpGRTm7zT=v+e5e3muuw)&m0SGO% zr3I5_bldoBjMQ{xooYo%3L%_HL)zWk%)$%YmAP}D-i3D`5&G_Eazl11I7VD~U z<~;72z3IgCN*^aLsQ^wt6EjISi@WxceYP%k~^bcVkjc)wlFZY?! zfpT{dx@c|5A0h@9E@yw@{J9J4OsUzWDC_@3?F8NLUHI*B|J;keystV&nkYzs;AJ`4 z8bAbEq==( zjUpvWZZIX?sz!%R&As*q%Yzz;*@Z&15qA@v4R0^^-*$jM@~hB-AlAL9MNgEqdUye-Bich8RlmE%iT)jG*RwGg3=CV#K8yD z&&;Pc!#8$Dv(Ta|zKXLp9Ge9}X26+blrJ?)ycU{}8A&AzZ%8XP(vPO&8MS>W&~HTjg1}X{uBiY{s z-b|N&1HYkwDAoTNus6Xsqx^5MD~h8;f&WL$-;{GRkp7mVM+N}=OIShGu~2j$0Dz0S NWl)Je>lf<>_zx|#$O!-d literal 0 HcmV?d00001 diff --git a/Сравнение_БП_2026_НСВМ_и_Шаблон_БП_БКВ_2027.xlsx b/Сравнение_БП_2026_НСВМ_и_Шаблон_БП_БКВ_2027.xlsx index ad3e01894c1a679ca8880ca94693af436d85394c..771ff5b06e119c4065c197d5e4b50fd44e8bec82 100644 GIT binary patch delta 198 zcmW;GyAHun0D$52LQ%K6U+TE@45o&Ykm&9uynw-a7K_DTB@*7jBr!A|MLL+hg+Cp> z{Y%~#Bf)bU+K-=ie(l1KS&;e7TV~biyr#$F@V4jBaAUY5NjpHH2uc6ja-|5O*lca delta 198 zcmW;GISv6)0D$4l!eDG;U&gb{D^wGnP)zR$C^GK;2*%g&eL^xe99tqxilFk!()3TZgVAd4Ja