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

Модуль:Sports table/columns

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

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

-- Столбцы показателей турнирной таблицы для всех стилей.

-- Строит заголовок таблицы (header) и статистические ячейки строк (row) и отдаёт словарь
-- обозначений статуса (status). Стиль задаётся моделью показателей (MODELS):
--   wdl — победы, ничьи и поражения (стили WDL и WL);
--   ha  — те же исходы раздельно дома и на выезде (WDLHA);
--   ot  — к ним победы и поражения в овертайме и в серии (WDL OT и WL OT).
-- Модель определяет, какие показатели читаются, как считаются очки, процент и отставание,
-- какие столбцы идут в блок исходов и в каком порядке стоят остальные. Всё прочее общее:
-- разбор параметров, подписи столбцов, сравнение забитого и пропущенного,
-- столбцы ранжирования, выездные голы, бонусные очки, отставание, запись ячеек.

-- Параметры таблицы разбираются один раз, при построении заголовка, а строки берут их готовыми.
-- Показатели каждой команды читаются из аргументов тоже один раз, даже если нужны дважды:
-- отставание сравнивает команду с опорной.

-- Подмодули стиля ([[Модуль:Sports table/WDL]] и остальные) состоят из одного вызова p.style;
-- турнирная таблица ([[Модуль:Sports table]]) вызывает у полученного стиля header, row и status
-- и читает признаки автосборки. Начало и конец заголовка, ширины столбцов, выравнивание чисел,
-- сноски личных встреч и строку итогов берёт из [[Модуль:Sports table/sub]].
-- Описание стилей и их параметров: [[Модуль:Sports table/doc]].

local p = {}

local p_sub = require('Модуль:Sports table/sub')
local yesno = require('Модуль:Yesno')

local total_row_code = p_sub.total_row_code
local decimal = p_sub.decimal

-- Словари значений параметров -----------------------------------------------------------

-- ranking_style: критерий ранжирования. Значение, которого здесь нет, — ранжирование по очкам.
local RANKING = {
	w = 'wins', win = 'wins', wins = 'wins',
	wperc = 'wperc', ['win percentage'] = 'wperc', wpct = 'wperc', ['%w'] = 'wperc',
	perc = 'perc', percentage = 'perc', ['%'] = 'perc',
	ppg = 'ppg', ['points per game'] = 'ppg', ptspergame = 'ppg',
	percpts = 'percpts', ['%pts'] = 'percpts',
	ppgpts = 'ppgpts',
	none = 'none', no = 'none',
	forpts = 'forpts',
}

-- Столбцы ранжирования каждого критерия. wins не выводит их, а выделяет столбец побед;
-- forpts выделяет вместе с очками столбец забитого. Выделенный столбец получает и сноску о личных встречах (hth_КОД).
local RANK_COLUMNS = {
	wins    = {wins = true},
	wperc   = {wperc = true},
	perc    = {perc = true},
	ppg     = {ppg = true},
	percpts = {perc = true, points = true},
	ppgpts  = {ppg = true, points = true},
	none    = {},
	forpts  = {points = true, forpts = true},
	points  = {points = true},
}

-- for_against_style: единица подсчёта «забито/пропущено».
local FOR_AGAINST = {
	g = 'goals', goal = 'goals', goals = 'goals',
	f = 'frames', frame = 'frames', frames = 'frames',
	m = 'matches', match = 'matches', matches = 'matches',
	game = 'games', games = 'games',
	p = 'points', point = 'points', points = 'points',
	s = 'sets', set = 'sets', sets = 'sets',
	r = 'runs', run = 'runs', runs = 'runs',
	g2 = 'pucks', goal2 = 'pucks', goals2 = 'pucks',
	l = 'legs', leg = 'legs', legs = 'legs',
	none = 'none', no = 'none',
}

-- Буква и слово единицы в подписях столбцов. against — подпись столбца «пропущено» там,
-- где буква с «П» неоднозначна: у партий получилось бы «ПП».
local UNITS = {
	goals   = {letter = 'М', word = 'Мячей'},
	frames  = {letter = 'Ф', word = 'Фреймов'},
	matches = {letter = 'М', word = 'Матчей'},
	games   = {letter = 'И', word = 'Игр'},
	points  = {letter = 'О', word = 'Очков'},
	sets    = {letter = 'С', word = 'Сетов'},
	runs    = {letter = 'Р', word = 'Ранов'},
	pucks   = {letter = 'Ш', word = 'Шайб'},
	legs    = {letter = 'П', word = 'Партий', against = 'Партия'},
}

-- Варианты столбца сравнения забитого и пропущенного в порядке старшинства:
-- из нескольких заданных выводится первый. Соотношение и среднее считаются одной формулой,
-- различается только подпись.
local COMPARISONS = {
	{id = 'ratio', params = {'use_goal_ratio'}},
	{id = 'average', params = {'use_goal_average'}},
	{id = 'goal_perc', params = {'use_goal_percentage'}},
	{id = 'against_perc', params = {'use_against_percentage'}},
	{id = 'point_perc', params = {'use_point_percentage'}},
	{id = 'per_match', params = {'use_goals_per_match', 'use_points_per_match'}},
	{id = 'scored', params = {'use_goals_scored'}},
}

-- Толстая черта слева, которой в стиле WDLHA отделены блоки дома и на выезде и столбцы после них.
local BLOCK_BORDER = 'border-width:1px 1px 1px 3px;'

-- Полужирное начертание ячейки.
local BOLD = 'font-weight:bold;'

-- Исходы, заданные не числом, отменяют подсчёт сыгранного (стили WDL и с овертаймом).
local WDL_FIELDS = {'win', 'draw', 'loss'}

-- Вспомогательные функции ---------------------------------------------------------------

-- Возвращает число из значения параметра или 0.
local function num(v)
	return tonumber(v) or 0
end

-- Возвращает значение для ячейки исхода, когда все исходы команды нулевые:
-- нечисловое значение (прочерк и т. п.) выводится как задано, пустое и незаданное — нулём.
local function raw_or_zero(s)
	return s and ((s == '' and 0) or tonumber(s) or s) or 0
end

-- Проверяет, что все перечисленные параметры команды code заданы числами.
local function all_numeric(Args, code, fields)
	for _, f in ipairs(fields) do
		if tonumber(Args[f .. '_' .. code]) == nil then
			return false
		end
	end
	return true
end

-- Записывает долю с тремя знаками после запятой: «0,625», «1,000».
local function share(x)
	return decimal(x, 3)
