Back-end wiki maintenance was completed on 4/16/25. If you encounter any strange behavior, please report it in the DevTalk Discord server.
If you are using the Vector 2022 skin, dark mode has moved to Preferences > Appearance.

Module:Jam series detailed stats

From VNDev Wiki

This module operates all the mechanics behind Special:RunQuery/Jam series detailed stats, including Template:Jam series detailed stats, Visual novel jam statistics/entryHelper, Visual novel jam statistics/particHelper, and the "Show more detailed stats" links which appear on List of ongoing visual novel jams and List of discontinued visual novel jams.

For more information about how this system works at the highest level, see Template:Jam series detailed stats/doc.

Histograms

Special:RunQuery/Jam series detailed stats (and the template it is based on) contains two histograms (frequency charts): one for participants and one for entries. These histograms summarize all jam occurrences across all series, and are the same regardless of the jam series selected. Due to limitations in Semantic MediaWiki/Semantic Result Formats, a helper page is needed to be able to display these histograms.

Visual novel jam statistics/entryHelper and Visual novel jam statistics/particHelper serve as the helper pages. They hold SMW subobjects representing the different buckets in the histograms, which are then queried using SMW to display the final graph on the detailed stats page.

To generate these helper pages, this module contains the prepHistData and prepHistDataCustom functions. Each function will generate a set of subobjects which can be queried to generate a histogram.

The prepHistData function accepts just two arguments - statType (which can be either "entries" or "participants"), and width, which is an integer representing the width of each histogram bucket. It will generate a bucket for 0, then begin at 1 and generate buckets from 1 to width, width+1 to 2*width, and so on until it reaches the bucket which contains the highest data point for that statType.

The prepHistDataCustom function accepts any number of arguments. The first argument is statType, which can be either "entries" or "participants". The remaining arguments should be the limits of histogram buckets, formatted similarly to 1-10, where 1 is the lower limit and 10 is the upper limit.

Example usage:

