Модуль:Sports table/totalscheck
Материал из вики «Вече»
Дополнительные действия
Для документации этого модуля может быть создана страница Модуль:Sports table/totalscheck/doc
-- Сверка итогов турнирной таблицы:
-- проверяет, согласуются ли между собой показатели команд, заданные вручную.
-- tc.check сверяет между собой суммы показателей всех команд, tc.check_matches — показатели
-- каждой команды с результатами её матчей (match_ХХХ_УУУ), если они заданы тем же набором параметров.
-- Обе возвращают тексты предупреждений. tc.is_cross_group решает, осмысленна ли сверка сумм для данного набора команд.
-- Вызывается из [[Модуль:Sports table]]: предупреждения он показывает только в предпросмотре,
-- а на сохранённую страницу ставит лишь отслеживающую категорию за расхождения у команд
-- с полностью заполненной шахматкой. tc.check_matches вызывает и [[Модуль:Sports group]] —
-- для итога полной сверки на странице шаблона. Описание проверок: [[Модуль:Sports table/doc]].
local tc = {}
-- Разбор счёта общий для семейства — ppp.score_pair в [[Модуль:Sports table/sub]].
local p_sub = require('Модуль:Sports table/sub')
-- Применяет tc.is_cross_group к выводимой части таблицы:
-- собирает group_ХХХ команд с позиций ii_start–ii_end, без строки итогов.
local function is_cross_group_selection(Args, team_list, ii_start, ii_end)
local groups = {}
for ii = ii_start, ii_end do
local code = team_list[ii]
if code and code ~= p_sub.total_row_code then
groups[#groups + 1] = Args['group_' .. code]
end
end
return tc.is_cross_group(groups)
end
-- Решает, собрана ли таблица из команд разных групп по одной — так строят рейтинги вида
-- «команды, занявшие третьи места в своих группах». В такой таблице команды между собой
-- не играли: их показатели взяты из матчей против команд, которых здесь нет, сумма побед
-- не обязана равняться сумме поражений, и предупреждение сверки было бы ложным всегда.
-- groups — массив групп команд. Возвращает true, если в нём больше одного элемента и все
-- они непустые и попарно различны: когда хотя бы две команды делят группу, сыгранные между
-- собой пары в таблице есть, и сверка осмысленна. Команда без group_ХХХ в массив не попадает
-- (присваивание nil его не удлиняет), так что решают только команды с заданной группой.
-- Функция открыта наружу: по тому же правилу [[Модуль:Sports table]] не включает строку итогов
-- в предпросмотре, а держать условие в двух местах значило бы рано или поздно их рассогласовать.
function tc.is_cross_group(groups)
local seen, teams = {}, 0
for _, g in ipairs(groups) do
if not g or g == '' then
return false
end
if seen[g] then
return false
end
seen[g] = true
teams = teams + 1
end
-- Из одной команды выборку не составить, и сверять нечего.
return teams > 1
end
-- Сверяет суммы показателей команд с позиций ii_start–ii_end. При верных данных совпадают
-- суммы побед и поражений, побед дома и поражений на выезде, побед на выезде и поражений дома,
-- ничьих дома и на выезде, побед и поражений в овертайме и в серии пенальти (буллитов),
-- забитых и пропущенных, а общее число ничьих чётно. Строка итогов пропускается:
-- при show_totals подмодуль стиля записывает в неё суммы (ppp.add_to_totals), и она учлась бы
-- дважды. Возвращает массив предупреждений, по одному на каждое несовпадение.
-- Последний аргумент — функция [[Модуль:Yesno]] вызывающего модуля: здесь она нужна только
-- для no_totals_check, и подключать модуль ради неё незачем. Без неё no_totals_check не действует.
function tc.check(Args, team_list, ii_start, ii_end, yesno)
local warn = {}
-- no_totals_check=yes отключает сверку вручную. Он нужен для таблиц из неполных групп —
-- например, из вторых и третьих мест каждой группы. Там команды между собой играли, и
-- tc.is_cross_group такую таблицу не распознаёт, но показатели всё равно включают матчи
-- против команд, которых в таблице нет, и суммы не сойдутся.
if yesno and yesno(Args['no_totals_check'] or 'no') then
return warn
end
-- Таблица из команд разных групп по одной — см. tc.is_cross_group.
if is_cross_group_selection(Args, team_list, ii_start, ii_end) then
return warn
end
local wtotal, dtotal, ltotal = 0, 0, 0
local hwtotal, hdtotal, hltotal = 0, 0, 0
local awtotal, adtotal, altotal = 0, 0, 0
local pkwtotal, pkltotal, otwtotal, otltotal = 0, 0, 0, 0
local gftotal, gatotal = 0, 0
for ii = ii_start, ii_end do
local team_code_ii = team_list[ii]
if team_code_ii ~= p_sub.total_row_code then
-- PKwin и SOwin — два имени одного показателя (серия пенальти или буллитов);
-- pf и pa — очки в неголевых видах спорта, они складываются с gf и ga.
wtotal = wtotal + (tonumber(Args['win_' .. team_code_ii]) or 0)
dtotal = dtotal + (tonumber(Args['draw_' .. team_code_ii]) or 0)
ltotal = ltotal + (tonumber(Args['loss_' .. team_code_ii]) or 0)
hwtotal = hwtotal + (tonumber(Args['hwin_' .. team_code_ii]) or 0)
hdtotal = hdtotal + (tonumber(Args['hdraw_' .. team_code_ii]) or 0)
hltotal = hltotal + (tonumber(Args['hloss_' .. team_code_ii]) or 0)
awtotal = awtotal + (tonumber(Args['awin_' .. team_code_ii]) or 0)
adtotal = adtotal + (tonumber(Args['adraw_' .. team_code_ii]) or 0)
altotal = altotal + (tonumber(Args['aloss_' .. team_code_ii]) or 0)
pkwtotal = pkwtotal + (tonumber(Args['PKwin_' .. team_code_ii] or Args['SOwin_' .. team_code_ii]) or 0)
pkltotal = pkltotal + (tonumber(Args['PKloss_' .. team_code_ii] or Args['SOloss_' .. team_code_ii]) or 0)
otwtotal = otwtotal + (tonumber(Args['OTwin_' .. team_code_ii]) or 0)
otltotal = otltotal + (tonumber(Args['OTloss_' .. team_code_ii]) or 0)
gftotal = gftotal + (tonumber(Args['gf_' .. team_code_ii]) or 0) + (tonumber(Args['pf_' .. team_code_ii]) or 0)
gatotal = gatotal + (tonumber(Args['ga_' .. team_code_ii]) or 0) + (tonumber(Args['pa_' .. team_code_ii]) or 0)
end
end
if wtotal ~= ltotal then
table.insert(warn, wtotal .. ' общее число побед ≠ ' .. ltotal .. ' общему числу поражений')
end
if hwtotal ~= altotal then
table.insert(warn, hwtotal .. ' число побед дома ≠ ' .. altotal .. ' числу поражений на выезде')
end
if awtotal ~= hltotal then
table.insert(warn, awtotal .. ' число побед на выезде ≠ ' .. hltotal .. ' числу поражений дома')
end
if dtotal % 2 ~= 0 then
table.insert(warn, dtotal .. ' общее число ничьих — нечётное')
end
if hdtotal ~= adtotal then
table.insert(warn, hdtotal .. ' число ничьих дома ≠ ' .. adtotal .. ' числу ничьих на выезде')
end
if pkwtotal ~= pkltotal then
table.insert(warn, pkwtotal .. ' число побед в послематчевых пенальти ≠ ' .. pkltotal .. ' числу поражений в послематчевых пенальти')
end
if otwtotal ~= otltotal then
table.insert(warn, otwtotal .. ' число побед в овертайме ≠ ' .. otltotal .. ' числу поражений в овертайме')
end
if gftotal ~= gatotal then
table.insert(warn, gftotal .. ' число забитых мячей ≠ ' .. gatotal .. ' числу пропущенных мячей')
end
return warn
end
-- Сверяет показатели каждой команды с результатами её матчей: match_ХХХ_УУУ, а при legs
-- больше 1 — matchL_ХХХ_УУУ для каждого круга L. Заявленные победы — сумма win_, hwin_,
-- awin_, OTwin_, PKwin_ и SOwin_; поражения — loss_, hloss_, aloss_, OTloss_, PKloss_
-- и SOloss_; ничьи — draw_, hdraw_ и adraw_; забитые и пропущенные — gf_ и ga_ вместе
-- с вариантами h и a и очковыми pf_ и pa_. Пары перебираются только среди команд с позиций
-- ii_start–ii_end, без строки итогов. У команды с исходами в овертайме или серии победы,
-- ничьи и поражения сверяются по границам, а не на равенство: такой матч записывается
-- и ничейным счётом, и счётом с победителем.
-- Нужна, когда показатели и результаты матчей заданы одним набором параметров —
-- для шахматки внутри таблицы или под ней либо в шаблоне, который выводит ту же шахматку
-- отдельным вызовом с show_matches=only. Команда без единого разобранного счёта не сверяется:
-- это не ошибка, а признак того, что матчи для этой таблицы не заданы.
-- Возвращает массив предупреждений и число тех из них, что относятся к командам
-- с полностью заполненной шахматкой: за них Sports table ставит категорию.
function tc.check_matches(Args, team_list, ii_start, ii_end)
local warn, confirmed = {}, 0
local derived = {}
local function ensure(code)
if not derived[code] then
derived[code] = {win = 0, draw = 0, loss = 0, gf = 0, ga = 0}
end
end
-- Показатели по результатам матчей: каждая упорядоченная пара —
-- хозяева и гости — в каждом круге.
local legs = tonumber(Args['legs']) or 1
for ii = ii_start, ii_end do
local a_code = team_list[ii]
if a_code and a_code ~= p_sub.total_row_code then
for jj = ii_start, ii_end do
local b_code = team_list[jj]
if b_code and b_code ~= p_sub.total_row_code and a_code ~= b_code then
for l = 1, legs do
local m = (legs == 1) and 'match_' or 'match' .. l .. '_'
local sa, sb = p_sub.score_pair(Args[m .. a_code .. '_' .. b_code])
if sa and sb then
ensure(a_code)
ensure(b_code)
derived[a_code].gf = derived[a_code].gf + sa
derived[a_code].ga = derived[a_code].ga + sb
derived[b_code].gf = derived[b_code].gf + sb
derived[b_code].ga = derived[b_code].ga + sa
if sa > sb then
derived[a_code].win = derived[a_code].win + 1
derived[b_code].loss = derived[b_code].loss + 1
elseif sa < sb then
derived[a_code].loss = derived[a_code].loss + 1
derived[b_code].win = derived[b_code].win + 1
else
derived[a_code].draw = derived[a_code].draw + 1
derived[b_code].draw = derived[b_code].draw + 1
end
end
end
end
end
end
end
-- Сверка с заявленными показателями — у команд, для которых разобран хотя бы один счёт.
for ii = ii_start, ii_end do
local code = team_list[ii]
if code and derived[code] then
local d = derived[code]
-- Победы и поражения в основное время отдельно от решённых в овертайме или серией:
-- такой матч в шахматке записан либо ничейным счётом (серия пенальти после ничьей
-- в футболе), либо счётом с победителем (гол в овертайме, буллит в хоккее).
local regular_win = (tonumber(Args['win_' .. code]) or 0) + (tonumber(Args['hwin_' .. code]) or 0) + (tonumber(Args['awin_' .. code]) or 0)
local extra_win = (tonumber(Args['OTwin_' .. code]) or 0) + (tonumber(Args['PKwin_' .. code]) or 0) + (tonumber(Args['SOwin_' .. code]) or 0)
local declared_draw = (tonumber(Args['draw_' .. code]) or 0) + (tonumber(Args['hdraw_' .. code]) or 0) + (tonumber(Args['adraw_' .. code]) or 0)
local regular_loss = (tonumber(Args['loss_' .. code]) or 0) + (tonumber(Args['hloss_' .. code]) or 0) + (tonumber(Args['aloss_' .. code]) or 0)
local extra_loss = (tonumber(Args['OTloss_' .. code]) or 0) + (tonumber(Args['PKloss_' .. code]) or 0) + (tonumber(Args['SOloss_' .. code]) or 0)
local declared_win = regular_win + extra_win
local declared_loss = regular_loss + extra_loss
local declared_gf = (tonumber(Args['gf_' .. code]) or 0) + (tonumber(Args['hgf_' .. code]) or 0) + (tonumber(Args['agf_' .. code]) or 0)
+ (tonumber(Args['pf_' .. code]) or 0) + (tonumber(Args['hpf_' .. code]) or 0) + (tonumber(Args['apf_' .. code]) or 0)
local declared_ga = (tonumber(Args['ga_' .. code]) or 0) + (tonumber(Args['hga_' .. code]) or 0) + (tonumber(Args['aga_' .. code]) or 0)
+ (tonumber(Args['pa_' .. code]) or 0) + (tonumber(Args['hpa_' .. code]) or 0) + (tonumber(Args['apa_' .. code]) or 0)
-- Шахматка команды заполнена настолько же, насколько её показатели:
-- число разобранных результатов равно заявленному числу матчей.
-- Только расхождения таких команд входят во второе возвращаемое значение и дают категорию.
-- Остальные — обычно отставание шахматки или показателей на тур в середине сезона,
-- либо технический результат, в котором нет чисел. Их видно в предпросмотре,
-- но в категорию они не идут: иначе она заполнилась бы шумом.
local complete = (d.win + d.draw + d.loss) == (declared_win + declared_draw + declared_loss)
local before = #warn
if extra_win + extra_loss == 0 then
if declared_win ~= d.win then
table.insert(warn, code .. ': заявлено побед — ' .. declared_win .. ', по результатам матчей — ' .. d.win)
end
if declared_draw ~= d.draw then
table.insert(warn, code .. ': заявлено ничьих — ' .. declared_draw .. ', по результатам матчей — ' .. d.draw)
end
if declared_loss ~= d.loss then
table.insert(warn, code .. ': заявлено поражений — ' .. declared_loss .. ', по результатам матчей — ' .. d.loss)
end
else
-- Матч, решённый в овертайме или серией, мог записаться и победой, и ничьей,
-- поэтому сверяются границы: победы по счёту — не меньше побед в основное время
-- и не больше всех побед; то же для поражений; ничьи по счёту — не меньше
-- заявленных ничьих и не больше их суммы с исходами в овертайме и серии.
if d.win < regular_win or d.win > declared_win then
table.insert(warn, code .. ': заявлено побед — ' .. regular_win .. ' в основное время и '
.. extra_win .. ' в овертайме или серии, по результатам матчей — ' .. d.win)
end
if d.loss < regular_loss or d.loss > declared_loss then
table.insert(warn, code .. ': заявлено поражений — ' .. regular_loss .. ' в основное время и '
.. extra_loss .. ' в овертайме или серии, по результатам матчей — ' .. d.loss)
end
if d.draw < declared_draw or d.draw > declared_draw + extra_win + extra_loss then
table.insert(warn, code .. ': заявлено ничьих — ' .. declared_draw .. ' и исходов в овертайме или серии — '
.. (extra_win + extra_loss) .. ', ничьих по результатам матчей — ' .. d.draw)
end
end
if declared_gf ~= d.gf then
table.insert(warn, code .. ': заявлено забито — ' .. declared_gf .. ', по результатам матчей — ' .. d.gf)
end
if declared_ga ~= d.ga then
table.insert(warn, code .. ': заявлено пропущено — ' .. declared_ga .. ', по результатам матчей — ' .. d.ga)
end
if complete then
confirmed = confirmed + (#warn - before)
end
end
end
return warn, confirmed
end
return tc