Module:PersonResearchOutcomesList

From MaRDI portal

Documentation for this module may be created at Module:PersonResearchOutcomesList/doc

-- Required module containing helper methods
local helper = require('Module:HelperMethods')

-- Required modules for SPARQL queries and HTML table generation
local sparql = require('SPARQL')
local mwHtml = require('mw.html')

-- Main table to hold all functions
local p = {}

-- Shared function to run the SPARQL query and return the data table
function p.fetchData(target1)
    local baseUrl = mw.site.server

    local sparqlQuery = [[
PREFIX target1: <https://portal.mardi4nfdi.de/entity/]] .. target1 .. [[>
PREFIX wdt: <https://portal.mardi4nfdi.de/prop/direct/>
PREFIX wd: <https://portal.mardi4nfdi.de/entity/>

SELECT
  ?publication_date ?work ?workLabel 
  ?workUrl
  (REPLACE(STR(?work), "^.*/", "") AS ?qid)
  ?item_type
  ?item_type_label
  ?zbmath_de_number
  ?journalLabel
  ?arxivId
  ?fullWorkUrl
  
WHERE {
  ?work wdt:P16 target1: .
  VALUES ?allowedValues { wd:Q5976449 wd:Q5976450 wd:Q5984635 wd:Q6534216 } 
  ?work wdt:P1460 ?allowedValues.

  OPTIONAL {
    ?work wdt:P28 ?publication_datetime .
  }
  OPTIONAL {
    ?work wdt:P170 ?last_updated .
  }
  BIND(COALESCE(xsd:date(?publication_datetime), xsd:date(?last_updated), "N/A") AS ?publication_date)

  OPTIONAL {
    ?work wdt:P1460 ?item_type .
    BIND(REPLACE(STR(?item_type), "^.*/(Q[0-9]+)$", "$1") AS ?item_type_short)
    BIND(IF(?item_type_short = "Q5976449", "Paper", 
         IF(?item_type_short = "Q5984635", "Dataset",
         IF(?item_type_short = "Q6534216", "Workflow",
         IF(?item_type_short = "Q5976450", "Software", "Other")))) AS ?item_type_label)
    BIND(CONCAT(
      IF(?item_type_short = "Q5976449", "]] .. baseUrl .. [[/wiki/Publication:", 
      IF(?item_type_short = "Q5984635", "]] .. baseUrl .. [[/wiki/Dataset:", 
      IF(?item_type_short = "Q6534216", "]] .. baseUrl .. [[/wiki/Workflow:", 
      IF(?item_type_short = "Q5976450", "]] .. baseUrl .. [[/wiki/Software:", "]] .. baseUrl .. [[/wiki/")))), 
      REPLACE(STR(?work), "^.*/Q", "")
    ) AS ?workUrl)
  }

  OPTIONAL {
    ?work wdt:P1451 ?zbmath_de_number .
  }

  OPTIONAL {
    ?work wdt:P200 ?journal .
  }

  OPTIONAL {
    ?work wdt:P21 ?arxivId .
  }

  OPTIONAL {
    ?work wdt:P205 ?fullWorkUrl .
  }

  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
}

ORDER BY DESC(?publication_date)
    ]]

    local jsonResults = sparql.runQuery(sparqlQuery)

    if jsonResults and jsonResults.error then
        mw.log("Error in SPARQL query: " .. tostring(jsonResults.error))
        return nil
    end

    if not jsonResults then
        return nil
    end

    if helper.countElementsInBindings(jsonResults.results.bindings) == 0 then
        return nil
    end

    local fieldOrder = {"workUrl", "workLabel", "work", "publication_date", "qid", "item_type", "item_type_label", "zbmath_de_number", "journalLabel", "arxivId", "fullWorkUrl"}
    return helper.convertJsonToTableOrdered(jsonResults, fieldOrder)
end