{{#invoke:Jam series detailed stats | prepHistData | statType=entries | width=10 }}
{{#invoke:Jam series detailed stats | prepHistDataCustom | statType=entries |1-10|11-25|26-30|31-40|41-300 }}

statDesc

The statDesc function generates the text description of the average, largest, and most recent occurrence statistics for a given jam. It is used in Template:Jam series detailed stats and has limited applications elsewhere.

It takes two arguments: statType (either entries or participants) and seriesName, which must be the official SMW-compatible name of a jam series.

shouldShow functions

The detailed jam stats page is intentionally restricted to jams that have at least one complete occurrence, and each statType (entries and participants) will only be shown if there is at least one complete occurrence where that statistic is recorded. The shouldShowDetailsButton and shouldShowDetailsCat functions control this behavior, and both functions rely on a helper function, shouldShowStat, which is not accessible via invoke.

The shouldShowDetailsButton function determines whether the button that links to more detailed stats should be shown on List of ongoing visual novel jams or List of discontinued visual novel jams (as appropriate) for a given jam series. It accepts a single argument, seriesName, and is invoked in Template:Jam series occurrence stats. If the button should be shown, it returns the string yes. Otherwise, it returns a blank string (nothing).

The shouldShowDetailsCat function determines whether the entries or participants section of the detailed stats page should be shown. It accepts two arguments: seriesName and statType. statType can be either "entries" or "participants". If the details for the given statType and seriesName should be shown, it returns the string yes. Otherwise, it returns a blank string (nothing).

Code




local p = {}

function p.prepHistData( frame )

    local statType = frame.args['statType']
    local width = frame.args['width']
    local q = { '[[Jam occurrence:+]]', '?Has ' .. statType .. '=count' }
    q.sort = 'Has ' .. statType
    q.order = 'descending'
    q.limit = 1
    q.mainheader = '-'
    local maxCount = mw.smw.ask( q )

    local numIntervals = math.ceil( maxCount[1].count / width )

    local zeroSubQ = { '[[Jam occurrence:+]]', '[[Has ' .. statType .. '::0]]' }
    zeroSubQ.format = 'count'
    local zeroCount = mw.smw.ask( zeroSubQ )

    local zeroCol = { 'Has sort order=0', 'Has label=None', 'Has count=' .. mw.smw.ask( zeroSubQ ) }

    local resultZero = mw.smw.subobject( zeroCol )
    local resultFinal = false
    if resultZero == true then resultFinal = true end

    for i=1,numIntervals do
        local subQ = { '[[Jam occurrence:+]]', '[[Has ' .. statType .. '::>' .. (width * (i-1)) + 1 .. ']]', '[[Has ' .. statType .. '::<' .. width * i ..']]' }
        subQ.format = 'count'
        local resultI = mw.smw.subobject ( { 'Has sort order=' .. i, 'Has label=' .. (width * (i-1)) + 1 .. '-' .. width * i, 'Has count=' .. mw.smw.ask( subQ ) } )
        if resultI ~= true then resultFinal = false end
    end

    return resultFinal

end

function p.prepHistDataCustom( frame )

    local statType = frame.args['statType']
    local groups = {}

    local toRet = ''

    for i,arg in pairs( frame.args ) do
        if arg ~= statType then
            --toRet = toRet .. arg .. '\n'
            table.insert(groups, mw.text.split(arg, '-', true ) )
        end
    end

    --return toRet

    local zeroSubQ = { '[[Jam occurrence:+]]', '[[Has ' .. statType .. '::0]]' }
    zeroSubQ.format = 'count'
    local zeroCount = mw.smw.ask( zeroSubQ )

    local zeroCol = { 'Has sort order=0', 'Has label=None', 'Has count=' .. mw.smw.ask( zeroSubQ ) }

    local resultZero = mw.smw.subobject( zeroCol )
    local resultFinal = false
    if resultZero == true then resultFinal = true end

    for i=1,#groups do
        if groups[i] ~= nil then
            local subQ = { '[[Jam occurrence:+]]', '[[Has ' .. statType .. '::>' .. groups[i][1] .. ']]', '[[Has ' .. statType .. '::<' .. groups[i][2] ..']]' }
            subQ.format = 'count'
            local resultI = mw.smw.subobject ( { 'Has sort order=' .. groups[i][1], 'Has label=' .. groups[i][1] .. '-' .. groups[i][2], 'Has count=' .. mw.smw.ask( subQ ) } )
            if resultI ~= true then resultFinal = false end
        end
    end

    return resultFinal

end

function p.statDesc( frame )

    local statType = 'entries'
    if frame.args['statType'] ~= nil then statType = frame.args['statType'] end

    local seriesName = 'Spooktober Visual Novel Jam'
    if frame.args['seriesName'] ~= nil then seriesName = frame.args['seriesName'] end

    local occurQuery = { '[[Jam occurrence:+]]', '[[Is occurrence of::' .. seriesName .. ']]', '[[Has end date::<' .. mw.getContentLanguage():formatDate('M j Y') .. ']]', '[[Has ' .. statType .. '::+]]', '?Has short name=shortName', '?Has ' .. statType .. '#-=count', '?Has end date#-F[U]=endDate' }
    local occur = mw.smw.ask(occurQuery)

    if type( occur ) ~= "table" then return 'ERROR' end

    local numOccur = 0
    local sumCount = 0
    local largestCount = 0
    local largestName = 'ERROR'
    local newestCount = 0
    local newestDate = 0
    local newestName = 'ERROR'

    for i=1, #occur do
        if occur[i] ~= nil then
            numOccur = numOccur + 1
            sumCount = sumCount + occur[i].count
            if occur[i].count > largestCount then
                largestCount = occur[i].count
                largestName = occur[i].shortName
            end
            if tonumber(occur[i].endDate) > newestDate then
                newestCount = occur[i].count
                newestDate = tonumber(occur[i].endDate)
                newestName = occur[i].shortName
            end
        end
    end

    local avgCount = p.round(sumCount / numOccur, 2)

    local toRet = ''
    toRet = toRet .. seriesName .. ' has had ' .. avgCount .. ' ' .. statType .. ' on average, across ' .. numOccur .. ' occurrences. This is '
    toRet = toRet .. p.prText( avgCount, statType ) .. ' of jam occurrences. \n\n'

    toRet = toRet .. 'The largest occurrence by ' .. statType .. ', which was ' .. largestName .. ', had ' .. largestCount .. ' ' .. statType .. '. This is '
    toRet = toRet .. p.prText( largestCount, statType ) .. ' of jam occurrences. \n\n'

    toRet = toRet .. 'The most recent occurrence, which was ' .. newestName .. ', had ' .. newestCount .. ' ' .. statType .. '. This is '
    toRet = toRet .. p.prText( newestCount, statType ) .. ' of jam occurrences.'

    return toRet

end

function p.percentileRank( count, statType )

    local cfQuery = { '[[Jam occurrence:+]]', '[[Has ' .. statType .. '::<' .. count .. ']]' }
    cfQuery.format = 'count'
    local fQuery = { '[[Jam occurrence:+]]', '[[Has ' .. statType .. '::' .. count .. ']]' }
    fQuery.format = 'count'
    local nQuery = { '[[Jam occurrence:+]]', '[[Has ' .. statType .. '::+]]' }
    nQuery.format = 'count'

    local cf = mw.smw.ask(cfQuery)
    local f = mw.smw.ask(fQuery)
    local n = mw.smw.ask(nQuery)

    local pr = 0.5 * f
    pr = cf - pr
    pr = pr / n
    pr = pr * 100

    return p.round(pr, 0)

end

function p.prText( count, statType )

    local text = 'higher'
    local prNum = p.percentileRank( count, statType )
    if prNum <= 50 then 
        text = 'lower'
        prNum = 100 - prNum
    end

   return text .. ' than ' .. prNum .. '%'

end

function p.round(num, numDecimalPlaces)
  local mult = 10^(numDecimalPlaces or 0)
  return math.floor(num * mult + 0.5) / mult
end

function p.shouldShowDetailsButton( frame )

    local seriesName = 'Spooktober Visual Novel Jam'
    if frame.args['seriesName'] ~= nil then seriesName = frame.args['seriesName'] end

    local toRet = ''
    if p.shouldShowStat('entries', seriesName) == 'yes' or p.shouldShowStat('participants', seriesName) == 'yes' then toRet = 'yes' end

    return toRet

end

function p.shouldShowDetailsCat( frame )
    return p.shouldShowStat(frame.args['statType'], frame.args['seriesName'])
end

function p.shouldShowStat(statType, seriesName)

    local occurQuery = { '[[Jam occurrence:+]]', '[[Is occurrence of::' .. seriesName .. ']]', '[[Has end date::<' .. mw.getContentLanguage():formatDate('M j Y') .. ']]', '[[Has ' .. statType .. '::+]]', '?Has short name=shortName', '?Has ' .. statType .. '#-=count', '?Has end date#-F[U]=endDate' }
    local occur = mw.smw.ask(occurQuery)

    if type( occur ) ~= "table" then return '' end
    if #occur == nil or #occur == 0 then return '' end

    local sumCount = 0

    for i=1, #occur do
        if occur[i] ~= nil then
            if occur[i].count ~= nil then sumCount = sumCount + occur[i].count end
        end
    end

    if sumCount == 0 then return '' end
    return 'yes'
end


return p