Модуль:TournamentResults: различия между версиями

Материал из ЧТМ
Перейти к навигации Перейти к поиску
Увеличение лимита строк с 50 до 100
Версия 1.2.2
 
(не показано 8 промежуточных версий 3 участников)
Строка 2: Строка 2:
-- Модуль:TournamentResults
-- Модуль:TournamentResults
-- Сборщик турнирных страниц из БД
-- Сборщик турнирных страниц из БД
-- тест
-- Версия 1.2.2
-- =======================================
-- =======================================
local p = {}
local p = {}
Строка 47: Строка 47:


     local output = {}
     local output = {}
-- Лимит строк, изначально был 50, превысили на странице, ставим 100
    -- Лимит строк, изначально был 50, ставим 100
     for i = 1, 100 do
     for i = 1, 100 do
         local title_type = cleanParam(args['title_type' .. i])
         local title_type = cleanParam(args['title_type' .. i])
Строка 63: Строка 63:


         if not (title or text or table_id or rating_prefix) then break end
         if not (title or text or table_id or rating_prefix) then break end
        -- =====================
        -- ПАРСИНГ ПАЛИТРЫ ЦВЕТОВ (colorsX)
        -- =====================
        local colors_str = cleanParam(args['colors' .. i])
        local current_colors = {}
        for k, v in pairs(COLORS) do current_colors[k] = { c = v.c, b = v.b } end
       
        if colors_str then
            for chunk in string.gmatch(colors_str, "[^;,%s]+") do
                local c_code, c_val, c_flag = string.match(chunk, "^(%w+):([^:]+):?(.*)$")
                if c_code and c_val then
                    local is_bold = current_colors[c_code] and current_colors[c_code].b or false
                    if c_code == "G" then is_bold = true end
                    if c_flag == "b" or c_flag == "B" then is_bold = true end
                    if c_flag == "n" or c_flag == "N" then is_bold = false end
                    current_colors[c_code] = { c = c_val, b = is_bold }
                end
            end
        end
        -- =====================
        -- ПАРСИНГ ЦВЕТОВ КОМАНД (team_colorsX)
        -- =====================
        local team_colors_str = cleanParam(args['team_colors' .. i])
        local team_color_overrides = {}
        if team_colors_str then
            for t_code, c_code in string.gmatch(team_colors_str, "([^:,%s]+):([^:,%s]+)") do
                team_color_overrides[t_code] = c_code
            end
        end


         -- Заголовки и текст
         -- Заголовки и текст
Строка 84: Строка 115:
             local min_size = 999
             local min_size = 999


            -- 1. Ищем все группы с нужным префиксом и определяем минимальный размер группы
             for key, node in pairs(tour_data) do
             for key, node in pairs(tour_data) do
                 if type(node) == "table" and node.type == "group" and string.match(key, "^" .. rating_prefix .. "_Group") then
                 if type(node) == "table" and node.type == "group" and string.match(key, "^" .. rating_prefix .. "_Group") then
                     local count = 0
                     local count = 0
                     if node.standings then
                     if node.standings then
                         for _ in ipairs(node.standings) do count = count + 1 end -- Считаем длину без #
                         for _ in ipairs(node.standings) do count = count + 1 end
                     end
                     end
                     if count > 0 then
                     if count > 0 then
Строка 101: Строка 131:
                 local target_teams = {}
                 local target_teams = {}


                -- 2. Извлекаем нужные команды и фильтруем их матчи
                 for _, g in ipairs(groups) do
                 for _, g in ipairs(groups) do
                     local node = g.node
                     local node = g.node
