// server.js - Полнофункциональный бэкенд
const express = require('express');
const cors = require('cors');
const bodyParser = require('body-parser');
const fs = require('fs');
const path = require('path');
const ExcelJS = require('exceljs');
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(cors());
app.use(bodyParser.json({ limit: '50mb' }));
app.use(express.static('public'));
// Хранилище проектов
const projectsDB = new Map();
const parametersDB = loadParametersDatabase();
// Загрузка базы параметров
function loadParametersDatabase() {
try {
const data = fs.readFileSync('parameters.json', 'utf8');
return JSON.parse(data);
} catch (e) {
console.log('Creating new parameters database...');
return initializeParametersDB();
}
}
function initializeParametersDB() {
// Здесь должна быть полная база из 10,000+ параметров
// Для примера создаем минимальный набор
return {
// ... (содержимое из предыдущего примера)
};
}
// API Endpoints
// Получение параметров по региону и отрасли
app.get('/api/parameters', (req, res) => {
const { region, industry } = req.query;
const params = calculateRegionalParameters(region, industry);
res.json({ success: true, parameters: params });
});
// Расчет проекта
app.post('/api/calculate', async (req, res) => {
try {
const { projectId, parameters } = req.body;
// Выполняем комплексные расчеты
const calculations = await performAdvancedCalculations(parameters);
// Анализ рисков AI
const riskAnalysis = await analyzeRisks(calculations, parameters);
// Генерация рекомендаций
const recommendations = generateAIRecommendations(calculations, riskAnalysis);
// Сохраняем проект
const project = {
id: projectId || 'proj_' + Date.now(),
parameters,
calculations,
riskAnalysis,
recommendations,
timestamp: new Date().toISOString()
};
projectsDB.set(project.id, project);
res.json({
success: true,
project,
metrics: calculations.metrics,
reports: {
financial: generateFinancialReport(calculations),
investment: generateInvestmentReport(calculations),
risks: riskAnalysis
}
});
} catch (error) {
console.error('Calculation error:', error);
res.status(500).json({ success: false, error: error.message });
}
});
// Экспорт в Excel
app.post('/api/export/excel', async (req, res) => {
try {
const { projectId } = req.body;
const project = projectsDB.get(projectId);
if (!project) {
return res.status(404).json({ success: false, error: 'Project not found' });
}
const workbook = await createExcelReport(project);
// Сохраняем файл
const filename = `project_${projectId}_${Date.now()}.xlsx`;
const filepath = path.join(__dirname, 'exports', filename);
await workbook.xlsx.writeFile(filepath);
res.json({
success: true,
filename,
downloadUrl: `/exports/${filename}`
});
} catch (error) {
console.error('Export error:', error);
res.status(500).json({ success: false, error: error.message });
}
});
// Экспорт в Project Expert формат
app.post('/api/export/project-expert', (req, res) => {
const { projectId } = req.body;
const project = projectsDB.get(projectId);
if (!project) {
return res.status(404).json({ success: false, error: 'Project not found' });
}
// Конвертация в XML формат Project Expert
const peXml = convertToProjectExpertXML(project);
res.json({
success: true,
format: 'xml',
data: peXml,
filename: `project_${projectId}.xml`
});
});
// Сохранение проекта
app.post('/api/project/save', (req, res) => {
const { project, userId } = req.body;
try {
// Сохраняем в файл
const userDir = path.join(__dirname, 'projects', userId);
if (!fs.existsSync(userDir)) {
fs.mkdirSync(userDir, { recursive: true });
}
const filename = `project_${project.id}_${Date.now()}.json`;
const filepath = path.join(userDir, filename);
fs.writeFileSync(filepath, JSON.stringify(project, null, 2));
res.json({
success: true,
filename,
saved: new Date().toISOString()
});
} catch (error) {
console.error('Save error:', error);
res.status(500).json({ success: false, error: error.message });
}
});
// Загрузка проекта
app.get('/api/project/load', (req, res) => {
const { userId, projectId } = req.query;
try {
const userDir = path.join(__dirname, 'projects', userId);
const files = fs.readdirSync(userDir);
const projectFile = files.find(f => f.includes(projectId));
if (!projectFile) {
return res.status(404).json({ success: false, error: 'Project not found' });
}
const filepath = path.join(userDir, projectFile);
const projectData = JSON.parse(fs.readFileSync(filepath, 'utf8'));
res.json({ success: true, project: projectData });
} catch (error) {
console.error('Load error:', error);
res.status(500).json({ success: false, error: error.message });
}
});
// Telegram вебхук
app.post('/api/telegram/webhook', (req, res) => {
const { message } = req.body;
if (message && message.text) {
const chatId = message.chat.id;
const text = message.text.toLowerCase();
let response = '';
if (text.includes('/start')) {
response = `???? Добро пожаловать в Проджект Эксперт!\n\nЯ помогу рассчитать финансовую модель вашего бизнеса.\n\nДоступные команды:\n/calculate - Новый расчет\n/templates - Шаблоны проектов\n/support - Связь с экспертом`;
} else if (text.includes('расчет') || text.includes('посчитать')) {
response = `Для расчета проекта перейдите на наш сайт или используйте веб-версию.\n\nСсылка: https://project-expert.ru/calculator`;
} else {
response = `Я передам ваш вопрос эксперту. Ожидайте ответа в течение 15 минут.\n\nТакже вы можете напрямую написать: @Dveneo`;
}
// Отправляем ответ в Telegram
sendTelegramMessage(chatId, response);
}
res.json({ ok: true });
});
// Функции расчетов
async function performAdvancedCalculations(parameters) {
const results = {};
// 1. Расчет выручки с учетом сезонности
results.revenue = calculateRevenue(parameters);
// 2. Расчет себестоимости
results.cost = calculateCost(parameters);
// 3. Расчет OPEX
results.opex = calculateOpexDetailed(parameters);
// 4. Расчет налогов
results.taxes = calculateTaxes(results.revenue, results.opex, parameters);
// 5. Расчет денежных потоков
results.cashFlow = calculateCashFlow(results, parameters);
// 6. Расчет инвестиционных показателей
results.metrics = calculateInvestmentMetrics(results, parameters);
// 7. Анализ чувствительности
results.sensitivity = analyzeSensitivity(results, parameters);
return results;
}
function calculateRevenue(params) {
const monthly = params.monthly_revenue || 1000000;
const growth = params.growth_rate || 0.03;
const seasonality = params.seasonality_coefficients || [1, 0.9, 0.8, 1.1, 1.2, 1.3, 1.1, 1.0, 0.9, 1.0, 1.1, 1.2];
const revenue = [];
let current = monthly;
for (let month = 0; month < 36; month++) {
const season = seasonality[month % 12];
const monthRevenue = current * season;
revenue.push(monthRevenue);
// Ежеквартальный рост
if ((month + 1) % 3 === 0) {
current *= (1 + growth);
}
}
return {
monthly: revenue,
total: revenue.reduce((a, b) => a + b, 0),
average: revenue.reduce((a, b) => a + b, 0) / revenue.length,
growth: growth
};
}
function calculateCost(params) {
// Детальный расчет себестоимости
const costs = {
materials: params.material_cost || 0,
labor: params.labor_cost || 0,
energy: params.energy_cost || 0,
depreciation: params.depreciation || 0,
other: params.other_costs || 0
};
const total = Object.values(costs).reduce((a, b) => a + b, 0);
return {
...costs,
total,
asPercentage: total / (params.monthly_revenue || 1000000) * 100
};
}
function calculateOpexDetailed(params) {
const opex = {
// Персонал
salaries: calculateSalaries(params),
taxes: calculateSalaryTaxes(params),
// Аренда
rent: params.rent_office + params.rent_production + params.rent_warehouse || 0,
utilities: params.utilities || 0,
// Маркетинг
marketing: calculateMarketingCost(params),
// Административные
admin: params.admin_costs || 0,
professional: params.professional_fees || 0,
// Прочие
other: params.other_opex || 0
};
opex.total = Object.values(opex).reduce((a, b) => a + b, 0);
return opex;
}
function calculateSalaries(params) {
// Расчет фонда оплаты труда
let total = 0;
// Управление
total += (params.ceo_salary || 0);
total += (params.cto_salary || 0);
total += (params.cfo_salary || 0);
// Производство
const workers = params.worker_count || 5;
total += (params.worker_salary || 0) * workers;
// Офис
total += (params.manager_salary || 0) * (params.manager_count || 3);
total += (params.admin_salary || 0);
return total;
}
function calculateSalaryTaxes(params) {
const salaries = calculateSalaries(params);
return salaries * 0.302; // 30.2% страховые взносы
}
function calculateMarketingCost(params) {
let total = 0;
// Digital
total += params.context_ads || 0;
total += params.smm || 0;
total += params.seo || 0;
// Традиционный
total += params.print_ads || 0;
total += params.events || 0;
// Комиссии
if (params.commission_percent) {
total += (params.monthly_revenue || 0) * (params.commission_percent / 100);
}
return total;
}
function calculateTaxes(revenue, opex, params) {
const taxSystem = params.tax_system || 'osn';
switch(taxSystem) {
case 'usn_6':
// УСН 6% от доходов
return revenue.total * 0.06;
case 'usn_15':
// УСН 15% от прибыли
const profit = revenue.total - opex.total;
return Math.max(profit * 0.15, revenue.total * 0.01);
case 'patent':
// Патент (фиксированная сумма)
return params.patent_cost || 0;
default:
// ОСН: НДС 20% + налог на прибыль 20%
const vat = revenue.total * 0.2;
const profitTax = (revenue.total - opex.total) * 0.2;
return vat + profitTax;
}
}
function calculateCashFlow(results, params) {
const cashFlow = [];
const capex = params.capex_total || 0;
for (let month = 0; month < 36; month++) {
const monthly = {
month: month + 1,
revenue: results.revenue.monthly[month] || 0,
cost: results.cost.total || 0,
opex: results.opex.total / 12 || 0,
taxes: results.taxes / 12 || 0,
capex: month < 3 ? capex / 3 : 0 // Распределяем CAPEX на первые 3 месяца
};
monthly.netCash = monthly.revenue - monthly.cost - monthly.opex - monthly.taxes - monthly.capex;
monthly.cumulative = (cashFlow[month - 1]?.cumulative || 0) + monthly.netCash;
cashFlow.push(monthly);
}
return cashFlow;
}
function calculateInvestmentMetrics(results, params) {
const cashFlow = results.cashFlow;
const capex = params.capex_total || 0;
// NPV (чистая приведенная стоимость)
const discountRate = 0.15; // 15%
let npv = -capex;
for (let i = 0; i < cashFlow.length; i++) {
npv += cashFlow[i].netCash / Math.pow(1 + discountRate, (i + 1) / 12);
}
// IRR (методом последовательных приближений)
let irr = calculateIRR(capex, cashFlow);
// Срок окупаемости
let paybackPeriod = null;
let cumulative = 0;
for (let i = 0; i < cashFlow.length; i++) {
cumulative += cashFlow[i].netCash;
if (cumulative >= capex && !paybackPeriod) {
paybackPeriod = i + 1; // месяц
break;
}
}
// ROI
const totalProfit = cashFlow.reduce((sum, month) => sum + month.netCash, 0);
const roi = ((totalProfit - capex) / capex * 100).toFixed(1);
// Точка безубыточности
const fixedCosts = results.opex.total / 12;
const contributionMargin = (results.revenue.average - results.cost.total) / results.revenue.average;
const breakEvenRevenue = fixedCosts / contributionMargin;
const breakEvenMonths = breakEvenRevenue / results.revenue.average;
return {
npv: Math.round(npv),
irr: (irr * 100).toFixed(1) + '%',
paybackPeriod: paybackPeriod ? paybackPeriod + ' месяцев' : 'Не окупится',
roi: roi + '%',
breakEven: Math.ceil(breakEvenMonths) + ' месяцев',
profitIndex: (npv + capex) / capex
};
}
function calculateIRR(initialInvestment, cashFlows) {
// Метод Ньютона для расчета IRR
let irr = 0.1; // Начальное предположение 10%
const maxIterations = 1000;
const tolerance = 0.0001;
for (let i = 0; i < maxIterations; i++) {
let npv = -initialInvestment;
let dnpv = 0;
for (let t = 0; t < cashFlows.length; t++) {
npv += cashFlows[t].netCash / Math.pow(1 + irr, (t + 1) / 12);
dnpv -= (cashFlows[t].netCash * (t + 1) / 12) / Math.pow(1 + irr, (t + 1) / 12 + 1);
}
if (Math.abs(npv) < tolerance) {
break;
}
irr = irr - npv / dnpv;
// Ограничиваем разумными значениями
if (irr < -0.9) irr = -0.9;
if (irr > 5) irr = 5;
}
return irr;
}
function analyzeSensitivity(results, params) {
const baseNPV = results.metrics.npv;
const sensitivities = [];
// Анализ чувствительности к ключевым параметрам
const factors = [
{ name: 'Выручка', change: -0.1 }, // -10%
{ name: 'Выручка', change: 0.1 }, // +10%
{ name: 'Затраты', change: -0.1 },
{ name: 'Затраты', change: 0.1 },
{ name: 'CAPEX', change: -0.1 },
{ name: 'CAPEX', change: 0.1 }
];
factors.forEach(factor => {
// Упрощенный расчет изменения NPV
let npvChange = 0;
switch(factor.name) {
case 'Выручка':
npvChange = baseNPV * factor.change * 1.5;
break;
case 'Затраты':
npvChange = baseNPV * factor.change * -1.2;
break;
case 'CAPEX':
npvChange = baseNPV * factor.change * -0.8;
break;
}
sensitivities.push({
factor: factor.name,
change: (factor.change * 100).toFixed(0) + '%',
npvChange: Math.round(npvChange),
newNPV: Math.round(baseNPV + npvChange)
});
});
return sensitivities;
}
async function analyzeRisks(calculations, params) {
const risks = [];
// Финансовые риски
if (calculations.metrics.paybackPeriod.includes('Не окупится')) {
risks.push({
level: 'high',
category: 'financial',
description: 'Проект не окупается в течение 3 лет',
probability: 0.8,
impact: 'Полная потеря инвестиций',
mitigation: 'Снижение CAPEX, увеличение маржи'
});
}
if (parseFloat(calculations.metrics.roi) < 15) {
risks.push({
level: 'medium',
category: 'financial',
description: 'ROI ниже 15% - низкая доходность',
probability: 0.6,
impact: 'Низкая привлекательность для инвесторов',
mitigation: 'Оптимизация операционных расходов'
});
}
// Операционные риски
if (calculations.cost.asPercentage > 70) {
risks.push({
level: 'high',
category: 'operational',
description: 'Высокая себестоимость (>70%)',
probability: 0.7,
impact: 'Низкая рентабельность',
mitigation: 'Поиск новых поставщиков, оптимизация процессов'
});
}
// Рыночные риски
if (params.competition_level === 'high') {
risks.push({
level: 'medium',
category: 'market',
description: 'Высокий уровень конкуренции',
probability: 0.5,
impact: 'Сложности с привлечением клиентов',
mitigation: 'Дифференциация предложения, нишевый маркетинг'
});
}
return risks;
}
function generateAIRecommendations(calculations, risks) {
const recommendations = [];
// Финансовые рекомендации
if (parseFloat(calculations.metrics.roi) < 20) {
recommendations.push({
priority: 'high',
area: 'Финансы',
action: 'Повысить ROI до 20%+',
steps: [
'Снизить операционные расходы на 15%',
'Увеличить средний чек на 10%',
'Оптимизировать налоговую нагрузку'
],
expectedImpact: 'Рост ROI на 5-8%'
});
}
// Операционные рекомендации
if (calculations.cost.asPercentage > 60) {
recommendations.push({
priority: 'high',
area: 'Операции',
action: 'Снизить себестоимость',
steps: [
'Поиск альтернативных поставщиков',
'Оптимизация производственных процессов',
'Внедрение системы контроля качества'
],
expectedImpact: 'Снижение себестоимости на 10-15%'
});
}
// Маркетинговые рекомендации
if (calculations.opex.marketing / calculations.revenue.average > 0.25) {
recommendations.push({
priority: 'medium',
area: 'Маркетинг',
action: 'Оптимизировать маркетинговые расходы',
steps: [
'Фокус на ROI-позитивные каналы',
'Внедрение CRO (Conversion Rate Optimization)',
'Развитие реферальной программы'
],
expectedImpact: 'Снижение CAC на 20-30%'
});
}
// Если рисков много, добавляем общие рекомендации
if (risks.length > 3) {
recommendations.push({
priority: 'high',
area: 'Управление рисками',
action: 'Создать резервный фонд',
steps: [
'Резерв на 3 месяца операционных расходов',
'Страхование ключевых рисков',
'Диверсификация клиентской базы'
],
expectedImpact: 'Снижение вероятности банкротства на 40%'
});
}
return recommendations;
}
// Генерация отчетов
function generateFinancialReport(calculations) {
return {
summary: {
totalRevenue: calculations.revenue.total,
totalCost: calculations.cost.total * 3, // 3 года
totalOpex: calculations.opex.total * 3,
netProfit: calculations.revenue.total - (calculations.cost.total * 3) - (calculations.opex.total * 3) - calculations.taxes,
profitability: ((calculations.revenue.total - (calculations.cost.total * 3) - (calculations.opex.total * 3) - calculations.taxes) / calculations.revenue.total * 100).toFixed(1) + '%'
},
monthly: calculations.cashFlow.slice(0, 12), // Первый год
metrics: calculations.metrics
};
}
function generateInvestmentReport(calculations) {
return {
investmentMetrics: calculations.metrics,
sensitivityAnalysis: calculations.sensitivity,
scenarios: {
pessimistic: generateScenario(calculations, -0.2),
base: calculations,
optimistic: generateScenario(calculations, 0.2)
}
};
}
function generateScenario(base, multiplier) {
// Генерация сценария с изменением ключевых параметров
return {
revenue: base.revenue.total * (1 + multiplier),
npv: base.metrics.npv * (1 + multiplier * 1.5),
roi: (parseFloat(base.metrics.roi) * (1 + multiplier * 0.8)).toFixed(1) + '%',
paybackPeriod: Math.round(parseInt(base.metrics.paybackPeriod) * (1 - multiplier * 0.3)) + ' месяцев'
};
}
// Создание Excel отчета
async function createExcelReport(project) {
const workbook = new ExcelJS.Workbook();
// Лист с основными показателями
const summarySheet = workbook.addWorksheet('Сводка');
summarySheet.columns = [
{ header: 'Показатель', width: 30 },
{ header: 'Значение', width: 20 },
{ header: 'Единица измерения', width: 20 }
];
const summaryData = [
['Месячная выручка', project.calculations.revenue.average, 'руб'],
['Себестоимость', project.calculations.cost.total, 'руб/мес'],
['OPEX', project.calculations.opex.total, 'руб/мес'],
['Чистая прибыль', project.calculations.cashFlow[0]?.netCash || 0, 'руб/мес'],
['NPV', project.calculations.metrics.npv, 'руб'],
['IRR', project.calculations.metrics.irr, '%'],
['ROI', project.calculations.metrics.roi, '%'],
['Срок окупаемости', project.calculations.metrics.paybackPeriod, 'месяцев']
];
summaryData.forEach(row => {
summarySheet.addRow(row);
});
// Лист с денежными потоками
const cashFlowSheet = workbook.addWorksheet('Денежные потоки');
cashFlowSheet.columns = [
{ header: 'Месяц', width: 10 },
{ header: 'Выручка', width: 15 },
{ header: 'Расходы', width: 15 },
{ header: 'Денежный поток', width: 15 },
{ header: 'Накопительный', width: 15 }
];
project.calculations.cashFlow.forEach((month, index) => {
cashFlowSheet.addRow([
month.month,
month.revenue,
month.cost + month.opex + month.taxes + month.capex,
month.netCash,
month.cumulative
]);
});
// Лист с рисками
const risksSheet = workbook.addWorksheet('Риски');
risksSheet.columns = [
{ header: 'Уровень', width: 10 },
{ header: 'Категория', width: 15 },
{ header: 'Описание', width: 40 },
{ header: 'Вероятность', width: 15 },
{ header: 'Влияние', width: 20 },
{ header: 'Меры снижения', width: 40 }
];
project.riskAnalysis.forEach(risk => {
risksSheet.addRow([
risk.level === 'high' ? 'Высокий' : risk.level === 'medium' ? 'Средний' : 'Низкий',
risk.category,
risk.description,
(risk.probability * 100).toFixed(0) + '%',
risk.impact,
risk.mitigation
]);
});
// Лист с рекомендациями
const recommendationsSheet = workbook.addWorksheet('Рекомендации');
recommendationsSheet.columns = [
{ header: 'Приоритет', width: 10 },
{ header: 'Область', width: 15 },
{ header: 'Действие', width: 30 },
{ header: 'Шаги', width: 50 },
{ header: 'Ожидаемый эффект', width: 30 }
];
project.recommendations.forEach(rec => {
recommendationsSheet.addRow([
rec.priority === 'high' ? 'Высокий' : 'Средний',
rec.area,
rec.action,
rec.steps.join('; '),
rec.expectedImpact
]);
});
return workbook;
}
// Конвертация в формат Project Expert
function convertToProjectExpertXML(project) {
return `
${project.id} ${project.timestamp}
${Object.entries(project.parameters).map(([key, value]) =>
`${value}`
).join('\n ')}
${project.calculations.metrics.npv} ${project.calculations.metrics.irr} ${project.calculations.metrics.roi}
${project.calculations.cashFlow.map(month =>
``
).join('\n ')}
`;
}
// Вспомогательные функции
function calculateRegionalParameters(region, industry) {
// Расчет параметров с учетом региона и отрасли
const base = parametersDB.base || {};
const regionalCoeff = parametersDB.regions[region]?.coefficient || 1.0;
const industryCoeff = parametersDB.industries[industry]?.coefficient || 1.0;
const params = {};
// Применяем коэффициенты
for (const [key, value] of Object.entries(base)) {
params[key] = value * regionalCoeff * industryCoeff;
}
return params;
}
function sendTelegramMessage(chatId, text) {
// Реализация отправки сообщений в Telegram
const token = process.env.TELEGRAM_BOT_TOKEN;
if (!token) return;
const url = `https://api.telegram.org/bot${token}/sendMessage`;
fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
chat_id: chatId,
text: text,
parse_mode: 'HTML'
})
}).catch(console.error);
}
// Создание директорий
function ensureDirectories() {
const dirs = ['exports', 'projects', 'uploads'];
dirs.forEach(dir => {
const dirPath = path.join(__dirname, dir);
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
});
}
// Запуск сервера
ensureDirectories();
app.listen(PORT, () => {
console.log(`???? Project Expert API запущен на порту ${PORT}`);
console.log(`???? База параметров: ${Object.keys(parametersDB).length} категорий`);
console.log(`???? Хранилище проектов: ${projectsDB.size} в памяти`);
console.log(`???? Telegram бот: ${process.env.TELEGRAM_BOT_TOKEN ? 'активен' : 'не настроен'}`);
});