Перейти к содержимому страницы
Открыть меню
Переключить меню настроек
Открыть персональное меню
Вы не представились системе
Ваш IP-адрес будет виден всем, если вы внесёте какие-либо изменения.

Модуль:Sports table/totalscheck

Материал из вики «Вече»
Версия от 00:09, 29 сентября 2026; wp>ru.wikipedia (Импорт из русской Википедии, версия 155338179)
(разн.) ← Предыдущая версия | Текущая версия (разн.) | Следующая версия → (разн.)

Для документации этого модуля может быть создана страница Модуль: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