Строка 111: Строка 140:
                     if target_idx and target_idx <= size then
                     if target_idx and target_idx <= size then
                         local target_code = node.standings[target_idx][1]
                         local target_code = node.standings[target_idx][1]
                        -- Определяем, кого отсекать (уравнивание)
                         local ignore_idx = nil
                         local ignore_idx = nil
                         if size > min_size then
                         if size > min_size then
                             if rating_place == 'last' then  
                             if rating_place == 'last' then ignore_idx = size - 1 else ignore_idx = size end
                                ignore_idx = size - 1 -- Если считаем последних, отсекаем предпоследних
                            else  
                                ignore_idx = size -- В иных случаях отсекаем самых последних
                            end
                         end
                         end


                         local ignore_code = nil
                         local ignore_code = nil
                         if ignore_idx and node.standings[ignore_idx] then
                         if ignore_idx and node.standings[ignore_idx] then ignore_code = node.standings[ignore_idx][1] end
                            ignore_code = node.standings[ignore_idx][1]
                        end


                        -- 3. Считаем очки
                         local pts, gf, ga = 0, 0, 0
                         local pts, gf, ga = 0, 0, 0
                         if node.matches then
                         if node.matches then
                             for _, m in ipairs(node.matches) do
                             for _, m in ipairs(node.matches) do
                                 local t1, t2, g1, g2 = m[1], m[2], m[3], m[4]
                                 local t1, t2, g1, g2 = m[1], m[2], m[3], m[4]
                                 if g1 ~= nil and g2 ~= nil then -- Матч состоялся
                                 if g1 ~= nil and g2 ~= nil then
                                     if t1 == target_code and t2 ~= ignore_code then
                                     if t1 == target_code and t2 ~= ignore_code then
                                         gf = gf + g1
                                         gf = gf + g1
Строка 146: Строка 166:
                         end
                         end


                         table.insert(target_teams, {
                         table.insert(target_teams, {code = target_code, pts = pts, gf = gf, ga = ga, gd = gf - ga})
                            code = target_code, pts = pts, gf = gf, ga = ga, gd = gf - ga
                        })
                     end
                     end
                 end
                 end


                 -- 4. Сортировка: Очки -> Разница -> Забитые мячи -> Алфавит
                 local places_str = cleanParam(args['places' .. i])
                 table.sort(target_teams, function(a, b)
                 if places_str then
                     if a.pts ~= b.pts then return a.pts > b.pts end
                    local manual_order = {}
                    if a.gd ~= b.gd then return a.gd > b.gd end
                    local rank = 1
                    if a.gf ~= b.gf then return a.gf > b.gf end
                    for code in string.gmatch(places_str, "[^,%s]+") do
                    return a.code < b.code
                        manual_order[code] = rank
                 end)
                        rank = rank + 1
                    end
                    table.sort(target_teams, function(a, b)
                        local oA = manual_order[a.code] or 999
                        local oB = manual_order[b.code] or 999
                        if oA ~= oB then return oA < oB end
                        return a.code < b.code
                    end)
                else
                    local r_tiebreaker_str = cleanParam(args['rating_tiebreaker' .. i])
                    local r_tie_order = {}
                    if r_tiebreaker_str then
                        local rank = 1
                        for code in string.gmatch(r_tiebreaker_str, "[^,%s]+") do
                            r_tie_order[code] = rank
                            rank = rank + 1
                        end
                     end
 
                    table.sort(target_teams, function(a, b)
                        if a.pts ~= b.pts then return a.pts > b.pts end
                        if a.gd ~= b.gd then return a.gd > b.gd end
                        if a.gf ~= b.gf then return a.gf > b.gf end
                        local oA = r_tie_order[a.code]
                        local oB = r_tie_order[b.code]
                        if oA and oB and oA ~= oB then return oA < oB end
                        return a.code < b.code
                    end)
 
                    for idx = 1, #target_teams - 1 do
                        local a = target_teams[idx]
                        local b = target_teams[idx+1]
                        if a.pts == b.pts and a.gd == b.gd and a.gf == b.gf then
                            local oA = r_tie_order[a.code]
                            local oB = r_tie_order[b.code]
                            if not (oA and oB and oA ~= oB) then
                                a.unresolved_tie = true
                                b.unresolved_tie = true
                            end
                        end
                    end
                 end


                -- 5. Парсим расцветку (формат: "2:G, 2:Y, 4:L")
                 local color_map = {}
                 local color_map = {}
                 if rating_colors then
                 if rating_colors then
Строка 169: Строка 227:
                 end
                 end


                -- 6. Передаем в MatchTable
                 local t_args = {}
                 local t_args = {}
                 t_args.title = rating_title
                 t_args.title = rating_title