end

-- Возвращает число, выровненное ppp.num_align, с минусом перед выровненной частью:
-- ppp.num_align выравнивает только неотрицательные числа.
local function signed(n, digits)
	if tonumber(n) and n < 0 then
		return '&minus;' .. p_sub.num_align(- n, digits)
	end
	return p_sub.num_align(n, digits)
end

-- Возвращает процент побед, в котором ничья идёт за половину победы: долей с тремя знаками
-- (pct_style=ratio, по умолчанию) или процентами с двумя. bonus — прибавить бонусные очки,
-- каждое тоже за половину победы.
local function win_share(o, r, bonus)
	local x = (2 * r.wins + r.draws + (bonus and r.s_pts or 0)) / (2 * r.matches)
	if o.pct_style == 'ratio' then
		return share(x)
	end
	return decimal(100 * x, 2)
end

-- Запись заголовка и ячеек --------------------------------------------------------------

-- Накопитель заголовка: tt — накопитель ppp.colhead, w — ширины столбцов из ppp.open_table,
-- pending — черта слева, которая ждёт первого столбца блока.
local Header = {}
Header.__index = Header

local function new_header(tt, w)
	return setmetatable({tt = tt, w = w, pending = ''}, Header)
end

-- Отдаёт черту слева, если она ждёт первого столбца блока, и снимает её.
function Header:border()
	local b = self.pending
	self.pending = ''
	return b
end

-- Добавляет заголовок столбца; border — черта слева.
function Header:add(width, text, border)
	self.tt = p_sub.colhead(self.tt, width, text, border)
end

-- Накопитель ячеек строки: t — вывод, cls — класс заливки строки, bg — её встроенный стиль,
-- fw — полужирное начертание строки (showteam, строка итогов) либо пустая строка, hth —
-- сноска о личных встречах, pending — как у накопителя заголовка.
local Row = {}
Row.__index = Row

local function new_row(t, bg_class, bg_col, ii_fw, hth)
	return setmetatable({t = t, bg_class = bg_class, cls = mw.text.trim(bg_class), bg = bg_col,
		fw = (ii_fw == BOLD) and BOLD or '', hth = hth, pending = ''}, Row)
end

Row.border = Header.border