-- Entry point: returns only the publications table
function p.buildTableFromSparql(frame)
    local target1 = frame.args[1]
    if not target1 or target1 == '' then
        return "No records found"
    end

    local dataTable = p.fetchData(target1)
    if not dataTable then
        return "No records found."
    end

    -- Replace URL labels (missing titles) with a readable fallback
    for _, row in ipairs(dataTable) do
        if row[2] and string.find(row[2], "https://") then
            local zbmath_de_number = row[8] or nil
            row[2] = helper.titleNotAvailableStr(zbmath_de_number)
        end
    end

    -- Build table manually so we can add journal as second line
    local tbl = mwHtml.create('table')
        :addClass('wikitable sortable')
        :attr('border', '1')

    -- Header row
    local header = tbl:tag('tr')
    header:tag('th'):wikitext('Publication')
    header:tag('th'):wikitext('Date of Publication')
    header:tag('th'):wikitext('Type')

    -- Data rows
    -- fieldOrder: 1=workUrl, 2=workLabel, 3=work, 4=publication_date,
    --             5=qid, 6=item_type, 7=item_type_label, 8=zbmath_de_number, 9=journalLabel, 10=arxivId
    for _, row in ipairs(dataTable) do
        local url        = row[1] or ""
        local label      = row[2] or ""
        local date       = row[4] or ""
        local typeLabel  = row[7] or ""
        local rawJournal = row[9] or ""
        local rawArxiv   = row[10] or ""

        local journal = mw.text.trim(tostring(rawJournal)):gsub("&nbsp;", ""):gsub("&#160;", "")
        local arxivId = mw.text.trim(tostring(rawArxiv)):gsub("&nbsp;", ""):gsub("&#160;", "")

        -- Treat URL-valued journal labels as empty
        if journal:match("^https?://") then
            journal = ""
        end

        -- Fall back to "arXiv preprint" if no journal but arXiv ID exists
        if journal == "" and arxivId ~= "" then
            journal = "(available as arXiv preprint)"
        end

        -- Also check P205 full work URL for arxiv.org
        if journal == "" then
            local fullWorkUrl = mw.text.trim(tostring(row[11] or ""))
            if fullWorkUrl:find("://arxiv.org", 1, true) then
                journal = "(available as arXiv preprint)"
            end
        end

        -- Build publication cell content: linked title + optional journal line
        local titleLink = "[" .. url .. " " .. label .. "]"
        local cellContent = titleLink
        if journal ~= "" and not string.find(journal, "https://") then
            journal = journal:gsub("\\&", "&"):gsub("&amp;", "&")
            cellContent = cellContent .. "<br/><small><i>" .. journal .. "</i></small>"
        end

        local tr = tbl:tag('tr')
        tr:tag('td'):wikitext(cellContent)
        tr:tag('td'):wikitext(date)
        tr:tag('td'):wikitext(typeLabel)
    end

    return mw.getCurrentFrame():preprocess(tostring(tbl))
end


-- Entry point: returns only the histogram chart
function p.buildChartFromSparql(frame)
    local target1 = frame.args[1]
    if not target1 or target1 == '' then
        return nil
    end

    local height = frame.args[2] or '350px'
    local width  = frame.args[3] or '100%'

    local dataTable = p.fetchData(target1)
    if not dataTable then
        return nil
    end

    local histogramChart = helper.generateHistogramChartFromTable(dataTable, 4)

    -- Modern styling overrides
    histogramChart.data.datasets[1].backgroundColor = 'rgba(192, 82, 42, 0.75)'
    histogramChart.data.datasets[1].borderColor = 'rgba(192, 82, 42, 1)'
    histogramChart.data.datasets[1].borderWidth = 0
    histogramChart.data.datasets[1].borderRadius = 5
    histogramChart.data.datasets[1].hoverBackgroundColor = 'rgba(192, 82, 42, 1)'
    histogramChart.data.datasets[1].label = 'Publications'
    histogramChart.options = {
        responsive = true,
        maintainAspectRatio = false,
        layout = {
            padding = { right = 0, left = 0 }
        },
        plugins = {
            legend = { display = false }
        },
        scales = {
            x = {
                grid = { display = false },
                ticks = { color = '#888', font = { size = 12 } },
                offset = true
            },
            y = {
                beginAtZero = true,
                grid = { color = 'rgba(0,0,0,0.06)', drawBorder = false },
                ticks = { color = '#888', font = { size = 12 }, precision = 0 }
            }
        }
    }

    local histogramChartJson = mw.text.jsonEncode(histogramChart)

    local chartContainer = mw.html.create('div')
        :addClass('wikiChartContainer')
        :css('height', height)
        :css('width', width)
        :attr('data-chartdata', histogramChartJson)

    return tostring(chartContainer)
end


-- Legacy entry point: original combined output (table + heading + chart)
-- Kept for backwards compatibility
function p.buildTableAndChartFromSparql(frame)
    local target1 = frame.args[1]
    if not target1 or target1 == '' then
        return "No records found"
    end

    local height = frame.args[2] or '400px'
    local width  = frame.args[3] or '800px'

    local dataTable = p.fetchData(target1)
    if not dataTable then
        return "No records found."
    end

    local headers  = {"Publication", "Date of Publication", "Type"}
    local htmlTable = helper.createHtmlTableWithMergedCols(dataTable, headers, {{1, 2}, {4}, {6, 7}})

    local histogramChart     = helper.generateHistogramChartFromTable(dataTable, 4)
    local histogramChartJson = mw.text.jsonEncode(histogramChart)

    local chartContainer = mw.html.create('div')
        :addClass('wikiChartContainer')
        :css('height', height)
        :css('width', width)
        :attr('data-chartdata', histogramChartJson)

    local heading = mw.html.create('h2')
        :wikitext('Research outcomes over time')

    local parentContainer = mw.html.create('div')
        :addClass('parent-container')
        :css('width', width)
        :node(htmlTable)
        :node(heading)
        :node(chartContainer)

    return tostring(parentContainer)
end


return p