Строка 177: Строка 234:
                     t_args['gf' .. idx] = t.gf
                     t_args['gf' .. idx] = t.gf
                     t_args['ga' .. idx] = t.ga
                     t_args['ga' .. idx] = t.ga
                   
                    if t.unresolved_tie then t_args['err' .. idx] = "yes" end


                     local c_code = color_map[idx]
                     local c_code = color_map[idx]
                     if c_code and COLORS[c_code] then
                   
                         t_args['color' .. idx] = COLORS[c_code].c
                    -- ПЕРЕОПРЕДЕЛЕНИЕ ЦВЕТА КОМАНДЫ (Рейтинг)
                    if team_color_overrides[t.code] then
                        c_code = team_color_overrides[t.code]
                        if c_code == "0" or c_code == "none" then c_code = nil end
                    end
 
                     if c_code and current_colors[c_code] then
                         t_args['color' .. idx] = current_colors[c_code].c
                        if current_colors[c_code].b then t_args['bold' .. idx] = "yes" end
                     end
                     end
                 end
                 end
Строка 207: Строка 274:
                     t_args.number_of_rounds = db_node.number_of_rounds or 1
                     t_args.number_of_rounds = db_node.number_of_rounds or 1
                     t_args.compact = (compact == "yes" or compact == "true")
                     t_args.compact = (compact == "yes" or compact == "true")
                    t_args.tiebreaker = cleanParam(args['tiebreaker' .. i])
                    local places_str = cleanParam(args['places' .. i])
                    local ordered_standings = {}
                    if places_str then
                        t_args.manual_sort = "yes"
                        local color_lookup = {}
                        for _, st in ipairs(db_node.standings) do
                            color_lookup[st[1]] = st[2]
                        end
                        for code in string.gmatch(places_str, "[^,%s]+") do
                            table.insert(ordered_standings, {code, color_lookup[code]})
                        end
                    else
                        ordered_standings = db_node.standings
                    end


                     local team_map = {}
                     local team_map = {}
                     for idx, st in ipairs(db_node.standings) do
                     for idx, st in ipairs(ordered_standings) do
                         local code = st[1]
                         local code = st[1]
                         local color_code = st[2]
                         local color_code = st[2]
                       
                        -- ПЕРЕОПРЕДЕЛЕНИЕ ЦВЕТА КОМАНДЫ (Группа)
                        if team_color_overrides[code] then
                            color_code = team_color_overrides[code]
                            if color_code == "0" or color_code == "none" then color_code = nil end
                        end
                         team_map[code] = idx
                         team_map[code] = idx
                         t_args['team' .. idx] = code
                         t_args['team' .. idx] = code


                         if COLORS[color_code] then
                         if color_code and current_colors[color_code] then
                             t_args['color' .. idx] = COLORS[color_code].c
                             t_args['color' .. idx] = current_colors[color_code].c
                             if COLORS[color_code].b then t_args['bold' .. idx] = "yes" end
                             if current_colors[color_code].b then t_args['bold' .. idx] = "yes" end
                         end
                         end
                     end
                     end
Строка 227: Строка 318:
                             local score_str = tostring(m[3]) .. ":" .. tostring(m[4])
                             local score_str = tostring(m[3]) .. ":" .. tostring(m[4])
                             local link_val = cleanParam(args['link' .. i .. '_' .. id_home .. '_' .. id_away])
                             local link_val = cleanParam(args['link' .. i .. '_' .. id_home .. '_' .. id_away])
                             if link_val then score_str = "[[" .. link_val .. "|" .. score_str .. "]]" end
                             if link_val then score_str = ("'''[[" .. link_val .. "|" .. score_str .. "]]'''") end
                             t_args['score' .. id_home .. '_' .. id_away] = score_str
                             t_args['score' .. id_home .. '_' .. id_away] = score_str
                         end
                         end
Строка 273: Строка 364:
                                 elseif flag == "pen" then score_str = score_str .. "(пен." .. tostring(p1) .. ":" .. tostring(p2) .. ")" end
                                 elseif flag == "pen" then score_str = score_str .. "(пен." .. tostring(p1) .. ":" .. tostring(p2) .. ")" end
                             end
                             end
                        end
                       
                        -- ПЕРЕОПРЕДЕЛЕНИЕ ЦВЕТА КОМАНДЫ (Плей-офф)
                        if team_color_overrides[t1] then
                            c_code1 = team_color_overrides[t1]
                            if c_code1 == "0" or c_code1 == "none" then c_code1 = nil end
                        end
                        if team_color_overrides[t2] then
                            c_code2 = team_color_overrides[t2]
                            if c_code2 == "0" or c_code2 == "none" then c_code2 = nil end
                         end
                         end