-- Добавляет ячейку. weight — начертание этой ячейки вместо начертания строки, border — черта слева,
-- class — класс вместо класса заливки строки. Обычное начертание не записывается: ячейка и так не полужирная.
-- Пустые атрибуты не выводятся; без атрибутов между чертой и значением ставится пробел,
-- иначе «+» или «-» в начале значения прочитались бы как разметка таблицы.
function Row:add(value, weight, border, class)
	class = class or self.cls
	local style = (border or '') .. (weight or self.fw) .. self.bg
	local t = self.t
	if class == '' then
		if style == '' then
			t[#t + 1] = '| ' .. value .. '\n'
		else
			t[#t + 1] = '| style="' .. style .. '" |' .. value .. '\n'
		end
	elseif style == '' then
		t[#t + 1] = '| class="' .. class .. '" |' .. value .. '\n'
	else
		t[#t + 1] = '| class="' .. class .. '" style="' .. style .. '" |' .. value .. '\n'
	end
end

-- Добавляет ячейку столбца ранжирования: полужирную классом st-bold
-- (процент побед без ничьих — st-perc, выровненный вправо), со сноской о личных встречах.
function Row:rank(value, class)
	self:add(value .. self.hth, '', nil, (class or 'st-bold') .. self.bg_class)
end

-- Подпись столбца исхода; custom — подпись из параметра …_header, если задана.
local function outcome_head(custom, title, letter)
	return custom or ('<abbr title="' .. title .. '">' .. letter .. '</abbr>')
end

-- Подпись столбца ничьих: draw_header, при use_tie — «Р» (равны), иначе «Н».
local function draw_head(o, suffix)
	if o.draw_head then
		return o.draw_head
	end
	if o.use_tie then
		return '<abbr title="Равны' .. (suffix or '') .. '">Р</abbr>'
	end
	return '<abbr title="Ничьи' .. (suffix or '') .. '">Н</abbr>'
end

-- Модели показателей --------------------------------------------------------------------

-- Каждая модель задаёт:
-- default_unit  — единица «забито/пропущено» для нераспознанного for_against_style;
-- diff_always   — столбец разницы выводится и рядом со столбцом сравнения (иначе только
--                 без него), если use_goal_diff не задан;
-- write_ha, write_ot — признаки для автосборки ([[Модуль:Sports table/auto]]);
-- order         — порядок столбцов; block — блок исходов, остальные — из COLUMNS;
-- totals_fields — показатели для строки итогов (ppp.add_to_totals);
-- read_options  — разбор параметров модели: очки за исходы, её столбцы и подписи;
-- read          — чтение показателей команды: исходы, забитое и пропущенное, поправка
--                 очков (s_pts), сыграно, очки;
-- perc          — процент для столбца процента;
-- gb            — отставание от опорной команды g;
-- block         — заголовок и ячейки блока исходов.
local MODELS = {}

-- Победы, ничьи, поражения ---------------------------------------------------------------

MODELS.wdl = {
	default_unit = 'goals',
	diff_always = true,
	order = {'played', 'block', 'fa', 'compare', 'diff', 'away_goals', 'bonus', 'points', 'perc', 'ppg', 'gb'},
	totals_fields = {'win', 'draw', 'loss', 'rw', {'gf', 'pf'}, {'ga', 'pa'}, 'adjust_points', 'startpoints'},
}

-- Очки за исходы. Без ничьих (show_draw=no, стиль WL) по умолчанию 2 за победу и 1 за поражение.
function MODELS.wdl.read_options(o, Args)
	o.winpoints = tonumber(Args['winpoints']) or (o.show_draw and 3 or 2)
	o.drawpoints = tonumber(Args['drawpoints']) or (o.show_draw and 1 or 0)
	o.losspoints = tonumber(Args['losspoints']) or (o.show_draw and 0 or 1)
	o.rwpoints = tonumber(Args['rwpoints']) or 1
end

function MODELS.wdl.read(o, Args, code)
	local r = {}
	r.wins, r.draws, r.losses = num(Args['win_' .. code]), num(Args['draw_' .. code]), num(Args['loss_' .. code])
	r.all_wins = r.wins
	r.rw = num(Args['rw_' .. code])
	-- Исходные gf_ и ga_ (синонимы — pf_ и pa_) нужны и дальше: для очков за забитое
	-- и для вывода нечислового значения как есть.
	r.gf_raw = Args['gf_' .. code] or Args['pf_' .. code]
	r.ga_raw = Args['ga_' .. code] or Args['pa_' .. code]
	r.gf, r.ga = tonumber(r.gf_raw or '0') or '?', tonumber(r.ga_raw or '0') or '?'
	-- Поправка очков (adjust_points_) и начальные очки (startpoints_) складываются.
	r.s_pts = num(Args['adjust_points_' .. code]) + num(Args['startpoints_' .. code])
	r.matches = r.wins + r.draws + r.losses
	r.numeric = all_numeric(Args, code, WDL_FIELDS)
	r.points = o.winpoints * r.wins + o.drawpoints * r.draws + o.losspoints * r.losses
		+ o.rwpoints * r.rw + r.s_pts
	if r.wins == 0 and r.draws == 0 and r.losses == 0 then
		r.win_text = raw_or_zero(Args['win_' .. code])
		r.draw_text = raw_or_zero(Args['draw_' .. code])
		r.loss_text = raw_or_zero(Args['loss_' .. code])
	else
		r.win_text, r.draw_text, r.loss_text = r.wins, r.draws, r.losses
	end
	-- Выигранные раунды выводятся как заданы, если заданы не числом.
	r.rw_text = tonumber(Args['rw_' .. code] or 0) and r.rw or (Args['rw_' .. code] or '')
	return r
end

function MODELS.wdl.perc(o, r)
	return win_share(o, r, true)
end

-- Отставание — полуразность побед и поражений.
function MODELS.wdl.gb(r, g)
	return (g.wins - r.wins + r.losses - g.losses) / 2
end

MODELS.wdl.block = {
	head = function(h, o)
		if o.show_win then
			h:add(h.w.num, outcome_head(o.win_head, 'Выигранные матчи', 'В'))
		end
		local loss = outcome_head(o.loss_head, 'Поражения', 'П')
		if o.loss_first then
			if o.show_loss then h:add(h.w.num, loss) end
			if o.show_draw then h:add(h.w.num, draw_head(o)) end
		else
			if o.show_draw then h:add(h.w.num, draw_head(o)) end
			if o.show_loss then h:add(h.w.num, loss) end
		end
		if o.rounds_won then
			h:add(h.w.num, o.rw_head or '<abbr title="Раундов выиграно">РВ</abbr>')
		end
	end,
	cell = function(c, o, r)
		local d = o.digits
		if o.show_win then
			c:add(p_sub.num_align(r.win_text, d) .. c.win_note, c.win_weight)
		end
		if o.loss_first then
			if o.show_loss then c:add(p_sub.num_align(r.loss_text, d)) end
			if o.show_draw then c:add(p_sub.num_align(r.draw_display, d)) end
		else
			if o.show_draw then c:add(p_sub.num_align(r.draw_display, d)) end
			if o.show_loss then c:add(p_sub.num_align(r.loss_text, d)) end
		end
		if o.rounds_won then
			c:add(p_sub.num_align(r.rw_text, d))
		end
	end,
}

-- Дома и на выезде -----------------------------------------------------------------------

MODELS.ha = {
	default_unit = 'goals',
	diff_always = true,
	write_ha = true,
	order = {'played', 'block', 'fa', 'compare', 'diff', 'gb', 'perc', 'away_goals', 'bonus', 'points', 'ppg', 'ha_side'},
	totals_fields = {'hwin', 'awin', 'hdraw', 'adraw', 'hloss', 'aloss',
		{'hgf', 'hpf'}, {'agf', 'apf'}, {'hga', 'hpa'}, {'aga', 'apa'}, 'hrw', 'arw', 'adjust_points', 'startpoints'},
	-- Подписи блоков: дополнение к всплывающей подсказке столбца.
	block_titles = {home = ' дома', away = ' на выезде', total = ''},
	-- Стороны и приставки их параметров.
	sides = {{'home', 'h'}, {'away', 'a'}},
}

-- Очки за исходы; hwinpoints и awinpoints — за победу дома и на выезде, по умолчанию
-- winpoints. only_totals — один блок итогов вместо блоков дома и на выезде; ha_side включает его
-- и добавляет в конец итоги дома и на выезде краткой записью. only_fa_totals —
-- забитое и пропущенное одной парой после блоков, а не в каждом блоке.
function MODELS.ha.read_options(o, Args, flag)
	o.hwinpoints = tonumber(Args['hwinpoints']) or tonumber(Args['winpoints']) or 3
	o.awinpoints = tonumber(Args['awinpoints']) or tonumber(Args['winpoints']) or 3
	o.drawpoints = tonumber(Args['drawpoints']) or 1
	o.losspoints = tonumber(Args['losspoints']) or 0
	o.rwpoints = tonumber(Args['rwpoints']) or 1
	o.ha_side = flag('ha_side', 'no')
	o.only_totals = o.ha_side or flag('only_totals', 'no')
	o.only_fa_totals = flag('only_fa_totals', 'no')
	o.block_border = o.only_totals and '' or BLOCK_BORDER
	-- Исходы, заданные не числом, отменяют подсчёт сыгранного;
	-- ничьи проверяются, только когда выводятся.
	o.outcome_fields = o.show_draw and {'hwin', 'hloss', 'awin', 'aloss', 'hdraw', 'adraw'}
		or {'hwin', 'hloss', 'awin', 'aloss'}
	o.blocks = o.only_totals and {'total'} or {'home', 'away'}
	if o.only_fa_totals then
		o.totals_extra = {{'gf', 'pf'}, {'ga', 'pa'}}
	end
end

function MODELS.ha.read(o, Args, code)
	local r = {home = {}, away = {}}
	for _, sp in ipairs(MODELS.ha.sides) do
		local s, pre = r[sp[1]], sp[2]
		s.wins, s.draws, s.losses = num(Args[pre .. 'win_' .. code]), num(Args[pre .. 'draw_' .. code]),
			num(Args[pre .. 'loss_' .. code])
		s.rw = num(Args[pre .. 'rw_' .. code])
		s.gf_raw = Args[pre .. 'gf_' .. code] or Args[pre .. 'pf_' .. code]
		s.ga_raw = Args[pre .. 'ga_' .. code] or Args[pre .. 'pa_' .. code]
		s.gf, s.ga = tonumber(s.gf_raw or '0') or '?', tonumber(s.ga_raw or '0') or '?'
	end
	local h, a = r.home, r.away
	r.wins, r.draws, r.losses = h.wins + a.wins, h.draws + a.draws, h.losses + a.losses
	r.all_wins = r.wins
	r.rw = h.rw + a.rw
	r.gf = (h.gf ~= '?' and a.gf ~= '?') and (h.gf + a.gf) or '?'
	r.ga = (h.ga ~= '?' and a.ga ~= '?') and (h.ga + a.ga) or '?'
	-- only_fa_totals: к сумме дома и на выезде прибавляются общие gf_ и ga_
	-- (синонимы — pf_ и pa_), если сама сумма определена.
	if o.only_fa_totals then
		if r.gf ~= '?' then r.gf = r.gf + num(Args['gf_' .. code] or Args['pf_' .. code]) end
		if r.ga ~= '?' then r.ga = r.ga + num(Args['ga_' .. code] or Args['pa_' .. code]) end
	end
	r.s_pts = num(Args['adjust_points_' .. code]) + num(Args['startpoints_' .. code])
	r.matches = r.wins + r.draws + r.losses
	r.numeric = all_numeric(Args, code, o.outcome_fields)
	r.points = o.hwinpoints * h.wins + o.awinpoints * a.wins + o.drawpoints * r.draws
		+ o.losspoints * r.losses + o.rwpoints * r.rw + r.s_pts
	r.goals_for_points = r.gf
	-- Если все исходы нулевые, в ячейки идут исходные значения.
	local zero = r.wins == 0 and r.draws == 0 and r.losses == 0
	for _, sp in ipairs(MODELS.ha.sides) do
		local s, pre = r[sp[1]], sp[2]
		if zero then
			s.win_text = raw_or_zero(Args[pre .. 'win_' .. code])
			s.draw_text = raw_or_zero(Args[pre .. 'draw_' .. code])
			s.loss_text = raw_or_zero(Args[pre .. 'loss_' .. code])
		else
			s.win_text, s.draw_text, s.loss_text = s.wins, s.draws, s.losses
		end
		s.rw_text = s.rw
		s.gf_text = (s.gf == '?') and (s.gf_raw or '') or s.gf
		s.ga_text = (s.ga == '?') and (s.ga_raw or '') or s.ga
	end
	r.total = {win_text = r.wins, draw_text = r.draws, loss_text = r.losses, rw_text = r.rw,
		gf_text = r.gf, ga_text = r.ga}
	if zero then
		-- В блоке итогов нечисловые значения не суммируются: выводится ноль.
		r.total.win_text, r.total.draw_text, r.total.loss_text = 0, 0, 0
	end
	r.gf_text, r.ga_text = r.gf, r.ga
	return r
end

-- Процент побед без бонусных очков.
function MODELS.ha.perc(o, r)
	return win_share(o, r, false)
end

-- Отставание — полуразность побед и поражений плюс разность ничьих.
function MODELS.ha.gb(r, g)
	return (g.wins - r.wins + r.losses - g.losses) / 2 + (g.draws - r.draws)
end

MODELS.ha.block = {
	head = function(h, o)
		for _, side in ipairs(o.blocks) do
			local suffix = MODELS.ha.block_titles[side]
			h.pending = o.block_border
			if o.show_win then
				h:add(h.w.num, outcome_head(o.win_head, 'Выигранные матчи' .. suffix, 'В'), h:border())
			end
			local loss = outcome_head(o.loss_head, 'Поражения' .. suffix, 'П')
			if o.loss_first then
				if o.show_loss then h:add(h.w.num, loss, h:border()) end
				if o.show_draw then h:add(h.w.num, draw_head(o, suffix), h:border()) end
			else
				if o.show_draw then h:add(h.w.num, draw_head(o, suffix), h:border()) end
				if o.show_loss then h:add(h.w.num, loss, h:border()) end
			end
			if o.rounds_won then
				h:add(h.w.num, o.rw_head or ('<abbr title="Раундов выиграно' .. suffix .. '">РВ</abbr>'), h:border())
			end
			if not o.hide_fa and not o.hide_fa_columns and not o.only_fa_totals then
				local u = o.unit
				h:add(h.w.num, '<abbr title="' .. u.word .. ' забито' .. suffix .. '">' .. u.letter .. 'З</abbr>', h:border())
				h:add(h.w.num, '<abbr title="' .. u.word .. ' пропущено' .. suffix .. '">'
					.. (u.against or (u.letter .. 'П')) .. '</abbr>', h:border())
			end
		end
		-- Столбцы после блоков отделены той же чертой.
		h.pending = o.block_border
	end,
	cell = function(c, o, r)
		local d = o.digits
		for _, side in ipairs(o.blocks) do
			local s = r[side]
			c.pending = o.block_border
			if o.show_win then
				c:add(p_sub.num_align(s.win_text, d) .. c.win_note, c.win_weight, c:border())
			end
			if o.loss_first then
				if o.show_loss then c:add(p_sub.num_align(s.loss_text, d), nil, c:border()) end
				if o.show_draw then c:add(p_sub.num_align(s.draw_display or s.draw_text, d), nil, c:border()) end
			else
				if o.show_draw then c:add(p_sub.num_align(s.draw_display or s.draw_text, d), nil, c:border()) end
				if o.show_loss then c:add(p_sub.num_align(s.loss_text, d), nil, c:border()) end
			end
			if o.rounds_won then
				c:add(p_sub.num_align(s.rw_text, d), nil, c:border())
			end
			if not o.hide_fa and not o.hide_fa_columns and not o.only_fa_totals then
				c:add(p_sub.num_align(s.gf_text, d), nil, c:border())
				c:add(p_sub.num_align(s.ga_text, d), nil, c:border())
			end
		end
		c.pending = o.block_border
	end,
}

-- Овертайм и серия -----------------------------------------------------------------------

MODELS.ot = {
	-- Нераспознанная единица — шайбы: стиль рассчитан прежде всего на хоккей.
	default_unit = 'pucks',
	diff_always = false,
	write_ot = true,
	order = {'played', 'block', 'fa', 'compare', 'diff', 'away_goals', 'bonus', 'points', 'perc', 'ppg', 'gb'},
	totals_fields = {'win', 'OTwin', 'draw', 'OTloss', 'loss', 'tiebr',
		{'PKwin', 'SOwin'}, {'PKloss', 'SOloss'}, {'gf', 'pf'}, {'ga', 'pa'}, 'adjust_points', 'startpoints'},
	perc_head = '<abbr title="Процент очков">% очков</abbr>',
}

-- Очки за исходы — от show_draw не зависят; SOwinpoints и SOlosspoints — синонимы PKwinpoints и PKlosspoints.
-- Столбцы исходов в овертайме по умолчанию выводятся, в серии — нет (SO… — синонимы PK…);
-- при …_after_loss поражения в овертайме и в серии стоят после обычных.
-- Подпись столбцов серии без …_header — «бул» при showSOwin и showSOloss, иначе «пен».
function MODELS.ot.read_options(o, Args, flag)
	o.winpoints = tonumber(Args['winpoints']) or 3
	o.otwinpoints = tonumber(Args['OTwinpoints']) or 2
	o.drawpoints = tonumber(Args['drawpoints']) or 1
	o.otlosspoints = tonumber(Args['OTlosspoints']) or 1
	o.pkwinpoints = tonumber(Args['PKwinpoints'] or Args['SOwinpoints']) or 1
	o.pklosspoints = tonumber(Args['PKlosspoints'] or Args['SOlosspoints']) or 0
	o.losspoints = tonumber(Args['losspoints']) or 0
	o.show_OTwin = flag('showOTwin', 'yes')
	o.show_OTloss = flag('showOTloss', 'yes')
	o.show_PKwin = yesno(Args['showPKwin'] or Args['showSOwin'] or 'no')
	o.show_PKloss = yesno(Args['showPKloss'] or Args['showSOloss'] or 'no')
	o.OTloss_after = flag('OTloss_after_loss', 'no')
	o.PKloss_after = yesno(Args['PKloss_after_loss'] or Args['SOloss_after_loss'] or 'no')
	o.show_tiebr = flag('show_tiebr', 'no')
	o.OTwin_head = Args['OTwin_header'] or '<abbr title="Выигранные матчи в овертайме">В(ОТ)</abbr>'
	o.OTloss_head = Args['OTloss_header'] or '<abbr title="Поражения в овертайме">П(ОТ)</abbr>'
	o.PKwin_head = Args['PKwin_header'] or Args['SOwin_header'] or (flag('showSOwin', 'no')
		and '<abbr title="Выигранные в серии буллитов матчи">В(бул)</abbr>'
		or '<abbr title="Выигранные в серии пенальти матчи">В(пен)</abbr>')
	o.PKloss_head = Args['PKloss_header'] or Args['SOloss_header'] or (flag('showSOloss', 'no')
		and '<abbr title="Поражения в серии буллитов">П(бул)</abbr>'
		or '<abbr title="Поражения в серии пенальти">П(пен)</abbr>')
	o.tiebr_head = Args['tiebr_header'] or '<abbr title="Тай-брейк">Т</abbr>'
end

function MODELS.ot.read(o, Args, code)
	local r = {}
	r.wins, r.draws, r.losses = num(Args['win_' .. code]), num(Args['draw_' .. code]), num(Args['loss_' .. code])
	r.otw, r.otl = num(Args['OTwin_' .. code]), num(Args['OTloss_' .. code])
	r.pkw = num(Args['PKwin_' .. code] or Args['SOwin_' .. code])
	r.pkl = num(Args['PKloss_' .. code] or Args['SOloss_' .. code])
	r.tiebr = num(Args['tiebr_' .. code])
	r.all_wins = r.wins + r.otw + r.pkw
	r.gf_raw = Args['gf_' .. code] or Args['pf_' .. code]
	r.ga_raw = Args['ga_' .. code] or Args['pa_' .. code]
	r.gf, r.ga = tonumber(r.gf_raw or '0') or '?', tonumber(r.ga_raw or '0') or '?'
	-- Поправка очков (adjust_points_), а если её нет — начальные очки (startpoints_):
	-- в этом стиле они не складываются.
	r.s_pts = tonumber(Args['adjust_points_' .. code]) or tonumber(Args['startpoints_' .. code]) or 0
	r.matches = r.wins + r.otw + r.draws + r.otl + r.pkw + r.pkl + r.losses
	r.numeric = all_numeric(Args, code, WDL_FIELDS)
	r.points = o.winpoints * r.wins + o.otwinpoints * r.otw + o.drawpoints * r.draws
		+ o.otlosspoints * r.otl + o.losspoints * r.losses
		+ o.pkwinpoints * r.pkw + o.pklosspoints * r.pkl + r.s_pts
	if r.wins == 0 and r.otw == 0 and r.pkw == 0 and r.draws == 0 and r.otl == 0 and r.pkl == 0
		and r.losses == 0 then
		r.win_text = raw_or_zero(Args['win_' .. code])
		r.otw_text = raw_or_zero(Args['OTwin_' .. code])
		r.pkw_text = raw_or_zero(Args['PKwin_' .. code] or Args['SOwin_' .. code])
		r.draw_text = raw_or_zero(Args['draw_' .. code])
		r.loss_text = raw_or_zero(Args['loss_' .. code])
		r.otl_text = raw_or_zero(Args['OTloss_' .. code])
		r.pkl_text = raw_or_zero(Args['PKloss_' .. code] or Args['SOloss_' .. code])
	else
		r.win_text, r.otw_text, r.pkw_text, r.draw_text = r.wins, r.otw, r.pkw, r.draws
		r.loss_text, r.otl_text, r.pkl_text = r.losses, r.otl, r.pkl
	end
	return r
end

-- Процент очков — доля от максимума, который дали бы победы в основное время во всех матчах.
-- percentage_format: 'hundred 1 decimal' и 'hundred 2 decimals' — в процентах с одним или
-- двумя знаками, иначе доля с тремя знаками.
function MODELS.ot.perc(o, r)
	local most = r.matches * o.winpoints
	if most == 0 then
		return '&mdash;'
	end
	local x = r.points / most
	if o.percentage_format == 'hundred 1 decimal' then
		return decimal(100 * x, 1)
	elseif o.percentage_format == 'hundred 2 decimals' then
		return decimal(100 * x, 2)
	end
	return share(x)
end

-- Отставание — полуразность побед и поражений в основное время.
function MODELS.ot.gb(r, g)
	return (g.wins - r.wins + r.losses - g.losses) / 2
end

MODELS.ot.block = {
	head = function(h, o)
		local w = h.w.num
		if o.show_win then h:add(w, outcome_head(o.win_head, 'Выигранные матчи', 'В')) end
		if o.show_OTwin then h:add(w, o.OTwin_head) end
		if o.show_PKwin then h:add(w, o.PKwin_head) end
		if o.show_draw and not o.loss_first then h:add(w, draw_head(o)) end
		if o.show_OTloss and not o.OTloss_after then h:add(w, o.OTloss_head) end
		if o.show_PKloss and not o.PKloss_after then h:add(w, o.PKloss_head) end
		if o.show_loss then h:add(w, outcome_head(o.loss_head, 'Поражения', 'П')) end
		if o.show_draw and o.loss_first then h:add(w, draw_head(o)) end
		if o.show_OTloss and o.OTloss_after then h:add(w, o.OTloss_head) end
		if o.show_PKloss and o.PKloss_after then h:add(w, o.PKloss_head) end
		if o.show_tiebr then h:add(w, o.tiebr_head) end
	end,
	cell = function(c, o, r)
		local d = o.digits
		local function put(v) c:add(p_sub.num_align(v, d)) end
		if o.show_win then c:add(p_sub.num_align(r.win_text, d) .. c.win_note, c.win_weight) end
		if o.show_OTwin then put(r.otw_text) end
		if o.show_PKwin then put(r.pkw_text) end
		if o.show_draw and not o.loss_first then put(r.draw_display) end
		if o.show_OTloss and not o.OTloss_after then put(r.otl_text) end
		if o.show_PKloss and not o.PKloss_after then put(r.pkl_text) end
		if o.show_loss then put(r.loss_text) end
		if o.show_draw and o.loss_first then put(r.draw_display) end
		if o.show_OTloss and o.OTloss_after then put(r.otl_text) end
		if o.show_PKloss and o.PKloss_after then put(r.pkl_text) end
		if o.show_tiebr then put(r.tiebr) end
	end,
}

-- Общие столбцы -------------------------------------------------------------------------

-- Столбцы, общие для моделей: head строит заголовок, cell — ячейку,
-- full — только в полной таблице, when — условие вывода.
local COLUMNS = {}

COLUMNS.played = {
	when = function(o) return o.show_played end,
	head = function(h, o) h:add(h.w.num, o.pld_head) end,
	cell = function(c, _, r) c:add(r.matches_text) end,
}

-- Столбцы ранжирования.
COLUMNS.points = {
	when = function(o) return o.rank.points end,
	head = function(h) h:add(h.w.points, '<abbr title="Очки">О</abbr>') end,
	cell = function(c, o, r) c:rank(signed(r.points, o.digits)) end,
}

COLUMNS.perc = {
	when = function(o) return o.rank.perc or o.rank.wperc end,
	head = function(h, o)
		h:add(h.w.ratio, (not o.rank.wperc and o.model.perc_head)
			or '<abbr title="Процент выигрышей">%В</abbr>')
	end,
	cell = function(c, o, r) c:rank(r.perc, o.rank.wperc and 'st-perc' or nil) end,
}

COLUMNS.ppg = {
	when = function(o) return o.rank.ppg end,
	head = function(h) h:add(h.w.points, '<abbr title="Очков за игру">ОЗИ</abbr>') end,
	cell = function(c, _, r) c:rank(r.ppg) end,
}

-- Забито и пропущено одной парой. У модели с блоками дома и на выезде эта пара выводится
-- только при only_fa_totals — иначе забитое и пропущенное стоят в каждом блоке.
COLUMNS.fa = {
	full = true,
	when = function(o)
		return not o.hide_fa and not o.hide_fa_columns and (o.blocks == nil or o.only_fa_totals)
	end,
	head = function(h, o)
		local u = o.unit
		h:add(h.w.num, '<abbr title="' .. u.word .. ' забито">' .. u.letter .. 'З</abbr>', h:border())
		h:add(h.w.num, '<abbr title="' .. u.word .. ' пропущено">' .. (u.against or (u.letter .. 'П')) .. '</abbr>')
	end,
	cell = function(c, o, r)
		c:add(p_sub.num_align(r.gf_text, o.digits), o.rank.forpts and BOLD or nil, c:border())
		c:add(p_sub.num_align(r.ga_text, o.digits))
	end,
}

-- Сравнение забитого и пропущенного.
COLUMNS.compare = {
	full = true,
	when = function(o) return not o.hide_fa and o.compare ~= nil end,
	head = function(h, o)
		local u, v = o.unit, o.compare
		local lower = mw.ustring.lower(u.word)
		if v == 'ratio' then
			h:add(h.w.ratio, '<abbr title="Соотношение ' .. lower .. '">С' .. u.letter .. '</abbr>', h:border())
		elseif v == 'average' then
			h:add(h.w.ratio, '<abbr title="Среднее число ' .. lower .. '">С' .. u.letter .. '</abbr>', h:border())
		elseif v == 'per_match' then
			h:add(h.w.ratio, '<abbr title="' .. u.word .. ' за матч">' .. u.letter .. 'ОЗМ</abbr>', h:border())
		elseif v == 'scored' then
			h:add(h.w.num, '<abbr title="' .. u.word .. ' забито">' .. u.letter .. 'З</abbr>', h:border())
		else
			h:add(h.w.ratio, '<abbr title="Процент ' .. lower .. '">%' .. u.letter .. '</abbr>', h:border())
		end
	end,
	cell = function(c, _, r) c:add(r.compare, nil, c:border()) end,
}

-- Разница забитого и пропущенного.
COLUMNS.diff = {
	full = true,
	when = function(o) return not o.hide_fa and o.diff end,
	head = function(h, o)
		local u = o.unit
		h:add(h.w.num, '<abbr title="Разница ' .. mw.ustring.lower(u.word) .. '">Р' .. u.letter .. '</abbr>', h:border())
	end,
	cell = function(c, _, r) c:add(r.diff, nil, c:border()) end,
}

COLUMNS.away_goals = {
	when = function(o) return o.show_away_goals end,
	head = function(h, o) h:add(h.w.num, o.away_goals_head) end,
	cell = function(c, _, r) c:add(r.away_goals) end,
}

-- Бонусные очки — поправка очков команды.
COLUMNS.bonus = {
	when = function(o) return o.show_bonus end,
	head = function(h, o) h:add(h.w.num, o.bonus_head) end,
	cell = function(c, o, r) c:add(signed(r.s_pts, o.digits)) end,
}

-- Отставание от опорной команды.
COLUMNS.gb = {
	when = function(o) return o.show_GB end,
	head = function(h) h:add(h.w.num, p_sub.gb_header) end,
	cell = function(c, _, r) c:add(r.gb_text) end,
}

-- Итоги дома и на выезде краткой записью «В–Н–П» (при loss_before_draw — «В–П–Н»,
-- без ничьих — «В–П»). Только у модели с блоками дома и на выезде.
COLUMNS.ha_side = {
	when = function(o) return o.ha_side end,
	head = function(h)
		h:add(h.w.num, '<abbr title="Домашний рекорд">Дома</abbr>')
		h:add(h.w.num, '<abbr title="Выездной рекорд">Выезд</abbr>')
	end,
	cell = function(c, o, r)
		for _, s in ipairs({r.home, r.away}) do
			local rest
			if not o.show_draw then
				rest = s.loss_text
			elseif o.loss_first then
				rest = s.loss_text .. '–' .. s.draw_text
			else
				rest = s.draw_text .. '–' .. s.loss_text
			end
			c:add(s.win_text .. '–' .. rest)
		end
	end,
}

-- Разбор параметров таблицы -------------------------------------------------------------

-- Разбирает параметры таблицы Args для модели model: какие столбцы выводить, в каком порядке
-- и по каким правилам считать. Возвращает набор параметров; в поле stats он хранит
-- прочитанные показатели команд (см. team_stats).
local function read_options(model, Args)
	local o = {model = model, stats = {}}
	local function flag(name, default)
		return yesno(Args[name] or default)
	end

	o.digits = Args['digitpad']
	o.show_played = not flag('hide_played', 'no')
	o.show_win = flag('show_win', 'yes')
	o.show_draw = flag('show_draw', 'yes')
	o.show_loss = flag('show_loss', 'yes')
	o.loss_first = yesno(Args['loss_before_draw'] or Args['loss_before_tie'] or 'no')
	o.use_tie = flag('use_tie', 'no')
	o.rounds_won = yesno(Args['show_rw'] or Args['rounds_won'] or 'no')
	o.show_hth = flag('show_hth', 'yes')
	o.show_totals = flag('show_totals', 'no')
	o.show_bonus = flag('show_bonus_points', 'no')
	o.show_away_goals = flag('show_away_goals', 'no')
	o.show_GB = flag('show_GB', 'no')
	o.GB_team = tonumber(Args['GB_team']) or 1
	o.pct_style = string.lower(Args['pct_style'] or 'ratio')
	o.percentage_format = Args['percentage_format']
	-- goalpoints — очки за каждое забитое; нечисловое значение — по одному очку.
	o.goalpoints = Args['goalpoints'] and (tonumber(Args['goalpoints']) or 1)

	-- Критерий ранжирования. pts_first и perc_first ставят его столбцы сразу за командой.
	o.rank = RANK_COLUMNS[RANKING[string.lower(Args['ranking_style'] or 'pts')] or 'points']
	o.rank_first = yesno((o.rank.points and Args['pts_first'])
		or ((o.rank.perc or o.rank.wperc) and Args['perc_first']) or 'no')

	-- Единица «забито/пропущено»: незаданная — мячи, нераспознанная — единица модели.
	local fa = FOR_AGAINST[string.lower(Args['for_against_style'] or 'goals')] or model.default_unit
	o.hide_fa = (fa == 'none')
	o.unit = UNITS[fa]
	o.hide_fa_columns = flag('hide_for_against_columns', 'no')

	-- Столбец сравнения и столбец разницы. Без use_goal_diff разница выводится по правилу модели:
	-- всегда либо только когда сравнения нет.
	for _, v in ipairs(COMPARISONS) do
		for _, param in ipairs(v.params) do
			if not o.compare and flag(param, 'no') then
				o.compare = v.id
			end
		end
	end
	if Args['use_goal_diff'] then
		o.diff = flag('use_goal_diff', 'yes')
	else
		o.diff = model.diff_always or not o.compare
	end

	-- Подписи столбцов; параметры …_header заменяют их своими.
	o.pld_head = Args['pld_header'] or '<abbr title="Игры">И</abbr>'
	o.bonus_head = Args['bonus_header'] or '<abbr title="Бонусные очки">БО</abbr>'
	o.away_goals_head = Args['away_goals_header'] or '<abbr title="Выездные голы">ВГ</abbr>'
	o.rw_head = Args['rw_header']
	o.win_head = Args['win_header']
	o.loss_head = Args['loss_header']
	o.draw_head = Args['draw_header']

	model.read_options(o, Args, flag)

	-- Порядок столбцов: при rank_first столбцы ранжирования идут первыми.
	local columns = {}
	local rank_ids = {points = true, perc = true, ppg = true}
	if o.rank_first then
		columns = {'points', 'perc', 'ppg'}
	end
	for _, id in ipairs(model.order) do
		if not (o.rank_first and rank_ids[id]) then
			columns[#columns + 1] = id
		end
	end
	o.columns = columns
	return o
end

-- Разобранные параметры: ключ — таблица аргументов, значение — результат read_options.
-- Ключи слабые: разобранное живёт, пока жив сам вызов.
local options_cache = setmetatable({}, {__mode = 'k'})

-- Возвращает параметры таблицы Args для модели model; refresh — разобрать заново.
-- Заголовок разбирает их всегда: между двумя выводами одной таблицы аргументы могут измениться.
local function options(model, Args, refresh)
	local o = options_cache[Args]
	if refresh or not o or o.model ~= model then
		o = read_options(model, Args)
		options_cache[Args] = o
	end
	return o
end

-- Возвращает показатели команды code: прочитанные моделью (model.read) и производные —
-- очки за забитое, процент, очки за игру, сравнение и разницу, тексты ячеек.
-- Каждая команда читается один раз.
local function team_stats(o, Args, code)
	local r = o.stats[code]
	if r then
		return r
	end
	r = o.model.read(o, Args, code)
	local m = r.matches
	if o.goalpoints and tonumber(r.goals_for_points or r.gf_raw) then
		r.points = r.points + o.goalpoints * tonumber(r.goals_for_points or r.gf_raw)
	end

	-- Если исходы заданы не числами, число матчей — из matches_КОД, и выводится оно как задано.
	r.matches_text = m
	if not r.numeric then
		m = tonumber(Args['matches_' .. code]) or m
		r.matches = m
		r.matches_text = Args['matches_' .. code] or m
	end

	if m == 0 then
		r.perc, r.ppg = '&mdash;', '&mdash;'
	else
		r.perc = o.model.perc(o, r)
		r.ppg = decimal(r.points / m, 2)
		if o.rank.wperc then
			-- Процент побед без учёта ничьих, с двумя знаками.
			r.perc = decimal(100 * r.all_wins / m, 2)
		end
	end

	-- Сравнение забитого и пропущенного: тире там, где делить не на что или значение не число.
	local gf, ga, v = r.gf, r.ga, o.compare
	local unknown = (gf == '?' or ga == '?')
	if v == 'ratio' or v == 'average' then
		r.compare = (unknown or ga == 0) and '&mdash;' or decimal(gf / ga, 3)
	elseif v == 'goal_perc' then
		r.compare = (unknown or ga == 0) and '&mdash;' or decimal(100 * gf / ga, 1)
	elseif v == 'against_perc' then
		r.compare = (unknown or gf == 0) and '&mdash;' or decimal(100 * ga / gf, 1)
	elseif v == 'point_perc' then
		r.compare = (unknown or gf + ga <= 0) and '&mdash;' or decimal(100 * gf / (gf + ga), 2)
	elseif v == 'per_match' then
		r.compare = (gf == '?' or m <= 0) and '&mdash;' or decimal(gf / m, 1)
	elseif v == 'scored' then
		r.compare = (gf == '?') and '&mdash;' or decimal(gf)
	end
	r.diff = p_sub.goal_diff(gf, ga)

	-- Нечисловое забитое или пропущенное выводится как задано.
	if not r.gf_text then
		r.gf_text = (gf == '?') and (r.gf_raw or '') or gf
		r.ga_text = (ga == '?') and (r.ga_raw or '') or ga
	end
	r.away_goals = Args['away_goals_' .. code] or ''

	-- Строка итогов: каждый матч учтён дважды, по разу за каждую команду, поэтому «сыграно»
	-- и ничьи выводятся делёнными на 2. Победы и поражения не делятся: матч даёт одну
	-- победу и одно поражение. В блоках дома и на выезде ничья тоже учтена один раз —
	-- у хозяев дома, у гостей на выезде.
	r.draw_display = r.draw_text
	if code == total_row_code then
		if tonumber(r.matches_text) then
			r.matches_text = decimal(tonumber(r.matches_text) / 2)
		end
		if tonumber(r.draw_text) then
			r.draw_display = r.draw_text / 2
		end
		if r.total then
			r.total.draw_display = r.total.draw_text / 2
		end
	end

	o.stats[code] = r
	return r
end

-- Стиль ---------------------------------------------------------------------------------

-- Строит подмодуль стиля для модели model_name ('wdl', 'ha' или 'ot').
-- defaults — значения параметров, которые стиль подставляет, если параметр не задан (у WL и WL OT — show_draw=no).
-- Подставляются прямо в Args: show_draw читают и вне стиля — по нему автосборка и подбор ширины столбца очков
-- (ppp.measure_points_width) выбирают очки за исходы.

-- Возвращает таблицу стиля: header, row, status и признаки автосборки auto_supported,
-- write_ha, write_ot (см. [[Модуль:Sports table/auto]]).
function p.style(model_name, defaults)
	local model = MODELS[model_name]
	local pp = {auto_supported = true, write_ha = model.write_ha, write_ot = model.write_ot}

	local function prepare(Args)
		for k, v in pairs(defaults or {}) do
			if Args[k] == nil then
				Args[k] = v
			end
		end
	end

	-- Строит заголовок таблицы: позицию, группу и команду выводит ppp.open_table,
	-- столбец результатов полной таблицы в конце — ppp.close_header, между ними —
	-- столбцы показателей в порядке модели. Возвращает накопитель заголовков {count, tab_text}.
	function pp.header(t, Args, pos_label, group_col, VTE_text, full_table, results_header_txt)
		prepare(Args)
		local o = options(model, Args, true)
		local tt, w = p_sub.open_table(t, Args, pos_label, group_col, VTE_text, full_table)
		local h = new_header(tt, w)
		for _, id in ipairs(o.columns) do
			if id == 'block' then
				if full_table then
					model.block.head(h, o)
				end
			else
				local col = COLUMNS[id]
				if (full_table or not col.full) and col.when(o) then
					col.head(h, o)
				end
			end
		end
		return p_sub.close_header(h.tt, full_table, results_header_txt)
	end

	-- Дописывает в t статистические ячейки строки команды code — те же и в том же порядке,
	-- что столбцы заголовка. Возвращает {t, notes_exist, hth_id_list}:
	-- признак примечаний и список сносок личных встреч передаются следующим строкам.
	function pp.row(frame, t, Args, notes_exist, hth_id_list, full_table, rand_val, team_list, code, ii_start, ii_end, ii_fw, bg_col, bg_class, ii)
		prepare(Args)
		local o = options(model, Args)
		-- При show_totals показатели команды прибавляются к строке итогов.
		if o.show_totals and code ~= total_row_code then
			p_sub.add_to_totals(Args, code, model.totals_fields)
			if o.totals_extra then
				p_sub.add_to_totals(Args, code, o.totals_extra)
			end
		end
		local r = team_stats(o, Args, code)

		-- Сноска о личных встречах (hth_КОД) — см. ppp.hth; show_hth=no отключает её.
		local hth_local = o.show_hth and Args['hth_' .. code] or nil
		local hth = p_sub.hth(frame, Args, full_table, hth_id_list, hth_local, notes_exist, team_list,
			code, ii_start, ii_end, rand_val)

		-- Отставание от опорной команды (GB_team — её позиция, по умолчанию первая).
		if o.show_GB then
			local gb
			local g_code = team_list[o.GB_team]
			if ii ~= o.GB_team and g_code then
				gb = model.gb(r, team_stats(o, Args, g_code))
			end
			r.gb_text = p_sub.gb_text(gb, o.digits)
		end

		local c = new_row(t, bg_class, bg_col, ii_fw, hth.str)
		-- При ранжировании по победам выделен столбец побед, и сноска hth стоит у него.
		c.win_weight = o.rank.wins and BOLD or nil
		c.win_note = o.rank.wins and hth.str or ''
		for _, id in ipairs(o.columns) do
			if id == 'block' then
				if full_table then
					model.block.cell(c, o, r)
				end
			else
				local col = COLUMNS[id]
				if (full_table or not col.full) and col.when(o) then
					col.cell(c, o, r)
				end
			end
		end
		return {t = t, notes_exist = hth.notes_exist, hth_id_list = hth.list}
	end

	-- Возвращает данные об обозначениях статуса — см. ppp.status.
	-- Обозначения стилей совпадают с общим словарём.
	function pp.status(Args)
		prepare(Args)
		return p_sub.status(Args)
	end

	return pp
end

return p