.<параметр>, например: * risk_score.no_archive → 25 * object_index.risk_weight → 0.4 * sla_price.base_cost → 15000 */ // ======================================================================== // ВСПОМОГАТЕЛЬНЫЕ // ======================================================================== /** * Получить значение коэффициента из БД. * Результат кэшируется в статической переменной (один SQL-запрос за вызов). * * @param string $key Ключ коэффициента (напр. 'risk_score.no_archive') * @param float $default Значение по умолчанию, если ключ не найден * @return float */ function get_coefficient(string $key, float $default = 0): float { static $cache = null; if ($cache === null) { try { $pdo = getDB(); $stmt = $pdo->query("SELECT `key`, `value` FROM formula_coefficients WHERE is_active=1"); $cache = []; while ($row = $stmt->fetch()) { $cache[$row['key']] = (float)$row['value']; } } catch (\Throwable $e) { $cache = []; } } return $cache[$key] ?? $default; } /** * Форматирование: 1 234 567 ₽ */ function fmt_money(float $amount): string { return number_format($amount, 0, '.', ' ') . ' ₽'; } /** * Форматирование: 95.3% */ function fmt_percent(float $value): string { return number_format($value, 1, '.', '') . '%'; } /** * Форматирование: 3.5 ч */ function fmt_hours(float $hours): string { return number_format($hours, 1, '.', '') . ' ч'; } /** * CSS-класс для статуса / приоритета */ function badge_class(string $status, string $type = 'default'): string { $map = [ 'P1' => 'badge-critical', 'P2' => 'badge-high', 'P3' => 'badge-medium', 'P4' => 'badge-low', 'open' => 'badge-open', 'in_progress' => 'badge-progress', 'completed' => 'badge-done', 'cancelled' => 'badge-cancelled', 'active' => 'badge-active', 'inactive' => 'badge-inactive', 'draft' => 'badge-draft', 'final' => 'badge-done', 'Risk' => 'badge-risk', 'Crisis' => 'badge-crisis', 'Stable' => 'badge-stable', 'Growth' => 'badge-growth', ]; return $map[$status] ?? 'badge-default'; } // ======================================================================== // 1. RISK SCORE (ОЦЕНКА РИСКА ОБЪЕКТА) // ======================================================================== // Формула: сумма весов нарушений (каждое — 0 или 1). // Веса хранятся в БД: // risk_score.no_archive — нет архива видеонаблюдения (умолч. 25) // risk_score.no_power_backup — нет резервного питания (умолч. 20) // risk_score.no_regulations — нет регламента обслуживания (умолч. 15) // risk_score.system_failures — частые сбои систем (умолч. 20) // risk_score.no_documentation — нет документации (умолч. 10) function calc_risk_score(bool $noArchive, bool $noPowerBackup, bool $noRegulations, bool $systemFailures, bool $noDocumentation): float { return ($noArchive ? get_coefficient('risk_score.no_archive', 25) : 0) + ($noPowerBackup ? get_coefficient('risk_score.no_power_backup', 20) : 0) + ($noRegulations ? get_coefficient('risk_score.no_regulations', 15) : 0) + ($systemFailures ? get_coefficient('risk_score.system_failures', 20) : 0) + ($noDocumentation ? get_coefficient('risk_score.no_documentation', 10) : 0); } /** * Интерпретация Risk Score * risk_score.bound_low — граница низкого риска (умолч. 20) * risk_score.bound_medium — граница среднего (умолч. 50) * risk_score.bound_high — граница высокого (умолч. 75) */ function risk_label(float $score): string { if ($score <= get_coefficient('risk_score.bound_low', 20)) return 'Низкий'; if ($score <= get_coefficient('risk_score.bound_medium', 50)) return 'Средний'; if ($score <= get_coefficient('risk_score.bound_high', 75)) return 'Высокий'; return 'Критический'; } /** * Множитель риска для расчёта SLA Price * risk_multiplier.low — (0-20) умолч. 1.0 * risk_multiplier.medium — (21-50) умолч. 1.3 * risk_multiplier.high — (51-75) умолч. 1.6 * risk_multiplier.critical— (76-100) умолч. 2.0 */ function risk_multiplier(float $score): float { if ($score <= get_coefficient('risk_score.bound_low', 20)) return get_coefficient('risk_multiplier.low', 1.0); if ($score <= get_coefficient('risk_score.bound_medium', 50)) return get_coefficient('risk_multiplier.medium', 1.3); if ($score <= get_coefficient('risk_score.bound_high', 75)) return get_coefficient('risk_multiplier.high', 1.6); return get_coefficient('risk_multiplier.critical', 2.0); } // ======================================================================== // 2. SLA COMPLEXITY INDEX (СЛОЖНОСТЬ ОБСЛУЖИВАНИЯ) // ======================================================================== // Параметры: // complexity.video_0_20 — до 20 камер (умолч. 10) // complexity.video_20_100 — 20-100 камер (умолч. 20) // complexity.video_100plus — 100+ камер (умолч. 35) // complexity.access_0_5 — до 5 точек СКУД (умолч. 10) // complexity.access_5_20 — 5-20 точек (умолч. 20) // complexity.access_20plus — 20+ точек (умолч. 30) // complexity.fire_simple — простая пожарка (умолч. 15) // complexity.fire_medium — средняя (умолч. 25) // complexity.fire_complex — сложная (>5000 м²) (умолч. 40) // complexity.it — IT-инфраструктура (умолч. 10) function calc_complexity(int $cameras, int $accessPoints, string $fireType, bool $hasIT): float { $video = $cameras <= 20 ? get_coefficient('complexity.video_0_20', 10) : ($cameras <= 100 ? get_coefficient('complexity.video_20_100', 20) : get_coefficient('complexity.video_100plus', 35)); $access = $accessPoints <= 5 ? get_coefficient('complexity.access_0_5', 10) : ($accessPoints <= 20 ? get_coefficient('complexity.access_5_20', 20) : get_coefficient('complexity.access_20plus', 30)); $fire = $fireType === 'simple' ? get_coefficient('complexity.fire_simple', 15) : ($fireType === 'medium' ? get_coefficient('complexity.fire_medium', 25) : ($fireType === 'complex' ? get_coefficient('complexity.fire_complex', 40) : 0)); $it = $hasIT ? get_coefficient('complexity.it', 10) : 0; return $video + $access + $fire + $it; } function complexity_label(float $score): string { if ($score <= 30) return 'Простой'; if ($score <= 70) return 'Средний'; if ($score <= 120) return 'Сложный'; return 'Enterprise'; } // ======================================================================== // 3. INFRASTRUCTURE LOAD (НАГРУЗКА НА ИНФРАСТРУКТУРУ) // ======================================================================== // infra.server_none — нет сервера (умолч. 15) // infra.server_weak — слабый сервер (умолч. 10) // infra.network_unstable — нестабильная сеть (умолч. 20) // infra.network_partial — частично (умолч. 10) // infra.power_none — нет UPS (умолч. 20) // infra.power_weak — слабый UPS (умолч. 10) function calc_infrastructure_load(string $serverState, string $networkState, string $powerState): float { $server = $serverState === 'none' ? get_coefficient('infra.server_none', 15) : ($serverState === 'weak' ? get_coefficient('infra.server_weak', 10) : 0); $network = $networkState === 'unstable' ? get_coefficient('infra.network_unstable', 20) : ($networkState === 'partial' ? get_coefficient('infra.network_partial', 10) : 0); $power = $powerState === 'none' ? get_coefficient('infra.power_none', 20) : ($powerState === 'weak' ? get_coefficient('infra.power_weak', 10) : 0); return $server + $network + $power; } // ======================================================================== // 4. SERVICE HISTORY INDEX (ИСТОРИЯ ОБСЛУЖИВАНИЯ) // ======================================================================== // history.none — нет обслуживания (умолч. 30) // history.irregular — нерегулярное (умолч. 20) // history.formal — формальный подрядчик (умолч. 10) // history.sla — есть SLA (умолч. 0) function calc_service_history(string $serviceState): float { if ($serviceState === 'none') return get_coefficient('history.none', 30); if ($serviceState === 'irregular') return get_coefficient('history.irregular', 20); if ($serviceState === 'formal') return get_coefficient('history.formal', 10); if ($serviceState === 'sla') return get_coefficient('history.sla', 0); return get_coefficient('history.none', 30); } // ======================================================================== // 5. OBJECT INDEX (ИТОГОВЫЙ ИНДЕКС ОБЪЕКТА) // ======================================================================== // object_index.risk_weight — вес Risk Score (умолч. 0.4) // object_index.complexity_weight — вес Complexity (умолч. 0.3) // object_index.infra_weight — вес Infra Load (умолч. 0.2) // object_index.history_weight — вес Service History (умолч. 0.1) function calc_object_index(float $risk, float $complexity, float $infra, float $history): float { return round( $risk * get_coefficient('object_index.risk_weight', 0.4) + $complexity * get_coefficient('object_index.complexity_weight', 0.3) + $infra * get_coefficient('object_index.infra_weight', 0.2) + $history * get_coefficient('object_index.history_weight', 0.1) , 2); } function object_class(float $index): string { if ($index <= 30) return 'A'; if ($index <= 60) return 'B'; if ($index <= 90) return 'C'; return 'D'; } function object_class_label(float $index): string { $cls = object_class($index); if ($cls === 'A') return 'Лёгкий SLA'; if ($cls === 'B') return 'Стандарт SLA'; if ($cls === 'C') return 'Сложный SLA'; if ($cls === 'D') return 'Enterprise / Высокий риск'; return ''; } // ======================================================================== // 6. SLA PRICE (СТОИМОСТЬ SLA) // ======================================================================== // sla_price.base_cost — базовая стоимость инженера (умолч. 15000) // sla_price.region_* — региональные коэффициенты // sla_price.risk_* — мультипликаторы риска (через risk_multiplier) function calc_sla_price(float $baseCost, float $objectIndex, float $regionFactor, float $riskMult): float { return round($baseCost * $objectIndex * $regionFactor * $riskMult, 2); } // ======================================================================== // 7. ENGINEER SCORE (ES) — ИТОГОВЫЙ ИНДЕКС ИНЖЕНЕРА // ======================================================================== // engineer.score.sla_weight — 0.25 // engineer.score.response_weight — 0.20 // engineer.score.resolution_weight — 0.20 // engineer.score.diagnosis_weight — 0.15 // engineer.score.reopen_weight — 0.10 // engineer.score.risk_coverage_weight — 0.10 function calc_engineer_score(float $slaCompliance, float $responseTimeScore, float $resolutionTimeScore, float $diagnosisAccuracy, float $reopenRateScore, float $riskCoverageScore): float { return round( (get_coefficient('engineer.score.sla_weight', 0.25) * $slaCompliance) + (get_coefficient('engineer.score.response_weight', 0.20) * $responseTimeScore) + (get_coefficient('engineer.score.resolution_weight', 0.20) * $resolutionTimeScore) + (get_coefficient('engineer.score.diagnosis_weight', 0.15) * $diagnosisAccuracy) + (get_coefficient('engineer.score.reopen_weight', 0.10) * $reopenRateScore) + (get_coefficient('engineer.score.risk_coverage_weight', 0.10) * $riskCoverageScore) , 2); } /** * Грейд инженера по итоговому Score * engineer.grade.senior — 90 * engineer.grade.strong — 80 * engineer.grade.middle — 70 */ function engineer_grade(float $score): string { if ($score >= get_coefficient('engineer.grade.senior', 90)) return 'Senior'; if ($score >= get_coefficient('engineer.grade.strong', 80)) return 'Strong'; if ($score >= get_coefficient('engineer.grade.middle', 70)) return 'Middle'; return 'Junior'; } // ======================================================================== // 8. ENGINEER CONTROL SCORE (ECS) — ИНДЕКС УПРАВЛЕНИЯ ИНЖЕНЕРА // ======================================================================== // ecs.sla_weight — 0.30 // ecs.task_dist_weight — 0.25 // ecs.incident_red_weight — 0.20 // ecs.team_perf_weight — 0.15 // ecs.response_coord_weight — 0.10 function calc_engineer_control_score(float $slaControl, float $taskDist, float $incidentRed, float $teamPerf, float $responseCoord): float { return round( (get_coefficient('ecs.sla_weight', 0.30) * $slaControl) + (get_coefficient('ecs.task_dist_weight', 0.25) * $taskDist) + (get_coefficient('ecs.incident_red_weight', 0.20) * $incidentRed) + (get_coefficient('ecs.team_perf_weight', 0.15) * $teamPerf) + (get_coefficient('ecs.response_coord_weight', 0.10) * $responseCoord) , 2); } // ======================================================================== // 9. SHS (SYSTEM HEALTH SCORE) — ЗДОРОВЬЕ СИСТЕМЫ // ======================================================================== // shs.sla_stability_weight — 0.22 // shs.revenue_stability_weight — 0.18 // shs.retention_weight — 0.18 // shs.engineer_perf_weight — 0.15 // shs.incident_stability_weight — 0.12 // shs.sales_flow_weight — 0.10 // shs.operational_eff_weight — 0.05 function calc_shs(float $slaStability, float $revenueStability, float $retention, float $engineerPerformance, float $incidentStability, float $salesFlow, float $operationalEfficiency): float { return round( (get_coefficient('shs.sla_stability_weight', 0.22) * $slaStability) + (get_coefficient('shs.revenue_stability_weight', 0.18) * $revenueStability) + (get_coefficient('shs.retention_weight', 0.18) * $retention) + (get_coefficient('shs.engineer_perf_weight', 0.15) * $engineerPerformance) + (get_coefficient('shs.incident_stability_weight', 0.12) * $incidentStability) + (get_coefficient('shs.sales_flow_weight', 0.10) * $salesFlow) + (get_coefficient('shs.operational_eff_weight', 0.05) * $operationalEfficiency) , 2); } /** * CEO SHS — отдельные веса для CEO-дашборда * ceo_shs.mrr_weight — 0.25 * ceo_shs.sla_weight — 0.20 * ceo_shs.retention_weight — 0.20 * ceo_shs.productivity_weight — 0.15 * ceo_shs.conversion_weight — 0.10 * ceo_shs.incident_weight — 0.10 */ function calc_ceo_shs(float $mrrGrowth, float $slaCompliance, float $retention, float $productivity, float $conversion, float $incidentStability): float { return round( (get_coefficient('ceo_shs.mrr_weight', 0.25) * $mrrGrowth) + (get_coefficient('ceo_shs.sla_weight', 0.20) * $slaCompliance) + (get_coefficient('ceo_shs.retention_weight', 0.20) * $retention) + (get_coefficient('ceo_shs.productivity_weight', 0.15) * $productivity) + (get_coefficient('ceo_shs.conversion_weight', 0.10) * $conversion) + (get_coefficient('ceo_shs.incident_weight', 0.10) * $incidentStability) , 2); } /** * Статус SHS * shs.zone_growth — 85 * shs.zone_stable — 70 * shs.zone_risk — 50 */ function shs_status(float $score): string { if ($score >= get_coefficient('shs.zone_growth', 85)) return 'Growth'; if ($score >= get_coefficient('shs.zone_stable', 70)) return 'Stable'; if ($score >= get_coefficient('shs.zone_risk', 50)) return 'Risk'; return 'Crisis'; } /** * Зона SHS с предупреждением */ function shs_zone(float $score): array { if ($score >= get_coefficient('shs.zone_growth', 85)) return ['zone' => 'green', 'label' => 'Growth', 'action' => '']; if ($score >= get_coefficient('shs.zone_stable', 70)) return ['zone' => 'yellow', 'label' => 'Stable', 'action' => 'Мониторинг']; if ($score >= get_coefficient('shs.zone_risk', 50)) return ['zone' => 'yellow', 'label' => 'Risk', 'action' => 'Снизить продажи, запустить аудит цикла, пересмотреть назначения']; return ['zone' => 'red', 'label' => 'Crisis', 'action' => 'Немедленный аудит, приостановка новых продаж, пересмотр команды']; } /** * Дельта SHS (изменение относительно предыдущей записи) * Если падение > 5 — warning, > 10 — critical * shs.delta_warning — 5 * shs.delta_critical — 10 */ function shs_delta(float $currentScore, ?float $previousScore): ?array { if ($previousScore === null) return null; $delta = $currentScore - $previousScore; $thresholdWarn = get_coefficient('shs.delta_warning', 5); $thresholdCrit = get_coefficient('shs.delta_critical', 10); if ($delta <= -$thresholdCrit) return ['delta' => round($delta, 1), 'level' => 'critical', 'message' => 'SHS упал более чем на ' . $thresholdCrit . ' пунктов — кризис']; if ($delta <= -$thresholdWarn) return ['delta' => round($delta, 1), 'level' => 'warning', 'message' => 'SHS упал более чем на ' . $thresholdWarn . ' пунктов — требуется внимание']; return ['delta' => round($delta, 1), 'level' => 'ok', 'message' => '']; } // ======================================================================== // 10. SLA STABILITY INDEX (SSI) // ======================================================================== // Учитывает не только compliance, но и severity нарушений // SSI = SLA_compliance × (1 - breach_severity) // ssi.breach_severity_p1 — 1.0 // ssi.breach_severity_p2 — 0.5 // ssi.breach_severity_p3 — 0.2 function calc_sla_stability_index(float $slaCompliance, float $breachSeverityWeighted): float { $ssi = $slaCompliance * (1 - $breachSeverityWeighted); return round(max(0, min(100, $ssi)), 2); } function calc_breach_severity(int $breachesP1, int $breachesP2, int $breachesP3): float { $total = $breachesP1 + $breachesP2 + $breachesP3; if ($total === 0) return 0; $weighted = $breachesP1 * get_coefficient('ssi.breach_severity_p1', 1.0) + $breachesP2 * get_coefficient('ssi.breach_severity_p2', 0.5) + $breachesP3 * get_coefficient('ssi.breach_severity_p3', 0.2); return min(1, $weighted / $total); } // ======================================================================== // 11. REVENUE STABILITY INDEX (RSI) через коэффициент вариации // ======================================================================== // RSI = max(0, 100 × (1 - σ(MRR) / mean(MRR))) // Основан на исторических значениях MRR за N периодов function calc_revenue_stability_index(array $mrrHistory): float { $count = count($mrrHistory); if ($count < 2) return 100; $mean = array_sum($mrrHistory) / $count; if ($mean <= 0) return 100; $variance = 0; foreach ($mrrHistory as $mrr) { $variance += ($mrr - $mean) ** 2; } $stdDev = sqrt($variance / $count); $cv = $stdDev / $mean; return round(max(0, min(100, 100 * (1 - $cv))), 2); } // ======================================================================== // 12. SALES FLOW INDEX (SFI) — ВОРОНКА ПРОДАЖ // ======================================================================== // SFI = (аудиты → SLA конверсия) × качество лидов function calc_sales_flow_index(int $audits, int $slaContracts, float $leadQuality = 1.0): float { if ($audits <= 0) return 0; $conversion = $slaContracts / $audits; return round(min(100, $conversion * 100 * $leadQuality), 2); } // ======================================================================== // 13. OPERATIONAL EFFICIENCY INDEX (OEI) // ======================================================================== // OEI = revenue / (engineer_hours × cost_per_hour) × 100 // Нормируется к 100 function calc_operational_efficiency_index(float $revenue, float $engineerHours, float $costPerHour): float { $total = $engineerHours * $costPerHour; if ($total <= 0) return 100; return round(min(100, ($revenue / $total) * 100), 2); } // ======================================================================== // 14. INCIDENT STABILITY INDEX (ISI) с весами severity // ======================================================================== // ISI = max(0, 100 - (incidents / objects × severity_weight)) // isi.severity_p1 — 1.0 // isi.severity_p2 — 0.5 // isi.severity_p3 — 0.2 function calc_incident_stability_index(int $incidentsP1, int $incidentsP2, int $incidentsP3, int $objectCount): float { if ($objectCount <= 0) return 100; $weighted = $incidentsP1 * get_coefficient('isi.severity_p1', 1.0) + $incidentsP2 * get_coefficient('isi.severity_p2', 0.5) + $incidentsP3 * get_coefficient('isi.severity_p3', 0.2); $rate = $weighted / $objectCount; return round(max(0, min(100, 100 - ($rate * 20))), 2); } // ======================================================================== // 15. AUTOMATION RULES (АВТОМАТИЧЕСКИЕ ПРАВИЛА) // ======================================================================== /** * Проверить все правила автоматизации. * Возвращает массив предупреждений. * * Правила из docs/Цифровая модель общая.md: * 1. IF SHS < 70 → снизить продажи, запустить аудит, пересмотреть назначения * 2. IF SLA < 90% → заморозить неприоритетные проекты, увеличить частоту проверок * 3. IF retention < 90% → обязательный аудит клиентов, пересмотр назначений * * rules.shs_threshold — 70 * rules.sla_threshold — 90 * rules.retention_threshold — 90 */ function check_automation_rules(?float $shs, ?float $slaCompliance, ?float $retention): array { $alerts = []; if ($shs !== null && $shs < get_coefficient('rules.shs_threshold', 70)) { $alerts[] = [ 'rule' => 'SHS', 'severity' => 'critical', 'message' => 'SHS ниже ' . get_coefficient('rules.shs_threshold', 70) . ' — снизить продажи, запустить аудит, пересмотреть назначения инженеров.', ]; } if ($slaCompliance !== null && $slaCompliance < get_coefficient('rules.sla_threshold', 90)) { $alerts[] = [ 'rule' => 'SLA', 'severity' => 'warning', 'message' => 'SLA Compliance ниже ' . get_coefficient('rules.sla_threshold', 90) . '% — заморозить неприоритетные проекты, увеличить частоту проверок.', ]; } if ($retention !== null && $retention < get_coefficient('rules.retention_threshold', 90)) { $alerts[] = [ 'rule' => 'Retention', 'severity' => 'critical', 'message' => 'Удержание клиентов ниже ' . get_coefficient('rules.retention_threshold', 90) . '% — обязательный аудит клиентов, пересмотр назначений инженеров.', ]; } return $alerts; } // ======================================================================== // 16. БАЗОВЫЕ KPI // ======================================================================== /** SLA Compliance: процент задач, закрытых в срок */ function calc_sla_compliance(int $closedInSla, int $total): float { if ($total <= 0) return 100; return round(($closedInSla / $total) * 100, 2); } /** Reopen Rate: процент повторных обращений */ function calc_reopen_rate(int $reopened, int $total): float { if ($total <= 0) return 0; return round(($reopened / $total) * 100, 2); } /** Diagnosis Accuracy: точность диагностики */ function calc_diagnosis_accuracy(int $confirmed, int $found): float { if ($found <= 0) return 100; return round(($confirmed / $found) * 100, 2); } /** Response Time Score: оценка времени реакции */ function calc_response_time_score(float $actualHours, float $slaHours): float { if ($slaHours <= 0) return 100; $ratio = $actualHours / $slaHours; if ($ratio <= 1.0) return 100; if ($ratio <= 1.2) return 80; return max(0, 100 - ($ratio - 1.2) * 50); } /** Resolution Time Score: оценка времени устранения */ function calc_resolution_time_score(float $actualHours, float $normHours): float { if ($normHours <= 0) return 100; $ratio = $actualHours / $normHours; if ($ratio <= 1.0) return 100; if ($ratio <= 1.5) return 70; return max(0, 100 - ($ratio - 1.5) * 40); } /** Risk Coverage Score: покрытие зон проверки */ function calc_risk_coverage(int $checkedZones, int $totalZones = 5): float { if ($totalZones <= 0) return 0; return round(($checkedZones / $totalZones) * 100, 2); } /** Documentation Quality: полнота отчётов */ function calc_documentation_quality(int $fullReports, int $totalVisits): float { if ($totalVisits <= 0) return 100; return round(($fullReports / $totalVisits) * 100, 2); } /** Team Load Balance: равномерность загрузки (stdDev) — чем меньше, тем лучше */ function calc_tlb(array $tasksPerEngineer): float { $count = count($tasksPerEngineer); if ($count < 2) return 0; $mean = array_sum($tasksPerEngineer) / $count; $variance = 0; foreach ($tasksPerEngineer as $t) { $variance += ($t - $mean) ** 2; } return round(sqrt($variance / $count), 2); } // ======================================================================== // 17. ФИНАНСОВЫЕ ПОКАЗАТЕЛИ // ======================================================================== /** MRR: месячный повторяющийся доход */ function calc_mrr(float $totalSlaAnnual): float { return round($totalSlaAnnual / 12, 2); } /** Revenue per Engineer */ function calc_rpe(float $slaRevenue, int $engineerCount): float { if ($engineerCount <= 0) return 0; return round($slaRevenue / $engineerCount, 2); } /** Cost per SLA Object (CPO) */ function calc_cpo(float $totalCost, int $objectCount): float { if ($objectCount <= 0) return 0; return round($totalCost / $objectCount, 2); } /** Average Revenue Per Client (ARPC) */ function calc_arpc(float $totalRevenue, int $clientCount): float { if ($clientCount <= 0) return 0; return round($totalRevenue / $clientCount, 2); } // ======================================================================== // 18. ОПЕРАЦИОННЫЕ KPI // ======================================================================== /** Utilization: загрузка сотрудника */ function calc_utilization(int $workingHours, int $availableHours): float { if ($availableHours <= 0) return 0; return round(($workingHours / $availableHours) * 100, 2); } /** Autonomy: самостоятельность */ function calc_autonomy(int $independent, int $total): float { if ($total <= 0) return 100; return round(($independent / $total) * 100, 2); } /** Escalation Rate */ function calc_escalation_rate(int $escalated, int $total): float { if ($total <= 0) return 0; return round(($escalated / $total) * 100, 2); } /** System Uptime: доступность системы */ function calc_uptime(float $uptimeHours, float $totalHours): float { if ($totalHours <= 0) return 100; return round(($uptimeHours / $totalHours) * 100, 2); } // ======================================================================== // 19. КЛИЕНТСКИЕ МЕТРИКИ // ======================================================================== /** Retention: удержание клиентов */ function calc_retention(int $activeClients, int $totalClients): float { if ($totalClients <= 0) return 100; return round(($activeClients / $totalClients) * 100, 2); } /** Retention с штрафом за потерю ключевых клиентов */ function calc_retention_key_client(float $baseRetention, int $lostKeyClients): float { $penalty = $lostKeyClients * get_coefficient('retention.key_client_penalty', 0.1); return round(max(0, $baseRetention - $penalty * 100), 2); } /** CSAT (удовлетворённость клиентов) */ function calc_csat(int $positive, int $total): float { if ($total <= 0) return 100; return round(($positive / $total) * 100, 2); } // ======================================================================== // 20. ИНЦИДЕНТЫ // ======================================================================== /** Incident Rate: количество инцидентов на объект */ function calc_incident_rate(int $incidents, int $objectCount): float { if ($objectCount <= 0) return 0; return round($incidents / $objectCount, 2); } /** Incident Reduction Rate: снижение инцидентов */ /** Incident Reduction Rate */ function calc_incident_reduction(int $previous, int $current): float { if ($previous <= 0) return 0; return round((($previous - $current) / $previous) * 100, 2); } // ======================================================================== // 21. БОНУСНАЯ СИСТЕМА // ======================================================================== // bonus.score_90plus — +20% // bonus.score_80_89 — +10% // bonus.score_below_80 — 0% function calc_bonus_percent(float $engineerScore): float { if ($engineerScore >= get_coefficient('bonus.score_90plus', 90)) return get_coefficient('bonus.premium_90plus', 20); if ($engineerScore >= get_coefficient('bonus.score_80_89', 80)) return get_coefficient('bonus.premium_80_89', 10); return get_coefficient('bonus.premium_below_80', 0); }