Строка 283: Строка 384:
                         t_args['s' .. k .. '_score'] = score_str
                         t_args['s' .. k .. '_score'] = score_str


                         if c_code1 and COLORS[c_code1] then
                         if c_code1 and current_colors[c_code1] then
                             t_args['s' .. k .. '_color1'] = COLORS[c_code1].c
                             t_args['s' .. k .. '_color1'] = current_colors[c_code1].c
                             if COLORS[c_code1].b then t_args['s' .. k .. '_bold1'] = "yes" end
                             if current_colors[c_code1].b then t_args['s' .. k .. '_bold1'] = "yes" end
                         end
                         end
                         if c_code2 and COLORS[c_code2] then
                         if c_code2 and current_colors[c_code2] then
                             t_args['s' .. k .. '_color2'] = COLORS[c_code2].c
                             t_args['s' .. k .. '_color2'] = current_colors[c_code2].c
                             if COLORS[c_code2].b then t_args['s' .. k .. '_bold2'] = "yes" end
                             if current_colors[c_code2].b then t_args['s' .. k .. '_bold2'] = "yes" end
                         end
                         end
                     end
                     end

Текущая версия от 03:37, 1 июня 2026

Документация Документация

Модуль для вывода полных результатов турниров в нужном формате. За один проход по базе данных вытаскивает оттуда все требуемые результаты, подсчитывает нужные показатели и выдаёт сырую информацию модулю MatchTable, который рисует нужные таблицы.

Параметры

Общие

  • year — год турнира.
  • tournament — название турнира, массив из базы данных, например, ["ЧТМ_2046_Qual"] (без [""]).

Строковые

С обязательной цифрой-номером строки.

Общие

  • title_type1 — числовой параметр. Уровень заголовка, то есть количество знаков равенства в вики-разметке от 2 до 6, если 0, то выдаётся псевдо-заголовок Такого вида.
  • title1 — сам заголовок, то, что в нём будет написано.
  • text1 (опционально) — текст между заголовком и таблицей, поддерживает вики-разметку и переносы строк.
  • table1 — таблица, заполняется кодом массива из базы данных конкретного турнира, например, ["1R_GroupA"] (без [""]).
  • ref1 (опционально) — примечание мелким шрифтом под таблицей.
  • team_colors1 — изменение цветов ячеек, заданных в базе данных. ДОМ:G, МОН:L, КИР:Y, ИНД:R, СЕН:0. 0 или none полностью отключают цвет для выбранной команды. Разделители могут быть запятыми или пробелами (ДОМ:G, МОН:Y или ДОМ:G МОН:Y). Коды команд обрабатываются строго до двоеточия.
  • colors1 — если требуется изменить стандартные цвета, то передайте этот параметр: colors1 = G:#99FF99, Y:#FFFF99. Вы также можете принудительно задавать или убирать выделение жирным с помощью :b (bold) или :n (normal) на конце значения: colors1 = R:red:b сделает цвет R красным и текст станет жирным, colors1 = G:lime:n сделает цвет G лаймовым, но отключит жирный шрифт (для G жирность включена по умолчанию всегда).

Частные

Эти параметры зависят от типа таблицы, которую вы хотите вывести в конкретной строке — таблицу группы или рейтинга. Для таблицы плей-офф особых параметров нет.

Группа
  • compact1 — если задано yes, то в таблице не показываются колонки «И», «В», «Н» и «П».
  • tiebreaker1 — в случае полного равенства команд необходимо расставить их вручную, указав через запятую трёхбуквенные коды.
  • places1 — если нужно расставить места не так, как подсчитано автоматически, то можно расставить вручную, указав через запятую трёхбуквенные коды.
