Модуль:PlayerAchievements/Engine

Материал из ЧТМ
Версия от 01:40, 4 июня 2026; Дача Цанавы (обсуждение | вклад) (Новая страница: «-- ========================================== -- Модуль:PlayerAchievements/Engine -- Чистый вычислительный движок для генерации статистики -- ========================================== local Engine = {} local Config = require('Module:Config') local CoreEngine = require('Module:StatEngine/Pure') local TournamentAwards = require('Module:StatEngine/TournamentAwards') local ModuleMegarating = require('Modul...»)
(разн.) ← Предыдущая версия | Текущая версия (разн.) | Следующая версия → (разн.)
Перейти к навигации Перейти к поиску

Для документации этого модуля может быть создана страница Модуль:PlayerAchievements/Engine/doc

-- ==========================================
-- Модуль:PlayerAchievements/Engine
-- Чистый вычислительный движок для генерации статистики
-- ==========================================

local Engine = {}

local Config = require('Module:Config')
local CoreEngine = require('Module:StatEngine/Pure')
local TournamentAwards = require('Module:StatEngine/TournamentAwards')
local ModuleMegarating = require('Module:Megarating')

-- Локальный кэш, чтобы при множественных вызовах движок считал всё ровно 1 раз
local cached_data = nil

local function plural(n, w1, w2, w5)
    local n10 = n % 10; local n100 = n % 100
    if n100 >= 11 and n100 <= 19 then return w5 end
    if n10 == 1 then return w1 end
    if n10 >= 2 and n10 <= 4 then return w2 end
    return w5
end

local function date_diff(date1, date2)
    local y1, m1, d1 = date1:match("(%d+)%-(%d+)%-(%d+)")
    local y2, m2, d2 = date2:match("(%d+)%-(%d+)%-(%d+)")
    if not y1 or not y2 then return 0, "" end
    y1, m1, d1 = tonumber(y1), tonumber(m1), tonumber(d1)
    y2, m2, d2 = tonumber(y2), tonumber(m2), tonumber(d2)

    local dy = y2 - y1; local dm = m2 - m1; local dd = d2 - d1
    if dd < 0 then dm = dm - 1; dd = dd + 30 end
    if dm < 0 then dy = dy - 1; dm = dm + 12 end

    local total_days = dy * 365 + dm * 30 + dd
    local parts = {}
    if dy > 0 then table.insert(parts, dy .. " " .. plural(dy, "год", "года", "лет")) end
    if dm > 0 then table.insert(parts, dm .. " " .. plural(dm, "месяц", "месяца", "месяцев")) end
    if dd > 0 then table.insert(parts, dd .. " " .. plural(dd, "день", "дня", "дней")) end

    return total_days, table.concat(parts, " ")
end

-- ==========================================
-- ГЛАВНАЯ ФУНКЦИЯ ДВИЖКА (Принимает загруженные базы данных)
-- ==========================================
function Engine.run(loaded_databases)
    if cached_data then return cached_data end

    local Players = {}
    local GlobalRecords = { AllTime = {}, PerTournament = {} }
    local CustomStats = { club100 = {}, stadiums = {}, finals = {} }
    local Leaders = {}
    local TeamLeaders = {}
    local StadiumLeaders = {}
    local ShoeTeams = {}
    local AssistTeams = {}

    local function get_p(name)
        if not Players[name] then
            Players[name] = {
                name = name,
                champs = 0, champs_years = {}, 
                champ_captain = 0, champ_captain_years = {},
                champ_streak = 0, cur_champ_streak = 0, last_champ_year = nil, champ_streak_end_year = nil,
                champ_team_streak = 0, cur_champ_team_streak = 0, last_champ_team = nil, champ_team_streak_code = "", champ_team_streak_end_year = nil,
                superchamps = 0, superchamps_years = {}, 
                golden_goals = 0, golden_goals_years = {}, 
                final_goals_real = 0, final_assists_real = 0, final_ga_real = 0,
                max_final_goals = 0, max_final_goals_occurences = {}, 
                finals_played = 0, mvp_final = 0, mvp_final_years = {},
                goals = 0, max_goals_team = 0, max_goals_team_code = "",
                max_goals_match = 0, max_goals_match_occurences = {},
                mega_tricks = 0, max_mega_tricks_team = 0, max_mega_tricks_team_code = "",
                classic_hat_tricks = 0, pokers = 0, pentas = 0, hexas = 0,
                pens_scored = 0, pens_in_game_scored = 0,
                max_assists_match = 0, max_assists_match_occurences = {}, 
                pm = 0, md_prizes = 0, md_places = 0,
                assists = 0, heel_goals = 0,
                golden_spheres = 0, golden_spheres_years = {}, total_spheres = 0, 
                golden_shoes = 0, golden_shoes_years = {}, total_shoes = 0,
                mvp = 0, max_team_mvp = 0, max_team_mvp_code = "",
                mvp_goalie = 0, mr_points_pct = 0, pens_saved = 0,
                matches = 0, field_matches = 0, goalie_matches = 0, goalie_goals = 0, head_goals = 0, clearances = 0,
                max_streaks = {}, cur_streaks = {}, last_played_hist = nil, cur_team = nil,
                max_streaks_team_matches_code = "", max_streaks_goalie_clean_code = "",
                max_gap_days = 0, max_gap_str = "", last_date = nil,
                wins = { goals = 0, goals_years = {}, mega_tricks = 0, mega_tricks_years = {}, pm = 0, pm_years = {}, mvp = 0, mvp_years = {}, mvp_goalie = 0, mvp_goalie_years = {}, head_goals = 0, head_goals_years = {}, clearances = 0, clearances_years = {}, gaa = 0, gaa_years = {} },
                tournaments = {}
            }
        end
        return Players[name]
    end

    local function get_t(p, year)
        if not p.tournaments[year] then
            p.tournaments[year] = {
                year = year, goals = 0, max_goals_team = 0, max_goals_team_code = "",
                mega_tricks = 0, pm = 0, matches = 0, pts_pct = 0, md_prizes = 0, md_places = 0,
                heel_goals = 0, mvp = 0, max_team_mvp = 0, max_team_mvp_code = "",
                playoff_wins = 0, head_goals = 0, clearances = 0, gaa = 0,
                playoff_goals = 0, winning_goals = 0, avg_goals = 0,
                max_team_assists = 0, max_team_assists_code = "",
                mvp_goalie = 0, mr_points_pct = 0, mr_points = 0, assists = 0, avg_assists = 0,
                ga = 0, max_team_ga = 0, max_team_ga_code = "",
                goalie_matches = 0, field_matches = 0,
                max_mega_tricks_team = 0, max_mega_tricks_team_code = ""
            }
        end
        return p.tournaments[year]
    end

    -- 1. ЕДИНСТВЕННЫЙ ПОЛНЫЙ ПРОХОД КОМБАЙНОМ!
    local GrandStats = CoreEngine.Harvester.run_all_time(loaded_databases, { need_players = true, need_teams = true, need_combos = true, keep_years = true })
    
    if Config.award_adjustments then
        for year, year_adj in pairs(Config.award_adjustments) do
            for award_type, combos in pairs(year_adj) do
                for combo_key, adj in pairs(combos) do
                    local p_name, t_code = string.match(combo_key, "^(.*)_(.*)$")
                    if p_name and t_code and adj.total then
                        local add = adj.total
                        if GrandStats.Players[p_name] then
                            if award_type == "goals" then GrandStats.Players[p_name].goals.total = GrandStats.Players[p_name].goals.total + add
                            elseif award_type == "assists" then GrandStats.Players[p_name].assists.total = GrandStats.Players[p_name].assists.total + add end
                        end
                        if GrandStats.Years[year] and GrandStats.Years[year].Players[p_name] then
                            if award_type == "goals" then GrandStats.Years[year].Players[p_name].goals.total = GrandStats.Years[year].Players[p_name].goals.total + add
                            elseif award_type == "assists" then GrandStats.Years[year].Players[p_name].assists.total = GrandStats.Years[year].Players[p_name].assists.total + add end
                        end
                        if GrandStats.PlayerTeam[combo_key] then
                            if award_type == "goals" then GrandStats.PlayerTeam[combo_key].goals.total = GrandStats.PlayerTeam[combo_key].goals.total + add
                            elseif award_type == "assists" then GrandStats.PlayerTeam[combo_key].assists.total = GrandStats.PlayerTeam[combo_key].assists.total + add end
                        end
                        if GrandStats.Years[year] and GrandStats.Years[year].PlayerTeam[combo_key] then
                            if award_type == "goals" then GrandStats.Years[year].PlayerTeam[combo_key].goals.total = GrandStats.Years[year].PlayerTeam[combo_key].goals.total + add
                            elseif award_type == "assists" then GrandStats.Years[year].PlayerTeam[combo_key].assists.total = GrandStats.Years[year].PlayerTeam[combo_key].assists.total + add end
                        end
                    end
                end
            end
        end
    end

    local flat_matches = {}
    local global_goals = {}

    for _, year in ipairs(Config.years) do
        local year_db = loaded_databases[year]
        if year_db then
            CustomStats.finals[year] = {}
            for id, m in pairs(year_db) do
                table.insert(flat_matches, { year = year, id = id, m = m, num = m.num_hist or 0 })
            end
        end
    end
    table.sort(flat_matches, function(a, b)
        if a.year ~= b.year then return a.year < b.year end
        return tostring(a.id) < tostring(b.id)
    end)

    local StreaksEngine = require('Module:StatEngine/StreaksCore').new()
    local current_scorer = nil
    local consecutive_goals = 0

    for _, item in ipairs(flat_matches) do
        local year = item.year; local match = item.m
        local is_playoff = Config.playoff_stages[match.stage] == true
        local is_final = (match.stage == "Финал")

        StreaksEngine:process_match(year, match, match.num_hist)

        if match.date then
            local p_teams = CoreEngine.Harvester.get_all_teams(match)
            for p_name, _ in pairs(p_teams) do
                local p = get_p(p_name)
                if p.last_date then
                    local days, str = date_diff(p.last_date, match.date)
                    if days > p.max_gap_days then p.max_gap_days = days; p.max_gap_str = str end
                end
                p.last_date = match.date
            end
        end

        local match_g = {}; local match_a = {}
        
        if match.goals then
            for _, g in ipairs(match.goals) do
                if g.scorer and not g.own_scorer then
                    if g.scorer == current_scorer then
                        consecutive_goals = consecutive_goals + 1
                        if consecutive_goals == 3 then get_p(g.scorer).classic_hat_tricks = get_p(g.scorer).classic_hat_tricks + 1 end
                    else current_scorer = g.scorer; consecutive_goals = 1 end
                else current_scorer = nil; consecutive_goals = 0 end

                if g.scorer then
                    local p = get_p(g.scorer); local t = get_t(p, year)
                    global_goals[g.scorer] = (global_goals[g.scorer] or 0) + 1
                    if global_goals[g.scorer] == 100 then table.insert(CustomStats.club100, {name = g.scorer, year = year}) end
                    
                    if match.stadium then
                        CustomStats.stadiums[match.stadium] = CustomStats.stadiums[match.stadium] or {}
                        CustomStats.stadiums[match.stadium][g.scorer] = (CustomStats.stadiums[match.stadium][g.scorer] or 0) + 1
                    end

                    match_g[g.scorer] = (match_g[g.scorer] or 0) + 1
                    local mg = match_g[g.scorer]
                    if mg > p.max_goals_match then p.max_goals_match = mg; p.max_goals_match_occurences = {year}
                    elseif mg == p.max_goals_match and mg > 0 then table.insert(p.max_goals_match_occurences, year) end
                    
                    if is_final then
                        if mg > p.max_final_goals then p.max_final_goals = mg; p.max_final_goals_occurences = {year}
                        elseif mg == p.max_final_goals and mg > 0 then table.insert(p.max_final_goals_occurences, year) end
                    end
                    if is_playoff then t.playoff_goals = t.playoff_goals + 1 end
                end
                
                if g.assist then
                    local p = get_p(g.assist)
                    match_a[g.assist] = (match_a[g.assist] or 0) + 1
                    local ma = match_a[g.assist]
                    if ma > p.max_assists_match then p.max_assists_match = ma; p.max_assists_match_occurences = {year}
                    elseif ma == p.max_assists_match and ma > 0 then table.insert(p.max_assists_match_occurences, year) end
                end
            end
        end

        if is_final then
            local f = CustomStats.finals[year]
            f.goals_count = match_g; f.assists_count = match_a

            if match.goals then
                for _, g in ipairs(match.goals) do
                    if g.scorer then get_p(g.scorer).final_goals_real = get_p(g.scorer).final_goals_real + 1 end
                    if g.assist then get_p(g.assist).final_assists_real = get_p(g.assist).final_assists_real + 1 end
                end
            end

            local function parse_aw(aw_data)
                if type(aw_data) == "table" then return aw_data end
                if type(aw_data) == "string" then return {aw_data} end
                return {}
            end
            
            f.superchampions = parse_aw(match.superchampions)
            f.golden_sphere = parse_aw(match.golden_sphere); f.silver_sphere = parse_aw(match.silver_sphere)
            f.bronze_sphere = parse_aw(match.bronze_sphere); f.wooden_sphere = parse_aw(match.wooden_sphere)
            f.golden_shoe = parse_aw(match.golden_shoe); f.silver_shoe = parse_aw(match.silver_shoe)
            f.bronze_shoe = parse_aw(match.bronze_shoe); f.wooden_shoe = parse_aw(match.wooden_shoe)
            f.golden_assistant = parse_aw(match.golden_assistant); f.silver_assistant = parse_aw(match.silver_assistant)
            f.bronze_assistant = parse_aw(match.bronze_assistant); f.wooden_assistant = parse_aw(match.wooden_assistant)
            f.elnur_award = parse_aw(match.elnur_award); f.best_goal = parse_aw(match.best_goal)
            f.mvp = match.mvp and match.mvp.player or nil
            
            f.has_shootout = (match.shootout_score1 ~= nil); f.shootout_winner = nil
            if match.shootout and f.has_shootout then
                local w_team = match.shootout_score1 > match.shootout_score2 and 1 or 2
                local w_score = 0; local max_score = math.max(match.shootout_score1, match.shootout_score2)
                for _, shot in ipairs(match.shootout) do
                    if shot.team == w_team and shot.result == "гол" then
                        w_score = w_score + 1; if w_score == max_score then f.shootout_winner = shot.taker; break end
                    end
                end
            end

            for _, p_name in ipairs(f.superchampions or {}) do get_p(p_name).superchamps = get_p(p_name).superchamps + 1; table.insert(get_p(p_name).superchamps_years, year) end
            for _, p_name in ipairs(f.golden_sphere or {}) do get_p(p_name).golden_spheres = get_p(p_name).golden_spheres + 1; table.insert(get_p(p_name).golden_spheres_years, year); get_p(p_name).total_spheres = get_p(p_name).total_spheres + 1 end
            for _, p_name in ipairs(f.silver_sphere or {}) do get_p(p_name).total_spheres = get_p(p_name).total_spheres + 1 end
            for _, p_name in ipairs(f.bronze_sphere or {}) do get_p(p_name).total_spheres = get_p(p_name).total_spheres + 1 end
            for _, p_name in ipairs(f.wooden_sphere or {}) do get_p(p_name).total_spheres = get_p(p_name).total_spheres + 1 end
            for _, p_name in ipairs(f.golden_shoe or {}) do get_p(p_name).golden_shoes = get_p(p_name).golden_shoes + 1; table.insert(get_p(p_name).golden_shoes_years, year); get_p(p_name).total_shoes = get_p(p_name).total_shoes + 1 end
            for _, p_name in ipairs(f.silver_shoe or {}) do get_p(p_name).total_shoes = get_p(p_name).total_shoes + 1 end
            for _, p_name in ipairs(f.bronze_shoe or {}) do get_p(p_name).total_shoes = get_p(p_name).total_shoes + 1 end
            for _, p_name in ipairs(f.wooden_shoe or {}) do get_p(p_name).total_shoes = get_p(p_name).total_shoes + 1 end

            if f.mvp then get_p(f.mvp).mvp_final = get_p(f.mvp).mvp_final + 1; table.insert(get_p(f.mvp).mvp_final_years, year) end
            if f.shootout_winner then get_p(f.shootout_winner).golden_goals = get_p(f.shootout_winner).golden_goals + 1; table.insert(get_p(f.shootout_winner).golden_goals_years, year) end

            local m_res = CoreEngine.Harvester.evaluate_match(match)
            local win_team = (m_res and m_res[1].pts > m_res[2].pts) and 1 or ((m_res and m_res[2].pts > m_res[1].pts) and 2 or 0)
            local p_teams = CoreEngine.Harvester.get_all_teams(match)
            local cap1 = match.squad1 and match.squad1.captain; local cap2 = match.squad2 and match.squad2.captain
            
            for p_name, t_id in pairs(p_teams) do
                local p = get_p(p_name)
                p.finals_played = p.finals_played + 1
                
                if t_id == win_team then
                    p.champs = p.champs + 1; table.insert(p.champs_years, year)
                    if (t_id == 1 and cap1 == p_name) or (t_id == 2 and cap2 == p_name) then
                        p.champ_captain = p.champ_captain + 1; table.insert(p.champ_captain_years, year)
                    end

                    local prev_idx = 0
                    for i, y in ipairs(Config.years) do if y == year then prev_idx = i - 1 break end end
                    local prev_year = Config.years[prev_idx]
                    local cur_t_code = (t_id == 1) and match.team1 or match.team2

                    if p.last_champ_year == prev_year then
                        p.cur_champ_streak = p.cur_champ_streak + 1
                        if p.last_champ_team == cur_t_code then p.cur_champ_team_streak = p.cur_champ_team_streak + 1 else p.cur_champ_team_streak = 1 end
                    else
                        p.cur_champ_streak = 1; p.cur_champ_team_streak = 1
                    end
                    p.last_champ_year = year; p.last_champ_team = cur_t_code
                    if p.cur_champ_streak > p.champ_streak then p.champ_streak = p.cur_champ_streak; p.champ_streak_end_year = year end
                    if p.cur_champ_team_streak > p.champ_team_streak then p.champ_team_streak = p.cur_champ_team_streak; p.champ_team_streak_code = cur_t_code; p.champ_team_streak_end_year = year end
                end
            end
        end
    end

    StreaksEngine:finish()
    local GlobalStreaks = StreaksEngine:get_results()
    for name, ep in pairs(GlobalStreaks.players) do
        local p = get_p(name)
        p.max_streaks.team_scored = ep.team_scored or 0; p.max_streaks.matches_played = ep.seq or 0
        p.max_streaks.goals = ep.goals or 0; p.max_streaks.double = ep.double or 0
        p.max_streaks.hat_trick = ep.hat_trick or 0; p.max_streaks.poker = ep.poker or 0
        p.max_streaks.both_ga = ep.both_ga or 0; p.max_streaks.ga = ep.ga or 0
        p.max_streaks.missed_matches = ep.missed_matches or 0; p.max_streaks.unbeaten = ep.unbeaten or 0
        p.max_streaks.win = ep.win or 0
    end
    for name, et in pairs(GlobalStreaks.team_streaks) do
        local p = get_p(name); p.max_streaks.team_matches = et.len or 0; p.max_streaks_team_matches_code = et.team or ""
    end
    for name, egk in pairs(GlobalStreaks.goalie_clean) do
        local p = get_p(name); p.max_streaks.goalie_clean = egk.len or 0; p.max_streaks_goalie_clean_code = egk.team or ""
    end

    for p_name, gs_p in pairs(GrandStats.Players) do
        local p = get_p(p_name)
        p.goals = gs_p.goals.total; p.assists = gs_p.assists.total; p.matches = gs_p.matches_total
        p.field_matches = gs_p.matches_field; p.goalie_matches = gs_p.matches_goalie
        p.pm = gs_p.plus_minus; p.clearances = gs_p.clearances; p.head_goals = gs_p.goals.head
        p.heel_goals = gs_p.goals.heel; p.goalie_goals = gs_p.goals.goalie
        p.pens_scored = gs_p.penalties.in_game.g + gs_p.penalties.shootout.g
        p.pens_in_game_scored = gs_p.penalties.in_game.g; p.pens_saved = gs_p.penalties.saved_as_goalie
        p.mvp = gs_p.mvp.is_mvp; p.mvp_goalie = gs_p.mvp.is_goalie_mvp
        p.mega_tricks = gs_p.goals.hat_trick + gs_p.goals.poker + gs_p.goals.penta + gs_p.goals.hexa
        p.pokers = gs_p.goals.poker + gs_p.goals.penta + gs_p.goals.hexa
        p.pentas = gs_p.goals.penta + gs_p.goals.hexa; p.hexas = gs_p.goals.hexa
        p.final_ga_real = p.final_goals_real + p.final_assists_real
        p.golden_goals = p.golden_goals + gs_p.megarating.final_gold_goals
        p.avg_goals = (gs_p.avg.goals_den >= 20) and (gs_p.avg.goals_num / gs_p.avg.goals_den) or 0
        p.avg_assists = (gs_p.avg.assists_den >= 20) and (gs_p.avg.assists_num / gs_p.avg.assists_den) or 0
        p.pts_pct = (p.matches >= 20 and gs_p.megarating.mr_matches > 0) and (gs_p.megarating.mr_points / (gs_p.megarating.mr_matches * 3) * 100) or 0
        p.gaa = (p.goalie_matches >= 20) and (gs_p.weighted_ga / p.goalie_matches) or 9999
    end

    for year, year_stats in pairs(GrandStats.Years) do
        for p_name, gs_t in pairs(year_stats.Players) do
            local p = get_p(p_name); local t = get_t(p, year)
            t.goals = gs_t.goals.total; t.assists = gs_t.assists.total; t.ga = t.goals + t.assists
            t.matches = gs_t.matches_total; t.field_matches = gs_t.matches_field; t.goalie_matches = gs_t.matches_goalie
            t.pm = gs_t.plus_minus; t.clearances = gs_t.clearances
            t.head_goals = gs_t.goals.head; t.heel_goals = gs_t.goals.heel
            t.mvp = gs_t.mvp.is_mvp; t.mvp_goalie = gs_t.mvp.is_goalie_mvp
            t.mega_tricks = gs_t.goals.hat_trick + gs_t.goals.poker + gs_t.goals.penta + gs_t.goals.hexa
            t.winning_goals = gs_t.advanced.winning_goals
            t.playoff_wins = gs_t.megarating.playoff_wins.r16 + gs_t.megarating.playoff_wins.qf + gs_t.megarating.playoff_wins.sf + (gs_t.megarating.is_champion and 1 or 0)
            if gs_t.megarating and gs_t.megarating.final_gold_goals and gs_t.megarating.final_gold_goals > 0 then
                for _ = 1, gs_t.megarating.final_gold_goals do table.insert(p.golden_goals_years, year) end
            end
            t.avg_goals = (gs_t.avg.goals_den >= 20) and (gs_t.avg.goals_num / gs_t.avg.goals_den) or 0
            t.avg_assists = (gs_t.avg.assists_den >= 20) and (gs_t.avg.assists_num / gs_t.avg.assists_den) or 0
            t.pts_pct = (t.matches >= 20 and gs_t.megarating.mr_matches > 0) and (gs_t.megarating.mr_points / (gs_t.megarating.mr_matches * 3) * 100) or 0
            t.gaa = (t.goalie_matches >= 5) and (gs_t.weighted_ga / t.goalie_matches) or 0
        end
    end

    for combo, c_data in pairs(GrandStats.PlayerTeam) do
        local p = get_p(c_data.name); local t_code = c_data.team
        if t_code ~= "Нейтрал" then
            if c_data.goals.total > p.max_goals_team then p.max_goals_team = c_data.goals.total; p.max_goals_team_code = t_code end
            local mt = c_data.goals.hat_trick + c_data.goals.poker + c_data.goals.penta + c_data.goals.hexa
            if mt > p.max_mega_tricks_team then p.max_mega_tricks_team = mt; p.max_mega_tricks_team_code = t_code end
            if c_data.mvp.is_mvp > p.max_team_mvp then p.max_team_mvp = c_data.mvp.is_mvp; p.max_team_mvp_code = t_code end

            if c_data.years then
                for year, yc in pairs(c_data.years) do
                    local t = get_t(p, year)
                    if yc.goals.total > t.max_goals_team then t.max_goals_team = yc.goals.total; t.max_goals_team_code = t_code end
                    local y_mt = yc.goals.hat_trick + yc.goals.poker + yc.goals.penta + yc.goals.hexa
                    if y_mt > t.max_mega_tricks_team then t.max_mega_tricks_team = y_mt; t.max_mega_tricks_team_code = t_code end
                    if yc.mvp.is_mvp > t.max_team_mvp then t.max_team_mvp = yc.mvp.is_mvp; t.max_team_mvp_code = t_code end
                    if yc.assists.total > t.max_team_assists then t.max_team_assists = yc.assists.total; t.max_team_assists_code = t_code end
                    local y_ga = yc.goals.total + yc.assists.total
                    if y_ga > t.max_team_ga then t.max_team_ga = y_ga; t.max_team_ga_code = t_code end
                end
            end
        end
    end

    local mr_hist = ModuleMegarating.get_public_history()
    for p_name, hp in pairs(mr_hist.players) do
        local p = get_p(p_name)
        p.mr_points_pct = hp.total_pct
        for year, ydata in pairs(hp.years) do
            local t = get_t(p, year); t.mr_points_pct = ydata.pct; t.mr_points = ydata.pts
        end
    end

    for year, year_db in pairs(loaded_databases) do
        local shoes = TournamentAwards.getTournamentAwards(year, year_db, "goals")
        ShoeTeams[year] = {}; for _, p in ipairs(shoes) do if p.rank <= 4 and not ShoeTeams[year][p.player] then ShoeTeams[year][p.player] = p.team_code end end
        local assists = TournamentAwards.getTournamentAwards(year, year_db, "assists")
        AssistTeams[year] = {}; for _, p in ipairs(assists) do if p.rank <= 4 and not AssistTeams[year][p.player] then AssistTeams[year][p.player] = p.team_code end end

        if year >= 2022 then
            local mds = {}
            for _, m in pairs(year_db) do if m.matchday then mds[m.matchday] = true end end
            for md, _ in pairs(mds) do
                local prizes = TournamentAwards.evaluateMatchdayPrizes(year_db, md)
                for _, cat in ipairs({"mvp", "scorer", "assistant", "productive", "effective", "useful", "goalie_good", "goalie_bad"}) do
                    if prizes[cat] then
                        for _, p in ipairs(prizes[cat]) do
                            local pl = get_p(p.name); local t = get_t(pl, year)
                            if p.prize then pl.md_prizes = pl.md_prizes + 1; t.md_prizes = t.md_prizes + 1 end
                            if p.prize_place then pl.md_places = pl.md_places + 1; t.md_places = t.md_places + 1 end
                        end
                    end
                end
            end
        end
    end

    local function evaluate_leader(year, metric_func)
        local max_val = -9999; local leaders = {}
        for p, p_stats in pairs(GrandStats.Years[year].Players) do
            local val = metric_func(p_stats)
            if val and val > 0 then
                if val > max_val then max_val = val; leaders = {p}
                elseif val == max_val then table.insert(leaders, p) end
            end
        end
        return leaders
    end

    local function evaluate_min_leader(year, metric_func, min_req)
        local min_val = 9999; local leaders = {}
        for p, p_stats in pairs(GrandStats.Years[year].Players) do
            if min_req(p_stats) then
                local val = metric_func(p_stats)
                if val then
                    if val < min_val then min_val = val; leaders = {p}
                    elseif val == min_val then table.insert(leaders, p) end
                end
            end
        end
        return leaders
    end

    for _, year in ipairs(Config.years) do
        if GrandStats.Years[year] then
            Leaders.goals = Leaders.goals or {}; Leaders.goals[year] = evaluate_leader(year, function(s) return s.goals.total end)
            Leaders.assists = Leaders.assists or {}; Leaders.assists[year] = evaluate_leader(year, function(s) return s.assists.total end)
            Leaders.matches = Leaders.matches or {}; Leaders.matches[year] = evaluate_leader(year, function(s) return s.matches_total end)
            Leaders.mvp = Leaders.mvp or {}; Leaders.mvp[year] = evaluate_leader(year, function(s) return s.mvp.is_mvp end)
            Leaders.mvp_goalie = Leaders.mvp_goalie or {}; Leaders.mvp_goalie[year] = evaluate_leader(year, function(s) return s.mvp.is_goalie_mvp end)
            Leaders.mega_tricks = Leaders.mega_tricks or {}; Leaders.mega_tricks[year] = evaluate_leader(year, function(s) return (s.goals.hat_trick + s.goals.poker + s.goals.penta + s.goals.hexa) end)
            Leaders.head_goals = Leaders.head_goals or {}; Leaders.head_goals[year] = evaluate_leader(year, function(s) return s.goals.head end)
            Leaders.pens_saved = Leaders.pens_saved or {}; Leaders.pens_saved[year] = evaluate_leader(year, function(s) return s.penalties.saved_as_goalie end)
            Leaders.clearances = Leaders.clearances or {}; Leaders.clearances[year] = evaluate_leader(year, function(s) return s.clearances end)
            Leaders.playoff_wins = Leaders.playoff_wins or {}; Leaders.playoff_wins[year] = evaluate_leader(year, function(s) return s.megarating.playoff_wins.r16 + s.megarating.playoff_wins.qf + s.megarating.playoff_wins.sf + (s.megarating.is_champion and 1 or 0) end)
            Leaders.plus_minus = Leaders.plus_minus or {}; Leaders.plus_minus[year] = evaluate_leader(year, function(s) return s.plus_minus end)
            Leaders.pens_scored = Leaders.pens_scored or {}; Leaders.pens_scored[year] = evaluate_leader(year, function(s) return s.penalties.in_game.g + s.penalties.shootout.g end)
            Leaders.avg_goals = Leaders.avg_goals or {}; Leaders.avg_goals[year] = evaluate_leader(year, function(s) return s.avg.goals_den >= 20 and s.avg.goals_num / s.avg.goals_den or 0 end)
            Leaders.gaa = Leaders.gaa or {}; Leaders.gaa[year] = evaluate_min_leader(year, function(s) return s.weighted_ga / s.matches_goalie end, function(s) return s.matches_goalie and s.matches_goalie >= 5 end)
        end
    end

    for combo, c_data in pairs(GrandStats.PlayerTeam) do
        local p = c_data.name; local t = c_data.team; local goals = c_data.goals.total
        if t ~= "Нейтрал" and goals > 0 then
            TeamLeaders[t] = TeamLeaders[t] or {players = {}, goals = 0}
            if goals > TeamLeaders[t].goals then TeamLeaders[t].goals = goals; TeamLeaders[t].players = {p}
            elseif goals == TeamLeaders[t].goals then table.insert(TeamLeaders[t].players, p) end
        end
    end

    for st, p_list in pairs(CustomStats.stadiums) do
        local max = 0; local best = {}
        for p, goals in pairs(p_list) do
            if goals > max then max = goals; best = {p}
            elseif goals == max then table.insert(best, p) end
        end
        if max > 0 then StadiumLeaders[st] = {players = best, goals = max} end
    end

    for _, p in pairs(Players) do
        for year, list in pairs(Leaders.goals or {}) do if Config.utils.has_value(list, p.name) then p.wins.goals = p.wins.goals + 1; table.insert(p.wins.goals_years, year) end end
        for year, list in pairs(Leaders.mega_tricks or {}) do if Config.utils.has_value(list, p.name) then p.wins.mega_tricks = p.wins.mega_tricks + 1; table.insert(p.wins.mega_tricks_years, year) end end
        for year, list in pairs(Leaders.plus_minus or {}) do if Config.utils.has_value(list, p.name) then p.wins.pm = p.wins.pm + 1; table.insert(p.wins.pm_years, year) end end
        for year, list in pairs(Leaders.mvp or {}) do if Config.utils.has_value(list, p.name) then p.wins.mvp = p.wins.mvp + 1; table.insert(p.wins.mvp_years, year) end end
        for year, list in pairs(Leaders.mvp_goalie or {}) do if Config.utils.has_value(list, p.name) then p.wins.mvp_goalie = p.wins.mvp_goalie + 1; table.insert(p.wins.mvp_goalie_years, year) end end
        for year, list in pairs(Leaders.head_goals or {}) do if Config.utils.has_value(list, p.name) then p.wins.head_goals = p.wins.head_goals + 1; table.insert(p.wins.head_goals_years, year) end end
        for year, list in pairs(Leaders.clearances or {}) do if Config.utils.has_value(list, p.name) then p.wins.clearances = p.wins.clearances + 1; table.insert(p.wins.clearances_years, year) end end
        for year, list in pairs(Leaders.gaa or {}) do if Config.utils.has_value(list, p.name) then p.wins.gaa = p.wins.gaa + 1; table.insert(p.wins.gaa_years, year) end end
    end

    local function get_val(p, key)
        if string.sub(key, 1, 12) == "max_streaks_" then return p.max_streaks[string.sub(key, 13)] or 0 end
        if string.sub(key, 1, 5) == "wins_" then return p.wins[string.sub(key, 6)] or 0 end
        return p[key]
    end

    local metrics_all = { {"champs", false}, {"champ_captain", false}, {"champ_streak", false}, {"champ_team_streak", false}, {"superchamps", false}, {"golden_goals", false}, {"final_goals_real", false}, {"final_assists_real", false}, {"final_ga_real", false}, {"max_final_goals", false}, {"finals_played", false}, {"goals", false}, {"max_goals_team", false}, {"max_goals_match", false}, {"mega_tricks", false}, {"max_mega_tricks_team", false}, {"classic_hat_tricks", false}, {"pokers", false}, {"pentas", false}, {"hexas", false}, {"pens_scored", false}, {"pens_in_game_scored", false}, {"max_assists_match", false}, {"pm", false}, {"pts_pct", false}, {"md_prizes", false}, {"md_places", false}, {"assists", false}, {"avg_assists", false}, {"heel_goals", false}, {"golden_spheres", false}, {"total_spheres", false}, {"golden_shoes", false}, {"total_shoes", false}, {"avg_goals", false}, {"gaa", true}, {"mvp", false}, {"max_team_mvp", false}, {"mvp_goalie", false}, {"mr_points_pct", false}, {"pens_saved", false}, {"matches", false}, {"field_matches", false}, {"goalie_matches", false}, {"head_goals", false}, {"clearances", false}, {"max_gap_days", false} }
    
    for _, row in ipairs(metrics_all) do
        local key = row[1]; local is_min = row[2]; local best = is_min and 9999 or -9999
        for _, p in pairs(Players) do
            local v = get_val(p, key)
            if v and v ~= 0 and (not is_min or v < 9999) then
                if (is_min and v < best) or (not is_min and v > best) then best = v end
            end
        end
        GlobalRecords.AllTime[key] = best
    end
    
    local streak_wins_keys = {"max_streaks_team_scored", "max_streaks_matches_played", "max_streaks_goals", "max_streaks_double", "max_streaks_hat_trick", "max_streaks_poker", "max_streaks_both_ga", "max_streaks_ga", "max_streaks_missed_matches", "max_streaks_goalie_clean", "max_streaks_team_matches", "max_streaks_unbeaten", "max_streaks_win", "wins_goals", "wins_mega_tricks", "wins_pm", "wins_mvp", "wins_mvp_goalie", "wins_head_goals", "wins_clearances", "wins_gaa"}
    for _, key in ipairs(streak_wins_keys) do
        local best = -9999
        for _, p in pairs(Players) do
            local v = get_val(p, key)
            if v and v ~= 0 and v > best then best = v end
        end
        GlobalRecords.AllTime[key] = best
    end

    local metrics_tourney = { {"goals", false}, {"max_goals_team", false}, {"mega_tricks", false}, {"pm", false}, {"matches", false}, {"pts_pct", false}, {"md_prizes", false}, {"md_places", false}, {"heel_goals", false}, {"mvp", false}, {"max_team_mvp", false}, {"playoff_wins", false}, {"head_goals", false}, {"clearances", false}, {"gaa", true}, {"playoff_goals", false}, {"winning_goals", false}, {"avg_goals", false}, {"max_team_assists", false}, {"mvp_goalie", false}, {"mr_points", false}, {"assists", false}, {"avg_assists", false}, {"ga", false}, {"max_team_ga", false} }
    
    for _, row in ipairs(metrics_tourney) do
        local key = row[1]; local is_min = row[2]; local best = is_min and 9999 or -9999
        for _, p in pairs(Players) do
            for _, t in pairs(p.tournaments) do
                local v = t[key]
                if v and v ~= 0 and (not is_min or v < 9999) then
                    if key == "gaa" and t.goalie_matches < 5 then v = nil end
                    if (key == "avg_goals" or key == "avg_assists") and t.field_matches < 20 then v = nil end
                    if key == "pts_pct" and t.matches < 20 then v = nil end
                    if v and ((is_min and v < best) or (not is_min and v > best)) then best = v end
                end
            end
        end
        GlobalRecords.PerTournament[key] = best
    end

    -- Формируем один массив данных, который раздадим всем остальным
    cached_data = {
        Players = Players,
        GlobalRecords = GlobalRecords,
        CustomStats = CustomStats,
        Leaders = Leaders,
        TeamLeaders = TeamLeaders,
        StadiumLeaders = StadiumLeaders,
        ShoeTeams = ShoeTeams,
        AssistTeams = AssistTeams,
        GrandStats = GrandStats,
        GlobalStreaks = GlobalStreaks,
        MegaratingHistory = mr_hist
    }

    return cached_data
end

return Engine