<?php
/**
 * ============================================
 * AegisOne Engineering — Расчётные формулы
 * ============================================
 *
 * Все коэффициенты вынесены в БД (таблица formula_coefficients)
 * и редактируются владельцем через /service/pages/owner/coefficients.php.
 *
 * Хардкоды ЗАПРЕЩЕНЫ — используй get_coefficient('key').
 *
 * Формат ключа: <формула>.<параметр>, например:
 *   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');
            $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);
}