Рейтинг
  • rating_title1 — название сворачиваемой таблицы, как правило, «Рейтинг вторых мест» или «Рейтинг третьих мест».
  • rating1 — префикс раунда из базы данных, рейтинг мест в котором вы хотите подсчитать, как правило 1R, 2R или 3R.
  • rating_place1 — числовой параметр, номер мест, чей рейтинг вы хотите подсчитать, как правило 2 или 3. Если задано last, то считается рейтинг последних мест.
  • rating_colors1 — цветовое оформление строк таблицы, указывайте количество строк и цвета. Например, 2:G, 2:Y, 8:L, 2:R, читается как «2 зелёных, 2 жёлтых, 8 светло-жёлтых, 2 красных». Строка rating_colors полностью игнорирует пробелы, запятые и тире, она ищет только паттерны Число:Буква. Можно писать 2:G, 4:Y, 2:G 4:Y — всё поймёт без проблем. Буквы цветов те же, что и в базе:
    • G{{{3}}} (lightgreen)
    • Y{{{3}}} (palegoldenrod)
    • L{{{3}}} (lightyellow)
    • R{{{3}}} (lightsalmon)
    • 0{{{3}}} (без раскраски)
  • rating_tiebreaker1 — в случае полного равенства команд необходимо расставить их вручную, указав через запятую трёхбуквенные коды.
  • places1 — если нужно расставить места не так, как подсчитано автоматически, то можно расставить вручную, указав через запятую трёхбуквенные коды.

Заготовки для копирования

Обёртка
{{#invoke:TournamentResults|build
|year = 
|tournament = 
}}
Заголовки без таблиц
|title_type=|title=|text=
Таблицы групп
|title_type=|title=|table=|compact=yes
Таблицы плей-офф
|title_type=|title=|table=
Таблицы рейтинга
|rating_title=|rating=|rating_place=|rating_colors=

Пожалуйста, добавляйте категории на страницу документации.

-- =======================================
-- Модуль:TournamentResults
-- Сборщик турнирных страниц из БД
-- Версия 1.2.2
-- =======================================
local p = {}

local matchTable = require('Модуль:MatchTable')

local COLORS = {
    ["G"] = { c = "lightgreen", b = true },
    ["Y"] = { c = "palegoldenrod", b = false },
    ["L"] = { c = "lightyellow", b = false },
    ["R"] = { c = "lightsalmon", b = false }
}

local function cleanParam(param)
    if type(param) == 'string' then
        local trimmed = mw.text.trim(param)
        if trimmed ~= '' then return trimmed end
    elseif param ~= nil then
        return param
    end
    return nil
end

function p.build(frame)
    local args = frame:getParent().args
    if next(args) == nil then args = frame.args end

    local year = cleanParam(args.year)
    local tournament = cleanParam(args.tournament)

    if not year or not tournament then
        return '<strong class="error">Ошибка: Не указаны параметры year и/или tournament.</strong>'
    end

    local success, full_db = pcall(mw.loadData, 'Модуль:Data/Tournaments/' .. year)
    if not success then
        return '<strong class="error">Ошибка: Не удалось загрузить БД за ' .. year .. ' год.</strong>'
    end

    local tour_data = full_db[tournament]
    if not tour_data then
        return '<strong class="error">Ошибка: Турнир "' .. tournament .. '" не найден.</strong>'
    end

    local output = {}
    -- Лимит строк, изначально был 50, ставим 100
    for i = 1, 100 do
        local title_type = cleanParam(args['title_type' .. i])
        local title = cleanParam(args['title' .. i])
        local text = cleanParam(args['text' .. i])
        local table_id = cleanParam(args['table' .. i])
        local ref = cleanParam(args['ref' .. i])
        local compact = cleanParam(args['compact' .. i])

        -- Параметры для сборки рейтинга
        local rating_prefix = cleanParam(args['rating' .. i])
        local rating_place = cleanParam(args['rating_place' .. i])
        local rating_colors = cleanParam(args['rating_colors' .. i])
        local rating_title = cleanParam(args['rating_title' .. i])

        if not (title or text or table_id or rating_prefix) then break end

        -- =====================
        -- ПАРСИНГ ПАЛИТРЫ ЦВЕТОВ (colorsX)
        -- =====================
        local colors_str = cleanParam(args['colors' .. i])
        local current_colors = {}
        for k, v in pairs(COLORS) do current_colors[k] = { c = v.c, b = v.b } end
        
        if colors_str then
            for chunk in string.gmatch(colors_str, "[^;,%s]+") do
                local c_code, c_val, c_flag = string.match(chunk, "^(%w+):([^:]+):?(.*)$")
                if c_code and c_val then
                    local is_bold = current_colors[c_code] and current_colors[c_code].b or false
                    if c_code == "G" then is_bold = true end
                    if c_flag == "b" or c_flag == "B" then is_bold = true end
                    if c_flag == "n" or c_flag == "N" then is_bold = false end
                    current_colors[c_code] = { c = c_val, b = is_bold }
                end
            end
        end

        -- =====================
        -- ПАРСИНГ ЦВЕТОВ КОМАНД (team_colorsX)
        -- =====================
        local team_colors_str = cleanParam(args['team_colors' .. i])
        local team_color_overrides = {}
        if team_colors_str then
            for t_code, c_code in string.gmatch(team_colors_str, "([^:,%s]+):([^:,%s]+)") do
                team_color_overrides[t_code] = c_code
            end
        end

        -- Заголовки и текст
        if title then
            if title_type == "0" or title_type == "7" then
                table.insert(output, "; " .. title)
            else
                local level = tonumber(title_type) or 2
                local eq = string.rep("=", level)
                table.insert(output, eq .. " " .. title .. " " .. eq)
            end
        end

        if text then table.insert(output, text) end

        -- =====================
        -- ОБРАБОТКА СВОДНОГО РЕЙТИНГА
        -- =====================
        if rating_prefix and rating_place then
            local groups = {}
            local min_size = 999

            for key, node in pairs(tour_data) do
                if type(node) == "table" and node.type == "group" and string.match(key, "^" .. rating_prefix .. "_Group") then
                    local count = 0
                    if node.standings then
                        for _ in ipairs(node.standings) do count = count + 1 end
                    end
                    if count > 0 then
                        table.insert(groups, {key = key, node = node, size = count})
                        if count < min_size then min_size = count end
                    end
                end
            end

            if #groups > 0 then
                local target_teams = {}

                for _, g in ipairs(groups) do
                    local node = g.node
                    local size = g.size

                    local target_idx
                    if rating_place == 'last' then target_idx = size else target_idx = tonumber(rating_place) end

                    if target_idx and target_idx <= size then
                        local target_code = node.standings[target_idx][1]
                        local ignore_idx = nil
                        if size > min_size then
                            if rating_place == 'last' then ignore_idx = size - 1 else ignore_idx = size end
                        end

                        local ignore_code = nil
                        if ignore_idx and node.standings[ignore_idx] then ignore_code = node.standings[ignore_idx][1] end

                        local pts, gf, ga = 0, 0, 0
                        if node.matches then
                            for _, m in ipairs(node.matches) do
                                local t1, t2, g1, g2 = m[1], m[2], m[3], m[4]
                                if g1 ~= nil and g2 ~= nil then
                                    if t1 == target_code and t2 ~= ignore_code then
                                        gf = gf + g1
                                        ga = ga + g2
                                        if g1 > g2 then pts = pts + 3 elseif g1 == g2 then pts = pts + 1 end
                                    elseif t2 == target_code and t1 ~= ignore_code then
                                        gf = gf + g2
                                        ga = ga + g1
                                        if g2 > g1 then pts = pts + 3 elseif g2 == g1 then pts = pts + 1 end
                                    end
                                end
                            end
                        end

                        table.insert(target_teams, {code = target_code, pts = pts, gf = gf, ga = ga, gd = gf - ga})
                    end
                end

                local places_str = cleanParam(args['places' .. i])
                if places_str then
                    local manual_order = {}
                    local rank = 1
                    for code in string.gmatch(places_str, "[^,%s]+") do
                        manual_order[code] = rank
                        rank = rank + 1
                    end
                    table.sort(target_teams, function(a, b)
                        local oA = manual_order[a.code] or 999
                        local oB = manual_order[b.code] or 999
                        if oA ~= oB then return oA < oB end
                        return a.code < b.code
                    end)
                else
                    local r_tiebreaker_str = cleanParam(args['rating_tiebreaker' .. i])
                    local r_tie_order = {}
                    if r_tiebreaker_str then
                        local rank = 1
                        for code in string.gmatch(r_tiebreaker_str, "[^,%s]+") do
                            r_tie_order[code] = rank
                            rank = rank + 1
                        end
                    end

                    table.sort(target_teams, function(a, b)
                        if a.pts ~= b.pts then return a.pts > b.pts end
                        if a.gd ~= b.gd then return a.gd > b.gd end
                        if a.gf ~= b.gf then return a.gf > b.gf end
                        local oA = r_tie_order[a.code]
                        local oB = r_tie_order[b.code]
                        if oA and oB and oA ~= oB then return oA < oB end
                        return a.code < b.code
                    end)

                    for idx = 1, #target_teams - 1 do
                        local a = target_teams[idx]
                        local b = target_teams[idx+1]
                        if a.pts == b.pts and a.gd == b.gd and a.gf == b.gf then
                            local oA = r_tie_order[a.code]
                            local oB = r_tie_order[b.code]
                            if not (oA and oB and oA ~= oB) then
                                a.unresolved_tie = true
                                b.unresolved_tie = true
                            end
                        end
                    end
                end

                local color_map = {}
                if rating_colors then
                    for count_str, col_code in string.gmatch(rating_colors, "(%d+):(%w+)") do
                        local count = tonumber(count_str)
                        for _ = 1, count do table.insert(color_map, col_code) end
                    end
                end

                local t_args = {}
                t_args.title = rating_title
                for idx, t in ipairs(target_teams) do
                    t_args['team' .. idx] = t.code
                    t_args['pts' .. idx] = t.pts
                    t_args['gf' .. idx] = t.gf
                    t_args['ga' .. idx] = t.ga
                    
                    if t.unresolved_tie then t_args['err' .. idx] = "yes" end

                    local c_code = color_map[idx]
                    
                    -- ПЕРЕОПРЕДЕЛЕНИЕ ЦВЕТА КОМАНДЫ (Рейтинг)
                    if team_color_overrides[t.code] then
                        c_code = team_color_overrides[t.code]
                        if c_code == "0" or c_code == "none" then c_code = nil end
                    end

                    if c_code and current_colors[c_code] then
                        t_args['color' .. idx] = current_colors[c_code].c
                        if current_colors[c_code].b then t_args['bold' .. idx] = "yes" end
                    end
                end

                local ok, rendered = pcall(matchTable._rating, t_args)
                if ok then table.insert(output, rendered) 
                else table.insert(output, '<strong class="error">Сбой рейтинга: ' .. tostring(rendered) .. '</strong>') end
            else
                table.insert(output, '<strong class="error">Ошибка: Группы для рейтинга "' .. rating_prefix .. '" не найдены.</strong>')
            end
        end

        -- Таблицы
        if table_id then
            local db_node = tour_data[table_id]

            if not db_node then
                table.insert(output, '<strong class="error">Ошибка: Массив ' .. table_id .. ' не найден в БД.</strong>')
            else
                local t_args = {}

                -- =====================
                -- ОБРАБОТКА ГРУППЫ
                -- =====================
                if db_node.type == "group" then
                    t_args.number_of_rounds = db_node.number_of_rounds or 1
                    t_args.compact = (compact == "yes" or compact == "true")
                    t_args.tiebreaker = cleanParam(args['tiebreaker' .. i])

                    local places_str = cleanParam(args['places' .. i])
                    local ordered_standings = {}

                    if places_str then
                        t_args.manual_sort = "yes"
                        local color_lookup = {}
                        for _, st in ipairs(db_node.standings) do
                            color_lookup[st[1]] = st[2]
                        end
                        for code in string.gmatch(places_str, "[^,%s]+") do
                            table.insert(ordered_standings, {code, color_lookup[code]})
                        end
                    else
                        ordered_standings = db_node.standings
                    end

                    local team_map = {}
                    for idx, st in ipairs(ordered_standings) do
                        local code = st[1]
                        local color_code = st[2]
                        
                        -- ПЕРЕОПРЕДЕЛЕНИЕ ЦВЕТА КОМАНДЫ (Группа)
                        if team_color_overrides[code] then
                            color_code = team_color_overrides[code]
                            if color_code == "0" or color_code == "none" then color_code = nil end
                        end

                        team_map[code] = idx
                        t_args['team' .. idx] = code

                        if color_code and current_colors[color_code] then
                            t_args['color' .. idx] = current_colors[color_code].c
                            if current_colors[color_code].b then t_args['bold' .. idx] = "yes" end
                        end
                    end

                    for _, m in ipairs(db_node.matches) do
                        local id_home = team_map[m[1]]
                        local id_away = team_map[m[2]]
                        if id_home and id_away and m[3] ~= nil and m[4] ~= nil then
                            local score_str = tostring(m[3]) .. ":" .. tostring(m[4])
                            local link_val = cleanParam(args['link' .. i .. '_' .. id_home .. '_' .. id_away])
                            if link_val then score_str = ("'''[[" .. link_val .. "|" .. score_str .. "]]'''") end
                            t_args['score' .. id_home .. '_' .. id_away] = score_str
                        end
                    end

                    local ok, rendered = pcall(matchTable._group, t_args)
                    if ok then table.insert(output, rendered) else table.insert(output, '<strong class="error">Сбой групп: ' .. tostring(rendered) .. '</strong>') end

                -- =====================
                -- ОБРАБОТКА ПЛЕЙ-ОФФ
                -- =====================
                elseif db_node.type == "knockout" or db_node.type == "playoff" then
                    local rounds = tonumber(db_node.number_of_rounds) or 1

                    for k, m in ipairs(db_node.matches) do
                        local t1 = m[1]
                        local t2 = m[2]
                        local g1_1 = m[3]
                        local g2_1 = m[4]

                        local score_str = "—"
                        local c_code1, c_code2

                        if rounds == 1 then
                            local flag, p1, p2 = m[5], m[6], m[7]
                            c_code1, c_code2 = m[8], m[9]

                            if g1_1 ~= nil and g2_1 ~= nil then
                                score_str = tostring(g1_1) .. ":" .. tostring(g2_1)
                                if flag == "aet" then score_str = score_str .. "(ET)"
                                elseif flag == "pen" then score_str = score_str .. "(пен." .. tostring(p1) .. ":" .. tostring(p2) .. ")" end
                            end

                        elseif rounds == 2 then
                            local g1_2, g2_2 = m[5], m[6]
                            local flag, p1, p2 = m[7], m[8], m[9]
                            c_code1, c_code2 = m[10], m[11]

                            if g1_1 ~= nil and g2_1 ~= nil then
                                score_str = tostring(g1_1) .. ":" .. tostring(g2_1)
                                if g1_2 ~= nil and g2_2 ~= nil then
                                    score_str = score_str .. ", " .. tostring(g1_2) .. ":" .. tostring(g2_2)
                                end
                                if flag == "aet" then score_str = score_str .. "(ET)"
                                elseif flag == "pen" then score_str = score_str .. "(пен." .. tostring(p1) .. ":" .. tostring(p2) .. ")" end
                            end
                        end
                        
                        -- ПЕРЕОПРЕДЕЛЕНИЕ ЦВЕТА КОМАНДЫ (Плей-офф)
                        if team_color_overrides[t1] then
                            c_code1 = team_color_overrides[t1]
                            if c_code1 == "0" or c_code1 == "none" then c_code1 = nil end
                        end
                        if team_color_overrides[t2] then
                            c_code2 = team_color_overrides[t2]
                            if c_code2 == "0" or c_code2 == "none" then c_code2 = nil end
                        end

                        t_args['s' .. k .. '_team1'] = t1
                        t_args['s' .. k .. '_team2'] = t2

                        local link_val = cleanParam(args['link' .. i .. '_' .. k]) or (k == 1 and cleanParam(args['link' .. i]))
                        if link_val and score_str ~= "—" then score_str = "[[" .. link_val .. "|" .. score_str .. "]]" end

                        t_args['s' .. k .. '_score'] = score_str

                        if c_code1 and current_colors[c_code1] then
                            t_args['s' .. k .. '_color1'] = current_colors[c_code1].c
                            if current_colors[c_code1].b then t_args['s' .. k .. '_bold1'] = "yes" end
                        end
                        if c_code2 and current_colors[c_code2] then
                            t_args['s' .. k .. '_color2'] = current_colors[c_code2].c
                            if current_colors[c_code2].b then t_args['s' .. k .. '_bold2'] = "yes" end
                        end
                    end

                    local ok, rendered = pcall(matchTable._playoff, t_args)
                    if ok then table.insert(output, rendered) else table.insert(output, '<strong class="error">Сбой плей-офф: ' .. tostring(rendered) .. '</strong>') end
                end
            end
        end

        if ref then table.insert(output, "<small>" .. ref .. "</small>") end
        table.insert(output, "") 
    end

    return table.concat(output, "\n")
end

return p